Implement explicit hook priority in vmethod interpose.

This resolves a getName order conflict between power-meter and rename.
develop
Alexander Gavrilov 2012-09-22 13:14:06 +04:00
parent f7e414e397
commit 038d62367e
3 changed files with 48 additions and 13 deletions

@ -247,8 +247,9 @@ void VMethodInterposeLinkBase::set_chain(void *chain)
addr_to_method_pointer_(chain_mptr, chain); addr_to_method_pointer_(chain_mptr, chain);
} }
VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr) VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority)
: host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr), : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method),
chain_mptr(chain_mptr), priority(priority),
applied(false), saved_chain(NULL), next(NULL), prev(NULL) applied(false), saved_chain(NULL), next(NULL), prev(NULL)
{ {
if (vmethod_idx < 0 || interpose_method == NULL) if (vmethod_idx < 0 || interpose_method == NULL)
@ -349,15 +350,26 @@ bool VMethodInterposeLinkBase::apply(bool enable)
return false; return false;
// Retrieve the current vtable entry // Retrieve the current vtable entry
void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
VMethodInterposeLinkBase *next_link = NULL;
while (old_link && old_link->host == host && old_link->priority > priority)
{
next_link = old_link;
old_link = old_link->prev;
}
void *old_ptr = next_link ? next_link->saved_chain : host->get_vmethod_ptr(vmethod_idx);
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr // Apply the new method ptr
set_chain(old_ptr); set_chain(old_ptr);
if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) if (next_link)
{
next_link->set_chain(interpose_method);
}
else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
{ {
set_chain(NULL); set_chain(NULL);
return false; return false;
@ -365,8 +377,13 @@ bool VMethodInterposeLinkBase::apply(bool enable)
// Push the current link into the home host // Push the current link into the home host
applied = true; applied = true;
host->interpose_list[vmethod_idx] = this;
prev = old_link; prev = old_link;
next = next_link;
if (next_link)
next_link->prev = this;
else
host->interpose_list[vmethod_idx] = this;
child_hosts.clear(); child_hosts.clear();
child_next.clear(); child_next.clear();
@ -374,13 +391,22 @@ bool VMethodInterposeLinkBase::apply(bool enable)
if (old_link && old_link->host == host) if (old_link && old_link->host == host)
{ {
// If the old link is home, just push into the plain chain // If the old link is home, just push into the plain chain
assert(old_link->next == NULL); assert(old_link->next == next_link);
old_link->next = this; old_link->next = this;
// Child links belong to the topmost local entry // Child links belong to the topmost local entry
child_hosts.swap(old_link->child_hosts); child_hosts.swap(old_link->child_hosts);
child_next.swap(old_link->child_next); child_next.swap(old_link->child_next);
} }
else if (next_link)
{
if (old_link)
{
assert(old_link->child_next.count(next_link));
old_link->child_next.erase(next_link);
old_link->child_next.insert(this);
}
}
else else
{ {
// If creating a new local chain, find children with same vmethod // If creating a new local chain, find children with same vmethod
@ -401,6 +427,8 @@ bool VMethodInterposeLinkBase::apply(bool enable)
} }
} }
assert (!next_link || (child_next.empty() && child_hosts.empty()));
// Chain subclass hooks // Chain subclass hooks
for (auto it = child_next.begin(); it != child_next.end(); ++it) for (auto it = child_next.begin(); it != child_next.end(); ++it)
{ {

@ -87,7 +87,8 @@ namespace DFHack
with code defined by DFHack, while retaining ability to with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are plugins, and multiple hooks for the same vmethod are
automatically chained in undefined order. automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage: Usage:
@ -105,6 +106,8 @@ namespace DFHack
}; };
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() { void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply()) if (!INTERPOSE_HOOK(my_hack, foo).apply())
@ -121,9 +124,11 @@ namespace DFHack
static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \ static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \
rtype interpose_fn_##name args rtype interpose_fn_##name args
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \ #define IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,priority) \
DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \ DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \
class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name); class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority);
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,0)
#define INTERPOSE_NEXT(name) (this->*interpose_##name.chain) #define INTERPOSE_NEXT(name) (this->*interpose_##name.chain)
#define INTERPOSE_HOOK(class, name) (class::interpose_##name) #define INTERPOSE_HOOK(class, name) (class::interpose_##name)
@ -140,6 +145,7 @@ namespace DFHack
int vmethod_idx; int vmethod_idx;
void *interpose_method; // Pointer to the code of the interposing method void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below void *chain_mptr; // Pointer to the chain field below
int priority;
bool applied; bool applied;
void *saved_chain; // Previous pointer to the code void *saved_chain; // Previous pointer to the code
@ -155,7 +161,7 @@ namespace DFHack
VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
void find_child_hosts(virtual_identity *cur, void *vmptr); void find_child_hosts(virtual_identity *cur, void *vmptr);
public: public:
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr); VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority);
~VMethodInterposeLinkBase(); ~VMethodInterposeLinkBase();
bool is_applied() { return applied; } bool is_applied() { return applied; }
@ -171,12 +177,13 @@ namespace DFHack
operator Ptr () { return chain; } operator Ptr () { return chain; }
template<class Ptr2> template<class Ptr2>
VMethodInterposeLink(Ptr target, Ptr2 src) VMethodInterposeLink(Ptr target, Ptr2 src, int priority)
: VMethodInterposeLinkBase( : VMethodInterposeLinkBase(
&Base::_identity, &Base::_identity,
vmethod_pointer_to_idx(target), vmethod_pointer_to_idx(target),
method_pointer_to_addr(src), method_pointer_to_addr(src),
&chain &chain,
priority
) )
{ src = target; /* check compatibility */ } { src = target; /* check compatibility */ }
}; };

@ -130,7 +130,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
INTERPOSE_NEXT(getName)(buf); \ INTERPOSE_NEXT(getName)(buf); \
} \ } \
}; \ }; \
IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cname##_hook, getName, 100);
KNOWN_BUILDINGS KNOWN_BUILDINGS
#undef BUILDING #undef BUILDING