ashmind / mirrorsharp

MirrorSharp is a code editor <textarea> built on Roslyn and CodeMirror
BSD 2-Clause "Simplified" License
220 stars 39 forks source link

Question: Extension in Mirrorsharp to run this forbiddentype analyzer #220

Closed Sicos1977 closed 1 year ago

Sicos1977 commented 1 year ago

Hi,

I made a class that uses the Roslyn compiler to check if a user is doing some scripting that is not allowed. For example trying to read something from the local filesystem.

At the moment I just check the written schript when the user tries to save it and then show an error when he is doing something that is not allowed.

Is it possbile that you make an extension in Micrrorsharp so that I can inject this class into Roslyn so that the user already gets a warning shown in Mirrorsharp that he is writing code that is not allowed?

/// <summary>
///     Controleert C# script op verboden types
/// </summary>
/// <remarks>
///     CREDIT: https://gist.github.com/haacked/00de560d00692b7f4859336c747af10e
/// </remarks>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
#pragma warning disable RS1036
internal class ForbiddenTypeAnalyzer : DiagnosticAnalyzer
#pragma warning restore RS1036
{
    #region Consts
    private const string DiagnosticId = nameof(ForbiddenTypeAnalyzer);
    private static readonly LocalizableString Description = "Restricts the set of types that may be used.";
    private const string Title = "Forbidden Type Analyzer";
    private const string MessageFormat = "Access to type {0} is forbidden from the script editor.";
    private const string Category = "API Usage";
    #endregion

    #region Fields
    private readonly HashSet<string> _forbiddenTypeNames;
    #endregion

    #region Properties
    /// <inheritdoc />
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
    #endregion

    #region DefaultTypeAccessDenyList
    private static readonly IEnumerable<string> DefaultTypeAccessDenyList = new[]
    {
        "System.IO.File",
        "System.IO.Directory",
        "System.IO.DirectoryInfo",
        "System.IO.DriveInfo",
        "System.IO.DriveType",
        "System.IO.File",
        "System.IO.FileAccess",
        "System.IO.FileAttributes",
        "System.IO.FileInfo",
        "System.IO.FileMode",
        "System.IO.FileOptions",
        "System.IO.FileShare",
        "System.IO.FileStream",
        "System.IO.FileSystemInfo",
        "System.IO.FileSystemWatcher",
        "System.IO.UnmanagedMemoryStream",
        "System.IO.UnmanagedMemoryAccessor",
        "System.IO.UnmanagedMemoryStream",
        "System.IO.IsolatedStorage",
        "System.IO.MemoryMappedFiles",
        "System.IO.Packaging",
        "System.IO.Compression",
        "System.IO.Pipes",
        "System.IO.Ports",
        "System.Runtime",
        "System.Buffers",
        "System.CodeDom",
        "System.ComponentModel",
        "System.Configuration",
        "System.Data",
        "System.Deployment",
        "System.Diagnostics",
        "System.Drawing",
        "System.Dynamics",
        "System.Formats",
        "System.Management",
        "System.Media",
        "System.Net",
        "System.Reflections",
        "System.Resources",
        "System.Security",
        "System.ServiceModel",
        "System.ServiceProcess",
        "System.StubHelpers",
        "System.Threading",
        "System.Timers",
        "System.Transactions",
        "System.Web",
        "System.Windows",
        "FxResources",
        "Internal",
        "Microsoft",
        "Windows"
    };
    #endregion

    #region Rule
    /// <summary>
    ///    The diagnostic rule for forbidden types
    /// </summary>
    private static readonly DiagnosticDescriptor Rule = new(
        DiagnosticId,
        Title,
        MessageFormat,
        Category,
        DiagnosticSeverity.Warning,
        isEnabledByDefault: true,
        Description);
    #endregion

    #region Constructors
    /// <inheritdoc />
    internal ForbiddenTypeAnalyzer() : this(DefaultTypeAccessDenyList)
    {
    }

    /// <inheritdoc />
    private ForbiddenTypeAnalyzer(IEnumerable<string> forbiddenTypeNames)
    {
        _forbiddenTypeNames = forbiddenTypeNames.ToHashSet(StringComparer.Ordinal);
    }
    #endregion

    #region Initialize
    /// <inheritdoc />
    public override void Initialize(AnalysisContext compilationContext)
    {
        compilationContext.EnableConcurrentExecution();
        compilationContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

        compilationContext.RegisterOperationAction(context =>
            {
                var type = context.Operation switch
                {
                    IObjectCreationOperation objectCreation => objectCreation.Type,
                    IInvocationOperation invocationOperation => invocationOperation.TargetMethod.ContainingType,
                    IMemberReferenceOperation memberReference => memberReference.Member.ContainingType,
                    IArrayCreationOperation arrayCreation => arrayCreation.Type,
                    IAddressOfOperation addressOf => addressOf.Type,
                    IConversionOperation conversion => conversion.OperatorMethod?.ContainingType,
                    IUnaryOperation unary => unary.OperatorMethod?.ContainingType,
                    IBinaryOperation binary => binary.OperatorMethod?.ContainingType,
                    IIncrementOrDecrementOperation incrementOrDecrement => incrementOrDecrement.OperatorMethod
                        ?.ContainingType,
                    _ => throw new NotSupportedException($"Unhandled OperationKind: {context.Operation.Kind}")
                };
                VerifyType(context.ReportDiagnostic, type, context.Operation.Syntax);
            },
            OperationKind.ObjectCreation,
            OperationKind.Invocation,
            OperationKind.EventReference,
            OperationKind.FieldReference,
            OperationKind.MethodReference,
            OperationKind.PropertyReference,
            OperationKind.ArrayCreation,
            OperationKind.AddressOf,
            OperationKind.Conversion,
            OperationKind.UnaryOperator,
            OperationKind.BinaryOperator,
            OperationKind.Increment,
            OperationKind.Decrement);
    }
    #endregion

    #region VerifyType
    private bool VerifyType(Action<Diagnostic> reportDiagnostic, ITypeSymbol type, SyntaxNode syntaxNode)
    {
        do
        {
            if (!VerifyTypeArguments(reportDiagnostic, type, syntaxNode, out type))
                return false;

            var typeName = type?.ToString();
            if (typeName is null)
                return true;

            if (_forbiddenTypeNames.Any(forbiddenTypeName => typeName.StartsWith(forbiddenTypeName, StringComparison.Ordinal)))
            {
                reportDiagnostic(Diagnostic.Create(Rule, syntaxNode.GetLocation(), typeName));
                return false;
            }

            type = type.ContainingType;
        } while (type is not null);

        return true;
    }
    #endregion

    #region VerifyTypeArguments
    private bool VerifyTypeArguments(Action<Diagnostic> reportDiagnostic, ITypeSymbol type, SyntaxNode syntaxNode, out ITypeSymbol originalDefinition)
    {
        switch (type)
        {
            case INamedTypeSymbol namedTypeSymbol:
                originalDefinition = namedTypeSymbol.ConstructedFrom;
                if (Enumerable.Any(namedTypeSymbol.TypeArguments, typeArgument =>
                        typeArgument.TypeKind != TypeKind.TypeParameter &&
                        typeArgument.TypeKind != TypeKind.Error &&
                        !VerifyType(reportDiagnostic, typeArgument, syntaxNode)))
                    return false;

                break;

            case IArrayTypeSymbol arrayTypeSymbol:
                originalDefinition = null;
                return VerifyType(reportDiagnostic, arrayTypeSymbol.ElementType, syntaxNode);

            case IPointerTypeSymbol pointerTypeSymbol:
                originalDefinition = null;
                return VerifyType(reportDiagnostic, pointerTypeSymbol.PointedAtType, syntaxNode);

            default:
                originalDefinition = type?.OriginalDefinition;
                break;
        }

        return true;
    }
    #endregion
}
Sicos1977 commented 1 year ago

