MSDN.WhiteKnight - Stack Overflow answers
Ответ на "Как можно запретить добавлять в проект вызовы некоторых методов?"
Answer 875964
Можно написать свою программу для этих целей, воспользовавшись существующими наработками по парсингу 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.
Выглядит это как-то так:
Протестировано для .NET 4.5 / VS 2012.
Недостатки способа:
В ходе проверки сборки может быть выполнен код из нее (например, статические конструкторы). В том числе, этот код может упасть с исключением и нарушить все.
Чтобы работало для не-AnyCPU проектов, понадобится две версии проверяющей программы (32-битная и 64-битная)
Хотя при непрохождении проверки построение завершается с ошибкой, сам скомпилированный файл сборки остается. Опять же, можно создать BAT-файл, удаляющий сборку при неудачном результате проверки.
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.