public class A
{
    public static void Sink(object o)
    {
    }

    public object FlowThrough(object o, bool cond)
    {
        if (cond)
        {
            return o;
        }
        else
        {
            return null;
        }
    }

    public void CallSinkIfTrue(object o, bool cond)
    {
        if (cond)
        {
            Sink(o);
        }
    }

    public void CallSinkIfFalse(object o, bool cond)
    {
        if (!cond)
        {
            Sink(o);
        }
    }

    public void CallSinkFromLoop(object o, bool cond)
    {
        while (cond)
        {
            Sink(o);
        }
    }

    public void LocalCallSensitivity(object o, bool c)
    {
        object o1 = o;
        object o2 = null;
        if (c)
        {
            object tmp = o1;
            o2 = 1 == 1 ? (tmp) : (tmp);
        }
        object o3 = o2;
        Sink(o3);
    }

    public void LocalCallSensitivity2(object o, bool b, bool c)
    {
        object o1 = o;
        object o2 = null;
        if (b || c)
        {
            object tmp = o1;
            o2 = 1 == 1 ? (tmp) : (tmp);
        }
        object o3 = o2;
        Sink(o3);
    }

    public void M1()
    {
        // should not exhibit flow
        CallSinkIfTrue(new object(), false);
        CallSinkIfFalse(new object(), true);
        CallSinkFromLoop(new object(), false);
        LocalCallSensitivity(new object(), false);
        Sink(FlowThrough(new object(), false));
        // should exhibit flow
        CallSinkIfTrue(new object(), true);
        CallSinkIfFalse(new object(), false);
        CallSinkFromLoop(new object(), true);
        LocalCallSensitivity(new object(), true);
        LocalCallSensitivity2(new object(), true, true);
        LocalCallSensitivity2(new object(), false, true);
        LocalCallSensitivity2(new object(), true, false);
        Sink(FlowThrough(new object(), true));
        // expected false positive
        LocalCallSensitivity2(new object(), false, false);
    }

    public void M2()
    {
        bool t = true;
        bool f = false;
        // should not exhibit flow
        CallSinkIfTrue(new object(), f);
        CallSinkIfFalse(new object(), t);
        CallSinkFromLoop(new object(), f);
        LocalCallSensitivity(new object(), f);
        Sink(FlowThrough(new object(), f));
        // should exhibit flow
        CallSinkIfTrue(new object(), t);
        CallSinkIfFalse(new object(), f);
        CallSinkFromLoop(new object(), t);
        LocalCallSensitivity(new object(), t);
        Sink(FlowThrough(new object(), t));
    }

    public void M3(InterfaceA b)
    {
        bool t = true;
        bool f = false;
        // should not exhibit flow
        b.CallSinkIfTrue(new object(), f);
        b.CallSinkIfFalse(new object(), t);
        b.LocalCallSensitivity(new object(), f);
        // should exhibit flow
        b.CallSinkIfTrue(new object(), t);
        b.CallSinkIfFalse(new object(), f);
        b.LocalCallSensitivity(new object(), t);
    }

    class B : InterfaceA
    {
        public void CallSinkIfTrue(object o, bool cond)
        {
            if (cond)
            {
                Sink(o);
            }
        }

        public void CallSinkIfFalse(object o, bool cond)
        {
            if (!cond)
            {
                Sink(o);
            }
        }

        public void LocalCallSensitivity(object o, bool c)
        {
            object o1 = o;
            object o2 = null;
            if (c)
            {
                object tmp = o1;
                o2 = 1 == 1 ? (tmp) : (tmp);
            }
            object o3 = o2;
            Sink(o3);
        }
    }
}

public class A2
{

    public static void Sink(object o)
    {
    }

    public virtual void M(object o)
    {
        Sink(o);
    }

    public static void CallM(A2 a2, object o)
    {
        a2.M(o);
    }

    public virtual object MOut() => new object();

    public static object CallMOut(A2 a2) => a2.MOut();

    public void Callsite(InterfaceB intF)
    {
        B b = new B();
        // in both possible implementations of foo, this callsite is relevant
        // in IntA, it improves virtual dispatch,
        // and in IntB, it improves the dataflow analysis.
        intF.Foo(b, new object(), false); // no flow to `Sink()` via `A2.M()`, but flow via `IntA.Foo()`

        CallM(b, new object()); // no flow to `Sink()`
        CallM(this, new object());  // flow to `Sink()`

        var o = CallMOut(this);
        Sink(o); // flow
        o = CallMOut(b);
        Sink(o); // no flow
    }

    public class B : A2
    {
        public override void M(object o)
        {

        }

        public override object MOut() => throw null;
    }

    public class IntA : InterfaceB
    {
        public void Foo(A2 obj, object o, bool cond)
        {
            obj.M(o);
            Sink(o);
        }
    }

    public class IntB : InterfaceB
    {

        public void Foo(A2 obj, object o, bool cond)
        {
            if (cond)
            {
                Sink(o);
            }
        }
    }
}

public interface InterfaceA
{
    void CallSinkIfTrue(object o, bool cond);
    void CallSinkIfFalse(object o, bool cond);
    void LocalCallSensitivity(object o, bool c);
}

public interface InterfaceB
{
    void Foo(A2 a, object o, bool cond);
}

public class A3
{
    public virtual void M1(object o)
    {
        this.M2(o);
    }

    public virtual void M2(object o)
    {
        Sink(o); // should not have flow
    }

    public static void Sink(object o)
    {
    }
}

public class A4 : A3
{
    public override void M2(object o)
    {
        Sink(o); // should have flow
    }

    private void CallM1()
    {
        base.M1(new object());
    }
}
