ILWeaving 帮助 - [ValidSystemPath] 属性

问题描述 投票:0回答:1

问题

我正在使用 Mono.Cecil 来 IL Weave 字符串属性获取器,这些获取器具有我的自定义 [ValidSystemPath] 属性。该属性的目的是确保属性只返回文件名和路径等的有效系统字符。问题是,代码当前不工作,但在编织期间没有引发任何异常。我是编织和 IL 的新手,所以我会从指导手中受益。

编织前的代码(C#)

private string path = "test|.txt";
[ValidSystemPath]
public string Path => path;

编织后的预期代码(C#)

这实际上是我试图编织的代码的灵感来源......https://stackoverflow.com/a/23182807/1995360

public string Path {
    get {
        string ReplaceIllegal(string p)
        {
            char[] invalid = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).ToArray();
            return string.Join("_", p.Split(invalid));
        }
        
        return ReplaceIllegal(path);
    }
}

我想使用嵌套方法,因为如果 getter 包含条件,则可能有多个 return 语句,因此我需要在每个 return 语句之前对嵌套方法进行简单调用。

Weaver 代码(C#)

private static void ValidSystemPath(ModuleDefinition module, TypeDefinition type, PropertyDefinition property)
{
    // Getter method - site of injection
    MethodDefinition getter = property.GetMethod;
    ILProcessor getterProcessor = getter.Body.GetILProcessor();

    // Import the methods
    MethodReference joinMethod = module.ImportReference(typeof(string).GetMethod("Join", new Type[] { typeof(string), typeof(string[]) }));
    MethodReference splitMethod = module.ImportReference(typeof(string).GetMethod("Split", new Type[] { typeof(char[]) }));
    MethodReference getInvalidPathCharsMethod = module.ImportReference(typeof(Path).GetMethod("GetInvalidPathChars", new Type[] { }));
    MethodReference getInvalidFileNameCharsMethod = module.ImportReference(typeof(Path).GetMethod("GetInvalidFileNameChars", new Type[] { }));
    MethodReference concatMethod = module.ImportReference(typeof(Enumerable).GetMethod("Concat"));
    MethodReference toArrayMethod = module.ImportReference(typeof(Enumerable).GetMethod("ToArray"));
    //MethodReference toArrayMethod = module.ImportReference(typeof(Enumerable).GetMethodExt("ToArray", new Type[] { typeof(IEnumerable<char>) }));

    // Create new nested method in getter
    MethodDefinition nested = new(
        $"<{getter.Name}>g__ReplaceIllegalChars|2_0",
        Mono.Cecil.MethodAttributes.Assembly | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Static,
        module.TypeSystem.String
    );
    type.Methods.Add(nested);

    // Write instructions for method
    ILProcessor nestedProcessor = nested.Body.GetILProcessor();
    nestedProcessor.Emit(OpCodes.Nop);
    nestedProcessor.Emit(OpCodes.Call, getInvalidFileNameCharsMethod);
    nestedProcessor.Emit(OpCodes.Call, getInvalidPathCharsMethod);
    nestedProcessor.Emit(OpCodes.Call, concatMethod);
    nestedProcessor.Emit(OpCodes.Call, toArrayMethod);
    nestedProcessor.Emit(OpCodes.Stloc_0); // Return value is top stack
    nestedProcessor.Emit(OpCodes.Ldstr, "_");
    nestedProcessor.Emit(OpCodes.Ldarg_0);
    nestedProcessor.Emit(OpCodes.Ldloc_0);
    nestedProcessor.Emit(OpCodes.Callvirt, splitMethod); // Non static
    nestedProcessor.Emit(OpCodes.Call, joinMethod);
    nestedProcessor.Emit(OpCodes.Stloc_1);
    nestedProcessor.Emit(OpCodes.Ldloc_1);

    //getterProcessor.Body.SimplifyMacros();
    // Add nested call before each return
    IEnumerable<Instruction> returnInstructions = getterProcessor.Body.Instructions.Where(instruction => instruction.OpCode == OpCodes.Ret);
    returnInstructions.ToList().ForEach(ret => getterProcessor.InsertBefore(ret, Instruction.Create(OpCodes.Call, nested)));
    /*foreach (Instruction ret in returnInstructions)
    {
        // Call nested method and return that value
        getterProcessor.InsertBefore(ret, Instruction.Create(OpCodes.Call, nested));
    }*/
    //getterProcessor.Body.OptimizeMacros();
}

织布工分解

  1. 找到注射部位并创建一个 ILProcessor。
  2. 为我们将进行的所有方法调用导入方法引用(字符串 Split/Join、Path GetInvalidPathChars/GetInvalidFileNameChars 和 Enumerable Concat/ToArray)。
  3. 创建嵌套方法并添加。
  4. 发出方法体。
  5. 在每个 return 语句之前添加嵌套方法调用。 (这里有很多代码被注释掉了,因为我正在测试插入每个方法调用的最佳方式。我也尝试过使用 SimplifyMacros() 和 OptimizeMacros() 但不确定他们做了什么所以被注释掉了)。

预期/实际运行时输出

"test_.txt" / "test|.txt"

感谢您为我提供的任何帮助,让这段代码正常工作。

c# aop cil mono.cecil compile-time-weaving
1个回答
0
投票

正如我在评论中提到的,不需要有效的 IL 就可以保存文件。这有时被一些混淆器使用,并且 IL 仅在方法执行之前固定。如果这不是您想要的,您需要确保您正在做的是生成正确的 IL,这将由运行时正确执行。

最好的方法(IMO)是编写你想要生成的代码,然后在 ILSpy/dnSpy 中查看生成的 IL。

您的代码存在的问题是:

缺少
ret
声明。

只需在生成的 IL 末尾添加

nestedProcessor.Emit(OpCodes.Ret);

使用参数

nestedProcessor.Emit(OpCodes.Ldarg_0);
行中,您正在加载参数 0,但没有定义任何参数。添加
nested.Parameters.Add(new ParameterDefinition(module.TypeSystem.String));
表示此函数接受一个
string
类型的参数。

使用当地人

nestedProcessor.Emit(OpCodes.Stloc_0);
nestedProcessor.Emit(OpCodes.Stloc_1);
行中,您使用的是局部变量,但这些变量也没有定义。

添加行

nested.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(char[])));
nested.Body.Variables.Add(new VariableDefinition(module.TypeSystem.String));

表示这个方法有2个局部变量,第一个是

char[]
类型,第二个是
string
类型。

泛型

在您的代码中,您使用了泛型(

Concat
ToArray
调用),这些泛型在用于 IL 之前需要专门化。调用
MakeGenericMethod
提供通用类型,在使用前正确地特化它们。

var concat = typeof(Enumerable).GetMethod("Concat");
var conact_spec = concat.MakeGenericMethod(typeof(char));
MethodReference concatMethod = module.ImportReference(conact_spec);

var toArray = typeof(Enumerable).GetMethod("ToArray");
var toArray_spec = toArray.MakeGenericMethod(typeof(char));
MethodReference toArrayMethod = module.ImportReference(toArray_spec);
© www.soinside.com 2019 - 2024. All rights reserved.