diff --git a/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs b/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs index 0322d910f..1964663fa 100644 --- a/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs @@ -15,7 +15,6 @@ namespace Microsoft.ComponentDetection.Detectors.Go; using Microsoft.ComponentDetection.Contracts.Internal; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.Extensions.Logging; -using MoreLinq; using Newtonsoft.Json; public class GoComponentWithReplaceDetector : FileComponentDetector, IExperimentalDetector @@ -257,7 +256,7 @@ private async Task UseGoCliToScanAsync(string location, ISingleFileCompone return true; } - private void AddGoComponent(IList goComponents, string line, ISingleFileComponentRecorder singleFileComponentRecorder) + private void TryRegisterDependencyFromModLine(string line, ISingleFileComponentRecorder singleFileComponentRecorder) { if (line.Trim().StartsWith("//")) { @@ -267,7 +266,7 @@ private void AddGoComponent(IList goComponents, string line, ISingl if (this.TryToCreateGoComponentFromModLine(line, out var goComponent)) { - goComponents.Add(goComponent); + singleFileComponentRecorder.RegisterUsage(new DetectedComponent(goComponent)); } else { @@ -277,90 +276,42 @@ private void AddGoComponent(IList goComponents, string line, ISingl } } - private void ReplaceGoComponents(IList goComponents, string line, ISingleFileComponentRecorder singleFileComponentRecorder) - { - if (this.TryToCreateReplacementGoComponentFromModLine(line, out var goComponent)) - { - var goComponentsWithReplacementVersion = goComponents.Where(component => component.Name == goComponent.Name); - if (goComponentsWithReplacementVersion.Any()) - { - foreach (var component in goComponentsWithReplacementVersion) - { - if (component.Name == goComponent.Name) - { - component.Version = goComponent.Version; - } - } - } - } - else - { - var lineTrim = line.Trim(); - this.Logger.LogWarning("Line could not be parsed for component [{LineTrim}]", lineTrim); - singleFileComponentRecorder.RegisterPackageParseFailure(lineTrim); - } - } - - private void TryRegisterDependencyFromModLine(IList goComponents, ISingleFileComponentRecorder singleFileComponentRecorder) - { - goComponents.ForEach(goComponent => singleFileComponentRecorder.RegisterUsage(new DetectedComponent(goComponent))); - } - private async Task ParseGoModFileAsync( ISingleFileComponentRecorder singleFileComponentRecorder, IComponentStream file, GoGraphTelemetryRecord goGraphTelemetryRecord) { using var reader = new StreamReader(file.Stream); - var goComponents = new List(); - var inRequireBlock = false; - var inReplaceBlock = false; + var startString = "require "; // There can be multiple require( ) sections in go 1.17+. loop over all of them. while (!reader.EndOfStream) { var line = await reader.ReadLineAsync(); - if (line != null && line.StartsWith("require (")) - { - inRequireBlock = true; - continue; - } - else if (line != null && line.StartsWith("replace (")) - { - inReplaceBlock = true; - continue; - } - else if (line != null && line.StartsWith(")")) - { - inRequireBlock = false; - inReplaceBlock = false; - continue; - } - if (line != null && line.StartsWith("require ")) - { - this.AddGoComponent(goComponents, line[8..], singleFileComponentRecorder); - } - else if (line != null && line.StartsWith("replace ")) + while (line != null && !line.StartsWith("require (")) { - this.ReplaceGoComponents(goComponents, line[8..], singleFileComponentRecorder); - } + if (line.StartsWith("go ")) + { + goGraphTelemetryRecord.GoModVersion = line[3..].Trim(); + } - if (inRequireBlock) - { - this.AddGoComponent(goComponents, line, singleFileComponentRecorder); - } - else if (inReplaceBlock) - { - this.ReplaceGoComponents(goComponents, line, singleFileComponentRecorder); + // In go >= 1.17, direct dependencies are listed as "require x/y v1.2.3", and transitive dependencies + // are listed in the require () section + if (line.StartsWith(startString)) + { + this.TryRegisterDependencyFromModLine(line[startString.Length..], singleFileComponentRecorder); + } + + line = await reader.ReadLineAsync(); } - else if (line != null && line.StartsWith("go ")) + + // Stopping at the first ) restrict the detection to only the require section. + while ((line = await reader.ReadLineAsync()) != null && !line.EndsWith(")")) { - goGraphTelemetryRecord.GoModVersion = line[3..].Trim(); + this.TryRegisterDependencyFromModLine(line, singleFileComponentRecorder); } } - - this.TryRegisterDependencyFromModLine(goComponents, singleFileComponentRecorder); } private bool TryToCreateGoComponentFromModLine(string line, out GoComponent goComponent) @@ -380,23 +331,6 @@ private bool TryToCreateGoComponentFromModLine(string line, out GoComponent goCo return true; } - private bool TryToCreateReplacementGoComponentFromModLine(string line, out GoComponent goComponent) - { - var lineComponents = Regex.Split(line.Trim(), @"\s+"); - - if (lineComponents.Length < 2) - { - goComponent = null; - return false; - } - - var name = lineComponents[2]; - var version = lineComponents[3]; - goComponent = new GoComponent(name, version); - - return true; - } - // For more information about the format of the go.sum file // visit https://golang.org/cmd/go/#hdr-Module_authentication_using_go_sum private void ParseGoSumFile( diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs index d2fb19ccc..c8e9c5f5a 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs @@ -315,66 +315,6 @@ public async Task TestGoModDetector_DetectorOnlyDetectInsideRequireSectionAsync( discoveredComponents.Where(component => component.Component.Id == "github.com/kr/pretty v0.1.0 - Go").Should().ContainSingle(); } - [TestMethod] - public async Task TestGoModDetector_DetectReplaceSectionAsync() - { - var goMod = - @"module github.com/Azure/azure-storage-blob-go - -require ( - github.com/Azure/azure-pipeline-go v0.2.1 - github.com/docker/distribution v2.7.1+incompatible - github.com/Masterminds/sprig/v3 v3.1.0 - -) -replace ( - github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible - github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d -) -"; - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("go.mod", goMod) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - - var detectedComponents = componentRecorder.GetDetectedComponents(); - detectedComponents.Should().HaveCount(3); - - var discoveredComponents = detectedComponents.ToArray(); - discoveredComponents.Where(component => component.Component.Id == "github.com/Azure/azure-pipeline-go v0.2.1 - Go").Should().ContainSingle(); - discoveredComponents.Where(component => component.Component.Id == "github.com/Masterminds/sprig/v3 v3.1.0 - Go").Should().ContainSingle(); - discoveredComponents.Where(component => component.Component.Id == "github.com/docker/distribution v0.0.0-20191216044856-a8371794149d - Go").Should().ContainSingle(); - } - - [TestMethod] - public async Task TestGoModDetector_SkipsGoSumFilesWithReplaceAsync() - { - var goMod = - @"module contoso.com/greetings -go 1.18 - -require github.com/go-sql-driver/mysql v1.7.1 // indirect -replace github.com/go-sql-driver/mysql => github.com/go-sql-driver/mysql v1.7.3"; - - var goSum = - @"github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U="; - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("go.mod", goMod) - .WithFile("go.mod", goMod, new[] { "go.mod" }) - .WithFile("go.sum", goSum) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().ContainSingle(); - - var component = componentRecorder.GetDetectedComponents().First(); - component.Component.Id.Should().Be("github.com/go-sql-driver/mysql v1.7.3 - Go"); - } - [TestMethod] public async Task TestGoDetector_GoCommandNotFoundAsync() {