Skip to content

Commit

Permalink
CatchOnlySystemExceptionAnalyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
Hecate2 committed Feb 7, 2025
1 parent 4d85198 commit d9361b1
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@
| NC4024 | Usage | Error | MultipleCatchBlockAnalyzer |
| NC4025 | Method | Error | EnumMethodsUsageAnalyzer |
| NC4026 | Usage | Error | SystemDiagnosticsUsageAnalyzer |
| NC4027 | Usage | Warning | CatchOnlySystemExceptionAnalyzer |
102 changes: 102 additions & 0 deletions src/Neo.SmartContract.Analyzer/CatchOnlySystemExceptionAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// =CatchOnlySystemExceptionAnalyzer.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;


namespace Neo.SmartContract.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CatchOnlySystemExceptionAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "CatchOnlySystemException";

private static readonly LocalizableString Title = "Catch System.Exception";
private static readonly LocalizableString MessageFormat = "Neo smart contract supports catching System.Exception only. The compiler will catch all exeptions even if you want to catch a limited class of exception.";
private static readonly LocalizableString Description = "This analyzer enforces catching only System.Exception.";
private const string Category = "Usage";

private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticId, Title, MessageFormat, Category,
DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeCatchClause, SyntaxKind.CatchClause);
}

private void AnalyzeCatchClause(SyntaxNodeAnalysisContext context)
{
var catchClause = (CatchClauseSyntax)context.Node;
var declaration = catchClause.Declaration;

if (declaration == null) return;

var type = declaration.Type;
if (type == null) return;

var exceptionType = context.SemanticModel.GetTypeInfo(type).Type;
if (exceptionType?.ToDisplayString() == "System.Exception") return;

var diagnostic = Diagnostic.Create(Rule, type.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}

[ExportCodeFixProvider(LanguageNames.CSharp)]
public class CatchOnlySystemExceptionCodeFixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(CatchOnlySystemExceptionAnalyzer.DiagnosticId);

public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var diagnostic = context.Diagnostics[0];
var diagnosticSpan = diagnostic.Location.SourceSpan;

var declaration = root.FindNode(diagnosticSpan) as TypeSyntax;
if (declaration == null) return;

context.RegisterCodeFix(
Microsoft.CodeAnalysis.CodeActions.CodeAction.Create(
title: "Change to System.Exception",
createChangedDocument: c => FixCatchTypeAsync(context.Document, declaration, c),
equivalenceKey: "ChangeToSystemException"),
diagnostic);
}

private async Task<Document> FixCatchTypeAsync(Document document, TypeSyntax type, System.Threading.CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken);
var newType = SyntaxFactory.ParseTypeName("System.Exception")
.WithLeadingTrivia(type.GetLeadingTrivia())
.WithTrailingTrivia(type.GetTrailingTrivia());

editor.ReplaceNode(type, newType);
return editor.GetChangedDocument();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// CatchOnlySystemAnalyzerUnitTest.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using Microsoft.CodeAnalysis.Testing;


namespace Neo.SmartContract.Analyzer.UnitTests
{
[TestClass]
public class CatchSystemExceptionTests
{
string testCode = @"
using System;
class Program
{
static void Main()
{
try { }
catch (ArgumentException ex) { }
}
}";

string fixedCode = @"
using System;
class Program
{
static void Main()
{
try { }
catch (System.Exception ex) { }
}
}";

string codeWithoutExceptionType = @"
using System;
class Program
{
static void Main()
{
try { }
catch { }
}
}";

string codeWithCorrectExceptionType = @"
using System;
class Program
{
static void Main()
{
try { }
catch (Exception e) { }
}
}";

DiagnosticResult expectedDiagnostic = DiagnosticResult
.CompilerWarning(CatchOnlySystemExceptionAnalyzer.DiagnosticId)
.WithSpan(9, 16, 9, 33);

[TestMethod]
public async Task TestAnalyzer()
{
var test = new CSharpAnalyzerTest<CatchOnlySystemExceptionAnalyzer, XUnitVerifier>
{
TestCode = testCode
};

test.ExpectedDiagnostics.AddRange([expectedDiagnostic]);
await test.RunAsync();

test = new CSharpAnalyzerTest<CatchOnlySystemExceptionAnalyzer, XUnitVerifier>
{
TestCode = codeWithoutExceptionType
};
// no ExpectedDiagnostics
await test.RunAsync();

test = new CSharpAnalyzerTest<CatchOnlySystemExceptionAnalyzer, XUnitVerifier>
{
TestCode = codeWithCorrectExceptionType
};
// no ExpectedDiagnostics
await test.RunAsync();
}

[TestMethod]
public async Task TestCodeFix()
{
var test = new CSharpCodeFixTest<CatchOnlySystemExceptionAnalyzer, CatchOnlySystemExceptionCodeFixProvider, XUnitVerifier>
{
TestCode = testCode,
FixedCode = fixedCode
};

test.ExpectedDiagnostics.AddRange([expectedDiagnostic]);
await test.RunAsync();
}
}
}

0 comments on commit d9361b1

Please sign in to comment.