зеркало из https://github.com/github/ruby.git
Fix cache incoherency for ME resolved through VM_METHOD_TYPE_REFINED
Previously, we didn't invalidate the method entry wrapped by VM_METHOD_TYPE_REFINED method entries which could cause calls to land in the wrong method like it did in the included test. Do the invalidation, and adjust rb_method_entry_clone() to accommodate this new invalidation vector. Fix:cfd7729ce7
See-also:e201b81f79
This commit is contained in:
Родитель
0704f40787
Коммит
cd4207869f
|
@ -2650,6 +2650,28 @@ class TestRefinement < Test::Unit::TestCase
|
|||
end;
|
||||
end
|
||||
|
||||
def test_inline_cache_invalidation
|
||||
klass = Class.new do
|
||||
def cached_foo_callsite = foo
|
||||
|
||||
def foo = :v1
|
||||
|
||||
host = self
|
||||
@refinement = Module.new do
|
||||
refine(host) do
|
||||
def foo = :unused
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
obj = klass.new
|
||||
obj.cached_foo_callsite # prime cache
|
||||
klass.class_eval do
|
||||
def foo = :v2 # invalidate
|
||||
end
|
||||
assert_equal(:v2, obj.cached_foo_callsite)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def eval_using(mod, s)
|
||||
|
|
|
@ -4379,6 +4379,8 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
|
|||
const rb_callable_method_entry_t *cme = vm_cc_cme(cc);
|
||||
VALUE v;
|
||||
|
||||
VM_ASSERT(! METHOD_ENTRY_INVALIDATED(cme));
|
||||
|
||||
switch (cme->def->type) {
|
||||
case VM_METHOD_TYPE_ISEQ:
|
||||
CC_SET_FASTPATH(cc, vm_call_iseq_setup, TRUE);
|
||||
|
|
31
vm_method.c
31
vm_method.c
|
@ -240,6 +240,13 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid)
|
|||
vm_cme_invalidate((rb_callable_method_entry_t *)cme);
|
||||
RB_DEBUG_COUNTER_INC(cc_invalidate_tree_cme);
|
||||
|
||||
// In case of refinement ME, also invalidate the wrapped ME that
|
||||
// could be cached at some callsite and is unreachable from any
|
||||
// RCLASS_CC_TBL.
|
||||
if (cme->def->type == VM_METHOD_TYPE_REFINED && cme->def->body.refined.orig_me) {
|
||||
vm_cme_invalidate((rb_callable_method_entry_t *)cme->def->body.refined.orig_me);
|
||||
}
|
||||
|
||||
if (cme->def->iseq_overload) {
|
||||
rb_callable_method_entry_t *monly_cme = (rb_callable_method_entry_t *)lookup_overloaded_cme(cme);
|
||||
if (monly_cme) {
|
||||
|
@ -676,12 +683,36 @@ rb_method_entry_create(ID called_id, VALUE klass, rb_method_visibility_t visi, r
|
|||
return me;
|
||||
}
|
||||
|
||||
// Return a cloned ME that's not invalidated (MEs are disposable for caching).
|
||||
const rb_method_entry_t *
|
||||
rb_method_entry_clone(const rb_method_entry_t *src_me)
|
||||
{
|
||||
rb_method_entry_t *me = rb_method_entry_alloc(src_me->called_id, src_me->owner, src_me->defined_class, src_me->def, METHOD_ENTRY_COMPLEMENTED(src_me));
|
||||
|
||||
METHOD_ENTRY_FLAGS_COPY(me, src_me);
|
||||
|
||||
// Also clone inner ME in case of refinement ME
|
||||
if (src_me->def &&
|
||||
src_me->def->type == VM_METHOD_TYPE_REFINED &&
|
||||
src_me->def->body.refined.orig_me) {
|
||||
const rb_method_entry_t *orig_me = src_me->def->body.refined.orig_me;
|
||||
VM_ASSERT(orig_me->def->type != VM_METHOD_TYPE_REFINED);
|
||||
|
||||
rb_method_entry_t *orig_clone = rb_method_entry_alloc(orig_me->called_id,
|
||||
orig_me->owner, orig_me->defined_class, orig_me->def, METHOD_ENTRY_COMPLEMENTED(orig_me));
|
||||
METHOD_ENTRY_FLAGS_COPY(orig_clone, orig_me);
|
||||
|
||||
// Clone definition, since writing a VALUE to a shared definition
|
||||
// can create reference edges we can't run WBs for.
|
||||
rb_method_definition_t *clone_def =
|
||||
rb_method_definition_create(VM_METHOD_TYPE_REFINED, src_me->called_id);
|
||||
|
||||
rb_method_refined_t refined = {
|
||||
.owner = src_me->def->body.refined.owner,
|
||||
.orig_me = orig_clone,
|
||||
};
|
||||
rb_method_definition_set(me, clone_def, &refined);
|
||||
}
|
||||
return me;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче