Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring symbolic execution analysis to run once per mehod #1281

Closed
wants to merge 1 commit into from

Conversation

valhristov
Copy link
Contributor

@valhristov valhristov commented Mar 28, 2018

Each existing SE rule was converted to a factory and analyzer (state) classes. The existing SE checks are not changed. There is a new diagnostic analyzer - SymbolicExecutionAnalyzer, which uses the registered factories to create analyzers for the corresponding rule when a SE analysis is requested.

Currently the factories are registered (hardcoded) in the SymbolicExecutionAnalyzer class' constructor. This could be changed in the future to reverse the dependency, but it will require more extensive testing, because we will introduce dependencies between diagnostic analyzers, which are generally meant to be independent. The hypothesis seems feasible, but it is not known whether it will work or not.

In general this will reduce the execution time of the its from ~600s to <500s (on my machine).

Best viewed without whitespace:
https://github.com/SonarSource/sonar-csharp/pull/1281/files?w=1

@@ -1848,32 +1848,6 @@
},
{
"id": "S3900",
"message": "Refactor this method to add validation of parameter 'modelType' before using it.",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all SE checks run simultaneously we could generate bigger/or-shorter exploded graphs, thus creating some FNs in large methods. We could investigate the further increase of the EG limits in the future.

{
context.RegisterExplodedGraphBasedAnalysis((e, c) => CheckForRedundantConditions(e, c));
}
ISymbolicExecutionAnalyzer ISymbolicExecutionAnalyzerFactory.Create(CSharpExplodedGraph explodedGraph) =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface is internal because the CSEG is internal. I did not wanted to make the whole class internal, hence the explicit implementation

VerifyAnalyzer(new[] { path }, new[] { diagnosticAnalyzer }, null, options, additionalReferences);
}

public static void VerifyAnalyzer(string path, IEnumerable<SonarDiagnosticAnalyzer> diagnosticAnalyzers,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding overload that allows you to run two analyzers in one test. This was needed for the InvalidCast rule which was split into two.

@@ -146,19 +146,16 @@ public void Walk()
OnExplorationEnded();
}

internal void AddExplodedGraphCheck<T>(T check)
public T GetOrAddCheck<T>(Func<T> factory)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the SE analyzer registers states that subscribe for check events, adding a check does not replace an existing one, but reuses it.

{
explodedGraph.ExplorationEnded -= OnExplorationEnded;

analyzerInstances.ForEach(analyzer => analyzer.Dispose());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsubscribe from events.

private void Analyze(CSharpExplodedGraph explodedGraph, SyntaxNodeAnalysisContext context)
{
var analyzerInstances = analyzerFactories
.Where(factory => factory.IsEnabled(context))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The analyzer factories could be disabled for some methods (e.g. for tests or private methods)

@@ -33,8 +34,7 @@
namespace SonarAnalyzer.Rules.CSharp
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
[Rule(DiagnosticId)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rule attribute is removed because we have unit tests that ensure each rule is registered in exactly one diagnostic analyzer. InvalidCast is reported from two analyzers.

@@ -32,7 +32,7 @@ namespace SonarAnalyzer.SymbolicExecution
{
internal abstract class AbstractExplodedGraph
{
internal const int MaxStepCount = 1000;
internal const int MaxStepCount = 2000;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Increased the exploration limit to avoid FNs.

namespace SonarAnalyzer.Rules.CSharp
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
[Rule(NullPointerDereference.DiagnosticId)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that the Symbolic Execution Engine has to know about the rules using it. As I told you I prefer to have all analyzer referencing the SEE.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1
It is not difficult to revert the dependency, but it will require significant testing, as we do not have dependent rules now and there could be potentially many cases where it could break. The current solution is cheap and still provides significant performance improvement.

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
analyzerFactories.SelectMany(factory => factory.SupportedDiagnostics).ToImmutableArray();

public SymbolicExecutionAnalyzer() : this(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here. IMO SEE should not know about other classes.

analyzerInstances.ForEach(analyzer => analyzer.Dispose());
}

void OnExplorationEnded(object sender, EventArgs e) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this shouldn't be this class reporting issues but the analyzers themselves. This class should only be responsible of doing the symbolic execution (the walk).

@valhristov
Copy link
Contributor Author

I would consider this PR as POC, we could look for better implementation in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants