Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ private static void ValidateContractPermission(INamedTypeSymbol symbol, string c

internal void Compile()
{
_diagnostics.AddRange(_engine.AnalyzerDiagnostics);

HashSet<INamedTypeSymbol> processed = new(SymbolEqualityComparer.Default);
foreach (SyntaxTree tree in _engine.Compilation!.SyntaxTrees)
{
Expand Down
29 changes: 29 additions & 0 deletions src/Neo.Compiler.CSharp/CompilationEngine/CompilationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Neo.Json;
using Neo.SmartContract.Analyzer;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
Expand All @@ -34,8 +37,10 @@ public class CompilationEngine(CompilationOptions options)
{
internal Compilation? Compilation;
internal MetadataReference? FrameworkReference;
internal ImmutableArray<Diagnostic> AnalyzerDiagnostics { get; private set; } = [];
internal CompilationOptions Options { get; private set; } = options;
private static readonly MetadataReference[] CommonReferences;
private static readonly ImmutableArray<DiagnosticAnalyzer> NeoAnalyzers = NeoAnalyzerSuite.Create();
private static readonly Guid FrameworkModuleVersionId = typeof(scfx::Neo.SmartContract.Framework.Attributes.SafeAttribute).Assembly.ManifestModule.ModuleVersionId;
private const string FrameworkAssemblyName = "Neo.SmartContract.Framework";
private static readonly StringComparison ProjectPathComparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
Expand Down Expand Up @@ -154,6 +159,7 @@ internal List<CompilationContext> Compile(IEnumerable<string> sourceFiles, IEnum

FrameworkReference = frameworkReference ?? referenceArray.FirstOrDefault(IsTrustedFrameworkReference);
Compilation = CSharpCompilation.Create(null, syntaxTrees, referenceArray, compilationOptions);
AnalyzeCompilation();
return CompileProjectContracts(Compilation);
}

Expand Down Expand Up @@ -224,6 +230,7 @@ private Compilation LoadProjectCompilation(string csproj, bool forceReload)
ResetProjectState();
Compilation = GetCompilation(projectPath);
FrameworkReference = ResolveProjectFrameworkReference(Compilation);
AnalyzeCompilation();
ProjectPath = projectPath;
return Compilation;
}
Expand All @@ -232,6 +239,7 @@ private void ResetProjectState()
{
Compilation = null;
FrameworkReference = null;
AnalyzerDiagnostics = [];
MetaReferences.Clear();
PreparedProjectPath = null;
ProjectPath = null;
Expand Down Expand Up @@ -272,6 +280,27 @@ internal static bool IsTrustedFrameworkReference(MetadataReference reference)
}
}

private void AnalyzeCompilation()
{
if (!Options.RunAnalyzers)
{
AnalyzerDiagnostics = [];
return;
}

AnalyzerDiagnostics = Compilation!
.WithAnalyzers(NeoAnalyzers)
.GetAnalyzerDiagnosticsAsync()
.GetAwaiter()
.GetResult()
.Where(diagnostic => diagnostic.Severity != DiagnosticSeverity.Hidden)
.OrderBy(diagnostic => diagnostic.Location.SourceTree?.FilePath ?? string.Empty, StringComparer.Ordinal)
.ThenBy(diagnostic => diagnostic.Location.IsInSource ? diagnostic.Location.SourceSpan.Start : -1)
.ThenBy(diagnostic => diagnostic.Id, StringComparer.Ordinal)
.ThenBy(diagnostic => diagnostic.GetMessage(), StringComparer.Ordinal)
.ToImmutableArray();
}

public List<CompilationContext> CompileProject(string csproj, List<INamedTypeSymbol> sortedClasses, Dictionary<INamedTypeSymbol, List<INamedTypeSymbol>> classDependencies, List<INamedTypeSymbol?> allClassSymbols, string? targetContractName = null)
{
if (sortedClasses == null || classDependencies == null || allClassSymbols == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public enum DebugType : byte
private CSharpParseOptions? parseOptions = null;
public bool SkipRestoreIfAssetsPresent { get; set; }

/// <summary>
/// Gets or sets whether the Neo contract analyzer suite runs before contract conversion.
/// </summary>
public bool RunAnalyzers { get; set; }

public CSharpParseOptions GetParseOptions()
{
if (parseOptions is null)
Expand Down
1 change: 1 addition & 0 deletions src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Neo.SmartContract.Analyzer\Neo.SmartContract.Analyzer.csproj" />
<ProjectReference Include="..\Neo.SmartContract.Framework\Neo.SmartContract.Framework.csproj">
<Aliases>scfx</Aliases>
</ProjectReference>
Expand Down
3 changes: 2 additions & 1 deletion src/Neo.Compiler.CSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ public static int Main(string[] args)
NoInline = parseResult.GetValue(noInlineOption),
AddressVersion = parseResult.GetValue(addressVersionOption),
PrintAbi = parseResult.GetValue(printAbiOption),
Debug = parseResult.GetValue(debugOption)
Debug = parseResult.GetValue(debugOption),
RunAnalyzers = true
};
return Handle(rootCommand, options, parseResult.GetValue(pathsArgument));
});
Expand Down
1 change: 1 addition & 0 deletions src/Neo.Compiler.CSharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The official C# compiler for Neo smart contracts. This compiler transforms C# co
## Features

- **C# to NeoVM Compilation**: Compile standard C# code to NeoVM bytecode
- **Contract Diagnostics**: Run the Neo contract analyzer suite before bytecode generation
- **Smart Contract Support**: Full support for Neo smart contract development
- **Optimization**: Multiple optimization levels for bytecode efficiency
- **Debug Information**: Generate debug info for testing and debugging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
44 changes: 44 additions & 0 deletions src/Neo.SmartContract.Analyzer/NeoAnalyzerSuite.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (C) 2015-2026 The Neo Project.
//
// NeoAnalyzerSuite.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.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;

namespace Neo.SmartContract.Analyzer;

public static class NeoAnalyzerSuite
{
/// <summary>
/// Creates the complete set of Neo smart contract analyzers.
/// </summary>
public static ImmutableArray<DiagnosticAnalyzer> Create() =>
GetLoadableTypes()
.Where(type => !type.IsAbstract && typeof(DiagnosticAnalyzer).IsAssignableFrom(type))
.OrderBy(type => type.FullName, StringComparer.Ordinal)
.Select(type => (DiagnosticAnalyzer)Activator.CreateInstance(type)!)
.ToImmutableArray();

private static IEnumerable<Type> GetLoadableTypes()
{
try
{
return typeof(NeoAnalyzerSuite).Assembly.GetTypes();
}
catch (ReflectionTypeLoadException exception)
{
return exception.Types.OfType<Type>();
}
}
}
Loading
Loading