diff --git a/class.c b/class.c index 783970dbf16b5a..69194ccab23756 100644 --- a/class.c +++ b/class.c @@ -80,10 +80,6 @@ #define METACLASS_OF(k) RBASIC(k)->klass #define SET_METACLASS_OF(k, cls) RBASIC_SET_CLASS(k, cls) -static void rb_class_remove_from_super_subclasses(VALUE klass); -static void rb_class_remove_from_module_subclasses(VALUE klass); -static void rb_class_classext_free_subclasses(rb_classext_t *ext); - rb_classext_t * rb_class_unlink_classext(VALUE klass, const rb_box_t *box) { @@ -105,11 +101,6 @@ rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) rb_free_const_table(tbl); } - if (is_prime) { - rb_class_remove_from_super_subclasses(klass); - rb_class_classext_free_subclasses(ext); - } - if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { RUBY_ASSERT(is_prime); // superclasses should only be used on prime size_t depth = RCLASSEXT_SUPERCLASS_DEPTH(ext); @@ -136,11 +127,6 @@ rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); } - if (is_prime) { - rb_class_remove_from_super_subclasses(klass); - rb_class_remove_from_module_subclasses(klass); - } - if (!is_prime) { // the prime classext will be freed with RClass SIZED_FREE(ext); } @@ -406,23 +392,25 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_box_t *bo * * Subclasses are only in the prime classext, so read from orig. */ - rb_subclass_entry_t *subclass_entry = RCLASSEXT_SUBCLASSES(orig); - if (subclass_entry) subclass_entry = subclass_entry->next; // skip dummy head - while (subclass_entry) { - VALUE iclass = subclass_entry->klass; - - /* every node in the subclass list should be an ICLASS built from this module */ - VM_ASSERT(iclass); - VM_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); - VM_ASSERT(RBASIC_CLASS(iclass) == klass); - - if (FL_TEST_RAW(iclass, RCLASS_BOXABLE)) { - // Non-boxable ICLASSes (included by classes in main/user boxes) can't - // hold per-box classexts, and their includer classes also can't, so - // method lookup through them always uses the prime classext. - class_duplicate_iclass_classext(iclass, ext, box); + VALUE subs_v = RCLASSEXT_SUBCLASSES(orig); + if (subs_v) { + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + VALUE iclass = entries[i]; + if (!iclass) continue; + + /* every node in the subclass list should be an ICLASS built from this module */ + VM_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); + VM_ASSERT(RBASIC_CLASS(iclass) == klass); + + if (FL_TEST_RAW(iclass, RCLASS_BOXABLE)) { + // Non-boxable ICLASSes (included by classes in main/user boxes) can't + // hold per-box classexts, and their includer classes also can't, so + // method lookup through them always uses the prime classext. + class_duplicate_iclass_classext(iclass, ext, box); + } } - subclass_entry = subclass_entry->next; } } @@ -481,36 +469,47 @@ rb_class_variation_count(VALUE klass) return RCLASS_VARIATION_COUNT(klass); } -static rb_subclass_entry_t * +static void push_subclass_entry_to_list(VALUE super, VALUE klass) { - rb_subclass_entry_t *entry, *head; - RUBY_ASSERT( (RB_TYPE_P(super, T_MODULE) && RB_TYPE_P(klass, T_ICLASS)) || (RB_TYPE_P(super, T_CLASS) && RB_TYPE_P(klass, T_CLASS)) || (RB_TYPE_P(klass, T_ICLASS) && !NIL_P(RCLASS_REFINED_CLASS(klass))) ); - entry = ZALLOC(rb_subclass_entry_t); - entry->klass = klass; - RB_VM_LOCKING() { - head = RCLASS_WRITABLE_SUBCLASSES(super); - if (!head) { - head = ZALLOC(rb_subclass_entry_t); - RCLASS_SET_SUBCLASSES(super, head); - } - entry->next = head->next; - entry->prev = head; + VALUE subs_v = RCLASS_SUBCLASSES(super); + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + + if (!subs || subs->count == subs->capacity) { + VALUE *old_entries = subs ? rb_imemo_subclasses_entries(subs_v) : NULL; + uint32_t live = 0; + for (uint32_t i = 0; subs && i < subs->count; i++) { + if (old_entries[i]) live++; + } - if (head->next) { - head->next->prev = entry; + uint32_t cap = subs ? subs->capacity : 2; + if (live * 2 >= cap) cap *= 2; + + VALUE new_v = rb_imemo_subclasses_new(cap); + struct rb_subclasses *new_subs = (struct rb_subclasses *)new_v; + VALUE *new_entries = rb_imemo_subclasses_entries(new_v); + for (uint32_t i = 0; subs && i < subs->count; i++) { + VALUE entry = old_entries[i]; + if (entry) { + new_entries[new_subs->count++] = entry; + RB_OBJ_WRITTEN(new_v, Qundef, entry); + } + } + RCLASS_SET_SUBCLASSES(super, new_v); + subs_v = new_v; + subs = new_subs; } - head->next = entry; - } - return entry; + rb_imemo_subclasses_entries(subs_v)[subs->count++] = klass; + RB_OBJ_WRITTEN(subs_v, Qundef, klass); + } } void @@ -519,8 +518,7 @@ rb_class_subclass_add(VALUE super, VALUE klass) if (super && !UNDEF_P(super)) { RUBY_ASSERT(RB_TYPE_P(super, T_CLASS) || RB_TYPE_P(super, T_MODULE)); RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_ICLASS)); - rb_subclass_entry_t *entry = push_subclass_entry_to_list(super, klass); - RCLASS_EXT_PRIME(klass)->subclass_entry = entry; + push_subclass_entry_to_list(super, klass); } } @@ -530,112 +528,32 @@ rb_module_add_to_subclasses_list(VALUE module, VALUE iclass) if (module && !UNDEF_P(module)) { RUBY_ASSERT(RB_TYPE_P(module, T_MODULE)); RUBY_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); - rb_subclass_entry_t *entry = push_subclass_entry_to_list(module, iclass); - RCLASS_EXT_PRIME(iclass)->module_subclass_entry = entry; - } -} - -static void -rb_subclass_entry_remove(rb_subclass_entry_t *entry) -{ - if (entry) { - rb_subclass_entry_t *prev = entry->prev, *next = entry->next; - - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; - } - - xfree(entry); - } -} - -static void -rb_class_remove_from_super_subclasses(VALUE klass) -{ - rb_classext_t *ext = RCLASS_EXT_PRIME(klass); - rb_subclass_entry_t *entry = RCLASSEXT_SUBCLASS_ENTRY(ext); - - if (!entry) return; - rb_subclass_entry_remove(entry); - RCLASSEXT_SUBCLASS_ENTRY(ext) = NULL; -} - -static void -rb_class_remove_from_module_subclasses(VALUE klass) -{ - rb_classext_t *ext = RCLASS_EXT_PRIME(klass); - rb_subclass_entry_t *entry = RCLASSEXT_MODULE_SUBCLASS_ENTRY(ext); - - if (!entry) return; - rb_subclass_entry_remove(entry); - RCLASSEXT_MODULE_SUBCLASS_ENTRY(ext) = NULL; -} - -static void -rb_class_classext_free_subclasses(rb_classext_t *ext) -{ - rb_subclass_entry_t *head = RCLASSEXT_SUBCLASSES(ext); - - if (head) { - // Detach all children's back-pointers before freeing the list, - // so they don't try to unlink from a freed entry later. - rb_subclass_entry_t *entry = head->next; // skip dummy head - while (entry) { - if (entry->klass) { - rb_classext_t *child_ext = RCLASS_EXT_PRIME(entry->klass); - if (RCLASSEXT_SUBCLASS_ENTRY(child_ext) == entry) { - RCLASSEXT_SUBCLASS_ENTRY(child_ext) = NULL; - } - if (RCLASSEXT_MODULE_SUBCLASS_ENTRY(child_ext) == entry) { - RCLASSEXT_MODULE_SUBCLASS_ENTRY(child_ext) = NULL; - } - } - entry = entry->next; - } - - entry = head; - while (entry) { - rb_subclass_entry_t *next = entry->next; - xfree(entry); - entry = next; - } - RCLASSEXT_SUBCLASSES(ext) = NULL; + push_subclass_entry_to_list(module, iclass); } } void rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg) { - rb_subclass_entry_t *tmp; - rb_subclass_entry_t *cur = RCLASS_SUBCLASSES_FIRST(klass); - /* do not be tempted to simplify this loop into a for loop, the order of - operations is important here if `f` modifies the linked list */ - while (cur) { - VALUE curklass = cur->klass; - tmp = cur->next; - // do not trigger GC during f, otherwise the cur will become - // a dangling pointer if the subclass is collected - f(curklass, arg); - cur = tmp; - } -} + VALUE subs_v = RCLASS_SUBCLASSES(klass); + if (!subs_v) return; -static void -class_detach_subclasses(VALUE klass, VALUE arg) -{ - rb_class_remove_from_super_subclasses(klass); + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + VALUE curklass = entries[i]; + if (curklass) { + f(curklass, arg); + } + } } static void class_switch_superclass(VALUE super, VALUE klass) { - RB_VM_LOCKING() { - class_detach_subclasses(klass, Qnil); - rb_class_subclass_add(super, klass); - } + // No need to remove from old super's subclasses list — the GC + // will nullify the weak reference when appropriate. + rb_class_subclass_add(super, klass); } /** @@ -1695,29 +1613,34 @@ rb_include_module(VALUE klass, VALUE module) rb_raise(rb_eArgError, "cyclic include detected"); if (RB_TYPE_P(klass, T_MODULE)) { - rb_subclass_entry_t *iclass = RCLASS_SUBCLASSES_FIRST(klass); - while (iclass) { - int do_include = 1; - VALUE check_class = iclass->klass; - /* During lazy sweeping, iclass->klass could be a dead object that - * has not yet been swept. */ - if (!rb_objspace_garbage_object_p(check_class)) { - while (check_class) { - RUBY_ASSERT(!rb_objspace_garbage_object_p(check_class)); - - if (RB_TYPE_P(check_class, T_ICLASS) && - (METACLASS_OF(check_class) == module)) { - do_include = 0; + VALUE subs_v = RCLASS_SUBCLASSES(klass); + if (subs_v) { + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + VALUE check_class = entries[i]; + if (!check_class) continue; + + int do_include = 1; + /* During lazy sweeping, the entry could be a dead object that + * has not yet been swept. */ + if (!rb_objspace_garbage_object_p(check_class)) { + VALUE walk = check_class; + while (walk) { + RUBY_ASSERT(!rb_objspace_garbage_object_p(walk)); + + if (RB_TYPE_P(walk, T_ICLASS) && + (METACLASS_OF(walk) == module)) { + do_include = 0; + } + walk = RCLASS_SUPER(walk); } - check_class = RCLASS_SUPER(check_class); - } - if (do_include) { - include_modules_at(iclass->klass, RCLASS_ORIGIN(iclass->klass), module, TRUE); + if (do_include) { + include_modules_at(check_class, RCLASS_ORIGIN(check_class), module, TRUE); + } } } - - iclass = iclass->next; } } } @@ -1950,29 +1873,32 @@ rb_prepend_module(VALUE klass, VALUE module) rb_vm_check_redefinition_by_prepend(klass); } if (RB_TYPE_P(klass, T_MODULE)) { - rb_subclass_entry_t *iclass = RCLASS_SUBCLASSES_FIRST(klass); + VALUE subs_v = RCLASS_SUBCLASSES(klass); VALUE klass_origin = RCLASS_ORIGIN(klass); struct rb_id_table *klass_m_tbl = RCLASS_M_TBL(klass); struct rb_id_table *klass_origin_m_tbl = RCLASS_M_TBL(klass_origin); - while (iclass) { - /* During lazy sweeping, iclass->klass could be a dead object that - * has not yet been swept. */ - if (!rb_objspace_garbage_object_p(iclass->klass)) { - const VALUE subclass = iclass->klass; - if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(subclass)) { - // backfill an origin iclass to handle refinements and future prepends - rb_id_table_foreach(RCLASS_M_TBL(subclass), clear_module_cache_i, (void *)subclass); - RCLASS_WRITE_M_TBL(subclass, klass_m_tbl); - VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(subclass)); - rb_class_set_super(subclass, origin); - RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(subclass)); - RCLASS_WRITE_ORIGIN(subclass, origin); - RICLASS_SET_ORIGIN_SHARED_MTBL(origin); + if (subs_v) { + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + const VALUE subclass = entries[i]; + if (!subclass) continue; + /* During lazy sweeping, the entry could be a dead object that + * has not yet been swept. */ + if (!rb_objspace_garbage_object_p(subclass)) { + if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(subclass)) { + // backfill an origin iclass to handle refinements and future prepends + rb_id_table_foreach(RCLASS_M_TBL(subclass), clear_module_cache_i, (void *)subclass); + RCLASS_WRITE_M_TBL(subclass, klass_m_tbl); + VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(subclass)); + rb_class_set_super(subclass, origin); + RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(subclass)); + RCLASS_WRITE_ORIGIN(subclass, origin); + RICLASS_SET_ORIGIN_SHARED_MTBL(origin); + } + include_modules_at(subclass, subclass, module, FALSE); } - include_modules_at(subclass, subclass, module, FALSE); } - - iclass = iclass->next; } } } diff --git a/common.mk b/common.mk index b1debdebc9730b..0d437db5073969 100644 --- a/common.mk +++ b/common.mk @@ -69,8 +69,8 @@ LIBRUBY_EXTS = ./.libruby-with-ext.time REVISION_H = ./.revision.time PLATFORM_D = $(TIMESTAMPDIR)/.$(PLATFORM_DIR).time ENC_TRANS_D = $(TIMESTAMPDIR)/.enc-trans.time -X_$(no_baseruby:no=NO_)BASERUBY = $(BASERUBY) -X_$(yes_baseruby:yes=NO_)BASERUBY = $(XRUBY) +X_$(CROSS_COMPILING:yes=)BASERUBY = $(BASERUBY) +X_$(CROSS_COMPILING:no=)BASERUBY = $(XRUBY) RDOC = $(X_BASERUBY) --enable-gems "$(tooldir)/rdoc-srcdir" RDOCOUT = $(EXTOUT)/rdoc HTMLOUT = $(EXTOUT)/html @@ -1580,7 +1580,7 @@ yes-test-bundled-gems-precheck: $(PRECHECK_BUNDLED_GEMS:yes=main) no-test-bundled-gems-precheck: yes-update-default-gemspecs no-update-default-gemspecs: update-default-gemspecs -update-default-gemspecs: $(PREP) +update-default-gemspecs: $(PREP) $(RBCONFIG) @$(MAKEDIRS) $(srcdir)/.bundle/specifications $(Q)$(MINIRUBY) -W0 -C "$(srcdir)" -I tool/lib -roptparse -routput -rbundled_gem \ -e "(out = Output.new).def_options(ARGV.options)" \ diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 96ff6b3f05d861..0f299c2ab3ce90 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -459,6 +459,7 @@ count_imemo_objects(int argc, VALUE *argv, VALUE self) INIT_IMEMO_TYPE_ID(imemo_callcache); INIT_IMEMO_TYPE_ID(imemo_constcache); INIT_IMEMO_TYPE_ID(imemo_fields); + INIT_IMEMO_TYPE_ID(imemo_subclasses); #undef INIT_IMEMO_TYPE_ID } diff --git a/gc.c b/gc.c index e0757a20dd48f4..b5eb0dd0fa71f0 100644 --- a/gc.c +++ b/gc.c @@ -1294,13 +1294,28 @@ rb_gc_handle_weak_references(VALUE obj) break; case T_IMEMO: { - GC_ASSERT(imemo_type(obj) == imemo_callcache); - - struct rb_callcache *cc = (struct rb_callcache *)obj; - if (cc->klass != Qundef && - (!rb_gc_handle_weak_references_alive_p(cc->klass) || - !rb_gc_handle_weak_references_alive_p((VALUE)cc->cme_))) { - vm_cc_invalidate(cc); + switch (imemo_type(obj)) { + case imemo_callcache: { + struct rb_callcache *cc = (struct rb_callcache *)obj; + if (cc->klass != Qundef && + (!rb_gc_handle_weak_references_alive_p(cc->klass) || + !rb_gc_handle_weak_references_alive_p((VALUE)cc->cme_))) { + vm_cc_invalidate(cc); + } + break; + } + case imemo_subclasses: { + struct rb_subclasses *subs = (struct rb_subclasses *)obj; + VALUE *entries = rb_imemo_subclasses_entries(obj); + for (uint32_t i = 0; i < subs->count; i++) { + if (entries[i] && !rb_gc_handle_weak_references_alive_p(entries[i])) { + entries[i] = 0; + } + } + break; + } + default: + rb_bug("rb_gc_handle_weak_references: unexpected imemo type"); } break; @@ -1330,6 +1345,9 @@ rb_gc_imemo_needs_cleanup_p(VALUE obj) case imemo_callinfo: return true; + case imemo_subclasses: + return FL_TEST_RAW(obj, IMEMO_SUBCLASSES_HEAP); + case imemo_tmpbuf: return ((rb_imemo_tmpbuf_t *)obj)->ptr != NULL; @@ -3333,6 +3351,9 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE box_value, void *a } mark_m_tbl(objspace, RCLASSEXT_CALLABLE_M_TBL(ext)); gc_mark_internal(RCLASSEXT_CC_TBL(ext)); + if (RCLASSEXT_SUBCLASSES(ext)) { + gc_mark_internal(RCLASSEXT_SUBCLASSES(ext)); + } gc_mark_internal(RCLASSEXT_CLASSPATH(ext)); } @@ -3353,6 +3374,9 @@ gc_mark_classext_iclass(rb_classext_t *ext, bool prime, VALUE box_value, void *a } mark_m_tbl(objspace, RCLASSEXT_CALLABLE_M_TBL(ext)); gc_mark_internal(RCLASSEXT_CC_TBL(ext)); + if (RCLASSEXT_SUBCLASSES(ext)) { + gc_mark_internal(RCLASSEXT_SUBCLASSES(ext)); + } } #define TYPED_DATA_REFS_OFFSET_LIST(d) (size_t *)(uintptr_t)RTYPEDDATA_TYPE(d)->function.dmark @@ -3988,18 +4012,6 @@ update_const_tbl(void *objspace, struct rb_id_table *tbl) rb_id_table_foreach_values(tbl, update_const_tbl_i, objspace); } -static void -update_subclasses(void *objspace, rb_classext_t *ext) -{ - rb_subclass_entry_t *entry = RCLASSEXT_SUBCLASSES(ext); - if (!entry) return; - while (entry) { - if (entry->klass) - UPDATE_IF_MOVED(objspace, entry->klass); - entry = entry->next; - } -} - static void update_superclasses(rb_objspace_t *objspace, rb_classext_t *ext) { @@ -4041,7 +4053,9 @@ update_classext(rb_classext_t *ext, bool is_prime, VALUE box_value, void *arg) UPDATE_IF_MOVED(objspace, RCLASSEXT_CC_TBL(ext)); UPDATE_IF_MOVED(objspace, RCLASSEXT_CVC_TBL(ext)); update_superclasses(objspace, ext); - update_subclasses(objspace, ext); + if (RCLASSEXT_SUBCLASSES(ext)) { + UPDATE_IF_MOVED(objspace, RCLASSEXT_SUBCLASSES(ext)); + } update_classext_values(objspace, ext, false); } @@ -4059,7 +4073,9 @@ update_iclass_classext(rb_classext_t *ext, bool is_prime, VALUE box_value, void update_m_tbl(objspace, RCLASSEXT_CALLABLE_M_TBL(ext)); UPDATE_IF_MOVED(objspace, RCLASSEXT_CC_TBL(ext)); UPDATE_IF_MOVED(objspace, RCLASSEXT_CVC_TBL(ext)); - update_subclasses(objspace, ext); + if (RCLASSEXT_SUBCLASSES(ext)) { + UPDATE_IF_MOVED(objspace, RCLASSEXT_SUBCLASSES(ext)); + } update_classext_values(objspace, ext, true); } diff --git a/imemo.c b/imemo.c index a6550a11a79b53..d1d8cc8cb97cb1 100644 --- a/imemo.c +++ b/imemo.c @@ -31,6 +31,7 @@ rb_imemo_name(enum imemo_type type) IMEMO_NAME(tmpbuf); IMEMO_NAME(cvar_entry); IMEMO_NAME(fields); + IMEMO_NAME(subclasses); #undef IMEMO_NAME } rb_bug("unreachable"); @@ -215,6 +216,32 @@ rb_imemo_fields_clear(VALUE fields_obj) RBASIC_CLEAR_CLASS(fields_obj); } +VALUE +rb_imemo_subclasses_new(uint32_t capacity) +{ + size_t embed_size = offsetof(struct rb_subclasses, as) + capacity * sizeof(VALUE); + struct rb_subclasses *subs; + + if (rb_gc_size_allocatable_p(embed_size)) { + subs = (struct rb_subclasses *)rb_imemo_new(imemo_subclasses, 0, embed_size, true); + subs->count = 0; + subs->capacity = capacity; + memset(subs->as.embed, 0, capacity * sizeof(VALUE)); + rb_gc_declare_weak_references((VALUE)subs); + } + else { + subs = (struct rb_subclasses *)rb_imemo_new(imemo_subclasses, 0, sizeof(struct rb_subclasses), true); + subs->as.external = NULL; + subs->count = 0; + subs->capacity = 0; + FL_SET_RAW((VALUE)subs, IMEMO_SUBCLASSES_HEAP); + rb_gc_declare_weak_references((VALUE)subs); + subs->as.external = ZALLOC_N(VALUE, capacity); + subs->capacity = capacity; + } + return (VALUE)subs; +} + /* ========================================================================= * memsize * ========================================================================= */ @@ -263,6 +290,13 @@ rb_imemo_memsize(VALUE obj) size += st_memsize(IMEMO_OBJ_FIELDS(obj)->as.complex.table); } break; + case imemo_subclasses: { + if (FL_TEST_RAW(obj, IMEMO_SUBCLASSES_HEAP)) { + struct rb_subclasses *subs = (struct rb_subclasses *)obj; + size += subs->capacity * sizeof(VALUE); + } + break; + } default: rb_bug("unreachable"); } @@ -506,6 +540,18 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) rb_gc_mark_and_move((VALUE *)&ent->cref); break; } + case imemo_subclasses: { + if (reference_updating) { + struct rb_subclasses *subs = (struct rb_subclasses *)obj; + VALUE *entries = rb_imemo_subclasses_entries(obj); + for (uint32_t i = 0; i < subs->count; i++) { + if (entries[i]) { + entries[i] = rb_gc_location(entries[i]); + } + } + } + break; + } case imemo_fields: { rb_gc_mark_and_move((VALUE *)&RBASIC(obj)->klass); @@ -641,6 +687,13 @@ rb_imemo_free(VALUE obj) imemo_fields_free(IMEMO_OBJ_FIELDS(obj)); RB_DEBUG_COUNTER_INC(obj_imemo_fields); break; + case imemo_subclasses: { + if (FL_TEST_RAW(obj, IMEMO_SUBCLASSES_HEAP)) { + struct rb_subclasses *subs = (struct rb_subclasses *)obj; + SIZED_FREE_N(subs->as.external, subs->capacity); + } + break; + } default: rb_bug("unreachable"); } diff --git a/internal/class.h b/internal/class.h index 04914d9bdf0581..45f2fde5f8dd93 100644 --- a/internal/class.h +++ b/internal/class.h @@ -27,13 +27,6 @@ # undef RCLASS_SUPER #endif -struct rb_subclass_entry { - VALUE klass; - struct rb_subclass_entry *next; - struct rb_subclass_entry *prev; -}; -typedef struct rb_subclass_entry rb_subclass_entry_t; - struct rb_cvar_class_tbl_entry { VALUE imemo_flags; uint32_t index; @@ -54,21 +47,10 @@ struct rb_classext_struct { VALUE cvc_tbl; VALUE *superclasses; /** - * The head of the subclasses linked list. This is a dummy entry (klass == 0) - * whose `next` points to the first real entry. Only used in prime classext. - */ - struct rb_subclass_entry *subclasses; - /** - * Back-pointer to this class's entry in its superclass's subclasses list. - * Used for O(1) removal when the class is freed. - */ - struct rb_subclass_entry *subclass_entry; - /** - * In the case that this is an `ICLASS`, `module_subclass_entry` points to the - * entry in the module's `subclasses` list that indicates that the klass has been - * included. Used for O(1) removal. + * imemo_subclasses VALUE tracking this class's subclasses. + * Only used in prime classext. Lazily allocated on first subclass addition. */ - struct rb_subclass_entry *module_subclass_entry; + VALUE subclasses; const VALUE origin_; const VALUE refined_class; @@ -151,8 +133,6 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj); #define RCLASSEXT_SUPERCLASS_DEPTH(ext) (ext->superclass_depth) #define RCLASSEXT_SUPERCLASSES(ext) (ext->superclasses) #define RCLASSEXT_SUBCLASSES(ext) (ext->subclasses) -#define RCLASSEXT_SUBCLASS_ENTRY(ext) (ext->subclass_entry) -#define RCLASSEXT_MODULE_SUBCLASS_ENTRY(ext) (ext->module_subclass_entry) #define RCLASSEXT_ORIGIN(ext) (ext->origin_) #define RCLASSEXT_REFINED_CLASS(ext) (ext->refined_class) // class.allocator/singleton_class.attached_object are not accessed directly via RCLASSEXT_* @@ -190,7 +170,6 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE */ #define RCLASS_CVC_TBL(c) (RCLASS_EXT_READABLE(c)->cvc_tbl) #define RCLASS_SUBCLASSES(c) (RCLASS_EXT_PRIME(c)->subclasses) -#define RCLASS_SUBCLASSES_FIRST(c) (RCLASS_EXT_PRIME(c)->subclasses ? RCLASS_EXT_PRIME(c)->subclasses->next : NULL) #define RCLASS_ORIGIN(c) (RCLASS_EXT_READABLE(c)->origin_) #define RICLASS_IS_ORIGIN_P(c) (RCLASS_EXT_READABLE(c)->iclass_is_origin) #define RCLASS_PERMANENT_CLASSPATH_P(c) (RCLASS_EXT_READABLE(c)->permanent_classpath) @@ -229,7 +208,7 @@ static inline void RCLASS_SET_CVC_TBL(VALUE klass, VALUE table); static inline void RCLASS_WRITE_CVC_TBL(VALUE klass, VALUE table); static inline void RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool with_self); -static inline void RCLASS_SET_SUBCLASSES(VALUE klass, rb_subclass_entry_t *head); +static inline void RCLASS_SET_SUBCLASSES(VALUE klass, VALUE subclasses); static inline void RCLASS_SET_ORIGIN(VALUE klass, VALUE origin); static inline void RCLASS_WRITE_ORIGIN(VALUE klass, VALUE origin); @@ -686,10 +665,10 @@ RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool w } static inline void -RCLASS_SET_SUBCLASSES(VALUE klass, rb_subclass_entry_t *head) +RCLASS_SET_SUBCLASSES(VALUE klass, VALUE subclasses) { rb_classext_t *ext = RCLASS_EXT_PRIME(klass); - RCLASSEXT_SUBCLASSES(ext) = head; + RB_OBJ_WRITE(klass, &RCLASSEXT_SUBCLASSES(ext), subclasses); } static inline void diff --git a/internal/imemo.h b/internal/imemo.h index fae2334fa0ffc1..e185ad602d41f1 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -42,6 +42,7 @@ enum imemo_type { imemo_callcache = 11, imemo_constcache = 12, imemo_fields = 13, + imemo_subclasses = 14, }; /* CREF (Class REFerence) is defined in method.h */ @@ -249,7 +250,27 @@ STATIC_ASSERT(imemo_fields_complex_offset, offsetof(struct RObject, as.heap.fiel #define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields) +#define IMEMO_SUBCLASSES_HEAP IMEMO_FL_USER0 + +struct rb_subclasses { + VALUE flags; + uint32_t count; + uint32_t capacity; + union { + VALUE *external; + VALUE embed[1]; + } as; +}; + +static inline VALUE * +rb_imemo_subclasses_entries(VALUE v) +{ + struct rb_subclasses *s = (struct rb_subclasses *)v; + return FL_TEST_RAW(v, IMEMO_SUBCLASSES_HEAP) ? s->as.external : s->as.embed; +} + VALUE rb_imemo_fields_new(VALUE owner, /* shape_id_t */ uint32_t shape_id, bool shareable); +VALUE rb_imemo_subclasses_new(uint32_t capacity); VALUE rb_imemo_fields_new_complex(VALUE owner, /* shape_id_t */ uint32_t shape_id, size_t capa, bool shareable); VALUE rb_imemo_fields_new_complex_tbl(VALUE owner, /* shape_id_t */ uint32_t shape_id, st_table *tbl, bool shareable); VALUE rb_imemo_fields_clone(VALUE fields_obj); diff --git a/lib/bundler/compact_index_client/parser.rb b/lib/bundler/compact_index_client/parser.rb index 43581fd7efe563..ad0d17ed4a4aea 100644 --- a/lib/bundler/compact_index_client/parser.rb +++ b/lib/bundler/compact_index_client/parser.rb @@ -71,7 +71,10 @@ def gem_parser # This method gets called at least once for every gem when parsing versions. def parse_version_checksum(line, checksums) return unless (name_end = line.index(" ")) # Artifactory bug causes blank lines in artifactor index files - return unless (checksum_start = line.index(" ", name_end + 1) + 1) + checksum_start = line.index(" ", name_end + 1) + return unless checksum_start + checksum_start += 1 + checksum_end = line.size - checksum_start line.freeze # allows slicing into the string to not allocate a copy of the line diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 6f5ab29b1556da..7a9567147103ae 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -38,7 +38,10 @@ def self.build(gemfile, lockfile, unlock) raise GemfileNotFound, "#{gemfile} not found" unless gemfile.file? - Dsl.evaluate(gemfile, lockfile, unlock) + Plugin.hook(Plugin::Events::GEM_BEFORE_EVAL, gemfile, lockfile) + Dsl.evaluate(gemfile, lockfile, unlock).tap do |definition| + Plugin.hook(Plugin::Events::GEM_AFTER_EVAL, definition) + end end # @@ -1210,16 +1213,20 @@ def excluded_git_sources def find_source_requirements preload_git_sources + # Only safe to exclude when locked_requirements (merged below) backfills the gap. + nothing_changed = nothing_changed? + excluded = nothing_changed ? excluded_git_sources : [] + # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) source_requirements = if precompute_source_requirements_for_indirect_dependencies? - all_requirements = source_map.all_requirements(excluded_git_sources) + all_requirements = source_map.all_requirements(excluded) { default: default_source }.merge(all_requirements) else - { default: Source::RubygemsAggregate.new(sources, source_map, excluded_git_sources) }.merge(source_map.direct_requirements) + { default: Source::RubygemsAggregate.new(sources, source_map, excluded) }.merge(source_map.direct_requirements) end - source_requirements.merge!(source_map.locked_requirements) if nothing_changed? + source_requirements.merge!(source_map.locked_requirements) if nothing_changed metadata_dependencies.each do |dep| source_requirements[dep.name] = sources.metadata_source end diff --git a/lib/bundler/plugin/events.rb b/lib/bundler/plugin/events.rb index 29c05098ae1443..3fbf60307ec105 100644 --- a/lib/bundler/plugin/events.rb +++ b/lib/bundler/plugin/events.rb @@ -30,6 +30,54 @@ def self.defined_event?(event) @events.key?(event) end + # @!parse + # A hook called before the Gemfile is evaluated + # Includes the Gemfile path and the Lockfile path + # GEM_BEFORE_EVAL = "before-eval" + define :GEM_BEFORE_EVAL, "before-eval" + + # @!parse + # A hook called after the Gemfile is evaluated + # Includes a Bundler::Definition + # GEM_AFTER_EVAL = "after-eval" + define :GEM_AFTER_EVAL, "after-eval" + + # @!parse + # A hook called before any gems install + # Includes an Array of Bundler::Dependency objects + # GEM_BEFORE_INSTALL_ALL = "before-install-all" + define :GEM_BEFORE_INSTALL_ALL, "before-install-all" + + # @!parse + # A hook called before each individual gem is downloaded from a remote source. + # Includes a spec-like object responding to the Gem::Specification API + # (for example, a Bundler spec proxy such as Bundler::EndpointSpecification + # or Bundler::RemoteSpecification). Does not fire when the gem is already + # present at the initial download-cache check. + # GEM_BEFORE_FETCH = "before-fetch" + define :GEM_BEFORE_FETCH, "before-fetch" + + # @!parse + # A hook called after each individual gem is downloaded from a remote source. + # Includes a spec-like object responding to the Gem::Specification API + # (for example, a Bundler spec proxy such as Bundler::EndpointSpecification + # or Bundler::RemoteSpecification). Does not fire when the gem is already + # present at the initial download-cache check. + # GEM_AFTER_FETCH = "after-fetch" + define :GEM_AFTER_FETCH, "after-fetch" + + # @!parse + # A hook called before a git source is fetched or checked out. + # Includes a Bundler::Source::Git reference. + # GIT_BEFORE_FETCH = "before-git-fetch" + define :GIT_BEFORE_FETCH, "before-git-fetch" + + # @!parse + # A hook called after a git source is fetched or checked out. + # Includes a Bundler::Source::Git reference. + # GIT_AFTER_FETCH = "after-git-fetch" + define :GIT_AFTER_FETCH, "after-git-fetch" + # @!parse # A hook called before each individual gem is installed # Includes a Bundler::ParallelInstaller::SpecInstallation. @@ -45,18 +93,18 @@ def self.defined_event?(event) # GEM_AFTER_INSTALL = "after-install" define :GEM_AFTER_INSTALL, "after-install" - # @!parse - # A hook called before any gems install - # Includes an Array of Bundler::Dependency objects - # GEM_BEFORE_INSTALL_ALL = "before-install-all" - define :GEM_BEFORE_INSTALL_ALL, "before-install-all" - # @!parse # A hook called after any gems install # Includes an Array of Bundler::Dependency objects # GEM_AFTER_INSTALL_ALL = "after-install-all" define :GEM_AFTER_INSTALL_ALL, "after-install-all" + # @!parse + # A hook called before any gems require + # Includes an Array of Bundler::Dependency objects. + # GEM_BEFORE_REQUIRE_ALL = "before-require-all" + define :GEM_BEFORE_REQUIRE_ALL, "before-require-all" + # @!parse # A hook called before each individual gem is required # Includes a Bundler::Dependency. @@ -69,17 +117,11 @@ def self.defined_event?(event) # GEM_AFTER_REQUIRE = "after-require" define :GEM_AFTER_REQUIRE, "after-require" - # @!parse - # A hook called before any gems require - # Includes an Array of Bundler::Dependency objects. - # GEM_BEFORE_REQUIRE_ALL = "before-require-all" - define :GEM_BEFORE_REQUIRE_ALL, "before-require-all" - # @!parse # A hook called after all gems required # Includes an Array of Bundler::Dependency objects. # GEM_AFTER_REQUIRE_ALL = "after-require-all" - define :GEM_AFTER_REQUIRE_ALL, "after-require-all" + define :GEM_AFTER_REQUIRE_ALL, "after-require-all" end end end diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index bb669ebba39d21..a002a2570a8a25 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -191,8 +191,13 @@ def specs(*) set_cache_path!(app_cache_path) if use_app_cache? if requires_checkout? && !@copied - fetch unless use_app_cache? - checkout + Plugin.hook(Plugin::Events::GIT_BEFORE_FETCH, self) + begin + fetch unless use_app_cache? + checkout + ensure + Plugin.hook(Plugin::Events::GIT_AFTER_FETCH, self) + end end local_specs diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index b5c3b9169d1649..7a94c1399afd7f 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -477,8 +477,13 @@ def download_gem(spec, download_cache_path, previous_spec = nil) Bundler.ui.confirm("Fetching #{version_message(spec, previous_spec)}") gem_remote_fetcher = remote_fetchers.fetch(spec.remote).gem_remote_fetcher - Gem.time("Downloaded #{spec.name} in", 0, true) do - Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher) + Plugin.hook(Plugin::Events::GEM_BEFORE_FETCH, spec) + begin + Gem.time("Downloaded #{spec.name} in", 0, true) do + Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher) + end + ensure + Plugin.hook(Plugin::Events::GEM_AFTER_FETCH, spec) end end diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index d792358da70176..bbe7bf0ab55ed8 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -2,7 +2,8 @@ module Gem::BundlerVersionFinder def self.bundler_version - return if bundle_config_version == "system" + bcv = bundle_config_version + return if bcv == "system" v = ENV["BUNDLER_VERSION"] v = nil if v&.empty? @@ -10,7 +11,7 @@ def self.bundler_version v ||= bundle_update_bundler_version return if v == true - v ||= bundle_config_version + v ||= bcv unless bcv == "lockfile" v ||= lockfile_version return unless v @@ -70,6 +71,9 @@ def self.lockfile_contents private_class_method :lockfile_contents def self.bundle_config_version + env_version = ENV["BUNDLE_VERSION"] + return env_version if env_version && !env_version.empty? + version = nil [bundler_local_config_file, bundler_global_config_file].each do |config_file| diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index c864af0b91d00b..0393a21494992c 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -325,7 +325,7 @@ def segments # :nodoc: end ## - # A recommended version for use with a ~> Requirement. + # A recommended version for use with a >= Requirement. def approximate_recommendation segments = self.segments @@ -334,7 +334,7 @@ def approximate_recommendation segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 - recommendation = "~> #{segments.join(".")}" + recommendation = ">= #{segments.join(".")}" recommendation += ".a" if prerelease? recommendation end diff --git a/object.c b/object.c index 4e98d9a5ccb30a..8dd701ec5b22aa 100644 --- a/object.c +++ b/object.c @@ -4071,7 +4071,7 @@ rb_Hash(VALUE val) * * Examples: * - * Hash({foo: 0, bar: 1}) # => {:foo=>0, :bar=>1} + * Hash({foo: 0, bar: 1}) # => {foo: 0, bar: 1} * Hash(nil) # => {} * Hash([]) # => {} * diff --git a/spec/bundler/bundler/compact_index_client/parser_spec.rb b/spec/bundler/bundler/compact_index_client/parser_spec.rb index 1f6b9e593bcd14..6015f66f33a79b 100644 --- a/spec/bundler/bundler/compact_index_client/parser_spec.rb +++ b/spec/bundler/bundler/compact_index_client/parser_spec.rb @@ -233,5 +233,17 @@ def set_info_data(name, value) VERSIONS expect(parser.info("a")).to eq(a_result) end + + it "handles lines without a checksum" do + compact_index.versions = <<~VERSIONS + created_at: 2024-05-01T00:00:04Z + --- + a 1.0.0,1.0.1,1.1.0 aaa111 + b 2.0.0,2.0.0-java + c 3.0.0,3.0.3,3.3.3 ccc333 + VERSIONS + + expect(parser.info("a")).to eq(a_result) + end end end diff --git a/spec/bundler/commands/doctor_spec.rb b/spec/bundler/commands/doctor_spec.rb index 5ceaf37f29c0d2..d350b4b3d10d00 100644 --- a/spec/bundler/commands/doctor_spec.rb +++ b/spec/bundler/commands/doctor_spec.rb @@ -34,6 +34,8 @@ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [unwritable_file] } allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:writable?).and_call_original + allow(File).to receive(:readable?).and_call_original allow(File).to receive(:exist?).with(unwritable_file).and_return(true) allow(File).to receive(:stat).with(unwritable_file) { stat } allow(stat).to receive(:uid) { Process.uid } @@ -108,6 +110,8 @@ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [@unwritable_file] } allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:writable?).and_call_original + allow(File).to receive(:readable?).and_call_original allow(File).to receive(:exist?).with(@unwritable_file) { true } allow(File).to receive(:stat).with(@unwritable_file) { @stat } end diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index 78e902cb73fa7a..1172d661ae69e2 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -317,5 +317,53 @@ expect(the_bundle).to include_gems("production_gem 1.0") expect(the_bundle).not_to include_gems("development_gem 1.0") end + + it "resolves indirect dependencies from a git source not in the requested groups" do + build_lib "activesupport", "1.0", path: lib_path("rails/activesupport") + build_git "activerecord", "1.0", path: lib_path("rails") do |s| + s.add_dependency "activesupport", "= 1.0" + end + + gemfile <<-G + source "https://gem.repo1" + + gem "activerecord", :git => "#{lib_path("rails")}" + + group :ci do + gem "myrack" + end + G + + bundle_config "only ci" + bundle :install + + expect(the_bundle).to include_gems("myrack 1.0.0") + expect(the_bundle).not_to include_gems("activerecord 1.0") + end + + it "resolves indirect dependencies from a git source not in the requested groups (without compact_index dependency API)" do + build_lib "activesupport", "1.0", path: lib_path("rails/activesupport") + build_git "activerecord", "1.0", path: lib_path("rails") do |s| + s.add_dependency "activesupport", "= 1.0" + end + + gemfile <<-G + source "https://gem.repo1" + + gem "activerecord", :git => "#{lib_path("rails")}" + + group :ci do + gem "myrack" + end + G + + # Force the RubygemsAggregate code path in find_source_requirements by + # making the dependency API unavailable. + bundle_config "only ci" + bundle :install, artifice: "endpoint_api_forbidden" + + expect(the_bundle).to include_gems("myrack 1.0.0") + expect(the_bundle).not_to include_gems("activerecord 1.0") + end end end diff --git a/spec/bundler/plugins/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb index 3f9053bbc83d48..ad8a4daeff9cb1 100644 --- a/spec/bundler/plugins/hook_spec.rb +++ b/spec/bundler/plugins/hook_spec.rb @@ -193,6 +193,129 @@ end end + context "before-eval hook" do + before do + build_repo2 do + build_plugin "before-eval-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_EVAL do |gemfile, lockfile| + puts "hooked eval start of \#{File.basename(gemfile)} to \#{File.basename(lockfile)}" + end + RUBY + end + end + + bundle "plugin install before-eval-plugin --source https://gem.repo2" + end + + it "runs before the Gemfile is evaluated" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + G + + expect(out).to include "hooked eval start of Gemfile to Gemfile.lock" + end + end + + context "after-eval hook" do + before do + build_repo2 do + build_plugin "after-eval-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_EVAL do |defn| + puts "hooked eval after with gems \#{defn.dependencies.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-eval-plugin --source https://gem.repo2" + end + + it "runs after the Gemfile is evaluated" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "rake" + G + + expect(out).to include "hooked eval after with gems myrack, rake" + end + end + + context "before-fetch and after-fetch hooks" do + before do + build_repo2 do + build_plugin "fetch-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_FETCH do |spec| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec.name} started fetch at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_FETCH do |spec| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install fetch-timing-plugin --source https://gem.repo2" + end + + it "runs around each gem download" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "gem rake started fetch at" + expect(out).to match(/gem rake took \d+\.\d+ to fetch/) + expect(out).to include "gem myrack started fetch at" + expect(out).to match(/gem myrack took \d+\.\d+ to fetch/) + end + end + + context "before-git-fetch and after-git-fetch hooks" do + before do + build_repo2 do + build_plugin "git-fetch-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_BEFORE_FETCH do |source| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "git source \#{source.name} started fetch at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_AFTER_FETCH do |source| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "git source \#{source.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install git-fetch-timing-plugin --source https://gem.repo2" + end + + it "runs around each git source fetch" do + build_git "foo", "1.0", path: lib_path("foo") + + relative_path = lib_path("foo").relative_path_from(bundled_app) + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "foo", :git => "#{relative_path}" + G + + expect(out).to include "git source foo started fetch at" + expect(out).to match(/git source foo took \d+\.\d+ to fetch/) + end + end + def install_gemfile_and_bundler_require install_gemfile <<-G source "https://gem.repo1" diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 1f5aa54c7d635c..ad83d0982363a9 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -3272,9 +3272,11 @@ def foo; TEST; end CloneTestM1 = CloneTestM0.clone CloneTestM2 = CloneTestM0.clone module CloneTestM1 + remove_const :TEST TEST = :M1 end module CloneTestM2 + remove_const :TEST TEST = :M2 end class CloneTestC1 diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 69e15bd4df687d..b519872d6718aa 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1694,7 +1694,7 @@ def test_matchdata_large_capture_groups_stack end def test_match_integer_at - m = /(\d+{4})(\d+{2})(\d+{2})/.match("20260308") + m = /(\d{4})(\d{2})(\d{2})/.match("20260308") assert_equal(20260308, m.integer_at(0)) assert_equal(2026, m.integer_at(1)) assert_equal(3, m.integer_at(2)) @@ -1711,7 +1711,7 @@ def test_match_integer_at end def test_match_integer_at_name - m = /(?\d+{4})(?\d+{2})(?\d+{2})/.match("20260308") + m = /(?\d{4})(?\d{2})(?\d{2})/.match("20260308") assert_equal(2026, m.integer_at("y")) assert_equal(3, m.integer_at("m")) assert_equal(8, m.integer_at("d")) diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb index 88ee9c6759f203..b5ef6293abfdb2 100644 --- a/test/rubygems/test_gem_bundler_version_finder.rb +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -104,6 +104,73 @@ def test_bundler_version_with_bundle_config_version end end + def test_bundler_version_with_bundle_version_env_system + ENV["BUNDLE_VERSION"] = "system" + + bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do + assert_nil bvf.bundler_version + end + end + + def test_bundler_version_with_bundle_version_env_overrides_config + ENV["BUNDLE_VERSION"] = "2.3.4" + + config_content = <<~CONFIG + BUNDLE_VERSION: "1.2.3" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_global_config_file, f.path) do + assert_equal v("2.3.4"), bvf.bundler_version + end + end + end + + def test_bundler_version_with_empty_bundle_version_env + ENV["BUNDLE_VERSION"] = "" + + config_content = <<~CONFIG + BUNDLE_VERSION: "1.2.3" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_global_config_file, f.path) do + assert_equal v("1.2.3"), bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_version_env_lockfile + ENV["BUNDLE_VERSION"] = "lockfile" + + bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + end + + def test_bundler_version_with_bundle_config_version_lockfile + config_content = <<~CONFIG + BUNDLE_VERSION: "lockfile" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_global_config_file, f.path) do + bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + end + end + end + def test_bundler_version_with_bundle_config_non_existent_file bvf.stub(:bundler_global_config_file, "/non/existent/path") do assert_nil bvf.bundler_version diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index 5cd4228f2fccb9..f58359e54c83a2 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -165,25 +165,25 @@ def test_spaceship end def test_approximate_recommendation - assert_approximate_equal "~> 1.0", "1" + assert_approximate_equal ">= 1.0", "1" assert_approximate_satisfies_itself "1" - assert_approximate_equal "~> 1.0", "1.0" + assert_approximate_equal ">= 1.0", "1.0" assert_approximate_satisfies_itself "1.0" - assert_approximate_equal "~> 1.2", "1.2" + assert_approximate_equal ">= 1.2", "1.2" assert_approximate_satisfies_itself "1.2" - assert_approximate_equal "~> 1.2", "1.2.0" + assert_approximate_equal ">= 1.2", "1.2.0" assert_approximate_satisfies_itself "1.2.0" - assert_approximate_equal "~> 1.2", "1.2.3" + assert_approximate_equal ">= 1.2", "1.2.3" assert_approximate_satisfies_itself "1.2.3" - assert_approximate_equal "~> 1.2.a", "1.2.3.a.4" + assert_approximate_equal ">= 1.2.a", "1.2.3.a.4" assert_approximate_satisfies_itself "1.2.3.a.4" - assert_approximate_equal "~> 1.9.a", "1.9.0.dev" + assert_approximate_equal ">= 1.9.a", "1.9.0.dev" assert_approximate_satisfies_itself "1.9.0.dev" end diff --git a/vm_method.c b/vm_method.c index e9f0d85cae4c0c..244ebbb720b0e0 100644 --- a/vm_method.c +++ b/vm_method.c @@ -439,7 +439,7 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid) RB_VM_LOCKING() { rb_vm_barrier(); - if (LIKELY(RCLASS_SUBCLASSES_FIRST(klass) == NULL) && + if (LIKELY(!RCLASS_SUBCLASSES(klass)) && !FL_TEST_RAW(klass, RCLASS_HAS_SUBCLASSES) && // Non-refinement ICLASSes (from module inclusion) previously had // subclasses reparented onto them, so they need the tree path for diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 0ce6485e0d471c..16045f7e8b8406 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -361,6 +361,7 @@ pub const imemo_callinfo: imemo_type = 10; pub const imemo_callcache: imemo_type = 11; pub const imemo_constcache: imemo_type = 12; pub const imemo_fields: imemo_type = 13; +pub const imemo_subclasses: imemo_type = 14; pub type imemo_type = u32; #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 23db792a52b06a..559134a9790525 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -444,6 +444,7 @@ pub const imemo_callinfo: imemo_type = 10; pub const imemo_callcache: imemo_type = 11; pub const imemo_constcache: imemo_type = 12; pub const imemo_fields: imemo_type = 13; +pub const imemo_subclasses: imemo_type = 14; pub type imemo_type = u32; #[repr(C)] #[derive(Debug, Copy, Clone)]