Problem
I'm using Mono.Cecil to IL Weave string property getters that have my custom [ValidSystemPath] attribute on them. The purpose of the attribute is to ensure the property only ever returns valid system characters for file names and paths etc. Problem is, the code is not currently working, yet is not raising any exceptions during weaving. I'm new to weaving and IL, so I'd benefit from a guiding-hand.
Code before weaving (C#)
private string path = "test|.txt";
[ValidSystemPath]
public string Path => path;
Expected code after weaving (C#)
This is effectively my source of inspiration for the code I'm trying to weave in... 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);
}
}
I'd like to use a nested method, because if the getter contains conditionals, there could be multiple return statements, thus I need to make a simple call to the nested method before each return statement.
Weaver code (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();
}
Breakdown of weaver
- Find the site of injection and create an ILProcessor.
- Import method references for all the method calls we'll be making (string Split/Join, Path GetInvalidPathChars/GetInvalidFileNameChars, and Enumerable Concat/ToArray).
- Create the nested method and add.
- Emit the method body.
- Add nested method calls before each return statement. (there's a lot of code commented out here, as I was testing the best way to insert each method call. I've also tried using SimplifyMacros() and OptimizeMacros() but was unsure what they did so commented out).
Expected / Actual runtime output
"test_.txt" / "test|.txt"
Thank you for any help you can provide me in getting this code working.