Skip to content

Commit 2d59df9

Browse files
authored
swift improvement to fix tech (#274)
1 parent 45012ac commit 2d59df9

File tree

3 files changed

+104
-51
lines changed

3 files changed

+104
-51
lines changed

commands/audit/sca/swift/swift.go

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"github.com/jfrog/gofrog/datastructures"
8+
version2 "github.com/jfrog/gofrog/version"
89
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
910
"github.com/jfrog/jfrog-cli-security/utils"
1011
"github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils"
@@ -22,7 +23,12 @@ import (
2223
const (
2324
// VersionForMainModule - We don't have information in swift on the current package, or main module, we only have information on its
2425
// dependencies.
25-
VersionForMainModule = "0.0.0"
26+
VersionForMainModule = "0.0.0"
27+
VERSION_SYNTAX_TYPE_INDEX = 1
28+
EXACT_VERSION_INDEX = 2
29+
FROM_VERSION_INDEX = 3
30+
START_RANGE_INDEX = 4
31+
END_RANGE_INDEX = 5
2632
)
2733

2834
type Dependencies struct {
@@ -36,7 +42,7 @@ func GetTechDependencyLocation(directDependencyName, directDependencyVersion str
3642
for _, descriptorPath := range descriptorPaths {
3743
path.Clean(descriptorPath)
3844
if !strings.HasSuffix(descriptorPath, "Package.swift") {
39-
log.Logger.Warn("Cannot support other files besides Package.swift: %s", descriptorPath)
45+
log.Warn("Cannot support other files besides Package.swift: %s", descriptorPath)
4046
continue
4147
}
4248
data, err := os.ReadFile(descriptorPath)
@@ -90,42 +96,81 @@ func parseSwiftLine(line, directDependencyName, directDependencyVersion, descrip
9096
return foundDependency, tempIndex, startLine, startCol
9197
}
9298

99+
func handleNonRangeMatches(match, name, fixVersion string, index int, submatches []string) string {
100+
log.Debug("Fixing dependency", name, "from version", submatches[index], "to", fixVersion)
101+
return strings.Replace(match, submatches[index], fixVersion, 1)
102+
}
103+
104+
func handleRangeMatches(match, name, fixVersion string, submatches []string) string {
105+
startVersion := submatches[START_RANGE_INDEX]
106+
endVersion := submatches[END_RANGE_INDEX]
107+
if version2.NewVersion(fixVersion).Compare(startVersion) < 1 && version2.NewVersion(fixVersion).Compare(endVersion) == 1 {
108+
// Replace the start of the range with `fixVersion`
109+
log.Debug("Fixing dependency", name, "from start version", startVersion, "to", fixVersion)
110+
return strings.Replace(match, startVersion, fixVersion, 1)
111+
}
112+
return match
113+
}
114+
115+
func updateDependency(content, name, version, fixVersion string) string {
116+
urlPattern := `(?:https://|http://|sso://)?` + regexp.QuoteMeta(strings.TrimSuffix(name, ".git")) + `(?:\.git)?`
117+
pattern := `\.package\(url:\s*"` + urlPattern + `",\s*(exact:\s*"(` + version + `)"|from:\s*"(` + version + `)"|"([\d\.]+)"\.\.\s*<?\s*"([\d\.]+)")\)`
118+
re := regexp.MustCompile(pattern)
119+
result := re.ReplaceAllStringFunc(content, func(match string) string {
120+
submatches := re.FindStringSubmatch(match)
121+
if len(submatches) == 0 {
122+
return match
123+
}
124+
125+
// Handle exact match
126+
if len(submatches) > VERSION_SYNTAX_TYPE_INDEX && strings.Contains(submatches[VERSION_SYNTAX_TYPE_INDEX], "exact") {
127+
return handleNonRangeMatches(match, name, fixVersion, EXACT_VERSION_INDEX, submatches)
128+
}
129+
130+
// Handle from match
131+
if len(submatches) > VERSION_SYNTAX_TYPE_INDEX && strings.Contains(submatches[VERSION_SYNTAX_TYPE_INDEX], "from") {
132+
return handleNonRangeMatches(match, name, fixVersion, FROM_VERSION_INDEX, submatches)
133+
}
134+
135+
// Handle range case
136+
if len(submatches) > 5 && submatches[START_RANGE_INDEX] != "" && submatches[END_RANGE_INDEX] != "" {
137+
return handleRangeMatches(match, name, fixVersion, submatches)
138+
}
139+
return match
140+
})
141+
return result
142+
}
143+
93144
func FixTechDependency(dependencyName, dependencyVersion, fixVersion string, descriptorPaths ...string) error {
94145
for _, descriptorPath := range descriptorPaths {
95146
path.Clean(descriptorPath)
96147
if !strings.HasSuffix(descriptorPath, "Package.swift") {
97-
log.Logger.Warn("Cannot support other files besides Package.swift: %s", descriptorPath)
148+
log.Warn("Cannot support other files besides Package.swift: ", descriptorPath)
98149
continue
99150
}
100151
data, err := os.ReadFile(descriptorPath)
101-
var newLines []string
102152
if err != nil {
153+
log.Warn("Error reading file: ", descriptorPath, err)
103154
continue
104155
}
105-
lines := strings.Split(string(data), "\n")
106-
foundDependency := false
107-
var tempIndex int
108-
for index, line := range lines {
109-
if strings.Contains(line, dependencyName) {
110-
foundDependency = true
111-
tempIndex = index
156+
updatedContent := updateDependency(string(data), dependencyName, dependencyVersion, fixVersion)
157+
if strings.Compare(string(data), updatedContent) != 0 {
158+
if err = os.WriteFile(descriptorPath, []byte(updatedContent), 0644); err != nil {
159+
return fmt.Errorf("failed to write file: %v", err)
112160
}
113-
// This means we are in a new dependency (we cannot find dependency name and version together)
114-
//nolint:gocritic
115-
if index > tempIndex && foundDependency && strings.Contains(line, ".package") {
116-
foundDependency = false
117-
} else if foundDependency && strings.Contains(line, dependencyVersion) {
118-
newLine := strings.Replace(line, dependencyVersion, fixVersion, 1)
119-
newLines = append(newLines, newLine)
120-
foundDependency = false
121-
} else {
122-
newLines = append(newLines, line)
161+
currentDir, err := coreutils.GetWorkingDirectory()
162+
if err != nil {
163+
return fmt.Errorf("could not run swift build due to %s", err)
123164
}
124-
}
125-
output := strings.Join(newLines, "\n")
126-
err = os.WriteFile(descriptorPath, []byte(output), 0644)
127-
if err != nil {
128-
return fmt.Errorf("failed to write file: %v", err)
165+
_, exePath, err := getSwiftVersionAndExecPath()
166+
if err != nil {
167+
return fmt.Errorf("could not run swift build due to %s", err)
168+
}
169+
if _, err = runSwiftCmd(exePath, currentDir, []string{"build"}); err != nil {
170+
return fmt.Errorf("could not run swift build due to %s", err)
171+
}
172+
} else {
173+
log.Debug("No fixes were done in file", descriptorPath)
129174
}
130175
}
131176
return nil

commands/audit/sca/swift/swift_test.go

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,19 @@ func TestBuildSwiftDependencyList(t *testing.T) {
4040
techutils.Swift.GetPackageTypeId() + "github.com/apple/swift-atomics:1.2.0",
4141
techutils.Swift.GetPackageTypeId() + "github.com/apple/swift-collections:1.1.4",
4242
techutils.Swift.GetPackageTypeId() + "github.com/apple/swift-system:1.4.0",
43+
techutils.Swift.GetPackageTypeId() + "github.com/apple/swift-http-types:1.0.2",
4344
techutils.Swift.GetPackageTypeId() + "github.com/apple/swift-nio:2.76.1",
4445
techutils.Swift.GetPackageTypeId() + packageInfo,
4546
}
47+
4648
auditBasicParams := (&xrayutils.AuditBasicParams{}).SetServerDetails(server)
4749
rootNode, uniqueDeps, err := BuildDependencyTree(auditBasicParams)
4850
assert.NoError(t, err)
4951
assert.ElementsMatch(t, uniqueDeps, expectedUniqueDeps, "First is actual, Second is Expected")
5052
assert.NotEmpty(t, rootNode)
5153

5254
assert.Equal(t, rootNode[0].Id, techutils.Swift.GetPackageTypeId()+packageInfo)
53-
assert.Len(t, rootNode[0].Nodes, 9)
55+
assert.Len(t, rootNode[0].Nodes, 11)
5456

5557
child1 := tests.GetAndAssertNode(t, rootNode[0].Nodes, "github.com/apple/swift-algorithms:1.2.0")
5658
assert.Len(t, child1.Nodes, 1)
@@ -64,14 +66,14 @@ func TestGetTechDependencyLocation(t *testing.T) {
6466
defer cleanUp()
6567
currentDir, err := coreutils.GetWorkingDirectory()
6668
assert.NoError(t, err)
67-
locations, err := GetTechDependencyLocation("github.com/apple/swift-algorithms", "1.2.0", filepath.Join(currentDir, "Package.swift"))
69+
locations, err := GetTechDependencyLocation("github.com/apple/swift-algorithms", "1.1.0", filepath.Join(currentDir, "Package.swift"))
6870
assert.NoError(t, err)
6971
assert.Len(t, locations, 1)
7072
assert.Equal(t, *locations[0].PhysicalLocation.Region.StartLine, 10)
7173
assert.Equal(t, *locations[0].PhysicalLocation.Region.StartColumn, 10)
7274
assert.Equal(t, *locations[0].PhysicalLocation.Region.EndLine, 31)
7375
assert.Equal(t, *locations[0].PhysicalLocation.Region.EndColumn, 80)
74-
assert.Contains(t, *locations[0].PhysicalLocation.Region.Snippet.Text, "github.com/apple/swift-algorithms\", from: \"1.2.0\"")
76+
assert.Contains(t, *locations[0].PhysicalLocation.Region.Snippet.Text, "github.com/apple/swift-algorithms\", from: \"1.1.0\"")
7577
}
7678

7779
func TestSwiftLineParse(t *testing.T) {
@@ -90,26 +92,31 @@ func TestSwiftLineParseFoundOnlyDependencyName(t *testing.T) {
9092
assert.Equal(t, startCol, 23)
9193
}
9294

93-
func TestFixTechDependencySingleLocation(t *testing.T) {
94-
_, cleanUp := sca.CreateTestWorkspace(t, filepath.Join("projects", "package-managers", "swift"))
95-
defer cleanUp()
96-
currentDir, err := coreutils.GetWorkingDirectory()
97-
assert.NoError(t, err)
98-
err = FixTechDependency("github.com/apple/swift-nio-http2", "1.0.0", "1.0.1", filepath.Join(currentDir, "Package.swift"))
99-
assert.NoError(t, err)
100-
file, err := os.ReadFile(filepath.Join(currentDir, "Package.swift"))
101-
assert.NoError(t, err)
102-
assert.Contains(t, string(file), ".package(url: \"https://github.com/apple/swift-nio-http2\", \"1.0.1\"..<\"1.19.1\")")
103-
}
95+
func TestFixTechDependencySingleLocation_Range(t *testing.T) {
96+
testCases := []struct {
97+
testName string
98+
dependencyName string
99+
dependencyVersion string
100+
fixVersion string
101+
stringToFind string
102+
}{
103+
{testName: "TestSingleLocation_Range", dependencyName: "github.com/apple/swift-nio-http2", dependencyVersion: "1.8.2", fixVersion: "1.8.3", stringToFind: ".package(url: \"https://github.com/apple/swift-nio-http2\", \"1.8.3\"..<\"1.19.1\")"},
104+
{testName: "TestSingleLocation_From", dependencyName: "github.com/apple/swift-algorithms", dependencyVersion: "1.1.0", fixVersion: "1.2.0", stringToFind: ".package(url: \"https://github.com/apple/swift-algorithms\", from: \"1.2.0\""},
105+
{testName: "TestSingleLocation_Exact", dependencyName: "github.com/apple/swift-http-types", dependencyVersion: "1.0.2", fixVersion: "1.0.3", stringToFind: ".package(url: \"https://github.com/apple/swift-http-types\", exact: \"1.0.3\""},
106+
{testName: "TestNoLocations_FixOutOfRange", dependencyName: "github.com/apple/swift-nio-http2", dependencyVersion: "1.8.3", fixVersion: "1.19.2", stringToFind: ".package(url: \"https://github.com/apple/swift-nio-http2\", \"1.0.0\"..<\"1.19.1\")"},
107+
}
108+
for _, tc := range testCases {
109+
t.Run(tc.testName, func(t *testing.T) {
110+
_, cleanUp := sca.CreateTestWorkspace(t, filepath.Join("projects", "package-managers", "swift"))
111+
defer cleanUp()
112+
currentDir, err := coreutils.GetWorkingDirectory()
113+
assert.NoError(t, err)
114+
err = FixTechDependency(tc.dependencyName, tc.dependencyVersion, tc.fixVersion, filepath.Join(currentDir, "Package.swift"))
115+
assert.NoError(t, err)
116+
file, err := os.ReadFile(filepath.Join(currentDir, "Package.swift"))
117+
assert.NoError(t, err)
118+
assert.Contains(t, string(file), tc.stringToFind)
119+
})
120+
}
104121

105-
func TestFixTechDependencyNoLocations(t *testing.T) {
106-
_, cleanUp := sca.CreateTestWorkspace(t, filepath.Join("projects", "package-managers", "swift"))
107-
defer cleanUp()
108-
currentDir, err := coreutils.GetWorkingDirectory()
109-
assert.NoError(t, err)
110-
err = FixTechDependency("github.com/apple/swift-nio-http2", "1.8.2", "1.8.3", filepath.Join(currentDir, "Package.swift"))
111-
assert.NoError(t, err)
112-
file, err := os.ReadFile(filepath.Join(currentDir, "Package.swift"))
113-
assert.NoError(t, err)
114-
assert.Contains(t, string(file), ".package(url: \"https://github.com/apple/swift-nio-http2\", \"1.0.0\"..<\"1.19.1\")")
115122
}

tests/testdata/projects/package-managers/swift/Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ let package = Package(
88
.macOS(.v10_15),
99
],
1010
dependencies: [
11-
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
11+
.package(url: "https://github.com/apple/swift-algorithms", from: "1.1.0"),
1212
.package(url: "https://github.com/apple/swift-nio-http2", "1.0.0"..<"1.19.1"),
13+
.package(url: "https://github.com/apple/swift-http-types", exact: "1.0.2")
1314
]
1415
)

0 commit comments

Comments
 (0)