Skip to content
This repository was archived by the owner on Aug 9, 2025. It is now read-only.

Commit a5b1556

Browse files
Merge pull request #214 from jimkeir/fix_project_scanning
Fix project scanning
2 parents 6ded406 + 4e08be9 commit a5b1556

File tree

4 files changed

+159
-67
lines changed

4 files changed

+159
-67
lines changed

CPPCheckPlugin/AnalyzerCppcheck.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public static string cppcheckExePath()
7575
return analyzerPath;
7676
}
7777

78-
private string getCPPCheckArgs(SourceFilesWithConfiguration configuredFiles, bool analysisOnSavedFile, bool multipleProjects, string tempFileName)
78+
private string getCPPCheckArgs(SourceFilesWithConfiguration configuredFiles, bool analysisOnSavedFile, string tempFileName)
7979
{
8080
if (!configuredFiles.Any())
8181
{
@@ -116,8 +116,7 @@ private string getCPPCheckArgs(SourceFilesWithConfiguration configuredFiles, boo
116116
unitedSuppressionsInfo.UnionWith(readSuppressions(SuppressionStorage.Project, path, filesToAnalyze.First().ProjectName));
117117
}
118118

119-
if (!multipleProjects)
120-
cppheckargs += (" --relative-paths=\"" + filesToAnalyze.First().BaseProjectPath + "\"");
119+
cppheckargs += (" --relative-paths=\"" + _projectBasePath + "\"");
121120
cppheckargs += (" -j " + _numCores.ToString());
122121
if (Properties.Settings.Default.InconclusiveChecksEnabled)
123122
cppheckargs += " --inconclusive ";
@@ -278,7 +277,7 @@ public override void analyze(List<SourceFilesWithConfiguration> allConfiguredFil
278277

279278
List<string> cppheckargs = new List<string>();
280279
foreach (var configuredFiles in allConfiguredFiles)
281-
cppheckargs.Add(getCPPCheckArgs(configuredFiles, analysisOnSavedFile, allConfiguredFiles.Count > 1, createNewTempFileName()));
280+
cppheckargs.Add(getCPPCheckArgs(configuredFiles, analysisOnSavedFile, createNewTempFileName()));
282281

283282
run(cppcheckExePath(), cppheckargs);
284283
}

CPPCheckPlugin/CPPCheckPluginPackage.cs

Lines changed: 146 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Diagnostics;
33
using System.Globalization;
44
using System.Runtime.InteropServices;
@@ -34,6 +34,8 @@ namespace VSPackage.CPPCheckPlugin
3434
[Guid(GuidList.guidCPPCheckPluginPkgString)]
3535
public sealed class CPPCheckPluginPackage : AsyncPackage
3636
{
37+
private int completedFileCount = 0, lastAnalyzerTotalFiles = 0;
38+
3739
public CPPCheckPluginPackage()
3840
{
3941
_instance = this;
@@ -328,7 +330,7 @@ private void documentSavedSync(Document document)
328330

329331
//dynamic project = document.ProjectItem.ContainingProject.Object;
330332
Project project = document.ProjectItem.ContainingProject;
331-
SourceFile sourceForAnalysis = await createSourceFileAsync(document.FullName, currentConfig, project);
333+
SourceFile sourceForAnalysis = await createSourceFileAsync(document.ProjectItem);
332334
if (sourceForAnalysis == null)
333335
return;
334336

@@ -397,12 +399,10 @@ private async Task scanProjectItemForSourceFilesAsync(ProjectItem item, SourceFi
397399
{
398400
await JoinableTaskFactory.SwitchToMainThreadAsync();
399401

400-
Debug.WriteLine("Scanning project item \"" + item.Name + "\"...");
401402
var itemType = await getTypeOfProjectItemAsync(item);
402403

403404
if (itemType == ProjectItemType.folder)
404405
{
405-
Debug.WriteLine("It's a folder, enumerating it.");
406406
foreach (ProjectItem subItem in item.ProjectItems)
407407
{
408408
await scanProjectItemForSourceFilesAsync(subItem, configuredFiles, configuration, project);
@@ -422,13 +422,14 @@ private async Task scanProjectItemForSourceFilesAsync(ProjectItem item, SourceFi
422422
if (!configuredFiles.Exists(itemFileName))
423423
{
424424
// Don't bother rebuilding the entire definition if it already exists.
425-
SourceFile sourceFile = await createSourceFileAsync(itemFileName, configuration, project);
426-
configuredFiles.addOrUpdateFile(sourceFile);
427-
scanProgressUpdated(configuredFiles.Count());
425+
SourceFile sourceFile = await createSourceFileAsync(item);
426+
if (sourceFile != null)
427+
{
428+
configuredFiles.addOrUpdateFile(sourceFile);
429+
scanProgressUpdated(configuredFiles.Count());
430+
}
428431
}
429432
}
430-
else
431-
Debug.WriteLine("It's something else, skipping it.");
432433
}
433434

434435
private async Task<SourceFilesWithConfiguration> getAllSupportedFilesFromProjectAsync(Project project)
@@ -500,14 +501,46 @@ private async Task checkProjectsAsync(List<Project> projects)
500501
SourceFilesWithConfiguration sourceFiles = new SourceFilesWithConfiguration();
501502
sourceFiles.Configuration = config;
502503

503-
await AddTextToOutputWindowAsync("Looking for C++ source files in the project \"" + project.Name + "\"\n");
504-
505504
foreach (ProjectItem projectItem in project.ProjectItems)
506505
{
507506
await scanProjectItemForSourceFilesAsync(projectItem, sourceFiles, config, project);
508507
}
509508

510-
allConfiguredFiles.Add(sourceFiles);
509+
// Although we're using the same base configuration, it's possible for each file to override that.
510+
// Group files into separate configs based on actual parameters. We'll be iterating in reverse order so
511+
// reverse the list first to keep the final order the same.
512+
List<SourceFile> allSourceFiles = sourceFiles.Files.ToList();
513+
allSourceFiles.Reverse();
514+
515+
while (allSourceFiles.Any())
516+
{
517+
SourceFilesWithConfiguration newConfig = new SourceFilesWithConfiguration();
518+
newConfig.Configuration = config;
519+
520+
SourceFile templateFile = allSourceFiles.Last();
521+
newConfig.addOrUpdateFile(templateFile);
522+
allSourceFiles.RemoveAt(allSourceFiles.Count - 1);
523+
524+
for (int i = allSourceFiles.Count - 1; i >= 0; i--)
525+
{
526+
SourceFile otherFile = allSourceFiles[i];
527+
528+
if (otherFile.Macros.All(templateFile.Macros.Contains) && templateFile.Macros.All(otherFile.Macros.Contains) &&
529+
otherFile.MacrosToUndefine.All(templateFile.MacrosToUndefine.Contains) && templateFile.MacrosToUndefine.All(otherFile.MacrosToUndefine.Contains) &&
530+
otherFile.IncludePaths.All(templateFile.IncludePaths.Contains) && templateFile.IncludePaths.All(otherFile.IncludePaths.Contains) &&
531+
otherFile.ProjectName == templateFile.ProjectName
532+
)
533+
{
534+
newConfig.addOrUpdateFile(otherFile);
535+
allSourceFiles.RemoveAt(i);
536+
}
537+
}
538+
539+
if (newConfig.Any())
540+
{
541+
allConfiguredFiles.Add(newConfig);
542+
}
543+
}
511544
}
512545

513546
_ = JoinableTaskFactory.RunAsync(async () => {
@@ -535,14 +568,32 @@ private async Task<ProjectItemType> getTypeOfProjectItemAsync(ProjectItem item)
535568

536569
case "{8E7B96A8-E33D-11D0-A6D5-00C04FB67F6A}":
537570
case VSConstants.ItemTypeGuid.PhysicalFile_string:
538-
var codeModel = item.FileCodeModel;
539-
if (codeModel != null)
571+
if (item.ConfigurationManager != null)
540572
{
541-
switch (codeModel.Language)
542-
{
543-
case CodeModelLanguageConstants.vsCMLanguageVC:
544-
return ProjectItemType.cppFile;
573+
try
574+
{
575+
VCFile vcFile = item.Object as VCFile;
576+
VCProject vcProject = item.ContainingProject.Object as VCProject;
577+
VCFileConfiguration fileConfig = vcFile.FileConfigurations.Item(vcProject.ActiveConfiguration.Name);
578+
579+
if (!fileConfig.ExcludedFromBuild)
580+
{
581+
if (item.FileCodeModel != null && item.FileCodeModel.Language == CodeModelLanguageConstants.vsCMLanguageVC)
582+
{
583+
switch (vcFile.ItemType)
584+
{
585+
case "ClInclude":
586+
return ProjectItemType.headerFile;
587+
588+
case "ClCompile":
589+
return ProjectItemType.cppFile;
590+
}
591+
}
592+
}
545593
}
594+
catch (Exception)
595+
{
596+
}
546597
}
547598

548599
return ProjectItemType.other;
@@ -578,83 +629,110 @@ public void stopAnalysis()
578629

579630
private void runAnalysis(List<SourceFilesWithConfiguration> configuredFiles, bool analysisOnSavedFile)
580631
{
632+
completedFileCount = 0;
633+
581634
foreach (var analyzer in _analyzers)
582635
{
636+
lastAnalyzerTotalFiles = 0;
583637
analyzer.analyze(configuredFiles, analysisOnSavedFile);
584638
}
585639
}
586640

587-
private static void recursiveAddToolDetails(SourceFile sourceForAnalysis, VCConfiguration vcconfig, dynamic subPropertySheets, VCCLCompilerTool tool)
641+
private static void recursiveAddToolDetails(SourceFile sourceForAnalysis, VCConfiguration projectConfig, VCCLCompilerTool tool, dynamic propertySheets, ref bool bInheritDefs, ref bool bInheritUndefs)
588642
{
589-
string macrosToUndefine = tool.UndefinePreprocessorDefinitions;
590-
591-
string[] definitions = tool.PreprocessorDefinitions.Split(';');
592-
bool bInheritDefs = definitions.Contains("\\\"$(INHERIT)\\\"");
593-
for (int i = 0; i < definitions.Length; ++i)
643+
// TODO: If the special keyword "\\\"$(INHERIT)\\\"" appears, we should inherit at that specific point.
644+
if (bInheritDefs)
594645
{
595-
definitions[i] = Environment.ExpandEnvironmentVariables(vcconfig.Evaluate(definitions[i]));
596-
}
646+
string[] macrosToDefine = tool.PreprocessorDefinitions.Split(';');
647+
bInheritDefs = !macrosToDefine.Contains("\\\"$(NOINHERIT)\\\"");
648+
for (int i = 0; i < macrosToDefine.Length; ++i)
649+
{
650+
macrosToDefine[i] = Environment.ExpandEnvironmentVariables(projectConfig.Evaluate(macrosToDefine[i]));
651+
}
597652

598-
sourceForAnalysis.addMacros(definitions);
599-
sourceForAnalysis.addMacrosToUndefine(macrosToUndefine.Split(';'));
653+
sourceForAnalysis.addMacros(macrosToDefine);
654+
}
600655

601-
if (bInheritDefs)
656+
if (bInheritUndefs)
602657
{
603-
foreach (var propSheet in subPropertySheets)
658+
string[] macrosToUndefine = tool.UndefinePreprocessorDefinitions.Split(';');
659+
bInheritUndefs = !macrosToUndefine.Contains("\\\"$(NOINHERIT)\\\"");
660+
for (int i = 0; i < macrosToUndefine.Length; ++i)
604661
{
605-
var test = propSheet.Name;
662+
macrosToUndefine[i] = Environment.ExpandEnvironmentVariables(projectConfig.Evaluate(macrosToUndefine[i]));
663+
}
664+
665+
sourceForAnalysis.addMacrosToUndefine(macrosToUndefine);
666+
}
606667

607-
dynamic toolsCollection = propSheet.Tools;
608-
foreach (var subTool in toolsCollection)
668+
if (propertySheets != null && (bInheritDefs || bInheritUndefs))
669+
{
670+
// Scan any inherited property sheets.
671+
foreach (var propSheet in propertySheets)
672+
{
673+
VCCLCompilerTool propSheetTool = (VCCLCompilerTool)propSheet.Tools.Item("VCCLCompilerTool");
674+
if (propSheetTool != null)
609675
{
610-
// Project-specific includes
611-
if (implementsInterface(subTool, "Microsoft.VisualStudio.VCProjectEngine.VCCLCompilerTool"))
612-
{
613-
recursiveAddToolDetails(sourceForAnalysis, vcconfig, propSheet.PropertySheets, subTool);
614-
}
676+
// When looping over the inherited property sheets, don't allow rules to filter back up the way.
677+
bool bInheritDefs1 = bInheritDefs, bInheritUndefs1 = bInheritUndefs;
678+
recursiveAddToolDetails(sourceForAnalysis, projectConfig, propSheetTool, propSheet.PropertySheets, ref bInheritDefs1, ref bInheritUndefs1);
615679
}
616680
}
617681
}
618682
}
619683

620-
private static async Task<SourceFile> createSourceFileAsync(string filePath, Configuration configuration, Project project)
684+
private static async Task<SourceFile> createSourceFileAsync(ProjectItem item)
621685
{
622686
try
623687
{
624688
await Instance.JoinableTaskFactory.SwitchToMainThreadAsync();
625689

626-
Debug.Assert(isVisualCppProjectKind(project.Kind));
627-
628-
VCProject vcProject = project.Object as VCProject;
629-
VCConfiguration vcconfig = vcProject.ActiveConfiguration;
690+
Debug.Assert(isVisualCppProjectKind(item.ContainingProject.Kind));
630691

631-
String configurationName = configuration.ConfigurationName;
632-
dynamic config = ((dynamic)project.Object).Configurations.Item(configurationName);
692+
VCFile vcFile = item.Object as VCFile;
693+
VCProject vcProject = item.ContainingProject.Object as VCProject;
694+
VCConfiguration vcconfig = vcProject.ActiveConfiguration;
695+
VCFileConfiguration fileConfig = vcFile.FileConfigurations.Item(vcconfig.Name);
633696

634-
// TODO: This no longer works, why?
635-
// string toolSetName = ((dynamic)vcconfig).PlatformToolsetFriendlyName;
636-
string toolSetName = null;
697+
string toolSetName = ((dynamic)vcconfig).PlatformToolsetFriendlyName;
637698

638699
string projectDirectory = vcProject.ProjectDirectory;
639-
string projectName = project.Name;
700+
string projectName = vcProject.Name;
640701
SourceFile sourceForAnalysis = null;
641-
dynamic toolsCollection = vcconfig.Tools;
642-
foreach (var tool in toolsCollection)
702+
703+
if (item.FileCount > 0)
643704
{
644-
// Project-specific includes
645-
if (implementsInterface(tool, "Microsoft.VisualStudio.VCProjectEngine.VCCLCompilerTool"))
705+
bool bInheritDefs = true, bInheritUndefs = true;
706+
string[] includePaths = { };
707+
708+
// Do the file-level first in case it disables inheritance. Include files don't have file-level config.
709+
if (implementsInterface(fileConfig.Tool, "Microsoft.VisualStudio.VCProjectEngine.VCCLCompilerTool"))
710+
{
711+
sourceForAnalysis = new SourceFile(item.FileNames[1], projectDirectory, projectName, toolSetName);
712+
includePaths = fileConfig.Tool.FullIncludePath.Split(';');
713+
714+
// Other details may be gathered from the file, project or any inherited property sheets.
715+
recursiveAddToolDetails(sourceForAnalysis, vcconfig, fileConfig.Tool, null, ref bInheritDefs, ref bInheritUndefs);
716+
}
717+
718+
// Now get the full include path
719+
VCCLCompilerTool projectTool = (VCCLCompilerTool)vcconfig.Tools.Item("VCCLCompilerTool");
720+
if (projectTool != null && implementsInterface(projectTool, "Microsoft.VisualStudio.VCProjectEngine.VCCLCompilerTool"))
646721
{
647-
sourceForAnalysis = new SourceFile(filePath, projectDirectory, projectName, toolSetName);
722+
if (sourceForAnalysis == null)
723+
{
724+
sourceForAnalysis = new SourceFile(item.FileNames[1], projectDirectory, projectName, toolSetName);
725+
includePaths = projectTool.FullIncludePath.Split(';');
726+
}
648727

649-
string includes = tool.FullIncludePath;
650-
string[] includePaths = includes.Split(';');
728+
// Take the full include path from file level, which is already fully resolved.
651729
for (int i = 0; i < includePaths.Length; ++i)
652730
{
653731
includePaths[i] = Environment.ExpandEnvironmentVariables(vcconfig.Evaluate(includePaths[i]));
654732
}
655733
sourceForAnalysis.addIncludePaths(includePaths);
656734

657-
recursiveAddToolDetails(sourceForAnalysis, vcconfig, vcconfig.PropertySheets, tool);
735+
recursiveAddToolDetails(sourceForAnalysis, vcconfig, projectTool, vcconfig.PropertySheets, ref bInheritDefs, ref bInheritUndefs);
658736
}
659737
}
660738

@@ -720,15 +798,24 @@ private async void checkProgressUpdated(object sender, ICodeAnalyzer.ProgressEve
720798
if (e.FilesChecked == 0 || e.TotalFilesNumber == 0)
721799
label = "cppcheck analysis in progress...";
722800
else
723-
label = "cppcheck analysis in progress (" + e.FilesChecked + " out of " + e.TotalFilesNumber + " files checked)";
801+
label = "cppcheck analysis in progress (" + (completedFileCount + e.FilesChecked) + " out of " + (completedFileCount + e.TotalFilesNumber) + " files checked)";
802+
803+
lastAnalyzerTotalFiles = e.TotalFilesNumber;
724804

725805
statusBar.Progress(true, label, progress, 100);
726806
}
727807
else
728808
{
729809
label = "cppcheck analysis completed";
810+
completedFileCount += lastAnalyzerTotalFiles;
811+
lastAnalyzerTotalFiles = 0;
730812

731-
statusBar.Progress(true, label, progress, 100);
813+
try
814+
{
815+
// This raises an exception during shutdown.
816+
statusBar.Progress(true, label, progress, 100);
817+
}
818+
catch (Exception) { }
732819

733820
_ = System.Threading.Tasks.Task.Run(async delegate
734821
{

CPPCheckPlugin/ICodeAnalyzer.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,13 @@ private void analyzerThreadFunc(string analyzerExePath)
175175
{
176176
_terminateThread = false;
177177
foreach (var arguments in _allArguments)
178-
startAnalyzerProcess(analyzerExePath, arguments);
178+
{
179+
// Don't start subsequent processes if we've been requested to cancel.
180+
if (!_terminateThread)
181+
{
182+
startAnalyzerProcess(analyzerExePath, arguments);
183+
}
184+
}
179185
}
180186

181187
private void startAnalyzerProcess(string analyzerExePath, string arguments)
@@ -204,7 +210,7 @@ private void startAnalyzerProcess(string analyzerExePath, string arguments)
204210
process.OutputDataReceived += new DataReceivedEventHandler(this.analyzerOutputHandler);
205211
process.ErrorDataReceived += new DataReceivedEventHandler(this.analyzerOutputHandler);
206212

207-
_ = CPPCheckPluginPackage.AddTextToOutputWindowAsync("Starting analyzer with arguments: " + arguments + "\n");
213+
_ = CPPCheckPluginPackage.AddTextToOutputWindowAsync("Starting analyzer with arguments: \"" + analyzerExePath + "\" " + arguments + "\n");
208214

209215
var timer = Stopwatch.StartNew();
210216
// Start the process.

CPPCheckPlugin/SourceFile.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public void addIncludePaths(IEnumerable<string> paths)
103103

104104
public void addMacro(string macro)
105105
{
106-
if (!String.IsNullOrEmpty(macro))
106+
if (!String.IsNullOrEmpty(macro) && !macro.Equals("\\\"\\\""))
107107
{
108108
_activeMacros.Add(macro);
109109
}
@@ -119,7 +119,7 @@ public void addMacros(IEnumerable<string> macros)
119119

120120
public void addMacroToUndefine(string macro)
121121
{
122-
if (!String.IsNullOrEmpty(macro))
122+
if (!String.IsNullOrEmpty(macro) && !macro.Equals("\\\"\\\""))
123123
{
124124
_macrosToUndefine.Add(macro);
125125
}

0 commit comments

Comments
 (0)