Seems there is already an option to do this

I only needed to create this class

#region Class ForbiddenTypeAnalyzerReference
internal class ForbiddenTypeAnalyzerReference : AnalyzerReference
{
    public override string FullPath { get; }
    public override object Id { get; }

    public override ImmutableArray<DiagnosticAnalyzer> GetAnalyzersForAllLanguages()
    {
        return ImmutableArray.Create<DiagnosticAnalyzer>(new ForbiddenTypeAnalyzer());
    }

    public override ImmutableArray<DiagnosticAnalyzer> GetAnalyzers(string language)
    {
        return ImmutableArray.Create<DiagnosticAnalyzer>(new ForbiddenTypeAnalyzer());
    }
}
#endregion

and then reference it like this

            var mirrorSharpOptions = new MirrorSharpOptions
                {
                    SelfDebugEnabled = debug,
                    IncludeExceptionDetails = true
                }
                .SetupCSharp(m =>
                {
                    m.AnalyzerReferences = m.AnalyzerReferences.Add(new ForbiddenTypeAnalyzerReference());
                    m.MetadataReferences = m.MetadataReferences.Clear();
                    foreach (var metadataReference in MetadataReferences.Get)
                    {
                        m.AddMetadataReferencesFromFiles(metadataReference);
                        if (debug) 
                            systemLogs.Insert(ServiceName.Website, $"Adding metadata reference '{metadataReference}'");
                    }
                });