-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
parse.go
166 lines (145 loc) · 4.92 KB
/
parse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package julia
import (
"io"
"sort"
"github.com/BurntSushi/toml"
"golang.org/x/exp/maps"
"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. If that fails, try the new format.
if _, 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 := maps.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
}