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); }