Skip to content

Commit e769e9a

Browse files
authored
fix: Fix NullReferenceException in .sln parsing when regex groups are missing (#36)
1 parent 2c0cb6f commit e769e9a

2 files changed

Lines changed: 187 additions & 4 deletions

File tree

src/JD.Efcpt.Build.Tasks/ResolveSqlProjAndInputs.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -531,8 +531,17 @@ private string ResolveSqlProjWithValidation(BuildLog log)
531531
if (!match.Success)
532532
continue;
533533

534-
var name = match.Groups["name"].Value;
535-
var relativePath = match.Groups["path"].Value
534+
var nameGroup = match.Groups["name"];
535+
var pathGroup = match.Groups["path"];
536+
537+
// Skip if required groups are missing or empty
538+
if (!nameGroup.Success || !pathGroup.Success ||
539+
string.IsNullOrWhiteSpace(nameGroup.Value) ||
540+
string.IsNullOrWhiteSpace(pathGroup.Value))
541+
continue;
542+
543+
var name = nameGroup.Value;
544+
var relativePath = pathGroup.Value
536545
.Replace('\\', Path.DirectorySeparatorChar)
537546
.Replace('/', Path.DirectorySeparatorChar);
538547
if (!IsProjectFile(Path.GetExtension(relativePath)))
@@ -696,12 +705,12 @@ private void WriteDumpFile(ResolutionState state)
696705

697706
#if NET7_0_OR_GREATER
698707
[GeneratedRegex("^\\s*Project\\(\"(?<typeGuid>[^\"]+)\"\\)\\s*=\\s*\"(?<name>[^\"]+)\",\\s*\"(?<path>[^\"]+)\",\\s*\"(?<guid>[^\"]+)\"",
699-
RegexOptions.Compiled)]
708+
RegexOptions.Compiled | RegexOptions.Multiline)]
700709
private static partial Regex SolutionProjectLineRegex();
701710
#else
702711
private static readonly Regex _solutionProjectLineRegex = new(
703712
"^\\s*Project\\(\"(?<typeGuid>[^\"]+)\"\\)\\s*=\\s*\"(?<name>[^\"]+)\",\\s*\"(?<path>[^\"]+)\",\\s*\"(?<guid>[^\"]+)\"",
704-
RegexOptions.Compiled);
713+
RegexOptions.Compiled | RegexOptions.Multiline);
705714
private static Regex SolutionProjectLineRegex() => _solutionProjectLineRegex;
706715
#endif
707716
}

