Files
trivy/pkg/dependency/parser/julia/manifest/parse.go
Teppei Fukuda 983ac15f22 ci: add depguard (#6963)
Signed-off-by: knqyf263 <knqyf263@gmail.com>
2024-06-20 02:48:08 +00:00

168 lines
5.0 KiB
Go

package julia
import (
"io"
"sort"
"github.com/BurntSushi/toml"
"github.com/samber/lo"
"golang.org/x/xerrors"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type primitiveManifest struct {
JuliaVersion string `toml:"julia_version"`
ManifestFormat string `toml:"manifest_format"`
Dependencies map[string][]primitiveDependency `toml:"deps"` // e.g. [[deps.Foo]]
}
type primitiveDependency struct {
Dependencies toml.Primitive `toml:"deps"` // by name. e.g. deps = ["Foo"] or [deps.Foo.deps]
UUID string `toml:"uuid"`
Version string `toml:"version"` // not specified for stdlib packages, which are of the Julia version
DependsOn []string `toml:"-"` // list of dependent UUID's.
}
type Parser struct{}
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var oldDeps map[string][]primitiveDependency
var primMan primitiveManifest
var manMetadata toml.MetaData
decoder := toml.NewDecoder(r)
// Try to read the old Manifest format. This can also read the v1.0 Manifest format, which we parse out later.
var err error
if manMetadata, err = decoder.Decode(&oldDeps); err != nil {
if _, err = r.Seek(0, io.SeekStart); err != nil {
return nil, nil, xerrors.Errorf("seek error: %w", err)
}
if manMetadata, err = decoder.Decode(&primMan); err != nil {
return nil, nil, xerrors.Errorf("decode error: %w", err)
}
}
// We can't know the Julia version on an old manifest.
// All newer manifests include a manifest version and a julia version.
if primMan.ManifestFormat == "" {
primMan = primitiveManifest{
JuliaVersion: "unknown",
Dependencies: oldDeps,
}
}
man, err := decodeManifest(&primMan, &manMetadata)
if err != nil {
return nil, nil, xerrors.Errorf("unable to decode manifest dependencies: %w", err)
}
if _, err := r.Seek(0, io.SeekStart); err != nil {
return nil, nil, xerrors.Errorf("seek error: %w", err)
}
// naive parser to get line numbers
pkgParser := naivePkgParser{r: r}
lineNumIdx := pkgParser.parse()
var pkgs ftypes.Packages
var deps ftypes.Dependencies
for name, manifestDeps := range man.Dependencies {
for _, manifestDep := range manifestDeps {
version := depVersion(manifestDep.Version, man.JuliaVersion)
pkgID := manifestDep.UUID
pkg := ftypes.Package{
ID: pkgID,
Name: name,
Version: version,
}
if pos, ok := lineNumIdx[manifestDep.UUID]; ok {
pkg.Locations = []ftypes.Location{
{
StartLine: pos.start,
EndLine: pos.end,
},
}
}
pkgs = append(pkgs, pkg)
if len(manifestDep.DependsOn) > 0 {
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: manifestDep.DependsOn,
})
}
}
}
sort.Sort(pkgs)
sort.Sort(deps)
return pkgs, deps, nil
}
// Returns the effective version of the `dep`.
// stdlib packages do not have a version in the manifest because they are packaged with julia itself
func depVersion(depVersion, juliaVersion string) string {
if depVersion == "" {
return juliaVersion
}
return depVersion
}
// Decodes a primitive manifest using the metadata from parse time.
func decodeManifest(man *primitiveManifest, metadata *toml.MetaData) (*primitiveManifest, error) {
// Decode each dependency into the new manifest
for depName, primDeps := range man.Dependencies {
var newPrimDeps []primitiveDependency
for _, primDep := range primDeps {
newPrimDep, err := decodeDependency(man, primDep, metadata)
if err != nil {
return nil, err
}
newPrimDeps = append(newPrimDeps, newPrimDep)
}
man.Dependencies[depName] = newPrimDeps
}
return man, nil
}
// Decodes a primitive dependency using the metadata from parse time.
func decodeDependency(man *primitiveManifest, dep primitiveDependency, metadata *toml.MetaData) (primitiveDependency, error) {
// Try to decode as []string first where the manifest looks like deps = ["A", "B"]
var possibleDeps []string
err := metadata.PrimitiveDecode(dep.Dependencies, &possibleDeps)
if err == nil {
var possibleUuids []string
for _, depName := range possibleDeps {
primDep := man.Dependencies[depName]
if len(primDep) > 1 {
return primitiveDependency{}, xerrors.Errorf("Dependency %q has invalid format (parsed multiple deps): %s", depName, primDep)
}
possibleUuids = append(possibleUuids, primDep[0].UUID)
}
sort.Strings(possibleUuids)
dep.DependsOn = possibleUuids
return dep, nil
}
// The other possibility is a map where the manifest looks like
// [deps.A.deps]
// B = "..."
var possibleDepsMap map[string]string
err = metadata.PrimitiveDecode(dep.Dependencies, &possibleDepsMap)
if err == nil {
possibleUuids := lo.Values(possibleDepsMap)
sort.Strings(possibleUuids)
dep.DependsOn = possibleUuids
return dep, nil
}
// We don't know what the shape of the data is -- i.e. an invalid manifest
return primitiveDependency{}, err
}