BinaryAnalysisPlatform / bap

Binary Analysis Platform
MIT License
2.07k stars 273 forks source link

tweaks the method resolution to keep super methods #1370

Closed ivg closed 2 years ago

ivg commented 2 years ago

This change affects both dynamic and static interpreters, so read carefully. Before this commit we used basically the same resolution procedure for all Primus Lisp definitions, with the only exception, in the end, we allow more than one method definition for the same name.

The caveat is that when more than one method of the same class is applicable, the most specific methods were chosen and the least specific, i.e., the super methods, were removed from the resolution. On one hand, this enables refinement of a method, on the other we don't have any notation to call the super method, so the refinement is more like a redefinition. This approach is suitable with normal function definitions that are required to be unique (and when we need refinement we can always factor out the common part from the parent definition and reuse it in the more specific one). But methods are used for different purposes - they process signals from the knowledge base or from the Primus Interpreter and when we add a more specific reaction to the signal we still want to keep other reactions (and the knowledge base will actually take care of the refinement by calling Value.merge on the method results).

To highlight the problem here is an example from the real world (that triggers this change). We have two definitions of the bap:pattern-actions method, one that is generic and handles 'funcstart and 'possiblefuncstart actions, and another that is specific to arm and handles arm/thumb interworking via setcontext. Right now they are all defined in the same file, and arm-specific method is triggered even for non-arm targets. Moreover, when arm is not enabled (i.e., not installed or specifically disabled with --no-arm), the method fails on the typechecker (and will fail as well in runtime) because the bap:arm-set-encoding primitive is not available. The immediate solution is to properly constrain the context of applicability of this method definition to (context (target arm)), which leads to disastrous results. Now the resolver thinks that the arm-specific method that handles contexts, is the overload of the more general method (that handles function starts), so it expels the parent method from the list and never calls it. Not what we wanted! In fact, we want all applicable methods to be called no matter their specificity. In other words, when a method is called, we want to call all methods, starting from the parents and ending with the children.

This is a little bit breaking change as if there exists the Primus Lisp code that was relying on the overriding behavior (none that I am aware of) it will no longer work as expected. The solution is to rely on the overloading of normal functions, e.g.,

(defmethod do-stuff (x)
  (declare (context worker))
  parent-code)

(defmethod do-stuff (x)
  (declare (context worker child))
  child-code)

should be rewritten as,

(defun do-stuff (x)
  (declare (context worker))
  parent-code)

(defun do-stuff (x)
  (declare (context worker child))
  child-code)

(defmethod do-stuff (x)
  (do-stuff x))