• 
      

    Add workarounds for spying on slippery functions.

    Review Request #10984 — Created April 1, 2020 and submitted — Latest diff uploaded

    Information

    kgb
    master

    Reviewers

    kgb

    A slippery function (coined here) is a function that KGB can't by
    normally spy on without that spy appearing to fall off. A function is
    slippery when its attribute on a class dynamically generates and returns
    the function. This can happen if wrapping a method using a
    poorly-written decorator that decorates at attribute access time and not
    at declaration time. An example of this would be the Stripe Python
    module's stripe.Customer.delete method, which will be a different
    instance every time it's accessed.

    In these cases, the function returned is not bound or unbound, it's just
    a standard function, so we have no idea what its owner is. The
    workaround involves passing an instance as the owner when calling
    spy_on() and then having FunctionSig check to see if it gets a new
    function each time it's accessed. If so, it records the function as
    slippery and fix up its function type. This will in turn trigger some
    behavior to staple a new method to the class and to handle calls
    differently.

    Since slippery functions are caused by descriptors (classes with
    __get__ methods), this also introduces the beginning of support for
    storing state on descriptors in FunctionSig. Since these descriptors
    are only accessible via a class dictionary (accessing their attribute in
    any way on an object just calls __get__()), we also now have
    functionality for looking up the actual defined function/descriptor
    based on the function name and preserving that.

    It's important to note that KGB has no way to reliably determine if a
    function is slippery by default. Callers who are noticing that a spy
    does not stick to a function should pass the instance as the owner in
    order to enable slippery function detection.

    It's also important to note that currently, we are unable to spy on an
    unbound method that generates slippery functions. We can't safely
    replace this on a class without new problems surfacing, and therefore
    can't intercept calls on any instances from that class. For now, we
    produce an error if trying to spy on an unbound method that generates
    slippery functions.

    KGB, Djblets, Review Board, and RBCommons unit tests (including new
    ones testing against Stripe) pass with all supported versions of Python.

    Commits

    Files