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)