示例#1
0
    void AddGuards()
    {
        foreach (var method in TargetType.Methods)
        {
            if (method.Name == ".ctor")
            {
                continue;
            }

            if (method.IsMatch("Finalize"))
            {
                continue;
            }

            if (method.IsStatic)
            {
                continue;
            }

            if (method.Name.StartsWith("Dispose"))
            {
                continue;
            }

            if (method.Name == "ThrowIfDisposed")
            {
                continue;
            }

            if (method.Name == "IsDisposed")
            {
                continue;
            }

            if (!method.HasBody)
            {
                continue;
            }

            if (method.IsPrivate)
            {
                continue;
            }

            var validSequencePoint = method.DebugInformation.SequencePoints.FirstOrDefault();
            method.Body.SimplifyMacros();
            var instructions = method.Body.Instructions;
            instructions.InsertAtStart(
                Instruction.Create(OpCodes.Ldarg_0),
                Instruction.Create(OpCodes.Call, throwIfDisposed));
            if (validSequencePoint != null)
            {
                CecilExtensions.HideLineFromDebugger(validSequencePoint);
            }

            method.Body.OptimizeMacros();
        }
    }
        // MethodReference ObjectDisposedExceptionRef;
        // MethodReference ExceptionRef;

        // enum OnFinalize
        // {
        //     Nothing,
        //     Dispose,
        //     Custom,
        // }

        public override void Execute()
        {
            var onFinalizeAttr = Config.Attributes().FirstOrDefault(x => x.Name == "OnFinalize").Value.ToString();
            // OnFinalize onFinalize = OnFinalize.Nothing;
            // MethodReference customMethod = null;
            MethodReference finalizer = null;

            if (!string.IsNullOrWhiteSpace(onFinalizeAttr))// && onFinalizeAttr.Contains('.'))
            {
                try
                {
                    finalizer = ModuleDefinition
                                .GetTypes()
                                .First(x =>
                                       x.IsClass &&
                                       x.FullName == Path.GetFileNameWithoutExtension(onFinalizeAttr)
                                       )
                                .Methods
                                .First(x =>
                                       x.IsStatic &&
                                       x.IsMatch(
                                           Path.GetExtension(onFinalizeAttr).TrimStart('.'),
                                           "IDisposable",
                                           ModuleDefinition.TypeSystem.Boolean.Name
                                           )
                                       );
                }
                catch {}

                if (finalizer == null)
                {
                    LogError($"Could not find static method '{onFinalizeAttr}(IDisposable, bool)'");
                    return;
                }
                else
                {
                    LogInfo($"Finalizer for disposable objects registered '{finalizer.FullName}'");
                }
            }

            /*switch (onFinalizeAttr?.Value.ToString())
             * {
             *  // case "Nothing":
             *  //     onFinalize = OnFinalize.Nothing;
             *  //     break;
             *  case "Dispose":
             *      onFinalize = OnFinalize.Dispose;
             *      break;
             *  case "Custom":
             *      onFinalize = OnFinalize.Custom;
             *      customMethod = ModuleDefinition
             *          .GetTypes()
             *          .Where(x =>
             *              x.IsClass &&
             *              !x.IsGeneratedCode() &&
             *              x.Methods.Any(x =>
             *                  x.IsStatic &&
             *                  x.CustomAttributes.ContainsOnFinalize() &&
             *                  x.IsMatch(ModuleDefinition.TypeSystem.Object.Name, ModuleDefinition.TypeSystem.Boolean.Name)
             *              )
             *          );
             *      if (customMethod == null)
             *      {
             *          LogError("Could not find static method with attribute 'DisposeGuard.Fody.OnFinalize'");
             *          return;
             *      }
             *      break;
             *      // default:
             *      //     onFinalize = OnFinalize.Nothing;
             *      //     break;
             *      // LogWarning("You must specify 'OnFinalize' parameter.");
             *      // return;
             * }*/

            var objectDisposedExceptionCtor = ModuleDefinition.ImportReference(
                FindType("System.ObjectDisposedException").Find(".ctor", "String")
                );
            var exceptionCtor = ModuleDefinition.ImportReference(
                FindType("System.Exception").Find(".ctor", "String")
                );

            foreach (var type in ModuleDefinition
                     .GetTypes()
                     .Where(x =>
                            x.IsClass &&
                                                 // !x.IsAbstract &&
                            !x.IsGeneratedCode() //&&
                                                 // !x.CustomAttributes.ContainsDoNotTrack()
                            )
                     )
            {
                // if (!type.Interfaces.Any(x => x.InterfaceType.FullName == "System.IDisposable"))
                // {
                //     continue;
                // }

                var disposeMethod = type.Methods
                                    .FirstOrDefault(x =>
                                                    !x.IsStatic &&
                                                    !x.HasParameters &&
                                                    (x.Name == "Dispose" || x.Name == "System.IDisposable.Dispose")
                                                    );

                if (disposeMethod == null)
                {
                    // LogInfo($"Cannot find dispose method in class {type.FullName}");
                    continue;
                }

                if (!isIDisposable(type))
                {
                    LogWarning($"Class '{type.FullName}' contains 'Dispose' method but not implements 'IDisposable' interface");
                    continue;
                }

                LogInfo($"Patching class '{type.FullName}'");

                var disposedField = createDisposedField();
                type.Fields.Add(disposedField);



                ////// MODIFY DISPOSE begin //////
                {
                    // var validSequencePoint = disposeMethod.DebugInformation.SequencePoints.FirstOrDefault();
                    disposeMethod.Body.SimplifyMacros();
                    disposeMethod.Body.Instructions.InsertAtStart(
                        Instruction.Create(OpCodes.Ldarg_0),
                        Instruction.Create(OpCodes.Ldc_I4_1),
                        Instruction.Create(OpCodes.Stfld, disposedField)
                        );
                    // if (validSequencePoint != null)
                    //     CecilExtensions.HideLineFromDebugger(validSequencePoint);
                    disposeMethod.Body.OptimizeMacros();
                }
                ////// MODIFY DISPOSE end //////



                var throwIfDisposedMethod = createThrowIfDisposedMethod(disposedField, type.FullName);
                type.Methods.Add(throwIfDisposedMethod);



                ////// ADD GUARDS begin //////
                foreach (var method in type.Methods)
                {
                    if (method.Name == ".ctor")
                    {
                        continue;
                    }
                    if (method.IsMatch("Finalize"))
                    {
                        continue;
                    }
                    if (method.IsStatic)
                    {
                        continue;
                    }
                    if (method.Name == "Dispose")
                    {
                        continue;
                    }
                    if (method.Name == "IsDisposed")
                    {
                        continue;
                    }
                    if (method.Name == throwIfDisposedMethod.Name)
                    {
                        continue;
                    }
                    // if (!method.HasBody)
                    //     continue;
                    if (method.IsPrivate)
                    {
                        continue;
                    }

                    var validSequencePoint = method.DebugInformation.SequencePoints.FirstOrDefault();
                    method.Body.SimplifyMacros();
                    method.Body.Instructions.InsertAtStart(
                        Instruction.Create(OpCodes.Ldarg_0),
                        Instruction.Create(OpCodes.Call, throwIfDisposedMethod)
                        );
                    if (validSequencePoint != null)
                    {
                        CecilExtensions.HideLineFromDebugger(validSequencePoint);
                    }
                    method.Body.OptimizeMacros();
                }
                ////// ADD GUARDS end //////



                ////// MODIFY FINALIZER begin //////
                MethodDefinition _finalizeMethod()
                {
                    var finalizeMethod = type.Methods.FirstOrDefault(x => !x.IsStatic && x.IsMatch("Finalize"));

                    if (finalizeMethod == null)
                    {
                        finalizeMethod = new MethodDefinition(
                            "Finalize",
                            MethodAttributes.HideBySig | MethodAttributes.Family | MethodAttributes.Virtual,
                            ModuleDefinition.TypeSystem.Void
                            );
                        finalizeMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
                        type.Methods.Add(finalizeMethod);
                    }
                    return(finalizeMethod);
                }

                if (finalizer != null)
                {
                    var method = _finalizeMethod();
                    method.Body.SimplifyMacros();
                    method.Body.Instructions.InsertAtStart(
                        Instruction.Create(OpCodes.Ldarg_0),
                        Instruction.Create(OpCodes.Ldarg_0),
                        Instruction.Create(OpCodes.Ldfld, disposedField),
                        Instruction.Create(OpCodes.Call, finalizer)
                        );
                    method.Body.OptimizeMacros();
                }

                /*switch (onFinalize)
                 * {
                 *  case OnFinalize.Dispose:
                 *      {
                 *          var method = _finalizeMethod();
                 *          method.Body.SimplifyMacros();
                 *          method.Body.Instructions.InsertAtStart(
                 *              Instruction.Create(OpCodes.Ldarg_0),
                 *              Instruction.Create(OpCodes.Call, disposeMethod)
                 *          );
                 *          method.Body.OptimizeMacros();
                 *      }
                 *      break;
                 *  case OnFinalize.Custom:
                 *      {
                 *          var method = _finalizeMethod();
                 *          method.Body.SimplifyMacros();
                 *          method.Body.Instructions.InsertAtStart(
                 *              Instruction.Create(OpCodes.Ldarg_0),
                 *              Instruction.Create(OpCodes.Ldarg_0),
                 *              Instruction.Create(OpCodes.Ldfld, disposedField),
                 *              Instruction.Create(OpCodes.Call, customMethod)
                 *          );
                 *          method.Body.OptimizeMacros();
                 *      }
                 *      break;
                 * }*/
                ////// MODIFY FINALIZER end //////

                // var disposeMethod = disposeMethods.FirstOrDefault(x => !x.HasParameters);
                // if (disposeMethod == null)
                // {
                //  // If the base type is not in the same assembly as the type we're processing
                //  // then we want to patch the Dispose method. If it is in the same
                //  // assembly then the patch code gets added to the Dispose method of the
                //  // base class, so we skip this type.
                //  if (type.BaseType.Scope == type.Scope)
                //      continue;

                //  disposeMethod = disposeMethods[0];
                // }

                // ProcessDisposeMethod(disposeMethod);

                // var constructors = type.Methods.Where(x => !x.IsStatic && x.IsConstructor).ToList();
                // if (constructors.Count != 0)
                // {
                //  foreach (var ctor in constructors)
                //  {
                //      ProcessConstructor(ctor);
                //  }
                // }
            }

            // CleanReferences();

            FieldDefinition createDisposedField()
            {
                return(new FieldDefinition(
                           "__disposed__" + suffix,
                           FieldAttributes.Private | FieldAttributes.HasFieldRVA,
                           ModuleDefinition.TypeSystem.Boolean
                           ));
            }

            MethodDefinition createThrowIfDisposedMethod(FieldDefinition disposedField, string className)
            {
                var method = new MethodDefinition(
                    "__ThrowIfDisposed__" + suffix,
                    MethodAttributes.Private | MethodAttributes.HideBySig,
                    ModuleDefinition.TypeSystem.Void
                    );

                var returnInstruction = Instruction.Create(OpCodes.Ret);

                method.Body.Instructions.InsertAtStart(
                    Instruction.Create(OpCodes.Ldarg_0),
                    Instruction.Create(OpCodes.Ldfld, disposedField),
                    Instruction.Create(OpCodes.Brfalse_S, returnInstruction),
                    Instruction.Create(OpCodes.Ldstr, className),
                    Instruction.Create(OpCodes.Newobj, objectDisposedExceptionCtor),
                    Instruction.Create(OpCodes.Throw),
                    returnInstruction
                    );

                return(method);
            }

            bool isIDisposable(TypeDefinition type)
            {
                if (type.Interfaces.Any(i => i.InterfaceType.FullName.Equals("System.IDisposable")))
                {
                    return(true);
                }
                if (type.FullName.Equals("System.IDisposable"))
                {
                    return(true);
                }
                return(type.BaseType != null && isIDisposable(type.BaseType.Resolve()));
            }
        }