tests/JD.Efcpt.Build.Tests/ResolveSqlProjAndInputsTests.cs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,4 +785,178 @@ private static TaskResult ExecuteTaskInvalidSolutionPath(SetupState setup)
785785
var success = task.Execute();
786786
return new TaskResult(setup, task, success);
787787
}
788+
789+
// ========== Malformed Solution File Tests ==========
790+
791+
[Scenario("Gracefully handles malformed project lines in .sln file with missing name")]
792+
[Fact]
793+
public async Task Handles_malformed_sln_missing_name()
794+
{
795+
await Given("solution file with malformed project line (missing name)", SetupMalformedSlnMissingName)
796+
.When("execute task with solution scan", ExecuteTaskSolutionScan)
797+
.Then("task succeeds without exception", r => r.Success)
798+
.And("sql project path resolved from valid line", r => r.Task.SqlProjPath == Path.GetFullPath(r.Setup.SqlProj))
799+
.Finally(r => r.Setup.Folder.Dispose())
800+
.AssertPassed();
801+
}
802+
803+
[Scenario("Gracefully handles malformed project lines in .sln file with missing path")]
804+
[Fact]
805+
public async Task Handles_malformed_sln_missing_path()
806+
{
807+
await Given("solution file with malformed project line (missing path)", SetupMalformedSlnMissingPath)
808+
.When("execute task with solution scan", ExecuteTaskSolutionScan)
809+
.Then("task succeeds without exception", r => r.Success)
810+
.And("sql project path resolved from valid line", r => r.Task.SqlProjPath == Path.GetFullPath(r.Setup.SqlProj))
811+
.Finally(r => r.Setup.Folder.Dispose())
812+
.AssertPassed();
813+
}
814+
815+
[Scenario("Gracefully handles .sln file with empty project name")]
816+
[Fact]
817+
public async Task Handles_sln_with_empty_project_name()
818+
{
819+
await Given("solution file with empty project name", SetupSlnEmptyProjectName)
820+
.When("execute task with solution scan", ExecuteTaskSolutionScan)
821+
.Then("task succeeds without exception", r => r.Success)
822+
.And("sql project path resolved from valid line", r => r.Task.SqlProjPath == Path.GetFullPath(r.Setup.SqlProj))
823+
.Finally(r => r.Setup.Folder.Dispose())
824+
.AssertPassed();
825+
}
826+
827+
[Scenario("Gracefully handles .sln file with empty project path")]
828+
[Fact]
829+
public async Task Handles_sln_with_empty_project_path()
830+
{
831+
await Given("solution file with empty project path", SetupSlnEmptyProjectPath)
832+
.When("execute task with solution scan", ExecuteTaskSolutionScan)
833+
.Then("task succeeds without exception", r => r.Success)
834+
.And("sql project path resolved from valid line", r => r.Task.SqlProjPath == Path.GetFullPath(r.Setup.SqlProj))
835+
.Finally(r => r.Setup.Folder.Dispose())
836+
.AssertPassed();
837+
}
838+
839+
[Scenario("Gracefully handles .sln file with only malformed lines")]
840+
[Fact]
841+
public async Task Handles_sln_with_only_malformed_lines()
842+
{
843+
await Given("solution file with only malformed project lines", SetupSlnOnlyMalformedLines)
844+
.When("execute task with solution scan", ExecuteTaskSolutionScan)
845+
.Then("task fails due to no sql project found", r => !r.Success)
846+
.And("no null reference exceptions occur", r => !r.Setup.Engine.Warnings.Any(w =>
847+
w.Message?.Contains("Object reference not set") == true))
848+
.Finally(r => r.Setup.Folder.Dispose())
849+
.AssertPassed();
850+
}
851+
852+
private static SolutionScanSetup SetupMalformedSlnMissingName()
853+
{
854+
var folder = new TestFolder();
855+
var projectDir = folder.CreateDir("src");
856+
folder.WriteFile("src/App.csproj", "<Project />");
857+
858+
var sqlproj = folder.WriteFile("db/Db.csproj", "<Project Sdk=\"MSBuild.Sdk.SqlProj/3.0.0\" />");
859+
// First line is malformed (missing closing quote for name), second line is valid
860+
var solutionPath = folder.WriteFile("Sample.sln",
861+
"""
862+
Microsoft Visual Studio Solution File, Format Version 12.00
863+
# Visual Studio Version 17
864+
Project("{11111111-1111-1111-1111-111111111111}") = "MalformedApp, "src\App.csproj", "{22222222-2222-2222-2222-222222222222}"
865+
EndProject
866+
Project("{11111111-1111-1111-1111-111111111111}") = "Db", "db\Db.csproj", "{33333333-3333-3333-3333-333333333333}"
867+
EndProject
868+
""");
869+
870+
var engine = new TestBuildEngine();
871+
return new SolutionScanSetup(folder, projectDir, sqlproj, solutionPath, engine);
872+
}
873+
874+
private static SolutionScanSetup SetupMalformedSlnMissingPath()
875+
{
876+
var folder = new TestFolder();
877+
var projectDir = folder.CreateDir("src");
878+
folder.WriteFile("src/App.csproj", "<Project />");
879+
880+
var sqlproj = folder.WriteFile("db/Db.csproj", "<Project Sdk=\"MSBuild.Sdk.SqlProj/3.0.0\" />");
881+
// First line is malformed (missing closing quote for path), second line is valid
882+
var solutionPath = folder.WriteFile("Sample.sln",
883+
"""
884+
Microsoft Visual Studio Solution File, Format Version 12.00
885+
# Visual Studio Version 17
886+
Project("{11111111-1111-1111-1111-111111111111}") = "App", "src\App.csproj, "{22222222-2222-2222-2222-222222222222}"
887+
EndProject
888+
Project("{11111111-1111-1111-1111-111111111111}") = "Db", "db\Db.csproj", "{33333333-3333-3333-3333-333333333333}"
889+
EndProject
890+
""");
891+
892+
var engine = new TestBuildEngine();
893+
return new SolutionScanSetup(folder, projectDir, sqlproj, solutionPath, engine);
894+
}
895+
896+
private static SolutionScanSetup SetupSlnEmptyProjectName()
897+
{
898+
var folder = new TestFolder();
899+
var projectDir = folder.CreateDir("src");
900+
folder.WriteFile("src/App.csproj", "<Project />");
901+
902+
var sqlproj = folder.WriteFile("db/Db.csproj", "<Project Sdk=\"MSBuild.Sdk.SqlProj/3.0.0\" />");
903+
// First line has empty name, second line is valid
904+
var solutionPath = folder.WriteFile("Sample.sln",
905+
"""
906+
Microsoft Visual Studio Solution File, Format Version 12.00
907+
# Visual Studio Version 17
908+
Project("{11111111-1111-1111-1111-111111111111}") = "", "src\App.csproj", "{22222222-2222-2222-2222-222222222222}"
909+
EndProject
910+
Project("{11111111-1111-1111-1111-111111111111}") = "Db", "db\Db.csproj", "{33333333-3333-3333-3333-333333333333}"
911+
EndProject
912+
""");
913+
914+
var engine = new TestBuildEngine();
915+
return new SolutionScanSetup(folder, projectDir, sqlproj, solutionPath, engine);
916+
}
917+
918+
private static SolutionScanSetup SetupSlnEmptyProjectPath()
919+
{
920+
var folder = new TestFolder();
921+
var projectDir = folder.CreateDir("src");
922+
folder.WriteFile("src/App.csproj", "<Project />");
923+
924+
var sqlproj = folder.WriteFile("db/Db.csproj", "<Project Sdk=\"MSBuild.Sdk.SqlProj/3.0.0\" />");
925+
// First line has empty path, second line is valid
926+
var solutionPath = folder.WriteFile("Sample.sln",
927+
"""
928+
Microsoft Visual Studio Solution File, Format Version 12.00
929+
# Visual Studio Version 17
930+
Project("{11111111-1111-1111-1111-111111111111}") = "App", "", "{22222222-2222-2222-2222-222222222222}"
931+
EndProject
932+
Project("{11111111-1111-1111-1111-111111111111}") = "Db", "db\Db.csproj", "{33333333-3333-3333-3333-333333333333}"
933+
EndProject
934+
""");
935+
936+
var engine = new TestBuildEngine();
937+
return new SolutionScanSetup(folder, projectDir, sqlproj, solutionPath, engine);
938+
}
939+
940+
private static SolutionScanSetup SetupSlnOnlyMalformedLines()
941+
{
942+
var folder = new TestFolder();
943+
var projectDir = folder.CreateDir("src");
944+
folder.WriteFile("src/App.csproj", "<Project />");
945+
946+
// Create the SQL project file but don't add it to solution properly
947+
folder.WriteFile("db/Db.csproj", "<Project Sdk=\"MSBuild.Sdk.SqlProj/3.0.0\" />");
948+
// All project lines are malformed or empty
949+
var solutionPath = folder.WriteFile("Sample.sln",
950+
"""
951+
Microsoft Visual Studio Solution File, Format Version 12.00
952+
# Visual Studio Version 17
953+
Project("{11111111-1111-1111-1111-111111111111}") = "", "", "{22222222-2222-2222-2222-222222222222}"
954+
EndProject
955+
Project("{11111111-1111-1111-1111-111111111111}") = "MissingPath, "src\App.csproj", "{33333333-3333-3333-3333-333333333333}"
956+
EndProject
957+
""");
958+
959+
var engine = new TestBuildEngine();
960+
return new SolutionScanSetup(folder, projectDir, "", solutionPath, engine);
961+
}
788962
}

0 commit comments

Comments
 (0)