-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(julia): Add Julia language analyzer support
- Loading branch information
1 parent
ad977a4
commit a65056c
Showing
26 changed files
with
703 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,7 @@ jobs: | |
dart | ||
swift | ||
bitnami | ||
julia | ||
os | ||
lang | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,6 +142,7 @@ language: | |
- go | ||
- elixir | ||
- dart | ||
- julia | ||
|
||
vuln: | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
Oops, something went wrong.