-
Notifications
You must be signed in to change notification settings - Fork 606
Add deprecation decorator and split Linux modules utilities #1567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add deprecation decorator and split Linux modules utilities #1567
Conversation
ikelos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but a few comments. Where we put the generic deprecation header is still an open question. I don't think configuration.requirements is the right place for it. It's more of a framework-wide helper...
ikelos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I prefer the improvements, but still some tweaks I think we could do. Lemme know what you think?
ikelos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking better, and definitely very thorough, but probably overkill. It doesn't need any of the requirements machinery, it just needs to semver check (which I think is a static/classmethod). You've done the hard part of finding the class from the method, so then just grab the version number and test it and throw an exception if they're wrong. We can put a standard exception handler to explain what they should do... 5:)
|
I made the requested changes, hopefully it matches more closely what you had in mind ? I think the result is more explicit now: I'll count the required framework bumps at the end. :) |
ikelos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeps getting better, just a couple more points and then I think we can get it across the line! 5:)
volatility3/framework/__init__.py
Outdated
| if replacement_version is not None and callable(replacement): | ||
| # example: replacement = volatility3.MyClass.my_dummy_function | ||
| # "MyClass.my_dummy_function" -> "MyClass" | ||
| replacement_base_class_name = replacement.__qualname__.split(".")[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
qualname doesn't quite do what we want I think? We want the class that was called, not the class where the method was defined...
For example:
>>> class A:
... version = (1,0,0)
... def thing(self):
... print(self.version)
...
>>> class B(A):
... version = (2,0,0)
...
>>> ver2 = B()
>>> ver2.thing()
(2, 0, 0)
>>> ver2.thing.__qualname__
'A.thing'
I think we want to be using __self__:
>>> ver2.thing.__self__
<__main__.B object at 0x7f3049a33ec0>
That also saves us needing up lookup the class using __globals__.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__self__ works on an stateful instance, however we are referencing linux_utilities_modules.Modules.lookup_module_address not linux_utilities_modules.Modules().lookup_module_address. I am not sure that instantiating the class is exactly what we want, as it might imply non-optional arguments sometimes.
That's a good point however, thanks for catching it ! Python internals are making it quite difficult to get B on this specific scenario.
I think using an extra class argument in deprecated_method will save us a lot of headaches, or sketchy/magical processing ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still seems to work for class methods?
>>> class A:
... version = (1,0,0)
... def thing(self):
... print(self.version)
... @classmethod
... def classthing(cls):
... print(cls.version)
...
>>> class B(A):
... version = (2,0,0)
...
>>> B.classthing
<bound method A.classthing of <class '__main__.B'>>
>>> B.classthing.__self__
<class '__main__.B'>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, nice catch as well. However it doesn't work with staticmethod (thanks Python ?), which we use a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a better reason to go back through versioned components and change them from staticmethod to classmethod? Also, this only affects new things so that we can deprecate old ones. We can just require new ones to be classmethods (will need documenting somewhere, and we should probably go back through versioned things and make the staticmethods into classmethods (in a different PR!)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I am not sure that it is worth the hassle, and staticmethod has valid use cases. Sometimes, a class only purpose is basically to group functions under it without necessarily making them communicate or access its properties ?
We could simply do the following to resolve the issue explicitely:
@Deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.lookup_module_address,
replacement_base_class=linux_utilities_modules.Modules, # <-- added
replacement_version=(1, 0, 0),
)This also prevents edge cases that we haven't anticipated when accessing objects internals ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The valid use case is a slight boost in efficiency because it doesn't need to lug its class around with it, but now we need the class to get to the version property and otherwise the two are essentially indistinguishable? I'm ok with staticmethods being used in certain places, but generally not in versioned classes (which essentially means all plugins). I remember being a little against going to staticmethod when someone first tried to put one in a year or two ago.
I think people will find it odd that they need to define a second variable that's already contained in the first in order to get deprecation warnings to work, but also, I'd like to be able to get to the version of any method defined on a versionable component...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the first times I mentioned it... 5:S #115 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok if this is a requirement for versioned methods it makes sense to apply this restriction. I will check this PR (and my other ones) for any staticmethod.
Spider-Sense kicked in 5 years ago for an issue we are encountering today 🕸️
ikelos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think we're there. Thanks for your patience!
|
@ikelos I was planning on bumping the framework version first ? Thanks for your patience as well :) |
|
In the end, one minor bump would be needed I think. |
|
Happy for you to throw it in a separate PR if you want. I guess it is an additive change... |
…lities Bump minor version following #1567
Hi,
This PR adds a dedicated deprecation decorator to centralize the process. Sample:
Happy to discuss on the verbosity too (WARNING, INFO, DEBUG ?).
A couple of modules related LinuxUtilities were deprecated and moved to a dedicated module. The "kernel" related utilities (even if it is a module) weren't moved out. The framework was updated to use the new APIs paths, so the deprecation messages will be output only for external plugins.
Note: Some plugins weren't bumped due to the lack of versioning, but there is already on-going work on this subject in #1547.
Versioning
A minor bump should be needed for the new decorator, but I'll wait for a review on the general idea first.