RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 875165
Accepted
4per
4per
Asked:2020-08-30 14:28:27 +0000 UTC2020-08-30 14:28:27 +0000 UTC 2020-08-30 14:28:27 +0000 UTC

如何禁止在项目中添加某些方法的调用?

  • 772

是否有任何技巧禁止在项目代码中添加对某些方法的调用?当然,组织方面的除外。特别是,我想删除使用所有System.Windows.Forms.MessageBox.Show()不包含参数的重载的能力IWin32Window owner:

Show(String)
Show(String, String)
Show(String, String, MessageBoxButtons)
Show(String, String, MessageBoxButtons, MessageBoxIcon)
Show(String, String, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton)
Show(String, String, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton, MessageBoxOptions)
Show(String, String, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton, MessageBoxOptions, Boolean)
Show(String, String, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton, MessageBoxOptions, String)
Show(String, String, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton, MessageBoxOptions, String, HelpNavigator)
Show(String, String, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton, MessageBoxOptions, String, HelpNavigator, Object)
Show(String, String, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton, MessageBoxOptions, String, String)
c#
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Best Answer
    VladD
    2020-09-02T21:51:18Z2020-09-02T21:51:18Z

    这里提到了 Roslyn API 解决方案,它也很容易制作。

    首先,让我们创建一个解析器,如本答案所示。我们无法提出合理的代码修复,因此我们只会进行分析并用红色曲线突出显示。

    我们的分析器代码如下:

    using System.Collections.Immutable;
    using System.Linq;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    using Microsoft.CodeAnalysis.Diagnostics;
    
    namespace OverloadAnalyzer
    {
        [DiagnosticAnalyzer(LanguageNames.CSharp)]
        public class OverloadAnalyzerAnalyzer : DiagnosticAnalyzer
        {
            public const string DiagnosticId = "OverloadAnalyzer";
    
            private static readonly string Title = "MessageBox.Show only with owner";
            private static readonly string MessageFormat = "Use overload with owner";
            private static readonly string Description =
                "You must always specify an owner for MessageBox.Show";
            private const string Category = "Framework usage";
    
            // соберём описание нашего правила
            private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
                DiagnosticId, Title, MessageFormat, Category,
                DiagnosticSeverity.Error, // укажен, что это ошибка, а не предупреждение
                isEnabledByDefault: true, description: Description);
    
            // список диагностик, которые мы выдаём
            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
                ImmutableArray.Create(Rule);
    
            // при инициализации подпишемся на анализ всех команд вызова функции
            public override void Initialize(AnalysisContext context)
            {
                context.RegisterSyntaxNodeAction(AnalyzeSymbol,
                                                 SyntaxKind.InvocationExpression);
            }
    
            // сам анализатор
            private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context)
            {
                // получаем синтаксический узел, приводим его к типу «вызов функции»
                var invocationNode = (InvocationExpressionSyntax)context.Node;
    
                // мелкая эвристика для ускорения: если среди имён в выражении нет Show,
                // дальнейший дорогой семантический анализ не имеет смысла
                if (!invocationNode.DescendantNodes()
                                   .OfType<IdentifierNameSyntax>()
                                   .Any(n => n.Identifier.ValueText == "Show"))
                    return;
    
                // получаем символ у семантической модели
                var symInfo = context.SemanticModel.GetSymbolInfo(invocationNode);
                var methodSymbol = (IMethodSymbol)symInfo.Symbol;
                // если его нет (например, у нас некомпилирующийся код), отваливаем
                if (methodSymbol == null)
                    return;
    
                // проверяем, что это действительно вызов MessageBox.Show
                if (methodSymbol.ContainingAssembly.Name != "System.Windows.Forms")
                    return;
                if (methodSymbol.ContainingType.ToDisplayString() !=
                        "System.Windows.Forms.MessageBox")
                    return;
    
                // если в списке параметров есть параметр с типом IWin32Window, всё хорошо
                if (methodSymbol.Parameters.Any(p => p.Type.Name == "IWin32Window"))
                    return;
    
                // иначе говорим юзеру «атата!»
                var diagnostic = Diagnostic.Create(Rule, invocationNode.GetLocation());
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
    

    我们得到这种行为:

    我是罗斯林更酷!

    如果您还想在构建期间检查代码(如果程序员禁用分析器或不使用 Visual Studio 怎么办?),您还需要编写一个命令行分析器,类似于此处描述的内容,并在何时运行它在构建服务器上构建(或在构建后步骤中)。

    我得到了以下命令行分析器:

    using System;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;
    
    using Microsoft.Build.Locator;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Symbols;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    using Microsoft.CodeAnalysis.MSBuild;
    
    namespace CommandLineOverloadAnalyzer
    {
        class Program
        {
            static void Usage()
            {
                Console.WriteLine("Usage: CommandLineOverloadAnalyzer.exe SolutionPath" +
                                  " MSBuildPath [\"batch\"]");
                Console.WriteLine("if batch specified, no extra output is done");
                Console.WriteLine("if no batch specified, MSBuildPath may be omitted" +
                                  " and will be asked for if needed");
            }
    
            static async Task<int> Main(string[] args)
            {
                if ((args.Length < 1 || args.Length > 3) ||
                    (args.Length == 3 && args[2] != "batch"))
                {
                    Usage();
                    return 1;
                }
                var solutionPath = args[0];
                var msBuildPath = (args.Length > 1) ? args[1] : null;
                bool batchMode = args.Length == 3;
    
                // найдём версию MSBuild
                var visualStudioInstances =
                    MSBuildLocator.QueryVisualStudioInstances().ToArray();
                var instance =
                    // указан путь, выбираем его
                    (msBuildPath != null) ? visualStudioInstances.SingleOrDefault(
                                                       i => i.MSBuildPath == msBuildPath) :
                    // только одна студия, выбираем её
                    visualStudioInstances.Length == 1 ? visualStudioInstances[0] :
                    // в пакетном режиме? падаем
                    batchMode ? null :
                    // спрашиваем у юзера
                    SelectVisualStudioInstance(visualStudioInstances);
                if (instance == null)
                {
                    Console.WriteLine("Cannot determine MSBuild path");
                    return 1;
                }
    
                void Out(string s)
                {
                    if (!batchMode)
                        Console.WriteLine(s);
                }
    
                Out($"Using MSBuild at '{instance.MSBuildPath}' to load projects.");
    
                // Экземпляр MSBuildLocator должен быть зарегистрирован
                // перед MSBuildWorkspace.Create(), иначе MEF-композиция не сработает
                MSBuildLocator.RegisterInstance(instance);
    
                bool hadProblems = false;
                using (var workspace = MSBuildWorkspace.Create())
                {
                    // Напечатаем сообщение при приходе WorkspaceFailed, чтобы
                    // помочь с диагностикой проблем загрузки проектов
                    workspace.WorkspaceFailed += (o, e) => Out(e.Diagnostic.Message);
    
                    Out($"Loading solution '{solutionPath}'");
    
                    // печатаем прогресс при интерактивной загрузке
                    var solution = await workspace.OpenSolutionAsync(solutionPath,
                        batchMode ? null : new ConsoleProgressReporter());
                    Out($"Finished loading solution '{solutionPath}'");
    
                    foreach (var project in solution.Projects)
                    {
                        Out($"Processing project {project.Name}");
                        var compilation = await project.GetCompilationAsync();
    
                        // для каждого файла в проекте отдельное синтаксическое дерево
                        foreach (var syntaxTree in compilation.SyntaxTrees)
                        {
                            var root = await syntaxTree.GetRootAsync();
                            // создаём семантический анализатор
                            var model = compilation.GetSemanticModel(syntaxTree);
                            // получаем все типы переменных
                            foreach (var invocationNode in root.DescendantNodes()
                                                 .OfType<InvocationExpressionSyntax>())
                            {
                                if (!invocationNode.DescendantNodes()
                                                   .OfType<IdentifierNameSyntax>()
                                                   .Any(n => n.Identifier.ValueText == "Show"))
                                    continue;
    
                                var symInfo = model.GetSymbolInfo(invocationNode);
                                var methodSymbol = (IMethodSymbol)symInfo.Symbol;
                                if (methodSymbol == null)
                                    continue;
                                if (methodSymbol.ContainingAssembly.Name !=
                                        "System.Windows.Forms")
                                    continue;
                                if (methodSymbol.ContainingType.ToDisplayString() !=
                                        "System.Windows.Forms.MessageBox")
                                    continue;
    
                                if (methodSymbol.Parameters.Any(p => p.Type.Name ==
                                        "IWin32Window"))
                                    continue;
    
                                var location = invocationNode.GetLocation().GetLineSpan();
                                Console.WriteLine(
                                    $"MessageBox.Show usage is wrong, location = {location}");
                                hadProblems = true;
                            }
                        }
                    }
                }
    
                return hadProblems ? 1 : 0;
            }
    
            private static VisualStudioInstance SelectVisualStudioInstance(
                VisualStudioInstance[] visualStudioInstances)
            {
                Console.WriteLine("Multiple installs of MSBuild detected, please select one:");
                for (int i = 0; i < visualStudioInstances.Length; i++)
                {
                    Console.WriteLine($"Instance {i + 1}");
                    Console.WriteLine($"    Name: {visualStudioInstances[i].Name}");
                    Console.WriteLine($"    Version: {visualStudioInstances[i].Version}");
                    Console.WriteLine(
                        $"    MSBuild Path: {visualStudioInstances[i].MSBuildPath}");
                }
    
                while (true)
                {
                    var userResponse = Console.ReadLine();
                    if (int.TryParse(userResponse, out int instanceNumber) &&
                        instanceNumber > 0 &&
                        instanceNumber <= visualStudioInstances.Length)
                    {
                        return visualStudioInstances[instanceNumber - 1];
                    }
                    Console.WriteLine("Input not accepted, try again.");
                }
            }
    
            private class ConsoleProgressReporter : IProgress<ProjectLoadProgress>
            {
                public void Report(ProjectLoadProgress loadProgress)
                {
                    var projectDisplay = Path.GetFileName(loadProgress.FilePath);
                    if (loadProgress.TargetFramework != null)
                    {
                        projectDisplay += $" ({loadProgress.TargetFramework})";
                    }
    
                    Console.WriteLine(
                        $"{loadProgress.Operation,-15}" +
                        $" {loadProgress.ElapsedTime,-15:m\\:ss\\.fffffff}" +
                        $" {projectDisplay}");
                }
            }
    
            private class SilentProgressReporter : IProgress<ProjectLoadProgress>
            {
                public void Report(ProjectLoadProgress loadProgress) { }
            }
        }
    }
    

    在 Post-Build 步骤中,只需指定

    "<путь к анализатору>\CommandLineOverloadAnalyzer.exe" "$(SolutionPath)" "$(MSBuildBinPath)" batch
    

    在我的测试运行中,我得到了:

    MessageBox.Show 用法错误,location = <完整路径>\RoslynTest\SampleApp\Program.cs: (13,12)-(13,46)

    • 16
  2. Petr Abdulin
    2020-08-30T15:00:17Z2020-08-30T15:00:17Z

    标准号 但例如,resharper 具有外部注释,允许您向第三方代码添加属性,例如Obsolete 。

    • 7
  3. MSDN.WhiteKnight
    2020-09-01T01:23:09Z2020-09-01T01:23:09Z

    您可以为这些目的编写自己的程序,使用解析 MSIL 代码的现有最佳实践。让我们编写以下程序,将程序集的路径作为输入,如果它不包含对禁止方法的调用,则返回代码 0,如果有则返回代码 1(代码在性能方面效率低下,仅作为示例):

    //Утилита для проверки сборки на наличие вызовов запрещенных методов
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Reflection;
    using System.Reflection.Emit;
    
    namespace AssValidator
    {
        class Program
        {
    
            public static OpCode FindOpCode(short val)
            {
                OpCode ret = OpCodes.Nop;
                FieldInfo[] mas = typeof(OpCodes).GetFields();
                for (int i = 0; i < mas.Length; i++)
                {
                    if (mas[i].FieldType == typeof(OpCode))
                    {
                        OpCode opcode = (OpCode)mas[i].GetValue(null);
                        if (opcode.Value == val)
                        {
                            ret = opcode;
                            break;
                        }
                    }
                }
                return ret;
            }
    
            //получает список методов, вызываемых указанным методом
            public static List<MethodBase> GetCalledMethods(MethodBase mi)
            {
                MethodBody mb = null;
    
                //получаем тело метода
                try
                {
                    mb = mi.GetMethodBody();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.GetType().ToString() + " " + ex.Message);                
                }
                if (mb == null) return new List<MethodBase>();
    
                //получаем IL-код
                var msil = mb.GetILAsByteArray();
    
                //получаем модуль, в котором расположен метод
                var module = mi.Module;
    
                List<MethodBase> methods = new List<MethodBase>();
    
                short op;
                int n = 0;
    
                //парсим IL-код...
                while (true)
                {
                    if (n >= msil.Length) break;
    
                    //получаем код операции
                    if (msil[n] == 0xfe)
                        op = (short)(msil[n + 1] | 0xfe00);
                    else
                        op = (short)(msil[n]);
    
                    //найдем имя операции
                    OpCode opcode = FindOpCode(op);
                    string str = opcode.Name;
                    int size = 0;
    
                    //найдем размер операции
                    switch (opcode.OperandType)
                    {
                        case OperandType.InlineBrTarget: size = 4; break;
                        case OperandType.InlineField: size = 4; break;
                        case OperandType.InlineMethod: size = 4; break;
                        case OperandType.InlineSig: size = 4; break;
                        case OperandType.InlineTok: size = 4; break;
                        case OperandType.InlineType: size = 4; break;
                        case OperandType.InlineI: size = 4; break;
                        case OperandType.InlineI8: size = 8; break;
                        case OperandType.InlineNone: size = 0; break;
                        case OperandType.InlineR: size = 8; break;
                        case OperandType.InlineString: size = 4; break;
                        case OperandType.InlineSwitch: size = 4; break;
                        case OperandType.InlineVar: size = 2; break;
                        case OperandType.ShortInlineBrTarget: size = 1; break;
                        case OperandType.ShortInlineI: size = 1; break;
                        case OperandType.ShortInlineR: size = 4; break;
                        case OperandType.ShortInlineVar: size = 1; break;
                        default:
                            throw new Exception("Unknown operand type.");
                    }
                    size += opcode.Size;
    
                    int token = 0;
                    if (str == "call" || str == "callvirt")
                    {
                        //если это вызов метода, найдем токен
                        token = (((msil[n + 1] | (msil[n + 2] << 8)) |
                            (msil[n + 3] << 0x10)) | (msil[n + 4] << 0x18));
    
                        //найдем метод в модуле по токену
                        try
                        {
                            var method = module.ResolveMethod(token);
                            if (!methods.Contains(method)) methods.Add(method);
                        }
                        catch (Exception ex)
                        {
                            //MessageBox.Show(ex.ToString());
                            Console.WriteLine(ex.GetType().ToString() + " " + ex.Message);
                        }
                    }
    
                    n += size; //пропускаем нужное число байтов
                }
    
                return methods;
            }
    
            //получает список методов, вызываемых всеми классами в указанной сборке
            public static List<MethodBase> GetCalledMethods(Assembly ass)
            {
                List<MethodBase> methods = new List<MethodBase>();
                var types = ass.GetTypes();
    
                StringBuilder sb = new StringBuilder();
                foreach (var t in types)
                {                
                    //поиск по методам...
                    var mlist = t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic |
                        BindingFlags.Instance | BindingFlags.Static);
                    foreach (var m in mlist)
                    {
                        var arr = GetCalledMethods(m);
                        foreach (var x in arr)
                        {
                            if (!methods.Contains(x)) methods.Add(x);
                        }
                    }
    
                    //поиск по конструкторам...
                    var clist = t.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic |
                        BindingFlags.Instance | BindingFlags.Static);
                    foreach (var m in clist)
                    {
                        var arr = GetCalledMethods(m);
                        foreach (var x in arr)
                        {
                            if (!methods.Contains(x)) methods.Add(x);
                        }
                    }
    
    
                }
                return methods;
            }
    
            //проверяет указанную сборку на наличие вызовов запрещенных методов
            static bool ValidateAssembly(string path)
            {            
    
                Assembly ass = Assembly.LoadFrom(path); //загружаем сборку
    
                var methods = GetCalledMethods(ass); //получаем все вызываемые методы
    
                foreach (var x in methods)
                {
                    var pars = x.GetParameters();
    
                    //вызов метода System.Windows.Forms.MessageBox.Show(string) запрещен
                    if (x.DeclaringType.ToString() == "System.Windows.Forms.MessageBox"
                        && x.Name == "Show"
                        && pars.Length == 1
                        && pars[0].ParameterType.Name == "String"
                        )
                    {
                        Console.WriteLine("Method call not allowed: MessageBox.Show(String)");
                        return false;
                    }
                }
    
                return true; //не найдено запрещенных методов
            }
    
            //AssValidator - точка входа
            static void Main(string[] args)
            {
    
                if (args.Length == 0)
                {
                    Console.WriteLine("Error: too few arguments!");                
                    Environment.Exit(0xff);
                }
    
                Console.WriteLine("Validating "+args[0]+" ...");
    
                try
                {
                    bool res = ValidateAssembly(args[0]);
                    if (!res) { Console.WriteLine("Assembly is invalid"); Environment.Exit(1); }
                    else { Console.WriteLine("Assembly is valid"); Environment.Exit(0); }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Validation error!");
                    Console.WriteLine(ex.ToString());
                    Environment.Exit(0xff);
                }
            }
        }
    }
    

    让我们编译它(最好在 Release 模式下,因为这个过程非常困难),并放置生成的文件,使其路径为D:\Distr\AssValidator\AssValidator.exe.

    在项目属性中,在“构建事件”选项卡上,设置构建后的事件:

    "D:\Distr\AssValidator\AssValidator.exe" $(TargetPath)
    

    现在,当尝试使用禁止的方法构建项目时,我们得到一个构建错误

    error MSB3073: выход из команды ""D:\Distr\AssValidator\AssValidator.exe" d:\...\WindowsFormsApplication1.exe" с кодом 1.
    

    它看起来像这样:

    VS 截图

    针对 .NET 4.5 / VS 2012 进行了测试。

    该方法的缺点:

    • 程序集验证可以在其中执行代码(例如,静态构造函数)。特别是,这段代码可能会出现异常并破坏一切。

    • 要为非AnyCPU项目工作,您将需要两个版本的检查程序(32 位和 64 位)

    • 虽然如果检查失败则构建失败,但编译后的程序集文件本身仍然存在。同样,您可以创建一个 BAT 文件,以在检查失败时删除该程序集。

    • 7

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    是否可以在 C++ 中继承类 <---> 结构?

    • 2 个回答
  • Marko Smith

    这种神经网络架构适合文本分类吗?

    • 1 个回答
  • Marko Smith

    为什么分配的工作方式不同?

    • 3 个回答
  • Marko Smith

    控制台中的光标坐标

    • 1 个回答
  • Marko Smith

    如何在 C++ 中删除类的实例?

    • 4 个回答
  • Marko Smith

    点是否属于线段的问题

    • 2 个回答
  • Marko Smith

    json结构错误

    • 1 个回答
  • Marko Smith

    ServiceWorker 中的“获取”事件

    • 1 个回答
  • Marko Smith

    c ++控制台应用程序exe文件[重复]

    • 1 个回答
  • Marko Smith

    按多列从sql表中选择

    • 1 个回答
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Suvitruf - Andrei Apanasik 什么是空? 2020-08-21 01:48:09 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5