forked from Space-Station-Multiverse/RobustToolbox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Analyzer to ban uncached regexes (space-wizards#5107)
Using static Regex functions that take in a pattern is bad, because they constantly have to be re-parsed. Cache the Regex instance.
- Loading branch information
Showing
4 changed files
with
127 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CSharp.Testing; | ||
using Microsoft.CodeAnalysis.Testing; | ||
using Microsoft.CodeAnalysis.Testing.Verifiers; | ||
using NUnit.Framework; | ||
using VerifyCS = | ||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer>; | ||
|
||
namespace Robust.Analyzers.Tests; | ||
|
||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)] | ||
[TestFixture] | ||
public sealed class NoUncachedRegexAnalyzerTest | ||
{ | ||
private static Task Verifier(string code, params DiagnosticResult[] expected) | ||
{ | ||
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>() | ||
{ | ||
TestState = | ||
{ | ||
Sources = { code } | ||
}, | ||
}; | ||
|
||
// ExpectedDiagnostics cannot be set, so we need to AddRange here... | ||
test.TestState.ExpectedDiagnostics.AddRange(expected); | ||
|
||
return test.RunAsync(); | ||
} | ||
|
||
[Test] | ||
public async Task Test() | ||
{ | ||
const string code = """ | ||
using System.Text.RegularExpressions; | ||
public static class Foo | ||
{ | ||
public static void Bad() | ||
{ | ||
Regex.Replace("foo", "bar", "baz"); | ||
} | ||
public static void Good() | ||
{ | ||
var r = new Regex("bar"); | ||
r.Replace("foo", "baz"); | ||
} | ||
} | ||
"""; | ||
|
||
await Verifier(code, | ||
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern. | ||
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using Robust.Roslyn.Shared; | ||
|
||
namespace Robust.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class NoUncachedRegexAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private const string RegexTypeName = "Regex"; | ||
private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}"; | ||
|
||
private static readonly DiagnosticDescriptor Rule = new ( | ||
Diagnostics.IdUncachedRegex, | ||
"Use of uncached static Regex function", | ||
"Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.", | ||
"Usage", | ||
DiagnosticSeverity.Warning, | ||
true); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public static readonly HashSet<string> BadFunctions = | ||
[ | ||
"Count", | ||
"EnumerateMatches", | ||
"IsMatch", | ||
"Match", | ||
"Matches", | ||
"Replace", | ||
"Split" | ||
]; | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation); | ||
} | ||
|
||
private static void CheckInvocation(OperationAnalysisContext context) | ||
{ | ||
if (context.Operation is not IInvocationOperation invocation) | ||
return; | ||
|
||
// All Regex functions we care about are static. | ||
var targetMethod = invocation.TargetMethod; | ||
if (!targetMethod.IsStatic) | ||
return; | ||
|
||
// Bail early. | ||
if (targetMethod.ContainingType.Name != "Regex") | ||
return; | ||
|
||
var regexType = context.Compilation.GetTypeByMetadataName(RegexType); | ||
if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType)) | ||
return; | ||
|
||
if (!BadFunctions.Contains(targetMethod.Name)) | ||
return; | ||
|
||
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation())); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters