From a65056cb35c00edbaa2e81ffcb33cd80753ebe5d Mon Sep 17 00:00:00 2001 From: Octogonapus Date: Fri, 19 May 2023 20:57:54 -0400 Subject: [PATCH] feat(julia): Add Julia language analyzer support --- .github/workflows/semantic-pr.yaml | 1 + docs/community/contribute/pr.md | 1 + docs/docs/coverage/language/index.md | 1 + docs/docs/coverage/language/julia.md | 24 ++ go.mod | 2 +- go.sum | 4 +- mkdocs.yml | 1 + pkg/detector/library/driver.go | 3 + pkg/fanal/analyzer/all/import.go | 1 + pkg/fanal/analyzer/const.go | 4 + pkg/fanal/analyzer/language/julia/pkg/pkg.go | 178 +++++++++++ .../analyzer/language/julia/pkg/pkg_test.go | 301 ++++++++++++++++++ .../pkg/testdata/dep_ext_v1.9/Manifest.toml | 16 + .../pkg/testdata/dep_ext_v1.9/Project.toml | 9 + .../julia/pkg/testdata/happy/Manifest.toml | 63 ++++ .../julia/pkg/testdata/happy/Project.toml | 17 + .../pkg/testdata/no_deps_v1.6/Manifest.toml | 2 + .../pkg/testdata/no_deps_v1.6/Project.toml | 3 + .../pkg/testdata/no_deps_v1.9/Manifest.toml | 7 + .../pkg/testdata/no_deps_v1.9/Project.toml | 3 + .../pkg/testdata/primary_v1.6/Manifest.toml | 27 ++ .../pkg/testdata/primary_v1.6/Project.toml | 9 + .../testdata/shadowed_dep_v1.9/Manifest.toml | 16 + .../testdata/shadowed_dep_v1.9/Project.toml | 7 + pkg/fanal/types/const.go | 4 + pkg/purl/purl.go | 2 + 26 files changed, 703 insertions(+), 3 deletions(-) create mode 100644 docs/docs/coverage/language/julia.md create mode 100644 pkg/fanal/analyzer/language/julia/pkg/pkg.go create mode 100644 pkg/fanal/analyzer/language/julia/pkg/pkg_test.go create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index b3cde58e2243..1b2758c51f2d 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -75,6 +75,7 @@ jobs: dart swift bitnami + julia os lang diff --git a/docs/community/contribute/pr.md b/docs/community/contribute/pr.md index 2982e3cecd72..67d6e5010e87 100644 --- a/docs/community/contribute/pr.md +++ b/docs/community/contribute/pr.md @@ -142,6 +142,7 @@ language: - go - elixir - dart +- julia vuln: diff --git a/docs/docs/coverage/language/index.md b/docs/docs/coverage/language/index.md index 18c3aa1f3909..4d354e389b7a 100644 --- a/docs/docs/coverage/language/index.md +++ b/docs/docs/coverage/language/index.md @@ -46,6 +46,7 @@ On the other hand, when the target is a post-build artifact, like a container im | [Dart](dart.md) | pubspec.lock | - | - | ✅ | ✅ | | [Swift](swift.md) | Podfile.lock | - | - | ✅ | ✅ | | | Package.resolved | - | - | ✅ | ✅ | +| [Julia](julia.md) | Manifest.toml | ✅ | ✅ | ✅ | ✅ | The path of these files does not matter. diff --git a/docs/docs/coverage/language/julia.md b/docs/docs/coverage/language/julia.md new file mode 100644 index 000000000000..3817dacfeb1b --- /dev/null +++ b/docs/docs/coverage/language/julia.md @@ -0,0 +1,24 @@ +# Julia + +## Features + +Trivy supports [Pkg.jl](https://pkgdocs.julialang.org/v1/), which is the Julia package manager. +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | License | Dependency graph | Position | +| --------------- | ------------- | :---------------------: | :--------------- | :-----: | :--------------: | :------: | +| Pkg.jl | Manifest.toml | ✅ | Excluded[^1] | - | ✅ | ✅ | + +### Pkg.jl + +Trivy searches for `Manifest.toml` to detect dependencies. + +Trivy also supports dependency trees; however, to display an accurate tree, it needs to know whether each package is a direct dependency of the project. +Since this information is not included in `Manifest.toml`, Trivy parses `Project.toml`, which should be located next to `Project.toml`. +If you want to see the dependency tree, please ensure that `Project.toml` is present. + +Scanning `Manifest.toml` and `Project.toml` together also removes developer dependencies. + +Dependency extensions are currently ignored. + +[^1]: When you scan `Manifest.toml` and `Project.toml` together. diff --git a/go.mod b/go.mod index b31d0f1b5744..93773ce7eca6 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/alicebob/miniredis/v2 v2.30.4 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 github.com/aquasecurity/defsec v0.93.2-0.20231024055158-015ab97ce898 - github.com/aquasecurity/go-dep-parser v0.0.0-20231120074854-8322cc2242bf + github.com/aquasecurity/go-dep-parser v0.0.0-20231122010617-fc7f2b470d89 github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 diff --git a/go.sum b/go.sum index 0bd9e6aceb22..bf607bd479d7 100644 --- a/go.sum +++ b/go.sum @@ -322,8 +322,8 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= github.com/aquasecurity/defsec v0.93.2-0.20231024055158-015ab97ce898 h1:gu7XQvv2CswgzOdOFHg/AmtR4vBonG35XvGxHHvcIr4= github.com/aquasecurity/defsec v0.93.2-0.20231024055158-015ab97ce898/go.mod h1:J30VViSgmoW2Ic/6aqVJO2qvuADsmZ3MYuNxPcU6Vt0= -github.com/aquasecurity/go-dep-parser v0.0.0-20231120074854-8322cc2242bf h1:kweQrNMfarPfjZGI1537GtuujhpzhsuT/MvmW2FwaBE= -github.com/aquasecurity/go-dep-parser v0.0.0-20231120074854-8322cc2242bf/go.mod h1:7+xrs6AWD5+onpmX8f7qIkAhUgkPP0mhUdBjxJBcfas= +github.com/aquasecurity/go-dep-parser v0.0.0-20231122010617-fc7f2b470d89 h1:IssyGUDzAYvTcv28EJMmqYWhQh5sW9HiJwDphEFNSWM= +github.com/aquasecurity/go-dep-parser v0.0.0-20231122010617-fc7f2b470d89/go.mod h1:7+xrs6AWD5+onpmX8f7qIkAhUgkPP0mhUdBjxJBcfas= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= github.com/aquasecurity/go-mock-aws v0.0.0-20230810212901-d6feebd39060 h1:V7nC90NpRDEubNpNEgRDtTfLH3RKQlZeY9/HSqxEze8= diff --git a/mkdocs.yml b/mkdocs.yml index 59ad99752b37..1790d913d073 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -100,6 +100,7 @@ nav: - Ruby: docs/coverage/language/ruby.md - Rust: docs/coverage/language/rust.md - Swift: docs/coverage/language/swift.md + - Julia: docs/coverage/language/julia.md - IaC: - Overview: docs/coverage/iac/index.md - Azure ARM Template: docs/coverage/iac/azure-arm.md diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index bea534ba0727..fbb08a12edf2 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -79,6 +79,9 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) { case ftypes.K8sUpstream: ecosystem = vulnerability.Kubernetes comparer = compare.GenericComparer{} + case ftypes.Julia: + log.Logger.Warn("Julia is supported for SBOM, not for vulnerability scanning") + return Driver{}, false default: log.Logger.Warnf("The %q library type is not supported for vulnerability scanning", libType) return Driver{}, false diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index 743ae833771e..51a25fd9ad31 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -18,6 +18,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/gradle" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/pom" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/julia/pkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/npm" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pnpm" diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 0720dcc78a24..56612d4c0c2e 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -90,6 +90,9 @@ const ( // Dart TypePubSpecLock Type = "pubspec-lock" + // Julia + TypeJulia Type = "julia" + // ============ // Non-packaged // ============ @@ -184,6 +187,7 @@ var ( TypeSwift, TypePubSpecLock, TypeMixLock, + TypeJulia, } // TypeLockfiles has all lock file analyzers diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg.go b/pkg/fanal/analyzer/language/julia/pkg/pkg.go new file mode 100644 index 000000000000..36c3fc9b1765 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg.go @@ -0,0 +1,178 @@ +package pkgjl + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "path/filepath" + "sort" + + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/BurntSushi/toml" + "github.com/samber/lo" + + julia "github.com/aquasecurity/go-dep-parser/pkg/julia/manifest" + godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeJulia, newJuliaAnalyzer) +} + +const version = 1 + +var requiredFiles = []string{ + types.JuliaManifest, + types.JuliaProject, +} + +type juliaAnalyzer struct { + lockParser godeptypes.Parser + comparer compare.GenericComparer +} + +type Project struct { + Dependencies map[string]string `toml:"deps"` +} + +func newJuliaAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &juliaAnalyzer{ + lockParser: julia.NewParser(), + comparer: compare.GenericComparer{}, + }, nil +} + +func (a juliaAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.JuliaManifest + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + // Parse Manifest.toml + app, err := a.parseJuliaManifest(path, r) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Parse Project.toml alongside Manifest.toml to identify the direct dependencies. This mutates `app`. + if err = a.removeExtraDependencies(input.FS, filepath.Dir(path), app); err != nil { + log.Logger.Warnf("Unable to parse %q to identify direct dependencies: %s", filepath.Join(filepath.Dir(path), types.JuliaProject), err) + } + + sort.Sort(app.Libraries) + apps = append(apps, *app) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("julia walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a juliaAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return slices.Contains(requiredFiles, fileName) +} + +func (a juliaAnalyzer) Type() analyzer.Type { + return analyzer.TypeJulia +} + +func (a juliaAnalyzer) Version() int { + return version +} + +func (a juliaAnalyzer) parseJuliaManifest(path string, r io.Reader) (*types.Application, error) { + return language.Parse(types.Julia, path, r, a.lockParser) +} + +// Removes dependencies not specified as direct dependencies in the Project.toml file. +// This is not strictly necessary, but given that test dependencies are in flux right now, this is future-proofing. +// https://pkgdocs.julialang.org/v1/creating-packages/#target-based-test-specific-dependencies +func (a juliaAnalyzer) removeExtraDependencies(fsys fs.FS, dir string, app *types.Application) error { + projectPath := filepath.Join(dir, types.JuliaProject) + project, err := parseJuliaProject(fsys, projectPath) + if errors.Is(err, fs.ErrNotExist) { + log.Logger.Debugf("Julia: %s not found", projectPath) + return nil + } else if err != nil { + return xerrors.Errorf("unable to parse %s: %w", projectPath, err) + } + + // Project.toml file can contain same libraries with different versions. + // Save versions separately for version comparison by comparator + pkgIDs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + return pkg.ID, pkg + }) + + // Identify direct dependencies + visited := map[string]types.Package{} + for _, uuid := range project.Dependencies { + // uuid is a direct dep since it's in the Project's dependencies section. Search through Libraries to mark the matching one as a direct dep. + if pkg, ok := pkgIDs[uuid]; ok { + // Mark as a direct dependency + pkg.Indirect = false + visited[pkg.ID] = pkg + } + } + + // Identify indirect dependencies + for _, pkg := range visited { + walkIndirectDependencies(pkg, pkgIDs, visited) + } + + // Include only the packages we visited so that we don't include any deps from the [extras] section + app.Libraries = maps.Values(visited) + return nil +} + +// Parses Project.toml +func parseJuliaProject(fsys fs.FS, path string) (Project, error) { + proj := Project{} + f, err := fsys.Open(path) + if err != nil { + return proj, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + if _, err = toml.NewDecoder(f).Decode(&proj); err != nil { + return proj, xerrors.Errorf("decode error: %w", err) + } + return proj, nil +} + +// Marks all indirect dependencies as indirect. Starts from `rootPkg`. Visited deps are added to `visited`. +func walkIndirectDependencies(rootPkg types.Package, allPkgIDs map[string]types.Package, visited map[string]types.Package) { + for _, pkgID := range rootPkg.DependsOn { + if _, ok := visited[pkgID]; ok { + continue + } + + dep, ok := allPkgIDs[pkgID] + if !ok { + continue + } + + dep.Indirect = true + visited[dep.ID] = dep + walkIndirectDependencies(dep, allPkgIDs, visited) + } +} diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go new file mode 100644 index 000000000000..9419750837b9 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go @@ -0,0 +1,301 @@ +package pkgjl + +import ( + "context" + "os" + "testing" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_juliaAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Julia, + FilePath: "Manifest.toml", + Libraries: []types.Package{ + { + ID: "ade2ca70-3891-5945-98fb-dc099432e06a", + Name: "Dates", + Version: "1.9.0", + Indirect: false, + Locations: []types.Location{{StartLine: 7, EndLine: 9}}, + DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}, + }, + { + ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", + Name: "JSON", + Version: "0.21.4", + Indirect: false, + Locations: []types.Location{{StartLine: 11, EndLine: 15}}, + DependsOn: []string{ + "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + "a63ad114-7e13-5084-954f-fe012c677804", + "ade2ca70-3891-5945-98fb-dc099432e06a", + }, + }, + { + ID: "a63ad114-7e13-5084-954f-fe012c677804", + Name: "Mmap", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 17, EndLine: 18}}, + }, + { + ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + Name: "Parsers", + Version: "2.5.10", + Indirect: true, + Locations: []types.Location{{StartLine: 20, EndLine: 24}}, + DependsOn: []string{ + "ade2ca70-3891-5945-98fb-dc099432e06a", + "aea7be01-6a6a-4083-8856-8a6e6704d82a", + "cf7118a7-6976-5b1a-9a39-7adc72f591a4", + }, + }, + { + ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", + Name: "PrecompileTools", + Version: "1.1.1", + Indirect: true, + Locations: []types.Location{{StartLine: 26, EndLine: 30}}, + DependsOn: []string{"21216c6a-2e73-6563-6e65-726566657250"}, + }, + { + ID: "21216c6a-2e73-6563-6e65-726566657250", + Name: "Preferences", + Version: "1.4.0", + Indirect: true, + Locations: []types.Location{{StartLine: 32, EndLine: 36}}, + DependsOn: []string{"fa267f1f-6049-4f14-aa54-33bafae1ed76"}, + }, + { + ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", + Name: "Printf", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 38, EndLine: 40}}, + DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}, + }, + { + ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", + Name: "Random", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 42, EndLine: 44}}, + DependsOn: []string{"9e88b42a-f829-5b0c-bbe9-9e923198166b", "ea8e919c-243c-51af-8825-aaa63cd721ce"}, + }, + { + ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", + Name: "SHA", + Version: "0.7.0", + Indirect: true, + Locations: []types.Location{{StartLine: 46, EndLine: 48}}, + }, + { + ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", + Name: "Serialization", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 50, EndLine: 51}}, + }, + { + ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", + Name: "TOML", + Version: "1.0.3", + Indirect: true, + Locations: []types.Location{{StartLine: 53, EndLine: 56}}, + DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"}, + }, + { + ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", + Name: "UUIDs", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 58, EndLine: 60}}, + DependsOn: []string{"9a3f8284-a2c9-5f02-9a11-845980a1fd5c", "ea8e919c-243c-51af-8825-aaa63cd721ce"}, + }, + { + ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + Name: "Unicode", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 62, EndLine: 63}}, + }, + }, + }, + }, + }, + }, + { + // If there are no libs, `toApplication` makes the app nil. Not sure why but this is the expected behavior. + name: "no_deps_v1.6", + dir: "testdata/no_deps_v1.6", + want: &analyzer.AnalysisResult{ + Applications: nil, + }, + }, + { + // If there are no libs, `toApplication` makes the app nil. Not sure why but this is the expected behavior. + name: "no_deps_v1.9", + dir: "testdata/no_deps_v1.9", + want: &analyzer.AnalysisResult{ + Applications: nil, + }, + }, + { + name: "dep_ext_v1.9", + dir: "testdata/dep_ext_v1.9", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Julia, + FilePath: "Manifest.toml", + Libraries: []types.Package{ + { + ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", + Name: "AbstractFFTs", + Version: "1.3.1", + Indirect: false, + Locations: []types.Location{{StartLine: 7, EndLine: 10}}, + DependsOn: nil, + }, + }, + }, + }, + }, + }, + { + name: "shadowed_dep_v1.9", + dir: "testdata/shadowed_dep_v1.9", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Julia, + FilePath: "Manifest.toml", + Libraries: []types.Package{ + { + ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", + Name: "A", + Version: "1.9.0", + Indirect: false, + Locations: []types.Location{{StartLine: 7, EndLine: 8}}, + DependsOn: []string{"f41f7b98-334e-11e9-1257-49272045fb24"}, + }, + { + ID: "edca9bc6-334e-11e9-3554-9595dbb4349c", + Name: "B", + Version: "1.9.0", + Indirect: false, + Locations: []types.Location{{StartLine: 15, EndLine: 16}}, + DependsOn: nil, + }, + { + ID: "f41f7b98-334e-11e9-1257-49272045fb24", + Name: "B", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 13, EndLine: 14}}, + DependsOn: nil, + }, + }, + }, + }, + }, + }, + { + name: "primary_v1.6", + dir: "testdata/primary_v1.6", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Julia, + FilePath: "Manifest.toml", + Libraries: []types.Package{ + { + ID: "ade2ca70-3891-5945-98fb-dc099432e06a", + Name: "Dates", + Version: "unknown", + Indirect: true, + Locations: []types.Location{{StartLine: 3, EndLine: 5}}, + DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}, + }, + { + ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", + Name: "JSON", + Version: "0.21.4", + Indirect: false, + Locations: []types.Location{{StartLine: 7, EndLine: 11}}, + DependsOn: []string{ + "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + "a63ad114-7e13-5084-954f-fe012c677804", + "ade2ca70-3891-5945-98fb-dc099432e06a", + }, + }, + { + ID: "a63ad114-7e13-5084-954f-fe012c677804", + Name: "Mmap", + Version: "unknown", + Indirect: true, + Locations: []types.Location{{StartLine: 13, EndLine: 14}}, + DependsOn: nil, + }, + { + ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + Name: "Parsers", + Version: "2.4.2", + Indirect: true, + Locations: []types.Location{{StartLine: 16, EndLine: 20}}, + DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"}, + }, + { + ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", + Name: "Printf", + Version: "unknown", + Indirect: true, + Locations: []types.Location{{StartLine: 22, EndLine: 24}}, + DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}, + }, + { + ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + Name: "Unicode", + Version: "unknown", + Indirect: true, + Locations: []types.Location{{StartLine: 26, EndLine: 27}}, + DependsOn: nil, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newJuliaAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml new file mode 100644 index 000000000000..c3b22a724c15 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.AbstractFFTs]] +git-tree-sha1 = "16b6dbc4cf7caee4e1e75c49485ec67b667098a0" +uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" +version = "1.3.1" + + [deps.AbstractFFTs.extensions] + AbstractFFTsChainRulesCoreExt = "ChainRulesCore" + + [deps.AbstractFFTs.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml new file mode 100644 index 000000000000..7d99fdb4f598 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml @@ -0,0 +1,9 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c" + +[compat] +AbstractFFTs = "1.3" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml new file mode 100644 index 000000000000..9efbc223539e --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml @@ -0,0 +1,63 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f65b9de676a27ce78ee011db6d477b3d44d1a7c5" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "a5aef8d4a6e8d81f171b2bd4be5265b01384c74c" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.5.10" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "259e206946c293698122f63e2b513a7c99a244e8" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.1.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml new file mode 100644 index 000000000000..54bb31e2750c --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml @@ -0,0 +1,17 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" + +[compat] +JSON = "0.21" +julia = "1.8" + +[extras] +FastHistograms = "06971c4e-2824-4b19-bcf3-55442efb9bc7" + +[targets] +test = ["FastHistograms"] diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml new file mode 100644 index 000000000000..f45eecff031f --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml @@ -0,0 +1,2 @@ +# This file is machine-generated - editing it directly is not advised + diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml new file mode 100644 index 000000000000..84c8138906dd --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml @@ -0,0 +1,3 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Manifest.toml new file mode 100644 index 000000000000..9ed5ab94bdf9 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Manifest.toml @@ -0,0 +1,7 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709" + +[deps] diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Project.toml new file mode 100644 index 000000000000..84c8138906dd --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.9/Project.toml @@ -0,0 +1,3 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Manifest.toml new file mode 100644 index 000000000000..6883d0041576 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Manifest.toml @@ -0,0 +1,27 @@ +# This file is machine-generated - editing it directly is not advised + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[Parsers]] +deps = ["Dates"] +git-tree-sha1 = "6c01a9b494f6d2a9fc180a08b182fcb06f0958a0" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.4.2" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Project.toml new file mode 100644 index 000000000000..b498587f7561 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/primary_v1.6/Project.toml @@ -0,0 +1,9 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" + +[compat] +JSON = "0.21" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml new file mode 100644 index 000000000000..dd4ea00b943d --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.A]] +uuid = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" + + [deps.A.deps] + B = "f41f7b98-334e-11e9-1257-49272045fb24" + +[[deps.B]] +uuid = "f41f7b98-334e-11e9-1257-49272045fb24" +[[deps.B]] +uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml new file mode 100644 index 000000000000..24fe6178c480 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml @@ -0,0 +1,7 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +A = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" +B = "edca9bc6-334e-11e9-3554-9595dbb4349c" diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 28e11d55beb9..0adb37d40010 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -71,6 +71,7 @@ const ( Pub LangType = "pub" Hex LangType = "hex" Bitnami LangType = "bitnami" + Julia LangType = "julia" K8sUpstream LangType = "kubernetes" EKS LangType = "eks" // Amazon Elastic Kubernetes Service @@ -129,4 +130,7 @@ const ( PubSpecLock = "pubspec.lock" MixLock = "mix.lock" + + JuliaProject = "Project.toml" + JuliaManifest = "Manifest.toml" ) diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 947c6eb696be..e3ae5bd122dc 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -445,6 +445,8 @@ func purlType(t ftypes.TargetType) string { return packageurl.TypeRPM case TypeOCI: return packageurl.TypeOCI + case ftypes.Julia: + return packageurl.TypeJulia } return string(t) }