Files
sbox-public/engine/Sandbox.Compiling/Compiler/Blacklist/BlacklistCodeWalker.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

65 lines
2.4 KiB
C#

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace Sandbox;
internal class BlacklistCodeWalker( SemanticModel semanticModel ) : CSharpSyntaxWalker
{
public static readonly DiagnosticDescriptor BlacklistDescriptor = new( "SB500", "Blacklist Error", "Prohibited type '{0}' used", "Sandbox.Compiling", DiagnosticSeverity.Error, true );
public List<Diagnostic> Diagnostics { get; set; } = [];
record LineSymbol( int Line, ISymbol Symbol );
HashSet<LineSymbol> LineSymbols = [];
private readonly SemanticModel _semanticModel = semanticModel;
static readonly SymbolDisplayFormat FullyQualifiedSymbolFormat = new SymbolDisplayFormat(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
memberOptions: SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeContainingType,
parameterOptions: SymbolDisplayParameterOptions.IncludeType,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes
);
/// <summary>
/// Visits every node type, unless that node type has been explicitly overridden.
/// This gives us complete coverage of every type of possible syntax node.
/// From there we can see if it references any blacklisted symbols.
/// </summary>
public override void DefaultVisit( SyntaxNode node )
{
var symbolInfo = _semanticModel.GetSymbolInfo( node );
// Consider the CandidateSymbols even though you'd assume it can't compile.. It can!
var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault();
if ( symbol is null )
{
base.DefaultVisit( node );
return;
}
var fullyQualifiedName = symbol.ToDisplayString( FullyQualifiedSymbolFormat );
if ( !CompilerRules.Blacklist.Any( x => x.IsMatch( fullyQualifiedName ) ) )
{
base.DefaultVisit( node );
return;
}
// This isn't strictly needed, it just makes the diagnostics a bit neater and easier to test against
var lineSymbol = new LineSymbol( node.GetLocation().GetMappedLineSpan().StartLinePosition.Line, symbol );
if ( LineSymbols.Add( lineSymbol ) == false )
{
base.DefaultVisit( node );
return;
}
Diagnostics.Add( Diagnostic.Create( BlacklistDescriptor, node.GetLocation(), fullyQualifiedName ) );
base.DefaultVisit( node );
}
}