private void CheckMethodBody(MethodDefinition method) { var synchronizedEvents = new Dictionary <MethodReference, List <MethodReference> > (); var thisSynchronized = new List <TypeReference> (); foreach (Instruction ins in method.Body.Instructions) { MethodReference candidate = null; switch (ins.OpCode.Code) { case Code.Newobj: if (ins.Previous != null && ins.Previous.OpCode.Code == Code.Ldftn) { MethodReference ctor = (MethodReference)ins.Operand; TypeReference type = ctor.DeclaringType; if (type.IsDelegate()) { string nspace = type.Namespace; // ldftn entry-point // newobj System.Void System.Threading.XXX::.ctor (System.Object,System.IntPtr) // i.e. creation of a System.Threading delegate if (nspace == "System.Threading") { string name = type.Name; if (name == "ThreadStart" || name == "ParameterizedThreadStart" || name == "WaitCallback" || name == "WaitOrTimerCallback" || name == "TimerCallback") { candidate = (MethodReference)ins.Previous.Operand; } // ldftn entry-point // newobj System.Void System.AsyncCallback::.ctor (System.Object,System.IntPtr) // i.e. creation of a async delegate } else if (nspace == "System") { if (type.Name == "AsyncCallback") { candidate = (MethodReference)ins.Previous.Operand; } // ldftn entry-point // newobj System.Void ThreadedDelegate::.ctor (System.Object,System.IntPtr) // i.e. creation of a delegate which is decorated with a threading attribute } else if (!ThreadRocks.ThreadedNamespace(nspace)) { // Delegates must be able to call the methods they are bound to. MethodDefinition target = ((MethodReference)ins.Previous.Operand).Resolve(); if (target != null) { ThreadModel callerModel = type.ThreadingModel(); if (!target.IsGeneratedCode() || target.IsProperty()) { ThreadModel targetModel = target.ThreadingModel(); if (!IsValidCall(callerModel, targetModel)) { string mesg = String.Format(CultureInfo.InvariantCulture, "{0} delegate cannot be bound to {1} {2} method.", callerModel, targetModel, target.Name); ++DefectCount; Log.WriteLine(this, "Defect: {0}", mesg); Defect defect = new Defect(this, method, method, ins, Severity.High, Confidence.High, mesg); Runner.Report(defect); } } else if (!callerModel.Is(ThreadModel.MainThread)) { anonymous_entry_points.Add(target); } } } } } break; case Code.Call: case Code.Callvirt: if (!method.IsGeneratedCode() || method.IsProperty()) { CheckForLegalCall(method, ins); } // ldftn entry-point // newobj XXX // callvirt System.Void SynchronizedType::add_Name (XXX) // i.e. adding a delegate to an event in a type which uses SynchronizingObject MethodReference call = (MethodReference)ins.Operand; TypeReference call_type = call.DeclaringType; if (ins.Previous.Is(Code.Newobj) && ins.Previous.Previous.Is(Code.Ldftn)) { // A few events are blacklisted because they do not use SynchronizingObject and // are therefore always threaded. if (IsNonSynchronizedSetter(call)) { candidate = (MethodReference)ins.Previous.Previous.Operand; // But most events do use SynchronizingObject and therefore their threading // depends on whether and how SynchronizingObject is initialized. } else if (HasSynchronizingObject(call_type)) { List <MethodReference> methods; if (!synchronizedEvents.TryGetValue(call, out methods)) { methods = new List <MethodReference> (); synchronizedEvents.Add(call, methods); } methods.AddIfNew((MethodReference)ins.Previous.Previous.Operand); // Misc threaded events. } else if (call_type.IsNamed("System.ComponentModel", "BackgroundWorker")) { if (call.Name == "add_DoWork") { candidate = (MethodReference)ins.Previous.Previous.Operand; } } // callvirt System.Void System.Diagnostics.Process::set_SynchronizingObject (System.ComponentModel.ISynchronizeInvoke) } else if (SetSynchronizingObject.Matches(call)) { if (ins.Previous.OpCode.Code == Code.Ldarg_0) { thisSynchronized.Add(call_type); } } break; } if (candidate != null) { Log.WriteLine(this, "{0} is a thread entry point", candidate); CheckEntryPoint(candidate); } } // For every method added to a threaded event, ThreadModel?method_model = null; foreach (KeyValuePair <MethodReference, List <MethodReference> > entry in synchronizedEvents) { // if the event is synchronized on this then the target must have the same thread // as the current method's type or better and it should not be treated as a entry point. if (thisSynchronized.Contains(entry.Key.DeclaringType)) { if (method_model == null) { method_model = method.DeclaringType.ThreadingModel(); } foreach (MethodReference mr in entry.Value) { MethodDefinition target = mr.Resolve(); if (target != null) { ThreadModel targetModel = target.ThreadingModel(); if (!IsValidCall(method_model.Value, targetModel)) { string mesg = String.Format(CultureInfo.InvariantCulture, "{0} {1} cannot be bound to {2} {3} method.", method_model, entry.Key, targetModel, target.Name); ReportDefect(method, Severity.High, Confidence.High, mesg); } } } // otherwise the method has to be treated as a thread entry point. } else { foreach (MethodReference mr in entry.Value) { Log.WriteLine(this, "{0} is a thread entry point", mr); CheckEntryPoint(mr); } } } }
public RuleResult CheckMethod(MethodDefinition method) { if (ThreadRocks.ThreadedNamespace(method.DeclaringType.Namespace)) { return(RuleResult.DoesNotApply); } Log.WriteLine(this); Log.WriteLine(this, "---------------------------------------"); Log.WriteLine(this, method); string name = method.Name; IList <ParameterDefinition> pdc = method.HasParameters ? method.Parameters : null; // Finalizers need to be single threaded. ThreadModel model = method.ThreadingModel(); if (method.IsFinalizer()) { if ((model & ~ThreadModel.AllowEveryCaller) != ThreadModel.SingleThread) { string mesg = "Finalizers should be decorated with [ThreadModel (ThreadModel.SingleThreaded)]."; ReportDefect(method, Severity.High, Confidence.High, mesg); } } // Make sure all of the thread entry points are properly decorated and // that all calls are legit. if (method.HasBody && opcodes_mask.Intersect(OpCodeEngine.GetBitmask(method))) { CheckMethodBody(method); } // A delegate used with a threaded event must use the same threading model // as the event. if (method.IsAddOn) { ParameterDefinition p = pdc [0]; TypeDefinition delegateType = p.ParameterType.Resolve(); if (delegateType != null && !ThreadRocks.ThreadedNamespace(delegateType.Namespace)) { ThreadModel delegateModel = delegateType.ThreadingModel(); if (model != delegateModel && !delegateModel.AllowsEveryCaller()) { string mesg = String.Format(CultureInfo.InvariantCulture, "{0} event must match {1} delegate.", model, delegateModel); ReportDefect(method, Severity.High, Confidence.High, mesg); } } } // An override of a base method or an implementation of an interface method // must use the same threading model as the original method. if (method.IsVirtual) { IEnumerable <TypeDefinition> superTypes = method.DeclaringType.AllSuperTypes(); bool new_slot = method.IsNewSlot; superTypes = from s in superTypes where (s.IsInterface == new_slot) select s; string [] parameters = pdc != null ? (from p in pdc.Cast <ParameterDefinition> () select p.ParameterType.GetFullName()).ToArray() : null; string return_type_name = method.ReturnType.GetFullName(); foreach (TypeDefinition type in superTypes) { MethodDefinition superMethod = type.GetMethod(name, return_type_name, parameters); if (superMethod != null && !ThreadRocks.ThreadedNamespace(superMethod.DeclaringType.Namespace)) { ThreadModel superModel = superMethod.ThreadingModel(); if (model != superModel) { string mesg = String.Format(CultureInfo.InvariantCulture, "{0} {1} must match {2} {3} method.", model, name, superModel, new_slot ? "interface" : "base"); ReportDefect(method, Severity.High, Confidence.High, mesg); } } } } // Serializable cannot be applied to static methods, but can be applied to // operators because they're just sugar for normal calls. if (method.IsStatic && model.Is(ThreadModel.Serializable) && !name.StartsWith("op_", StringComparison.Ordinal)) { string mesg = "Static members cannot be decorated with Serializable."; ReportDefect(method, Severity.High, Confidence.High, mesg); } return(Runner.CurrentRuleResult); }