• 
      

    Add workarounds for spying on slippery functions.

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

    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.

    Summary ID
    Add workarounds for spying on slippery functions.
    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.
    b2872ce37c9c58731371f703cefb7ccbe82a2d40
    Description From Last Updated

    F821 undefined name 'spy'

    reviewbotreviewbot

    F821 undefined name 'spy'

    reviewbotreviewbot

    F821 undefined name 'spy'

    reviewbotreviewbot

    F821 undefined name 'spy'

    reviewbotreviewbot
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    chipx86
    chipx86
    david
    1. Ship It!
    2. 
        
    chipx86
    Review request changed
    Status:
    Completed
    Change Summary:
    Pushed to master (697ca2a)