public IEnumerable<Violation> Apply(TestCase testCase)
        {
            var calledAssertingMethods = testCase.GetCalledAssertingMethods();
            var tracker = new MethodValueTracker(testCase.TestMethod);

            // For each asserting method with >= 1 parameters:
            foreach (var cm in calledAssertingMethods)
            {
                var methodRef = cm.MethodReference;
                var parameterPurposes = testCase.Framework.GetParameterPurposes(methodRef);
                if (!IsSingleTruthCheckingMethod(methodRef, parameterPurposes))
                    continue;

                foreach (var valueGraph in tracker.ValueGraphs)
                {
                    IList<MethodValueTracker.Value> consumedValues =
                        tracker.GetConsumedValues(valueGraph, cm.Instruction).ToList();
                    if (consumedValues.Count == 0)
                        continue; // not part of value graph
                    var interestingValue = consumedValues[0];
                    var producers = UltimateProducers(interestingValue);
                    if (producers.Count > 1)
                    {
                        yield return new Violation(this, testCase, cm.Instruction, string.Format("{0}.{1} performs a boolean test on a composite boolean value",
                            cm.MethodReference.DeclaringType.Name,
                            cm.MethodReference.Name));
                    }
                }
            }
        }
 public void TestThatValueConsumptionForInstructionMatchesPath()
 {
     var method = GetType().FindMethod("MethodWithBranch()");
     var tracker = new MethodValueTracker(method);
     var graph = tracker.ValueGraphs[1]; // second path
     // Find the last instruction that stores to y (first local)
     var instr = method.Body.Instructions.Where(i => i.OpCode == OpCodes.Stloc_0).Last();
     var producers = tracker.GetConsumedValues(graph, instr).Select(v => v.Producer.OpCode).ToList();
     // Expect the producer of the conssumed value to load from u (third local)
     CollectionAssert.AreEqual(new[] { OpCodes.Ldloc_2 }, producers);
 }
 private ICollection<Instruction> UltimateProducers(MethodValueTracker.Value v)
 {
     // We track backwards and record values without parents. The producers of
     // those values are considered the ultimate producers of the given value.
     var result = new List<Instruction>();
     var values = new Queue<MethodValueTracker.Value>();
     values.Enqueue(v);
     while (values.Count > 0)
     {
         var aValue = values.Dequeue();
         if (aValue.Parents.Count == 0)
         {
             result.Add(aValue.Producer);
             continue;
         }
         foreach (var parent in aValue.Parents)
         {
             values.Enqueue(parent);
         }
     }
     return result;
 }
 private IEnumerable<OpCode> FindAllSourceValueOpCodes(MethodValueTracker tracker, Instruction instruction)
 {
     var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], instruction);
     var sourceValues = consumedValues.SelectMany(tracker.FindSourceValues);
     return sourceValues.Select(v => v.Producer.OpCode).Distinct();
 }
 public void TestThatTrackerExposesTwoValueGraphsForMethodWithTwoPaths()
 {
     var method = GetType().FindMethod("MethodWithBranch()");
     var tracker = new MethodValueTracker(method);
     Assert.AreEqual(2, tracker.ValueGraphs.Count);
 }
 private bool IsProducedByUnapprovedCall(MethodValueTracker.Value value)
 {
     var i = value.Producer;
     if (i.OpCode.FlowControl != FlowControl.Call)
         return false;
     var calledMethod = i.Operand as MethodReference;
     if (calledMethod.IsConstructor())
         return false; // constructing an object locally is ok
     var propertyDef = TryGetPropertyDefForPropertyGetter(calledMethod);
     if (propertyDef != null && IsStaticGetOnlyProperty(propertyDef))
         return false;
     if (IsDataConversionCall(calledMethod))
         return false;
     if (_framework.IsDataAccessorMethod(calledMethod))
         return false;
     if (IsGeneratedByTypeOf(value))
         return false;
     return true;
 }
 private bool HasForbiddenProducer(MethodValueTracker.Value value, IList<FieldDefinition> whitelistedFields)
 {
     var producer = value.Producer;
     if (IsProducedByUnapprovedCall(value) || (IsFieldLoad(producer, whitelistedFields) && !IsStaticReadonlyFieldLoad(producer)))
         return true;
     return false;
 }
 private static bool IsProducedByDoubleCilComparison(MethodValueTracker.Value value, out MethodValueTracker.Value originalComparisonOutcome)
 {
     // Assumption: The instruction is a binary comparison.
     originalComparisonOutcome = null;
     var parents = value.Parents.ToList();
     var producer = value.Producer;
     if (producer.OpCode != OpCodes.Ceq)
         return false; // lte/gte/neq = cgt/clt/ceq followed by ceq
     var ldc0 = parents.Where(v => v.Producer.OpCode == OpCodes.Ldc_I4_0).FirstOrDefault();
     if (ldc0 == null)
         return false; // one of the ceq operands should've been produced by ldc.i4.0
     var other = parents[1 - parents.IndexOf(ldc0)];
     if (!IsBinaryComparison(other.Producer))
         return false; // the other operand should've been produced by a binary comparison
     originalComparisonOutcome = other;
     return true; // match!
 }
 public void TestThatQueryInstructionCannotBeNull()
 {
     var method = GetType().FindMethod("DirectConstantUseByStaticMethod()");
     var tracker = new MethodValueTracker(method);
     tracker.GetConsumedValues(tracker.ValueGraphs[0], null);
 }
        public void TestThatObjectConstructionIsHandled()
        {
            var method = GetType().FindMethod("ObjectConstruction()");
            var tracker = new MethodValueTracker(method);
            var opCodes = FindAllSourceValueOpCodes(tracker, LastCall(method));

            CollectionAssert.AreEquivalent(new[] { OpCodes.Newobj }, opCodes);
        }
 public void TestThatGraphCannotBeNullWhenQueryingConsumedValues()
 {
     var method = GetType().FindMethod("DirectConstantUseByStaticMethod()");
     var tracker = new MethodValueTracker(method);
     tracker.GetConsumedValues(null, LastCall(method));
 }
        public void TestThatConsumedValuesIncludesFirstArgOfStaticCallInStatic()
        {
            var method = GetType().FindMethod("StaticMethodWithStaticCall(System.Int32)");
            var tracker = new MethodValueTracker(method);
            var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method));
            var opCodes = consumedValues.Select(v => v.Producer.OpCode);

            // Ldarg_0 should be in the list!
            CollectionAssert.AreEqual(new[] { OpCodes.Ldarg_0 }, opCodes);
        }
        public void TestThatConsumedValuesExcludesThisObjectForVirtualCall()
        {
            var method = GetType().FindMethod("DirectConstantUseByVirtualMethod()");
            var tracker = new MethodValueTracker(method);
            var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method));
            var opCodes = consumedValues.Select(v => v.Producer.OpCode);

            // Ldarg_0 should not be in the list!
            CollectionAssert.AreEqual(new[] { OpCodes.Ldc_I4_S }, opCodes);
        }
 public void TestThatConsumedValuesAreEmptyWhenQueryInstructionIsNotPartOfMethod()
 {
     var method = GetType().FindMethod("DirectConstantUseByStaticMethod()");
     var method2 = GetType().FindMethod("DirectConstantUseByVirtualMethod()");
     var tracker = new MethodValueTracker(method);
     var values = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method2));
     Assert.AreEqual(0, values.Count());
 }
 public void TestThatConsumedValueCountCorrespondsToActualConsumption()
 {
     var method = GetType().FindMethod("DirectConstantUseByStaticMethod()");
     var tracker = new MethodValueTracker(method);
     var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method));
     Assert.AreEqual(1, consumedValues.Count());
 }
        public void TestThatConstantConsumedViaBoxedValueIsFound()
        {
            var method = GetType().FindMethod("BoxedConstantUseByStaticMethod()");
            var tracker = new MethodValueTracker(method);
            var consumedValue = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method)).First();
            var roots = tracker.FindSourceValues(consumedValue);

            Assert.AreEqual(OpCodes.Ldc_I4_S, roots.First().Producer.OpCode);
        }
 private static bool IsProducedByBinaryComparison(MethodValueTracker.Value value, out IList<MethodValueTracker.Value> operands)
 {
     var producer = value.Producer;
     if (IsBinaryComparison(producer))
     {
         // Note: <=, => and != result in cgt, lgt and ceq, respectively, followed by
         // ceq with one operand being 0 (so produced by ldc.i4.0). Therefore, to handle
         // these comparisons, we need to backtrack one value in this particular case.
         MethodValueTracker.Value originalComparisonOutcome;
         if (IsProducedByDoubleCilComparison(value, out originalComparisonOutcome))
         {
             // Backtrack to the value produced by the comparison behind ceq!
             value = originalComparisonOutcome;
         }
         operands = value.Parents.ToList();
         return true;
     }
     operands = null;
     return false;
 }
        public void TestThatSourceValueIsTrackedThroughRefManipulation()
        {
            var method = GetType().FindMethod("UseOfValueManipulatedAsReference()");
            var tracker = new MethodValueTracker(method);
            var opCodes = FindAllSourceValueOpCodes(tracker, LastCall(method));

            CollectionAssert.AreEquivalent(new[] { OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5 }, opCodes);
        }
 private Instruction FirstForbiddenProducer(Graph<MethodValueTracker.Value> valueGraph, MethodValueTracker.Value v, IList<FieldDefinition> whitelistedFields)
 {
     return valueGraph.Walk(v).Where(val => HasForbiddenProducer(val, whitelistedFields)).Select(value => value.Producer).FirstOrDefault();
 }
        public void TestThatSourceValueIsTrackedThroughReturnValue()
        {
            var method = GetType().FindMethod("UseOfReturnValue()");
            var tracker = new MethodValueTracker(method);
            var opCodes = FindAllSourceValueOpCodes(tracker, LastCall(method));

            CollectionAssert.AreEquivalent(new[] { OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6 }, opCodes);
        }
 private bool IsGeneratedByTypeOf(MethodValueTracker.Value value)
 {
     var i = value.Producer;
     var calledMethod = i.Operand as MethodReference;
     // typeof(X) generates a call to System.Type::GetTypeFromHandle, which can be manually
     // called as well. Check if its argument is placed on the stack through ldtoken, in
     // which case we assume it wasn't.
     if (!GetTypeFromHandleSignature.Equals(calledMethod.FullName))
         return false;
     var parent = value.Parents.FirstOrDefault();
     if (parent == null)
         return false;
     var parentProducer = parent.Producer;
     return parentProducer.OpCode == OpCodes.Ldtoken;
 }
        public void TestThatSourceValueIsTrackedThroughtOutValueDespitePreviousUseOfOutVariable()
        {
            var method = GetType().FindMethod("UseOfOutValueTwice()");
            var tracker = new MethodValueTracker(method);
            var opCodes = FindAllSourceValueOpCodes(tracker, LastCall(method));

            CollectionAssert.AreEquivalent(new[] { OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2 }, opCodes);
        }
        public IEnumerable<Violation> Apply(TestCase testCase)
        {
            // Store the framework so that we don't have to pass it around everywhere.
            _framework = testCase.Framework;

            var calledAssertingMethods = testCase.GetCalledAssertingMethods();
            var tracker = new MethodValueTracker(testCase.TestMethod);

            var whitelistedFields = FindWhitelistedFields(testCase.TestMethod.DeclaringType);

            // For each asserting method with >= 1 parameters:
            foreach (var cm in calledAssertingMethods.Where(cm => cm.MethodDefinition.HasParameters))
            {
                var method = cm.MethodDefinition;
                //TODO: if the method is a helper, we need to "unfold" the helper
                // to get to the real asserting methods, and this will require us
                // to join value-generation graphs across method calls...
                var paramPurposes = _framework.GetParameterPurposes(method);
                if (paramPurposes == null) continue; // unknown method, rule does not apply

                foreach (var valueGraph in tracker.ValueGraphs)
                {
                    IList<MethodValueTracker.Value> consumedValues = tracker.GetConsumedValues(valueGraph, cm.Instruction).ToList();
                    if (consumedValues.Count == 0)
                        continue; // not part of value graph

                    // Build a list of arguments with the details we need to know if the rule applies.
                    var arguments = method.Parameters
                        .Select((p, index) => new ArgumentDetails { Method = method, Index = index, Purpose = paramPurposes[index], ConsumedValue = consumedValues[index] }).ToList();

                    // Handle cases like Assert.IsTrue(x == 5) by expanding arguments
                    ExpandIfSingleTruthCheckingMethod(method, ref arguments);

                    // We're only interested in arguments that represent expectations!
                    var interestingArguments = arguments.Where(a => IsPerhapsExpectation(a.Purpose)).ToList();

                    // This might happen with for example Assert.Fail("some reason").
                    if (interestingArguments.Count == 0)
                        continue;

                    // Add in the "forbidden producer", if any, for each argument. A forbidden producer is an
                    // instruction that generates a value externally, such as a call.
                    interestingArguments = interestingArguments.Select(
                            a => { a.ForbiddenProducer = FirstForbiddenProducer(valueGraph, a.ConsumedValue, whitelistedFields); return a; }).ToList();

                    // If there is at least one locally produced argument, the rule doesn't apply.
                    if (interestingArguments.Any(IsLocallyProduced))
                        continue;

                    if (interestingArguments.All(a => a.Purpose == ParameterPurpose.ExpectedOrActual))
                    {
                        // Since we don't know exactly which parameter that represents the expectation, we
                        // just generate a single violation.
                        yield return new Violation(this, testCase, interestingArguments[0].ConsumedValue.Consumer,
                            CreateViolationMessageForUncertainCase(interestingArguments[0]));
                        continue;
                    }

                    foreach (var a in interestingArguments.Where(IsExternallyProduced))
                    {

                        // Generate a violation at the location of the forbidden producer!
                        yield return new Violation(this, testCase, a.ForbiddenProducer, CreateViolationMessage(a));
                    }
                }
            }
        }
        public void TestThatSourceValuesAreFoundThroughSequenceOfCalculations()
        {
            var method = GetType().FindMethod("CalculatedValueUseByStaticMethod(System.Int32,System.Int32)");
            var tracker = new MethodValueTracker(method);
            var opCodes = FindAllSourceValueOpCodes(tracker, LastCall(method));

            CollectionAssert.AreEquivalent(new[] { OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldc_I4_1 }, opCodes);
        }
 public void TestThatTrackerExposesValueGraphsForAllCasesOfSwitchStatement()
 {
     var method = GetType().FindMethod("MethodWithSwitch()");
     var tracker = new MethodValueTracker(method);
     Assert.AreEqual(3, tracker.ValueGraphs.Count);
 }
 private static void PrintNode(StringBuilder builder, MethodValueTracker.Value node, int indent)
 {
     builder.Append("".PadRight(indent * 2));
     PrintValue(builder, node);
     foreach (var parent in node.Parents)
     {
         PrintNode(builder, parent, indent + 1);
     }
 }
        public void TestThatValueConsumptionForInstructionOutsidePathIsEmpty()
        {
            var method = GetType().FindMethod("MethodWithBranch()");
            var tracker = new MethodValueTracker(method);
            var graph = tracker.ValueGraphs[0]; // first path
            // Find the last instruction that stores to y, part of second path
            var instr = method.Body.Instructions.Where(i => i.OpCode == OpCodes.Stloc_0).Last();

            var consumed = tracker.GetConsumedValues(graph, instr);
            Assert.AreEqual(0, consumed.Count());
        }
 private static void PrintValue(StringBuilder builder, MethodValueTracker.Value node)
 {
     builder.AppendFormat("Value produced by {0} and consumed by {1} [#parents={2}].\n",
         InstructionToString(node.Producer), InstructionToString(node.Consumer),
         node.Parents.Count);
 }