diff --git a/NEWS.md b/NEWS.md index 8670a0e6f4dbc0..a41cf0bb349f63 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ since the **3.0.0** release, except for bug fixes. Note that each entry is kept to a minimum, see links for details. ## Language changes + * Pin operator now takes an expression. [[Feature #17411]] ```ruby @@ -13,6 +14,50 @@ Note that each entry is kept to a minimum, see links for details. #=> [[3, 5], [5, 7], [11, 13]] ``` +* Multiple assignment evaluation order has been made consistent with + single assignment evaluation order. With single assignment, Ruby + uses a left-to-right evaluation order. With this code: + + ```ruby + foo[0] = bar + ``` + + The following evaluation order is used: + + 1. `foo` + 2. `bar` + 3. `[]=` called on the result of `foo` + + In Ruby before 3.1.0, multiple assignment did not follow this + evaluation order. With this code: + + ```ruby + foo[0], bar.baz = a, b + ``` + + Versions of Ruby before 3.1.0 would evaluate in the following + order + + 1. `a` + 2. `b` + 3. `foo` + 4. `[]=` called on the result of `foo` + 5. `bar` + 6. `baz=` called on the result of `bar` + + Starting in Ruby 3.1.0, evaluation order is now consistent with + single assignment, with the left hand side being evaluated before + the right hand side: + + 1. `foo` + 2. `bar` + 3. `a` + 4. `b` + 5. `[]=` called on the result of `foo` + 6. `baz=` called on the result of `bar` + + [[Bug #4443]] + ## Command line options ## Core classes updates @@ -96,6 +141,7 @@ Excluding feature bug fixes. ## Miscellaneous changes +[Bug #4443]: https://bugs.ruby-lang.org/issues/4443 [Feature #12194]: https://bugs.ruby-lang.org/issues/12194 [Feature #14256]: https://bugs.ruby-lang.org/issues/14256 [Feature #15198]: https://bugs.ruby-lang.org/issues/15198 diff --git a/benchmark/README.md b/benchmark/README.md index 39a5aa139b8a32..c222164be3ab41 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -37,7 +37,7 @@ Usage: benchmark-driver [options] RUBY|YAML... --bundler Install and use gems specified in Gemfile --filter REGEXP Filter out benchmarks with given regexp --run-duration SECONDS Warmup estimates loop_count to run for this duration (default: 3) - -v, --verbose Verbose mode. Multiple -v options increase visilibity (max: 2) + -v, --verbose Verbose mode. Multiple -v options increase visibility (max: 2) ``` ## make benchmark diff --git a/benchmark/masgn.yml b/benchmark/masgn.yml new file mode 100644 index 00000000000000..4be9333e232c1f --- /dev/null +++ b/benchmark/masgn.yml @@ -0,0 +1,29 @@ +prelude: | + a = [nil] * 3 + b = Class.new{attr_writer :a, :b, :c}.new + c, d, e, f = nil, nil, nil, nil +benchmark: + array2_2: "c = (a[0], a[1] = 1, 2)" + array2_3: "c = (a[0], a[1] = 1, 2, 3)" + array3_2: "c = (a[0], a[1], a[2] = 1, 2)" + array3_3: "c = (a[0], a[1], a[2] = 1, 2, 3)" + attr2_2: "c = (b.a, b.b = 1, 2)" + attr2_3: "c = (b.a, b.b = 1, 2, 3)" + attr3_2: "c = (b.a, b.b, b.c = 1, 2)" + attr3_3: "c = (b.a, b.b, b.c = 1, 2, 3)" + lvar2_2: "c = (d, e = 1, 2)" + lvar2_3: "c = (d, e = 1, 2, 3)" + lvar3_2: "c = (d, e, f = 1, 2)" + lvar3_3: "c = (d, e, f = 1, 2, 3)" + array2_2p: "(a[0], a[1] = 1, 2; nil)" + array2_3p: "(a[0], a[1] = 1, 2, 3; nil)" + array3_2p: "(a[0], a[1], a[2] = 1, 2; nil)" + array3_3p: "(a[0], a[1], a[2] = 1, 2, 3; nil)" + attr2_2p: "(b.a, b.b = 1, 2; nil)" + attr2_3p: "(b.a, b.b = 1, 2, 3; nil)" + attr3_2p: "(b.a, b.b, b.c = 1, 2; nil)" + attr3_3p: "(b.a, b.b, b.c = 1, 2, 3; nil)" + lvar2_2p: "(d, e = 1, 2; nil)" + lvar2_3p: "(d, e = 1, 2, 3; nil)" + lvar3_2p: "(d, e, f = 1, 2; nil)" + lvar3_3p: "(d, e, f = 1, 2, 3; nil)" diff --git a/ccan/list/list.h b/ccan/list/list.h index 7d219307bc6273..c434ad8106be0f 100644 --- a/ccan/list/list.h +++ b/ccan/list/list.h @@ -658,7 +658,7 @@ static inline void list_prepend_list_(struct list_head *to, * @off: offset(relative to @i) at which list node data resides. * * This is a low-level wrapper to iterate @i over the entire list, used to - * implement all oher, more high-level, for-each constructs. It's a for loop, + * implement all other, more high-level, for-each constructs. It's a for loop, * so you can break and continue as normal. * * WARNING! Being the low-level macro that it is, this wrapper doesn't know diff --git a/compile.c b/compile.c index ebc7afee35e180..0b6e8a456e4716 100644 --- a/compile.c +++ b/compile.c @@ -1098,19 +1098,6 @@ LAST_ELEMENT(LINK_ANCHOR *const anchor) return anchor->last; } -static LINK_ELEMENT * -POP_ELEMENT(ISEQ_ARG_DECLARE LINK_ANCHOR *const anchor) -{ - LINK_ELEMENT *elem = anchor->last; - anchor->last = anchor->last->prev; - anchor->last->next = 0; - verify_list("pop", anchor); - return elem; -} -#if CPDEBUG < 0 -#define POP_ELEMENT(anchor) POP_ELEMENT(iseq, (anchor)) -#endif - static LINK_ELEMENT * ELEM_FIRST_INSN(LINK_ELEMENT *elem) { @@ -4609,27 +4596,163 @@ when_splat_vals(rb_iseq_t *iseq, LINK_ANCHOR *const cond_seq, const NODE *vals, return COMPILE_OK; } +/* Multiple Assignment Handling + * + * In order to handle evaluation of multiple assignment such that the left hand side + * is evaluated before the right hand side, we need to process the left hand side + * and see if there are any attributes that need to be assigned. If so, we add + * instructions to evaluate the receiver of any assigned attributes before we + * process the right hand side. + * + * For a multiple assignment such as: + * + * l1.m1, l2[0] = r3, r4 + * + * We start off evaluating l1 and l2, then we evaluate r3 and r4, then we + * assign the result of r3 to l1.m1, and then the result of r4 to l2.m2. + * On the VM stack, this looks like: + * + * self # putself + * l1 # send + * l1, self # putself + * l1, l2 # send + * l1, l2, 0 # putobject 0 + * l1, l2, 0, [r3, r4] # after evaluation of RHS + * l1, l2, 0, [r3, r4], r4, r3 # expandarray + * l1, l2, 0, [r3, r4], r4, r3, l1 # topn 5 + * l1, l2, 0, [r3, r4], r4, l1, r3 # swap + * l1, l2, 0, [r3, r4], r4, m1= # send + * l1, l2, 0, [r3, r4], r4 # pop + * l1, l2, 0, [r3, r4], r4, l2 # topn 3 + * l1, l2, 0, [r3, r4], r4, l2, 0 # topn 3 + * l1, l2, 0, [r3, r4], r4, l2, 0, r4 # topn 2 + * l1, l2, 0, [r3, r4], r4, []= # send + * l1, l2, 0, [r3, r4], r4 # pop + * l1, l2, 0, [r3, r4] # pop + * [r3, r4], l2, 0, [r3, r4] # setn 3 + * [r3, r4], l2, 0 # pop + * [r3, r4], l2 # pop + * [r3, r4] # pop + * + * This is made more complex when you have to handle splats, post args, + * and arbitrary levels of nesting. You need to keep track of the total + * number of attributes to set, and for each attribute, how many entries + * are on the stack before the final attribute, in order to correctly + * calculate the topn value to use to get the receiver of the attribute + * setter method. + * + * A brief description of the VM stack for simple multiple assignment + * with no splat (rhs_array will not be present if the return value of + * the multiple assignment is not needed): + * + * lhs_attr1, lhs_attr2, ..., rhs_array, ..., rhs_arg2, rhs_arg1 + * + * For multiple assignment with splats, while processing the part before + * the splat (splat+post here is an array of the splat and the post arguments): + * + * lhs_attr1, lhs_attr2, ..., rhs_array, splat+post, ..., rhs_arg2, rhs_arg1 + * + * When processing the splat and post arguments: + * + * lhs_attr1, lhs_attr2, ..., rhs_array, ..., post_arg2, post_arg1, splat + * + * When processing nested multiple assignment, existing values on the stack + * are kept. So for: + * + * (l1.m1, l2.m2), l3.m3, l4* = [r1, r2], r3, r4 + * + * The stack layout would be the following before processing the nested + * multiple assignment: + * + * l1, l2, [[r1, r2], r3, r4], [r4], r3, [r1, r2] + * + * In order to handle this correctly, we need to keep track of the nesting + * level for each attribute assignment, as well as the attribute number + * (left hand side attributes are processed left to right) and number of + * arguments to pass to the setter method. struct masgn_attrasgn tracks + * this information. + * + * We also need to track information for the entire multiple assignment, such + * as the total number of arguments, and the current nesting level, to + * handle both nested multiple assignment as well as cases where the + * rhs is not needed. We also need to keep track of all attribute + * assignments in this, which we do using a linked listed. struct masgn_state + * tracks this information. + */ + +struct masgn_attrasgn { + INSN *before_insn; + struct masgn_attrasgn *next; + int line; + int argn; + int num_args; + int lhs_pos; +}; + +struct masgn_state { + struct masgn_attrasgn *first_memo; + struct masgn_attrasgn *last_memo; + int lhs_level; + int num_args; + bool nested; +}; + +static int compile_massign0(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const rhs, LINK_ANCHOR *const lhs, LINK_ANCHOR *const post, const NODE *const node, struct masgn_state *state, int popped); static int -compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node) +compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const rhs, LINK_ANCHOR *const lhs, LINK_ANCHOR *const post, const NODE *const node, struct masgn_state *state, int lhs_pos) { switch (nd_type(node)) { case NODE_ATTRASGN: { + if (!state) { + rb_bug("no masgn_state"); + } + INSN *iobj; - VALUE dupidx; int line = nd_line(node); - CHECK(COMPILE_POPPED(ret, "masgn lhs (NODE_ATTRASGN)", node)); + CHECK(COMPILE_POPPED(pre, "masgn lhs (NODE_ATTRASGN)", node)); + + LINK_ELEMENT *insn_element = LAST_ELEMENT(pre); + iobj = (INSN *)get_prev_insn((INSN *)insn_element); /* send insn */ + ELEM_REMOVE(LAST_ELEMENT(pre)); + ELEM_REMOVE((LINK_ELEMENT *)iobj); + pre->last = iobj->link.prev; - iobj = (INSN *)get_prev_insn((INSN *)LAST_ELEMENT(ret)); /* send insn */ const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0); int argc = vm_ci_argc(ci) + 1; ci = ci_argc_set(iseq, ci, argc); OPERAND_AT(iobj, 0) = (VALUE)ci; RB_OBJ_WRITTEN(iseq, Qundef, ci); - dupidx = INT2FIX(argc); - INSERT_BEFORE_INSN1(iobj, line, topn, dupidx); + if (argc == 1) { + ADD_INSN(lhs, line, swap); + } + else { + ADD_INSN1(lhs, line, topn, INT2FIX(argc)); + } + + struct masgn_attrasgn *memo; + memo = malloc(sizeof(struct masgn_attrasgn)); + if (!memo) { + return 0; + } + memo->before_insn = (INSN *)LAST_ELEMENT(lhs); + memo->line = line; + memo->argn = state->num_args + 1; + memo->num_args = argc; + state->num_args += argc; + memo->lhs_pos = lhs_pos; + memo->next = NULL; + if (!state->first_memo) { + state->first_memo = memo; + } + else { + state->last_memo->next = memo; + } + state->last_memo = memo; + + ADD_ELEM(lhs, (LINK_ELEMENT *)iobj); if (vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) { int argc = vm_ci_argc(ci); ci = ci_argc_set(iseq, ci, argc - 1); @@ -4638,15 +4761,31 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const n INSERT_BEFORE_INSN1(iobj, line, newarray, INT2FIX(1)); INSERT_BEFORE_INSN(iobj, line, concatarray); } - ADD_INSN(ret, line, pop); /* result */ + ADD_INSN(lhs, line, pop); + if (argc != 1) { + ADD_INSN(lhs, line, pop); + } + for (int i=0; i < argc; i++) { + ADD_INSN(post, line, pop); + } break; } case NODE_MASGN: { - DECL_ANCHOR(anchor); - INIT_ANCHOR(anchor); - CHECK(COMPILE_POPPED(anchor, "nest masgn lhs", node)); - ELEM_REMOVE(FIRST_ELEMENT(anchor)); - ADD_SEQ(ret, anchor); + DECL_ANCHOR(nest_rhs); + INIT_ANCHOR(nest_rhs); + DECL_ANCHOR(nest_lhs); + INIT_ANCHOR(nest_lhs); + + int prev_level = state->lhs_level; + bool prev_nested = state->nested; + state->nested = 1; + state->lhs_level = lhs_pos - 1; + CHECK(compile_massign0(iseq, pre, nest_rhs, nest_lhs, post, node, state, 1)); + state->lhs_level = prev_level; + state->nested = prev_nested; + + ADD_SEQ(lhs, nest_rhs); + ADD_SEQ(lhs, nest_lhs); break; } default: { @@ -4654,7 +4793,7 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const n INIT_ANCHOR(anchor); CHECK(COMPILE_POPPED(anchor, "masgn lhs", node)); ELEM_REMOVE(FIRST_ELEMENT(anchor)); - ADD_SEQ(ret, anchor); + ADD_SEQ(lhs, anchor); } } @@ -4666,7 +4805,7 @@ compile_massign_opt_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *lhs { if (lhsn) { CHECK(compile_massign_opt_lhs(iseq, ret, lhsn->nd_next)); - CHECK(compile_massign_lhs(iseq, ret, lhsn->nd_head)); + CHECK(compile_massign_lhs(iseq, ret, ret, ret, ret, lhsn->nd_head, NULL, 0)); } return COMPILE_OK; } @@ -4735,98 +4874,112 @@ compile_massign_opt(rb_iseq_t *iseq, LINK_ANCHOR *const ret, return 1; } -static void -adjust_stack(rb_iseq_t *iseq, LINK_ANCHOR *const ret, int line, int rlen, int llen) -{ - if (rlen < llen) { - do {ADD_INSN(ret, line, putnil);} while (++rlen < llen); - } - else if (rlen > llen) { - do {ADD_INSN(ret, line, pop);} while (--rlen > llen); - } -} - static int -compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) +compile_massign0(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const rhs, LINK_ANCHOR *const lhs, LINK_ANCHOR *const post, const NODE *const node, struct masgn_state *state, int popped) { const NODE *rhsn = node->nd_value; const NODE *splatn = node->nd_args; const NODE *lhsn = node->nd_head; + const NODE *lhsn_count = lhsn; int lhs_splat = (splatn && NODE_NAMED_REST_P(splatn)) ? 1 : 0; - if (!popped || splatn || !compile_massign_opt(iseq, ret, rhsn, lhsn)) { - int llen = 0; - int expand = 1; - DECL_ANCHOR(lhsseq); + int llen = 0; + int lpos = 0; + int expand = 1; - INIT_ANCHOR(lhsseq); + while (lhsn_count) { + llen++; + lhsn_count = lhsn_count->nd_next; + } + while (lhsn) { + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, lhsn->nd_head, state, (llen - lpos) + lhs_splat + state->lhs_level)); + lpos++; + lhsn = lhsn->nd_next; + } - while (lhsn) { - CHECK(compile_massign_lhs(iseq, lhsseq, lhsn->nd_head)); - llen += 1; - lhsn = lhsn->nd_next; - } + if (lhs_splat) { + if (nd_type(splatn) == NODE_POSTARG) { + /*a, b, *r, p1, p2 */ + const NODE *postn = splatn->nd_2nd; + const NODE *restn = splatn->nd_1st; + int plen = (int)postn->nd_alen; + int ppos = 0; + int flag = 0x02 | (NODE_NAMED_REST_P(restn) ? 0x01 : 0x00); - NO_CHECK(COMPILE(ret, "normal masgn rhs", rhsn)); + ADD_INSN2(lhs, nd_line(splatn), expandarray, + INT2FIX(plen), INT2FIX(flag)); - if (!popped) { - ADD_INSN(ret, nd_line(node), dup); - } - else if (!lhs_splat) { - INSN *last = (INSN*)ret->last; - if (IS_INSN(&last->link) && - IS_INSN_ID(last, newarray) && - last->operand_size == 1) { - int rlen = FIX2INT(OPERAND_AT(last, 0)); - /* special case: assign to aset or attrset */ - if (llen == 2) { - POP_ELEMENT(ret); - adjust_stack(iseq, ret, nd_line(node), rlen, llen); - ADD_INSN(ret, nd_line(node), swap); - expand = 0; - } - else if (llen > 2 && llen != rlen) { - POP_ELEMENT(ret); - adjust_stack(iseq, ret, nd_line(node), rlen, llen); - ADD_INSN1(ret, nd_line(node), reverse, INT2FIX(llen)); - expand = 0; - } - else if (llen > 2) { - last->insn_id = BIN(reverse); - expand = 0; - } - } - } - if (expand) { - ADD_INSN2(ret, nd_line(node), expandarray, - INT2FIX(llen), INT2FIX(lhs_splat)); - } - ADD_SEQ(ret, lhsseq); + if (NODE_NAMED_REST_P(restn)) { + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, restn, state, 1 + plen + state->lhs_level)); + } + while (postn) { + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, postn->nd_head, state, (plen - ppos) + state->lhs_level)); + ppos++; + postn = postn->nd_next; + } + } + else { + /* a, b, *r */ + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, splatn, state, 1 + state->lhs_level)); + } + } - if (lhs_splat) { - if (nd_type(splatn) == NODE_POSTARG) { - /*a, b, *r, p1, p2 */ - const NODE *postn = splatn->nd_2nd; - const NODE *restn = splatn->nd_1st; - int num = (int)postn->nd_alen; - int flag = 0x02 | (NODE_NAMED_REST_P(restn) ? 0x01 : 0x00); - ADD_INSN2(ret, nd_line(splatn), expandarray, - INT2FIX(num), INT2FIX(flag)); + if (!state->nested) { + NO_CHECK(COMPILE(rhs, "normal masgn rhs", rhsn)); + } - if (NODE_NAMED_REST_P(restn)) { - CHECK(compile_massign_lhs(iseq, ret, restn)); - } - while (postn) { - CHECK(compile_massign_lhs(iseq, ret, postn->nd_head)); - postn = postn->nd_next; - } - } - else { - /* a, b, *r */ - CHECK(compile_massign_lhs(iseq, ret, splatn)); - } - } + if (!popped) { + ADD_INSN(rhs, nd_line(node), dup); + } + if (expand) { + ADD_INSN2(rhs, nd_line(node), expandarray, + INT2FIX(llen), INT2FIX(lhs_splat)); + } + return COMPILE_OK; +} + +static int +compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) +{ + if (!popped || node->nd_args || !compile_massign_opt(iseq, ret, node->nd_value, node->nd_head)) { + struct masgn_state state; + state.lhs_level = popped ? 0 : 1; + state.nested = 0; + state.num_args = 0; + state.first_memo = NULL; + state.last_memo = NULL; + + DECL_ANCHOR(pre); + INIT_ANCHOR(pre); + DECL_ANCHOR(rhs); + INIT_ANCHOR(rhs); + DECL_ANCHOR(lhs); + INIT_ANCHOR(lhs); + DECL_ANCHOR(post); + INIT_ANCHOR(post); + int ok = compile_massign0(iseq, pre, rhs, lhs, post, node, &state, popped); + + struct masgn_attrasgn *memo = state.first_memo, *tmp_memo; + while (memo) { + VALUE topn_arg = INT2FIX((state.num_args - memo->argn) + memo->lhs_pos); + for(int i = 0; i < memo->num_args; i++) { + INSERT_BEFORE_INSN1(memo->before_insn, memo->line, topn, topn_arg); + } + tmp_memo = memo->next; + free(memo); + memo = tmp_memo; + } + CHECK(ok); + + ADD_SEQ(ret, pre); + ADD_SEQ(ret, rhs); + ADD_SEQ(ret, lhs); + if (!popped && state.num_args >= 1) { + /* make sure rhs array is returned before popping */ + ADD_INSN1(ret, nd_line(node), setn, INT2FIX(state.num_args)); + } + ADD_SEQ(ret, post); } return COMPILE_OK; } @@ -5216,9 +5369,22 @@ add_ensure_range(rb_iseq_t *iseq, struct ensure_range *erange, erange->next = ne; } +static bool +can_add_ensure_iseq(const rb_iseq_t *iseq) +{ + if (ISEQ_COMPILE_DATA(iseq)->in_rescue && ISEQ_COMPILE_DATA(iseq)->ensure_node_stack) { + return false; + } + else { + return true; + } +} + static void add_ensure_iseq(LINK_ANCHOR *const ret, rb_iseq_t *iseq, int is_return) { + assert(can_add_ensure_iseq(iseq)); + struct iseq_compile_data_ensure_node_stack *enlp = ISEQ_COMPILE_DATA(iseq)->ensure_node_stack; struct iseq_compile_data_ensure_node_stack *prev_enlp = enlp; @@ -6710,7 +6876,7 @@ compile_break(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i const int line = nd_line(node); unsigned long throw_flag = 0; - if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0) { + if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) { /* while/until */ LABEL *splabel = NEW_LABEL(0); ADD_LABEL(ret, splabel); @@ -6769,7 +6935,7 @@ compile_next(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in const int line = nd_line(node); unsigned long throw_flag = 0; - if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0) { + if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("next in while loop\n"); ADD_LABEL(ret, splabel); @@ -6782,7 +6948,7 @@ compile_next(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in ADD_INSN(ret, line, putnil); } } - else if (ISEQ_COMPILE_DATA(iseq)->end_label) { + else if (ISEQ_COMPILE_DATA(iseq)->end_label && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("next in block\n"); ADD_LABEL(ret, splabel); @@ -6842,7 +7008,7 @@ compile_redo(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in { const int line = nd_line(node); - if (ISEQ_COMPILE_DATA(iseq)->redo_label) { + if (ISEQ_COMPILE_DATA(iseq)->redo_label && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("redo in while"); ADD_LABEL(ret, splabel); @@ -6854,7 +7020,7 @@ compile_redo(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in ADD_INSN(ret, line, putnil); } } - else if (iseq->body->type != ISEQ_TYPE_EVAL && ISEQ_COMPILE_DATA(iseq)->start_label) { + else if (iseq->body->type != ISEQ_TYPE_EVAL && ISEQ_COMPILE_DATA(iseq)->start_label && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("redo in block"); @@ -6940,7 +7106,14 @@ compile_rescue(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, lstart->rescued = LABEL_RESCUE_BEG; lend->rescued = LABEL_RESCUE_END; ADD_LABEL(ret, lstart); - CHECK(COMPILE(ret, "rescue head", node->nd_head)); + + bool prev_in_rescue = ISEQ_COMPILE_DATA(iseq)->in_rescue; + ISEQ_COMPILE_DATA(iseq)->in_rescue = true; + { + CHECK(COMPILE(ret, "rescue head", node->nd_head)); + } + ISEQ_COMPILE_DATA(iseq)->in_rescue = prev_in_rescue; + ADD_LABEL(ret, lend); if (node->nd_else) { ADD_INSN(ret, line, pop); @@ -7101,7 +7274,7 @@ compile_return(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, CHECK(COMPILE(ret, "return nd_stts (return val)", retval)); - if (type == ISEQ_TYPE_METHOD) { + if (type == ISEQ_TYPE_METHOD && can_add_ensure_iseq(iseq)) { add_ensure_iseq(ret, iseq, 1); ADD_TRACE(ret, RUBY_EVENT_RETURN); ADD_INSN(ret, line, leave); diff --git a/cont.c b/cont.c index bdd308569cee42..35007cac3515b6 100644 --- a/cont.c +++ b/cont.c @@ -436,7 +436,7 @@ fiber_pool_allocate_memory(size_t * count, size_t stride) #if defined(MADV_FREE_REUSE) // On Mac MADV_FREE_REUSE is necessary for the task_info api // to keep the accounting accurate as possible when a page is marked as reusable - // it can possibly not occuring at first call thus re-iterating if necessary. + // it can possibly not occurring at first call thus re-iterating if necessary. while (madvise(base, (*count)*stride, MADV_FREE_REUSE) == -1 && errno == EAGAIN); #endif return base; @@ -657,7 +657,7 @@ fiber_pool_stack_free(struct fiber_pool_stack * stack) #elif defined(MADV_FREE_REUSABLE) // Acknowledge the kernel down to the task info api we make this // page reusable for future use. - // As for MADV_FREE_REUSE below we ensure in the rare occassions the task was not + // As for MADV_FREE_REUSE below we ensure in the rare occasions the task was not // completed at the time of the call to re-iterate. while (madvise(base, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN); #elif defined(MADV_FREE) diff --git a/debug_counter.h b/debug_counter.h index 3c20821db67985..9452f4c737ebe4 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -46,10 +46,10 @@ RB_DEBUG_COUNTER(cc_not_found_in_ccs) // count for CC lookup success in CCS RB_DEBUG_COUNTER(cc_ent_invalidate) // count for invalidating cc (cc->klass = 0) RB_DEBUG_COUNTER(cc_cme_invalidate) // count for invalidating CME -RB_DEBUG_COUNTER(cc_invalidate_leaf) // count for invalidating klass if klass has no-sublcasses +RB_DEBUG_COUNTER(cc_invalidate_leaf) // count for invalidating klass if klass has no-subclasses RB_DEBUG_COUNTER(cc_invalidate_leaf_ccs) // corresponding CCS RB_DEBUG_COUNTER(cc_invalidate_leaf_callable) // complimented cache (no-subclasses) -RB_DEBUG_COUNTER(cc_invalidate_tree) // count for invalidating klass if klass has sublcasses +RB_DEBUG_COUNTER(cc_invalidate_tree) // count for invalidating klass if klass has subclasses RB_DEBUG_COUNTER(cc_invalidate_tree_cme) // cme if cme is found in this class or superclasses RB_DEBUG_COUNTER(cc_invalidate_tree_callable) // complimented cache (subclasses) RB_DEBUG_COUNTER(cc_invalidate_negative) // count for invalidating negative cache diff --git a/doc/extension.rdoc b/doc/extension.rdoc index c33dea22286337..f8e0a1ec3d6c6b 100644 --- a/doc/extension.rdoc +++ b/doc/extension.rdoc @@ -2127,7 +2127,7 @@ To make "Ractor-safe" C extension, we need to check the following points: (1) Do not share unshareable objects between ractors For example, C's global variable can lead sharing an unshareable objects -betwee ractors. +between ractors. VALUE g_var; VALUE set(VALUE self, VALUE v){ return g_var = v; } diff --git a/doc/irb/irb.rd.ja b/doc/irb/irb.rd.ja index 81247ce4b0c714..fd03b35a0ada6b 100644 --- a/doc/irb/irb.rd.ja +++ b/doc/irb/irb.rd.ja @@ -381,7 +381,7 @@ rubyでは, 以下のプログラムはエラーになります. パイルしてローカル変数を決定するからです. それに対し, irbは実行可能に なる(式が閉じる)と自動的に評価しているからです. 上記の例では, - evel "foo = 0" + eval "foo = 0" を行なった時点で評価を行ない, その時点で変数が定義されるため, 次式で 変数fooは定義されているからです. diff --git a/doc/syntax/methods.rdoc b/doc/syntax/methods.rdoc index 1b75922578e8ec..b9ec8da5ff2cc1 100644 --- a/doc/syntax/methods.rdoc +++ b/doc/syntax/methods.rdoc @@ -615,7 +615,7 @@ defined with .... end Since Ruby 3.0, there can be leading arguments before ... -both in definitions and in invokations (but in definitions they can be +both in definitions and in invocations (but in definitions they can be only positional arguments without default values). def request(method, path, **headers) diff --git a/ext/digest/sha2/sha2.c b/ext/digest/sha2/sha2.c index c86eab37a0b5f9..f55de33eb3c376 100644 --- a/ext/digest/sha2/sha2.c +++ b/ext/digest/sha2/sha2.c @@ -94,7 +94,7 @@ /* * Define the followingsha2_* types to types of the correct length on - * the native archtecture. Most BSD systems and Linux define u_intXX_t + * the native architecture. Most BSD systems and Linux define u_intXX_t * types. Machines with very recent ANSI C headers, can use the * uintXX_t definintions from inttypes.h by defining SHA2_USE_INTTYPES_H * during compile or in the sha.h header file. diff --git a/ext/gdbm/gdbm.gemspec b/ext/gdbm/gdbm.gemspec index 6fd42c022fce77..d15d20847d6966 100644 --- a/ext/gdbm/gdbm.gemspec +++ b/ext/gdbm/gdbm.gemspec @@ -18,4 +18,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.extensions = ["ext/gdbm/extconf.rb"] spec.required_ruby_version = ">= 2.3.0" + spec.metadata["msys2_mingw_dependencies"] = "gdbm" end diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index 32ad9d3311d9e2..e8c5923b18aba1 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'mkmf' -ok = true if RUBY_ENGINE == "ruby" +ok = true if RUBY_ENGINE == "ruby" || RUBY_ENGINE == "truffleruby" hdr = nil case when macro_defined?("_WIN32", "") diff --git a/ext/io/console/io-console.gemspec b/ext/io/console/io-console.gemspec index 5e875cc06e19a0..dabe9e68f82605 100644 --- a/ext/io/console/io-console.gemspec +++ b/ext/io/console/io-console.gemspec @@ -25,15 +25,15 @@ Gem::Specification.new do |s| if Gem::Platform === s.platform and s.platform =~ 'java' s.files.delete_if {|f| f.start_with?("ext/")} s.extensions.clear - s.require_paths.unshift("jruby") s.files.concat(%w[ - jruby/io/console.rb - jruby/io/console/bsd_console.rb - jruby/io/console/common.rb - jruby/io/console/linux_console.rb - jruby/io/console/native_console.rb - jruby/io/console/stty_console.rb - jruby/io/console/stub_console.rb + lib/io/console.rb + lib/io/console/ffi/bsd_console.rb + lib/io/console/ffi/common.rb + lib/io/console/ffi/console.rb + lib/io/console/ffi/linux_console.rb + lib/io/console/ffi/native_console.rb + lib/io/console/ffi/stty_console.rb + lib/io/console/ffi/stub_console.rb ]) end diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index 26a564f4c3dce3..43a18f58af29e2 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -53,7 +53,7 @@ monitor_ptr(VALUE monitor) static int mc_owner_p(struct rb_monitor *mc) { - return mc->owner == rb_thread_current(); + return mc->owner == rb_fiber_current(); } static VALUE @@ -65,7 +65,7 @@ monitor_try_enter(VALUE monitor) if (!rb_mutex_trylock(mc->mutex)) { return Qfalse; } - RB_OBJ_WRITE(monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); mc->count = 0; } mc->count += 1; @@ -78,7 +78,7 @@ monitor_enter(VALUE monitor) struct rb_monitor *mc = monitor_ptr(monitor); if (!mc_owner_p(mc)) { rb_mutex_lock(mc->mutex); - RB_OBJ_WRITE(monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); mc->count = 0; } mc->count++; @@ -90,7 +90,7 @@ monitor_check_owner(VALUE monitor) { struct rb_monitor *mc = monitor_ptr(monitor); if (!mc_owner_p(mc)) { - rb_raise(rb_eThreadError, "current thread not owner"); + rb_raise(rb_eThreadError, "current fiber not owner"); } return Qnil; } @@ -161,7 +161,7 @@ monitor_enter_for_cond(VALUE v) struct wait_for_cond_data *data = (struct wait_for_cond_data *)v; struct rb_monitor *mc = monitor_ptr(data->monitor); - RB_OBJ_WRITE(data->monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(data->monitor, &mc->owner, rb_fiber_current()); mc->count = NUM2LONG(data->count); return Qnil; } diff --git a/ext/nkf/nkf-utf8/config.h b/ext/nkf/nkf-utf8/config.h index 51dc2a5152bad1..36898c0b4b1d98 100644 --- a/ext/nkf/nkf-utf8/config.h +++ b/ext/nkf/nkf-utf8/config.h @@ -30,7 +30,7 @@ /* --exec-in, --exec-out option * require pipe, fork, execvp and so on. * please undef this on MS-DOS, MinGW - * this is still buggy arround child process + * this is still buggy around child process */ /* #define EXEC_IO */ diff --git a/ext/nkf/nkf-utf8/nkf.c b/ext/nkf/nkf-utf8/nkf.c index cc438a50d6761f..08b372ffab2497 100644 --- a/ext/nkf/nkf-utf8/nkf.c +++ b/ext/nkf/nkf-utf8/nkf.c @@ -581,7 +581,7 @@ static const unsigned char cv[]= { 0x00,0x00}; -/* X0201 kana conversion table for daguten */ +/* X0201 kana conversion table for dakuten */ /* 90-9F A0-DF */ static const unsigned char dv[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -602,7 +602,7 @@ static const unsigned char dv[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00}; -/* X0201 kana conversion table for han-daguten */ +/* X0201 kana conversion table for han-dakuten */ /* 90-9F A0-DF */ static const unsigned char ev[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -623,7 +623,7 @@ static const unsigned char ev[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00}; -/* X0201 kana to X0213 conversion table for han-daguten */ +/* X0201 kana to X0213 conversion table for han-dakuten */ /* 90-9F A0-DF */ static const unsigned char ev_x0213[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -3817,7 +3817,7 @@ oconv_newline(void (*func)(nkf_char, nkf_char)) LF new line SP space - This fold algorthm does not preserve heading space in a line. + This fold algorithm does not preserve heading space in a line. This is the main difference from fmt. */ @@ -6787,7 +6787,7 @@ options(unsigned char *cp) case 'S': /* Shift_JIS input */ input_encoding = nkf_enc_from_index(SHIFT_JIS); continue; - case 'Z': /* Convert X0208 alphabet to asii */ + case 'Z': /* Convert X0208 alphabet to ascii */ /* alpha_f bit:0 Convert JIS X 0208 Alphabet to ASCII bit:1 Convert Kankaku to one space diff --git a/ext/nkf/nkf.c b/ext/nkf/nkf.c index 37717e4799aa07..76f7648d1bbe6e 100644 --- a/ext/nkf/nkf.c +++ b/ext/nkf/nkf.c @@ -458,7 +458,7 @@ rb_nkf_guess(VALUE obj, VALUE src) * with this and -x option, nkf can be used as UTF converter. * (In other words, without this and -x option, nkf doesn't save some characters) * - * When nkf convert string which related to path, you should use this opion. + * When nkf convert string which related to path, you should use this option. * * === --cap-input * diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index c3619a96566536..cf7acb5c6f9597 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -89,7 +89,7 @@ static void buffer_append(struct dump_config *dc, const char *cstr, unsigned lon static void dump_append_ld(struct dump_config *dc, const long number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%ld", number); RUBY_ASSERT(required <= width); @@ -99,7 +99,7 @@ dump_append_ld(struct dump_config *dc, const long number) static void dump_append_lu(struct dump_config *dc, const unsigned long number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%lu", number); RUBY_ASSERT(required <= width); @@ -123,7 +123,7 @@ dump_append_g(struct dump_config *dc, const double number) static void dump_append_d(struct dump_config *dc, const int number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%d", number); RUBY_ASSERT(required <= width); @@ -133,7 +133,7 @@ dump_append_d(struct dump_config *dc, const int number) static void dump_append_sizet(struct dump_config *dc, const size_t number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%"PRIuSIZE, number); RUBY_ASSERT(required <= width); @@ -144,7 +144,7 @@ static void dump_append_c(struct dump_config *dc, char c) { if (c <= 0x1f) { - const int width = (sizeof(c) * CHAR_BIT / 4) + 5; + const unsigned int width = (sizeof(c) * CHAR_BIT / 4) + 5; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "\\u00%02x", c); RUBY_ASSERT(required <= width); diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 7a92e5df68b8a8..0ade7adde7b6cc 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -1787,7 +1787,7 @@ Init_ossl_ocsp(void) * single_response = basic_response.find_response(certificate_id) * * unless single_response - * raise 'basic_response does not have the status for the certificiate' + * raise 'basic_response does not have the status for the certificate' * end * * Then check the validity. A status issued in the future must be rejected. diff --git a/ext/pathname/pathname.gemspec b/ext/pathname/pathname.gemspec index 8593f9e9c791f8..317029afb11a31 100644 --- a/ext/pathname/pathname.gemspec +++ b/ext/pathname/pathname.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] spec.extensions = %w[ext/pathname/extconf.rb] end diff --git a/ext/win32/lib/win32/sspi.rb b/ext/win32/lib/win32/sspi.rb index a73819f24ea92a..20205fd4d6e4f8 100644 --- a/ext/win32/lib/win32/sspi.rb +++ b/ext/win32/lib/win32/sspi.rb @@ -223,7 +223,7 @@ class NegotiateAuth B64_TOKEN_PREFIX = ["NTLMSSP"].pack("m").delete("=\n") # Given a connection and a request path, performs authentication as the current user and returns - # the response from a GET request. The connnection should be a Net::HTTP object, and it should + # the response from a GET request. The connection should be a Net::HTTP object, and it should # have been constructed using the Net::HTTP.Proxy method, but anything that responds to "get" will work. # If a user and domain are given, will authenticate as the given user. # Returns the response received from the get method (usually Net::HTTPResponse) diff --git a/ext/win32ole/sample/xml.rb b/ext/win32ole/sample/xml.rb index 36c3db32ef04d8..5a239c93367598 100644 --- a/ext/win32ole/sample/xml.rb +++ b/ext/win32ole/sample/xml.rb @@ -1032,8 +1032,8 @@ def loadXML(arg0) end # VOID save - # save the document to a specified desination - # VARIANT arg0 --- desination [IN] + # save the document to a specified destination + # VARIANT arg0 --- destination [IN] def save(arg0) ret = _invoke(64, [arg0], [VT_VARIANT]) @lastargs = WIN32OLE::ARGV @@ -6224,8 +6224,8 @@ def loadXML(arg0) end # VOID save - # save the document to a specified desination - # VARIANT arg0 --- desination [IN] + # save the document to a specified destination + # VARIANT arg0 --- destination [IN] def save(arg0) ret = @dispatch._invoke(64, [arg0], [VT_VARIANT]) @lastargs = WIN32OLE::ARGV @@ -6831,8 +6831,8 @@ def loadXML(arg0) end # VOID save - # save the document to a specified desination - # VARIANT arg0 --- desination [IN] + # save the document to a specified destination + # VARIANT arg0 --- destination [IN] def save(arg0) ret = @dispatch._invoke(64, [arg0], [VT_VARIANT]) @lastargs = WIN32OLE::ARGV diff --git a/ext/win32ole/win32ole_type.c b/ext/win32ole/win32ole_type.c index fa39bf3696ecd2..48dbc9dbde638b 100644 --- a/ext/win32ole/win32ole_type.c +++ b/ext/win32ole/win32ole_type.c @@ -56,7 +56,7 @@ static const rb_data_type_t oletype_datatype = { /* * Document-class: WIN32OLE_TYPE * - * WIN32OLE_TYPE objects represent OLE type libarary information. + * WIN32OLE_TYPE objects represent OLE type library information. */ static void diff --git a/ext/win32ole/win32ole_variant.c b/ext/win32ole/win32ole_variant.c index 93f0636593ce78..5ab29669f60dcd 100644 --- a/ext/win32ole/win32ole_variant.c +++ b/ext/win32ole/win32ole_variant.c @@ -371,7 +371,7 @@ check_type_val2variant(VALUE val) * Win32OLE converts Ruby object into OLE variant automatically when * invoking OLE methods. If OLE method requires the argument which is * different from the variant by automatic conversion of Win32OLE, you - * can convert the specfied variant type by using WIN32OLE_VARIANT class. + * can convert the specified variant type by using WIN32OLE_VARIANT class. * * param = WIN32OLE_VARIANT.new(10, WIN32OLE::VARIANT::VT_R4) * oleobj.method(param) diff --git a/gc.c b/gc.c index deaa56ff60173a..f4e52fdb8162cf 100644 --- a/gc.c +++ b/gc.c @@ -3344,7 +3344,7 @@ objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void * heap. This will be a snapshot of the state of the heap before we * call the callback over each page that exists in this buffer. Thus it * is safe for the callback to allocate objects without possibly entering - * an infinte loop. */ + * an infinite loop. */ struct heap_page *page = 0; size_t pages_count = 0; list_for_each(&heap_eden->pages, page, page_node) { @@ -4033,7 +4033,7 @@ id2ref_obj_tbl(rb_objspace_t *objspace, VALUE objid) * r = ObjectSpace._id2ref(s.object_id) #=> "I am a string" * r == s #=> true * - * On multi-ractor mode, if the object is not sharable, it raises + * On multi-ractor mode, if the object is not shareable, it raises * RangeError. */ diff --git a/gems/bundled_gems b/gems/bundled_gems index 587b94b60a7fa6..fa2f585f6a8ddf 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,4 +6,4 @@ test-unit 3.4.1 https://github.com/test-unit/test-unit 3.4.1 rexml 3.2.5 https://github.com/ruby/rexml rss 0.2.9 https://github.com/ruby/rss 0.2.9 typeprof 0.13.0 https://github.com/ruby/typeprof -rbs 1.1.1 https://github.com/ruby/rbs +rbs 1.2.0 https://github.com/ruby/rbs diff --git a/hash.c b/hash.c index 1498f30f5af8e1..a36310209b474f 100644 --- a/hash.c +++ b/hash.c @@ -2120,7 +2120,6 @@ rb_hash_lookup(VALUE hash, VALUE key) * If +key+ is not found and no block was given, * returns +default_value+: * {}.fetch(:nosuch, :default) # => :default - * {}.fetch(:nosuch) # => nil * * If +key+ is not found and a block was given, * yields +key+ to the block and returns the block's return value: diff --git a/include/ruby/backward/2/stdarg.h b/include/ruby/backward/2/stdarg.h index c2a9ca1e2f2348..5c5e1b31ce5574 100644 --- a/include/ruby/backward/2/stdarg.h +++ b/include/ruby/backward/2/stdarg.h @@ -20,7 +20,7 @@ * extension libraries. They could be written in C++98. * @brief Defines old #_ * - * Nobody should ever use these macros any longer. No konwn compilers lack + * Nobody should ever use these macros any longer. No known compilers lack * prototypes today. It's 21st century. Just forget them. */ diff --git a/include/ruby/internal/attr/cold.h b/include/ruby/internal/attr/cold.h index fcee507456006a..6db57fc9c26340 100644 --- a/include/ruby/internal/attr/cold.h +++ b/include/ruby/internal/attr/cold.h @@ -25,7 +25,7 @@ /** Wraps (or simulates) `__attribute__((cold))` */ #if RBIMPL_COMPILER_IS(SunPro) -# /* Recent SunPro has __has_attribute, and is borken. */ +# /* Recent SunPro has __has_attribute, and is broken. */ # /* It reports it has attribute cold, reality isn't (warnings issued). */ # define RBIMPL_ATTR_COLD() /* void */ #elif RBIMPL_HAS_ATTRIBUTE(cold) diff --git a/include/ruby/internal/compiler_since.h b/include/ruby/internal/compiler_since.h index 92abb8acc8b2db..b213cfd8b9293c 100644 --- a/include/ruby/internal/compiler_since.h +++ b/include/ruby/internal/compiler_since.h @@ -30,7 +30,7 @@ * @param y Minor version. * @param z Patchlevel. * @retval true cc >= x.y.z. - * @retval false oherwise. + * @retval false otherwise. */ #define RBIMPL_COMPILER_SINCE(cc, x, y, z) \ (RBIMPL_COMPILER_IS(cc) && \ @@ -48,7 +48,7 @@ * @param y Minor version. * @param z Patchlevel. * @retval true cc < x.y.z. - * @retval false oherwise. + * @retval false otherwise. */ #define RBIMPL_COMPILER_BEFORE(cc, x, y, z) \ (RBIMPL_COMPILER_IS(cc) && \ diff --git a/include/ruby/internal/core.h b/include/ruby/internal/core.h index 53a00a4603b2ea..279a697ea10f22 100644 --- a/include/ruby/internal/core.h +++ b/include/ruby/internal/core.h @@ -18,7 +18,7 @@ * Do not expect for instance `__VA_ARGS__` is always available. * We assume C99 for ruby itself but we don't assume languages of * extension libraries. They could be written in C++98. - * @brief Core data structures, definitions and manupulations. + * @brief Core data structures, definitions and manipulations. */ #include "ruby/internal/core/rarray.h" #include "ruby/internal/core/rbasic.h" diff --git a/include/ruby/internal/warning_push.h b/include/ruby/internal/warning_push.h index b8a21aaeab9d00..ca521290c96d66 100644 --- a/include/ruby/internal/warning_push.h +++ b/include/ruby/internal/warning_push.h @@ -25,7 +25,7 @@ * * Q: Why all the macros defined in this file are function-like macros? * - * A: Sigh. This is because of Doxgen. Its `SKIP_FUNCTION_MACROS = YES` + * A: Sigh. This is because of Doxygen. Its `SKIP_FUNCTION_MACROS = YES` * configuration setting requests us that if we want it to ignore these * macros, then we have to do two things: (1) let them be defined as * function-like macros, and (2) place them separately in their own line, diff --git a/insns.def b/insns.def index 5704bf44e7f491..8d609927b5871f 100644 --- a/insns.def +++ b/insns.def @@ -592,25 +592,6 @@ swap /* none */ } -/* reverse stack top N order. */ -DEFINE_INSN -reverse -(rb_num_t n) -(...) -(...) -// attr rb_snum_t sp_inc = 0; -{ - rb_num_t i; - VALUE *sp = STACK_ADDR_FROM_TOP(n); - - for (i=0; i/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] end diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec index 1a883934e58882..5d23ef0f6144b7 100644 --- a/lib/cgi/cgi.gemspec +++ b/lib/cgi/cgi.gemspec @@ -26,6 +26,6 @@ Gem::Specification.new do |spec| `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] end diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb index aab8b000cbc205..69a252b5e36fe7 100644 --- a/lib/cgi/util.rb +++ b/lib/cgi/util.rb @@ -49,9 +49,12 @@ def escapeHTML(string) table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}] string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table) string.encode!(origenc) if origenc - return string + string + else + string = string.b + string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) + string.force_encoding(enc) end - string.gsub(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) end begin @@ -90,7 +93,8 @@ def unescapeHTML(string) when Encoding::ISO_8859_1; 256 else 128 end - string.gsub(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do + string = string.b + string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do match = $1.dup case match when 'apos' then "'" @@ -116,6 +120,7 @@ def unescapeHTML(string) "&#{match};" end end + string.force_encoding enc end # Synonym for CGI.escapeHTML(str) diff --git a/lib/irb/color.rb b/lib/irb/color.rb index fce4d53191f807..40e9e04c975685 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -101,22 +101,22 @@ def inspect_colorable?(obj, seen: {}.compare_by_identity) end end - def clear - return '' unless colorable? + def clear(colorable: colorable?) + return '' unless colorable "\e[#{CLEAR}m" end - def colorize(text, seq) - return text unless colorable? + def colorize(text, seq, colorable: colorable?) + return text unless colorable seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('') - "#{seq}#{text}#{clear}" + "#{seq}#{text}#{clear(colorable: colorable)}" end # If `complete` is false (code is incomplete), this does not warn compile_error. # This option is needed to avoid warning a user when the compile_error is happening # because the input is not wrong but just incomplete. - def colorize_code(code, complete: true, ignore_error: false) - return code unless colorable? + def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?) + return code unless colorable symbol_state = SymbolState.new colored = +'' @@ -134,7 +134,7 @@ def colorize_code(code, complete: true, ignore_error: false) line = Reline::Unicode.escape_for_print(line) if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol) colored << seq.map { |s| "\e[#{s}m" }.join('') - colored << line.sub(/\Z/, clear) + colored << line.sub(/\Z/, clear(colorable: colorable)) else colored << line end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 82df06da2b94fb..3b3b9b393626a3 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -339,7 +339,7 @@ def check_code_block(code, tokens = @tokens) # "syntax error, unexpected end-of-input, expecting keyword_end" # # example: - # if ture + # if true # hoge # if false # fuga diff --git a/lib/matrix/matrix.gemspec b/lib/matrix/matrix.gemspec index d2ff9ce7c6d271..6f68cbd8165568 100644 --- a/lib/matrix/matrix.gemspec +++ b/lib/matrix/matrix.gemspec @@ -23,7 +23,4 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - - spec.add_development_dependency "bundler" - spec.add_development_dependency "rake" end diff --git a/lib/matrix/version.rb b/lib/matrix/version.rb index 4a8bc36aaa78b7..82b835f038c6fc 100644 --- a/lib/matrix/version.rb +++ b/lib/matrix/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Matrix - VERSION = "0.3.1" + VERSION = "0.4.1" end diff --git a/lib/net/ftp.rb b/lib/net/ftp.rb index 88e8655c1ceb3d..bb746098df9aa7 100644 --- a/lib/net/ftp.rb +++ b/lib/net/ftp.rb @@ -330,14 +330,19 @@ def return_code=(s) # :nodoc: # SOCKS_SERVER, then a SOCKSSocket is returned, else a Socket is # returned. def open_socket(host, port) # :nodoc: - return Timeout.timeout(@open_timeout, OpenTimeout) { - if defined? SOCKSSocket and ENV["SOCKS_SERVER"] - @passive = true + if defined? SOCKSSocket and ENV["SOCKS_SERVER"] + @passive = true + Timeout.timeout(@open_timeout, OpenTimeout) do SOCKSSocket.open(host, port) - else - Socket.tcp(host, port) end - } + else + begin + Socket.tcp host, port, nil, nil, connect_timeout: @open_timeout + rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions + raise Net::OpenTimeout, "Timeout to open TCP connection to "\ + "#{host}:#{port} (exceeds #{@open_timeout} seconds)" + end + end end private :open_socket @@ -542,18 +547,22 @@ def makepasv # :nodoc: def transfercmd(cmd, rest_offset = nil) # :nodoc: if @passive host, port = makepasv - conn = open_socket(host, port) - if @resume and rest_offset - resp = sendcmd("REST " + rest_offset.to_s) - if !resp.start_with?("3") + begin + conn = open_socket(host, port) + if @resume and rest_offset + resp = sendcmd("REST " + rest_offset.to_s) + if !resp.start_with?("3") + raise FTPReplyError, resp + end + end + resp = sendcmd(cmd) + # skip 2XX for some ftp servers + resp = getresp if resp.start_with?("2") + if !resp.start_with?("1") raise FTPReplyError, resp end - end - resp = sendcmd(cmd) - # skip 2XX for some ftp servers - resp = getresp if resp.start_with?("2") - if !resp.start_with?("1") - raise FTPReplyError, resp + ensure + conn.close if conn && $! end else sock = makeport @@ -1045,10 +1054,11 @@ def writable? TIME_PARSER = ->(value, local = false) { unless /\A(?\d{4})(?\d{2})(?\d{2}) (?\d{2})(?\d{2})(?\d{2}) - (?:\.(?\d+))?/x =~ value + (?:\.(?\d{1,17}))?/x =~ value + value = value[0, 97] + "..." if value.size > 100 raise FTPProtoError, "invalid time-val: #{value}" end - usec = fractions.to_i * 10 ** (6 - fractions.to_s.size) + usec = ".#{fractions}".to_r * 1_000_000 if fractions Time.public_send(local ? :local : :utc, year, month, day, hour, min, sec, usec) } FACT_PARSERS = Hash.new(CASE_DEPENDENT_PARSER) @@ -1356,7 +1366,7 @@ def close end # - # Returns +true+ iff the connection is closed. + # Returns +true+ if and only if the connection is closed. # def closed? @sock == nil or @sock.closed? diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 36920c4a915d9d..834ff0dee718e2 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -201,7 +201,7 @@ module Net # Unicode", RFC 2152, May 1997. # class IMAP < Protocol - VERSION = "0.1.1" + VERSION = "0.2.1" include MonitorMixin if defined?(OpenSSL::SSL) @@ -229,6 +229,9 @@ class IMAP < Protocol # it raises a Net::OpenTimeout exception. The default value is 30 seconds. attr_reader :open_timeout + # Seconds to wait until an IDLE response is received. + attr_reader :idle_response_timeout + # The thread to receive exceptions. attr_accessor :client_thread @@ -304,6 +307,16 @@ def self.add_authenticator(auth_type, authenticator) @@authenticators[auth_type] = authenticator end + # Builds an authenticator for Net::IMAP#authenticate. + def self.authenticator(auth_type, *args) + auth_type = auth_type.upcase + unless @@authenticators.has_key?(auth_type) + raise ArgumentError, + format('unknown auth type - "%s"', auth_type) + end + @@authenticators[auth_type].new(*args) + end + # The default port for IMAP connections, port 143 def self.default_port return PORT @@ -365,6 +378,30 @@ def capability end end + # Sends an ID command, and returns a hash of the server's + # response, or nil if the server does not identify itself. + # + # Note that the user should first check if the server supports the ID + # capability. For example: + # + # capabilities = imap.capability + # if capabilities.include?("ID") + # id = imap.id( + # name: "my IMAP client (ruby)", + # version: MyIMAP::VERSION, + # "support-url": "mailto:bugs@example.com", + # os: RbConfig::CONFIG["host_os"], + # ) + # end + # + # See RFC 2971, Section 3.3, for defined fields. + def id(client_id=nil) + synchronize do + send_command("ID", ClientID.new(client_id)) + @responses.delete("ID")&.last + end + end + # Sends a NOOP command to the server. It does nothing. def noop send_command("NOOP") @@ -408,7 +445,7 @@ def starttls(options = {}, verify = true) # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5". # # Authentication is done using the appropriate authenticator object: - # see @@authenticators for more information on plugging in your own + # see +add_authenticator+ for more information on plugging in your own # authenticator. # # For example: @@ -417,12 +454,7 @@ def starttls(options = {}, verify = true) # # A Net::IMAP::NoResponseError is raised if authentication fails. def authenticate(auth_type, *args) - auth_type = auth_type.upcase - unless @@authenticators.has_key?(auth_type) - raise ArgumentError, - format('unknown auth type - "%s"', auth_type) - end - authenticator = @@authenticators[auth_type].new(*args) + authenticator = self.class.authenticator(auth_type, *args) send_command("AUTHENTICATE", auth_type) do |resp| if resp.instance_of?(ContinuationRequest) data = authenticator.process(resp.data.text.unpack("m")[0]) @@ -552,6 +584,60 @@ def list(refname, mailbox) end end + # Sends a NAMESPACE command [RFC2342] and returns the namespaces that are + # available. The NAMESPACE command allows a client to discover the prefixes + # of namespaces used by a server for personal mailboxes, other users' + # mailboxes, and shared mailboxes. + # + # This extension predates IMAP4rev1 (RFC3501), so most IMAP servers support + # it. Many popular IMAP servers are configured with the default personal + # namespaces as `("" "/")`: no prefix and "/" hierarchy delimiter. In that + # common case, the naive client may not have any trouble naming mailboxes. + # + # But many servers are configured with the default personal namespace as + # e.g. `("INBOX." ".")`, placing all personal folders under INBOX, with "." + # as the hierarchy delimiter. If the client does not check for this, but + # naively assumes it can use the same folder names for all servers, then + # folder creation (and listing, moving, etc) can lead to errors. + # + # From RFC2342: + # + # Although typically a server will support only a single Personal + # Namespace, and a single Other User's Namespace, circumstances exist + # where there MAY be multiples of these, and a client MUST be prepared + # for them. If a client is configured such that it is required to create + # a certain mailbox, there can be circumstances where it is unclear which + # Personal Namespaces it should create the mailbox in. In these + # situations a client SHOULD let the user select which namespaces to + # create the mailbox in. + # + # The user of this method should first check if the server supports the + # NAMESPACE capability. The return value is a +Net::IMAP::Namespaces+ + # object which has +personal+, +other+, and +shared+ fields, each an array + # of +Net::IMAP::Namespace+ objects. These arrays will be empty when the + # server responds with nil. + # + # For example: + # + # capabilities = imap.capability + # if capabilities.include?("NAMESPACE") + # namespaces = imap.namespace + # if namespace = namespaces.personal.first + # prefix = namespace.prefix # e.g. "" or "INBOX." + # delim = namespace.delim # e.g. "/" or "." + # # personal folders should use the prefix and delimiter + # imap.create(prefix + "foo") + # imap.create(prefix + "bar") + # imap.create(prefix + %w[path to my folder].join(delim)) + # end + # end + def namespace + synchronize do + send_command("NAMESPACE") + return @responses.delete("NAMESPACE")[-1] + end + end + # Sends a XLIST command, and returns a subset of names from # the complete set of all names available to the client. # +refname+ provides a context (for instance, a base directory @@ -973,7 +1059,7 @@ def idle(timeout = nil, &response_handler) unless @receiver_thread_terminating remove_response_handler(response_handler) put_string("DONE#{CRLF}") - response = get_tagged_response(tag, "IDLE") + response = get_tagged_response(tag, "IDLE", @idle_response_timeout) end end end @@ -1059,6 +1145,7 @@ def self.format_datetime(time) # If +options[:ssl]+ is a hash, it's passed to # OpenSSL::SSL::SSLContext#set_params as parameters. # open_timeout:: Seconds to wait until a connection is opened + # idle_response_timeout:: Seconds to wait until an IDLE response is received # # The most common errors are: # @@ -1088,6 +1175,7 @@ def initialize(host, port_or_options = {}, @tag_prefix = "RUBY" @tagno = 0 @open_timeout = options[:open_timeout] || 30 + @idle_response_timeout = options[:idle_response_timeout] || 5 @parser = ResponseParser.new @sock = tcp_socket(@host, @port) begin @@ -1211,10 +1299,19 @@ def receive_responses end end - def get_tagged_response(tag, cmd) + def get_tagged_response(tag, cmd, timeout = nil) + if timeout + deadline = Time.now + timeout + end until @tagged_responses.key?(tag) raise @exception if @exception - @tagged_response_arrival.wait + if timeout + timeout = deadline - Time.now + if timeout <= 0 + return nil + end + end + @tagged_response_arrival.wait(timeout) end resp = @tagged_responses.delete(tag) case resp.name @@ -1656,6 +1753,74 @@ def validate_internal(data) end end + class ClientID # :nodoc: + + def send_data(imap, tag) + imap.__send__(:send_data, format_internal(@data), tag) + end + + def validate + validate_internal(@data) + end + + private + + def initialize(data) + @data = data + end + + def validate_internal(client_id) + client_id.to_h.each do |k,v| + unless StringFormatter.valid_string?(k) + raise DataFormatError, client_id.inspect + end + end + rescue NoMethodError, TypeError # to_h failed + raise DataFormatError, client_id.inspect + end + + def format_internal(client_id) + return nil if client_id.nil? + client_id.to_h.flat_map {|k,v| + [StringFormatter.string(k), StringFormatter.nstring(v)] + } + end + + end + + module StringFormatter + + LITERAL_REGEX = /[\x80-\xff\r\n]/n + + module_function + + # Allows symbols in addition to strings + def valid_string?(str) + str.is_a?(Symbol) || str.respond_to?(:to_str) + end + + # Allows nil, symbols, and strings + def valid_nstring?(str) + str.nil? || valid_string?(str) + end + + # coerces using +to_s+ + def string(str) + str = str.to_s + if str =~ LITERAL_REGEX + Literal.new(str) + else + QuotedString.new(str) + end + end + + # coerces non-nil using +to_s+ + def nstring(str) + str.nil? ? nil : string(str) + end + + end + # Common validators of number and nz_number types module NumValidator # :nodoc class << self @@ -1747,6 +1912,18 @@ def ensure_mod_sequence_value(num) # raw_data:: Returns the raw data string. UntaggedResponse = Struct.new(:name, :data, :raw_data) + # Net::IMAP::IgnoredResponse represents intentionaly ignored responses. + # + # This includes untagged response "NOOP" sent by eg. Zimbra to avoid some + # clients to close the connection. + # + # It matches no IMAP standard. + # + # ==== Fields: + # + # raw_data:: Returns the raw data string. + IgnoredResponse = Struct.new(:raw_data) + # Net::IMAP::TaggedResponse represents tagged responses. # # The server completion result response indicates the success or @@ -1774,8 +1951,7 @@ def ensure_mod_sequence_value(num) # Net::IMAP::ResponseText represents texts of responses. # The text may be prefixed by the response code. # - # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text) - # ;; text SHOULD NOT begin with "[" or "=" + # resp_text ::= ["[" resp-text-code "]" SP] text # # ==== Fields: # @@ -1787,12 +1963,15 @@ def ensure_mod_sequence_value(num) # Net::IMAP::ResponseCode represents response codes. # - # resp_text_code ::= "ALERT" / "PARSE" / - # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" / + # resp_text_code ::= "ALERT" / + # "BADCHARSET" [SP "(" astring *(SP astring) ")" ] / + # capability_data / "PARSE" / + # "PERMANENTFLAGS" SP "(" + # [flag_perm *(SP flag_perm)] ")" / # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / - # "UIDVALIDITY" SPACE nz_number / - # "UNSEEN" SPACE nz_number / - # atom [SPACE 1*] + # "UIDNEXT" SP nz_number / "UIDVALIDITY" SP nz_number / + # "UNSEEN" SP nz_number / + # atom [SP 1*] # # ==== Fields: # @@ -1872,6 +2051,39 @@ def ensure_mod_sequence_value(num) # MailboxACLItem = Struct.new(:user, :rights, :mailbox) + # Net::IMAP::Namespace represents a single [RFC-2342] namespace. + # + # Namespace = nil / "(" 1*( "(" string SP (<"> QUOTED_CHAR <"> / + # nil) *(Namespace_Response_Extension) ")" ) ")" + # + # Namespace_Response_Extension = SP string SP "(" string *(SP string) + # ")" + # + # ==== Fields: + # + # prefix:: Returns the namespace prefix string. + # delim:: Returns nil or the hierarchy delimiter character. + # extensions:: Returns a hash of extension names to extension flag arrays. + # + Namespace = Struct.new(:prefix, :delim, :extensions) + + # Net::IMAP::Namespaces represents the response from [RFC-2342] NAMESPACE. + # + # Namespace_Response = "*" SP "NAMESPACE" SP Namespace SP Namespace SP + # Namespace + # + # ; The first Namespace is the Personal Namespace(s) + # ; The second Namespace is the Other Users' Namespace(s) + # ; The third Namespace is the Shared Namespace(s) + # + # ==== Fields: + # + # personal:: Returns an array of Personal Net::IMAP::Namespace objects. + # other:: Returns an array of Other Users' Net::IMAP::Namespace objects. + # shared:: Returns an array of Shared Net::IMAP::Namespace objects. + # + Namespaces = Struct.new(:personal, :other, :shared) + # Net::IMAP::StatusData represents the contents of the STATUS response. # # ==== Fields: @@ -2291,8 +2503,12 @@ def response_untagged return response_cond when /\A(?:FLAGS)\z/ni return flags_response + when /\A(?:ID)\z/ni + return id_response when /\A(?:LIST|LSUB|XLIST)\z/ni return list_response + when /\A(?:NAMESPACE)\z/ni + return namespace_response when /\A(?:QUOTA)\z/ni return getquota_response when /\A(?:QUOTAROOT)\z/ni @@ -2307,6 +2523,8 @@ def response_untagged return status_response when /\A(?:CAPABILITY)\z/ni return capability_response + when /\A(?:NOOP)\z/ni + return ignored_response else return text_response end @@ -2316,7 +2534,7 @@ def response_untagged end def response_tagged - tag = atom + tag = astring_chars match(T_SPACE) token = match(T_ATOM) name = token.value.upcase @@ -2876,14 +3094,18 @@ def modseq_data return name, modseq end + def ignored_response + while lookahead.symbol != T_CRLF + shift_token + end + return IgnoredResponse.new(@str) + end + def text_response token = match(T_ATOM) name = token.value.upcase match(T_SPACE) - @lex_state = EXPR_TEXT - token = match(T_TEXT) - @lex_state = EXPR_BEG - return UntaggedResponse.new(name, token.value) + return UntaggedResponse.new(name, text) end def flags_response @@ -3114,11 +3336,15 @@ def capability_response token = match(T_ATOM) name = token.value.upcase match(T_SPACE) + UntaggedResponse.new(name, capability_data, @str) + end + + def capability_data data = [] while true token = lookahead case token.symbol - when T_CRLF + when T_CRLF, T_RBRA break when T_SPACE shift_token @@ -3126,30 +3352,142 @@ def capability_response end data.push(atom.upcase) end + data + end + + def id_response + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + token = match(T_LPAR, T_NIL) + if token.symbol == T_NIL + return UntaggedResponse.new(name, nil, @str) + else + data = {} + while true + token = lookahead + case token.symbol + when T_RPAR + shift_token + break + when T_SPACE + shift_token + next + else + key = string + match(T_SPACE) + val = nstring + data[key] = val + end + end + return UntaggedResponse.new(name, data, @str) + end + end + + def namespace_response + @lex_state = EXPR_DATA + token = lookahead + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + personal = namespaces + match(T_SPACE) + other = namespaces + match(T_SPACE) + shared = namespaces + @lex_state = EXPR_BEG + data = Namespaces.new(personal, other, shared) return UntaggedResponse.new(name, data, @str) end - def resp_text - @lex_state = EXPR_RTEXT + def namespaces token = lookahead - if token.symbol == T_LBRA - code = resp_text_code + # empty () is not allowed, so nil is functionally identical to empty. + data = [] + if token.symbol == T_NIL + shift_token else - code = nil + match(T_LPAR) + loop do + data << namespace + break unless lookahead.symbol == T_SPACE + shift_token + end + match(T_RPAR) + end + data + end + + def namespace + match(T_LPAR) + prefix = match(T_QUOTED, T_LITERAL).value + match(T_SPACE) + delimiter = string + extensions = namespace_response_extensions + match(T_RPAR) + Namespace.new(prefix, delimiter, extensions) + end + + def namespace_response_extensions + data = {} + token = lookahead + if token.symbol == T_SPACE + shift_token + name = match(T_QUOTED, T_LITERAL).value + data[name] ||= [] + match(T_SPACE) + match(T_LPAR) + loop do + data[name].push match(T_QUOTED, T_LITERAL).value + break unless lookahead.symbol == T_SPACE + shift_token + end + match(T_RPAR) + end + data + end + + # text = 1*TEXT-CHAR + # TEXT-CHAR = + def text + match(T_TEXT, lex_state: EXPR_TEXT).value + end + + # resp-text = ["[" resp-text-code "]" SP] text + def resp_text + token = match(T_LBRA, T_TEXT, lex_state: EXPR_RTEXT) + case token.symbol + when T_LBRA + code = resp_text_code + match(T_RBRA) + accept_space # violating RFC + ResponseText.new(code, text) + when T_TEXT + ResponseText.new(nil, token.value) end - token = match(T_TEXT) - @lex_state = EXPR_BEG - return ResponseText.new(code, token.value) end + # See https://www.rfc-editor.org/errata/rfc3501 + # + # resp-text-code = "ALERT" / + # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] / + # capability-data / "PARSE" / + # "PERMANENTFLAGS" SP "(" + # [flag-perm *(SP flag-perm)] ")" / + # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / + # "UNSEEN" SP nz-number / + # atom [SP 1*] def resp_text_code - @lex_state = EXPR_BEG - match(T_LBRA) token = match(T_ATOM) name = token.value.upcase case name when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n result = ResponseCode.new(name, nil) + when /\A(?:BADCHARSET)\z/n + result = ResponseCode.new(name, charset_list) + when /\A(?:CAPABILITY)\z/ni + result = ResponseCode.new(name, capability_data) when /\A(?:PERMANENTFLAGS)\z/n match(T_SPACE) result = ResponseCode.new(name, flag_list) @@ -3160,19 +3498,28 @@ def resp_text_code token = lookahead if token.symbol == T_SPACE shift_token - @lex_state = EXPR_CTEXT - token = match(T_TEXT) - @lex_state = EXPR_BEG + token = match(T_TEXT, lex_state: EXPR_CTEXT) result = ResponseCode.new(name, token.value) else result = ResponseCode.new(name, nil) end end - match(T_RBRA) - @lex_state = EXPR_RTEXT return result end + def charset_list + result = [] + if accept(T_SPACE) + match(T_LPAR) + result << charset + while accept(T_SPACE) + result << charset + end + match(T_RPAR) + end + result + end + def address_list token = lookahead if token.symbol == T_NIL @@ -3269,7 +3616,7 @@ def astring if string_token?(token) return string else - return atom + return astring_chars end end @@ -3299,34 +3646,49 @@ def case_insensitive_string return token.value.upcase end - def atom - result = String.new - while true - token = lookahead - if atom_token?(token) - result.concat(token.value) - shift_token - else - if result.empty? - parse_error("unexpected token %s", token.symbol) - else - return result - end - end - end - end - + # atom = 1*ATOM-CHAR + # ATOM-CHAR = ATOM_TOKENS = [ T_ATOM, T_NUMBER, T_NIL, T_LBRA, - T_RBRA, T_PLUS ] - def atom_token?(token) - return ATOM_TOKENS.include?(token.symbol) + def atom + -combine_adjacent(*ATOM_TOKENS) + end + + # ASTRING-CHAR = ATOM-CHAR / resp-specials + # resp-specials = "]" + ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA] + + def astring_chars + combine_adjacent(*ASTRING_CHARS_TOKENS) + end + + def combine_adjacent(*tokens) + result = "".b + while token = accept(*tokens) + result << token.value + end + if result.empty? + parse_error('unexpected token %s (expected %s)', + lookahead.symbol, args.join(" or ")) + end + result + end + + # See https://www.rfc-editor.org/errata/rfc3501 + # + # charset = atom / quoted + def charset + if token = accept(T_QUOTED) + token.value + else + atom + end end def number @@ -3344,22 +3706,62 @@ def nil_atom return nil end - def match(*args) + SPACES_REGEXP = /\G */n + + # This advances @pos directly so it's safe before changing @lex_state. + def accept_space + if @token + shift_token if @token.symbol == T_SPACE + elsif @str[@pos] == " " + @pos += 1 + end + end + + # The RFC is very strict about this and usually we should be too. + # But skipping spaces is usually a safe workaround for buggy servers. + # + # This advances @pos directly so it's safe before changing @lex_state. + def accept_spaces + shift_token if @token&.symbol == T_SPACE + if @str.index(SPACES_REGEXP, @pos) + @pos = $~.end(0) + end + end + + def match(*args, lex_state: @lex_state) + if @token && lex_state != @lex_state + parse_error("invalid lex_state change to %s with unconsumed token", + lex_state) + end + begin + @lex_state, original_lex_state = lex_state, @lex_state + token = lookahead + unless args.include?(token.symbol) + parse_error('unexpected token %s (expected %s)', + token.symbol.id2name, + args.collect {|i| i.id2name}.join(" or ")) + end + shift_token + return token + ensure + @lex_state = original_lex_state + end + end + + # like match, but does not raise error on failure. + # + # returns and shifts token on successful match + # returns nil and leaves @token unshifted on no match + def accept(*args) token = lookahead - unless args.include?(token.symbol) - parse_error('unexpected token %s (expected %s)', - token.symbol.id2name, - args.collect {|i| i.id2name}.join(" or ")) + if args.include?(token.symbol) + shift_token + token end - shift_token - return token end def lookahead - unless @token - @token = next_token - end - return @token + @token ||= next_token end def shift_token diff --git a/lib/net/net-smtp.gemspec b/lib/net/net-smtp.gemspec index bd0a2dd84c2594..3c210429b911d7 100644 --- a/lib/net/net-smtp.gemspec +++ b/lib/net/net-smtp.gemspec @@ -22,9 +22,11 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + spec.files = %w[ + LICENSE.txt + lib/net/smtp.rb + net-smtp.gemspec + ] spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index a97c0c33952d24..56820befc64b6a 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -168,7 +168,7 @@ class SMTPUnsupportedCommand < ProtocolError # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5) # class SMTP < Protocol - VERSION = "0.2.1" + VERSION = "0.2.1-patch-ssl-context" Revision = %q$Revision$.split[1] @@ -191,12 +191,9 @@ class << self alias default_ssl_port default_tls_port end - def SMTP.default_ssl_context(verify_peer=true) + def SMTP.default_ssl_context(ssl_context_params = nil) context = OpenSSL::SSL::SSLContext.new - context.verify_mode = verify_peer ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE - store = OpenSSL::X509::Store.new - store.set_default_paths - context.cert_store = store + context.set_params(ssl_context_params ? ssl_context_params : {}) context end @@ -409,14 +406,14 @@ def debug_output=(arg) # # :call-seq: - # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil) { |smtp| ... } + # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } # start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } # # Creates a new Net::SMTP object and connects to the server. # # This method is equivalent to: # - # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname) + # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil) # # === Example # @@ -450,6 +447,11 @@ def debug_output=(arg) # If the hostname in the server certificate is different from +address+, # it can be specified with +tls_hostname+. # + # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to + # +OpenSSL::SSL::SSLContext#set_params+ + # + # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. + # # === Errors # # This method may raise: @@ -465,14 +467,14 @@ def debug_output=(arg) # def SMTP.start(address, port = nil, *args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil, - tls_verify: true, tls_hostname: nil, + tls_verify: true, tls_hostname: nil, ssl_context_params: nil, &block) raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 1..6)" if args.size > 4 helo ||= args[0] || 'localhost' user ||= args[1] secret ||= password || args[2] authtype ||= args[3] - new(address, port).start(helo: helo, user: user, secret: secret, authtype: authtype, tls_verify: tls_verify, tls_hostname: tls_hostname, &block) + new(address, port).start(helo: helo, user: user, secret: secret, authtype: authtype, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params, &block) end # +true+ if the SMTP session has been started. @@ -482,7 +484,7 @@ def started? # # :call-seq: - # start(helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil) { |smtp| ... } + # start(helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } # start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } # # Opens a TCP connection and starts the SMTP session. @@ -501,6 +503,11 @@ def started? # If the hostname in the server certificate is different from +address+, # it can be specified with +tls_hostname+. # + # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to + # +OpenSSL::SSL::SSLContext#set_params+ + # + # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. + # # === Block Usage # # When this methods is called with a block, the newly-started SMTP @@ -539,17 +546,23 @@ def started? # * IOError # def start(*args, helo: nil, - user: nil, secret: nil, password: nil, authtype: nil, tls_verify: true, tls_hostname: nil) + user: nil, secret: nil, password: nil, authtype: nil, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4 helo ||= args[0] || 'localhost' user ||= args[1] secret ||= password || args[2] authtype ||= args[3] + ssl_context_params = ssl_context_params ? ssl_context_params : {} + if ssl_context_params.has_key?(:verify_mode) + tls_verify = ssl_context_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE + else + ssl_context_params[:verify_mode] = tls_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + end if @tls && @ssl_context_tls.nil? - @ssl_context_tls = SMTP.default_ssl_context(tls_verify) + @ssl_context_tls = SMTP.default_ssl_context(ssl_context_params) end if @starttls && @ssl_context_starttls.nil? - @ssl_context_starttls = SMTP.default_ssl_context(tls_verify) + @ssl_context_starttls = SMTP.default_ssl_context(ssl_context_params) end @tls_hostname = tls_hostname if block_given? @@ -575,7 +588,12 @@ def finish private def tcp_socket(address, port) - TCPSocket.open address, port + begin + Socket.tcp address, port, nil, nil, connect_timeout: @open_timeout + rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions + raise Net::OpenTimeout, "Timeout to open TCP connection to "\ + "#{address}:#{port} (exceeds #{@open_timeout} seconds)" + end end def do_start(helo_domain, user, secret, authtype) @@ -584,9 +602,7 @@ def do_start(helo_domain, user, secret, authtype) check_auth_method(authtype || DEFAULT_AUTH_TYPE) check_auth_args user, secret end - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do - tcp_socket(@address, @port) - end + s = tcp_socket(@address, @port) logging "Connection opened: #{@address}:#{@port}" @socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s) check_response critical { recv_response() } diff --git a/lib/time.rb b/lib/time.rb index a40e2febc633c2..bd20a1a8e9b79b 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# shareable_constant_value: literal require 'date' diff --git a/lib/uri.rb b/lib/uri.rb index b544a5ee652e96..5e820f46c32207 100644 --- a/lib/uri.rb +++ b/lib/uri.rb @@ -70,7 +70,6 @@ # - URI::REGEXP - (in uri/common.rb) # - URI::REGEXP::PATTERN - (in uri/common.rb) # - URI::Util - (in uri/common.rb) -# - URI::Escape - (in uri/common.rb) # - URI::Error - (in uri/common.rb) # - URI::InvalidURIError - (in uri/common.rb) # - URI::InvalidComponentError - (in uri/common.rb) diff --git a/lib/uri/common.rb b/lib/uri/common.rb index d818592b742338..915c0e9519b993 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -321,7 +321,7 @@ def self.encode_www_form_component(str, enc=nil) # # See URI.encode_www_form_component, URI.decode_www_form. def self.decode_www_form_component(str, enc=Encoding::UTF_8) - raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/ =~ str + raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc) end diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index a4192c65571766..cfa0de6b74f061 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -643,7 +643,7 @@ def host=(v) # def hostname v = self.host - /\A\[(.*)\]\z/ =~ v ? $1 : v + v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v end # Sets the host part of the URI as the argument with brackets for IPv6 addresses. @@ -659,7 +659,7 @@ def hostname # it is wrapped with brackets. # def hostname=(v) - v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v + v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':') self.host = v end @@ -1514,9 +1514,19 @@ def find_proxy(env=ENV) proxy_uri = env["CGI_#{name.upcase}"] end elsif name == 'http_proxy' - unless proxy_uri = env[name] - if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost'] + p_port = ENV_JAVA['http.proxyPort'] + if p_user = ENV_JAVA['http.proxyUser'] + p_pass = ENV_JAVA['http.proxyPass'] + proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}" + else + proxy_uri = "http://#{p_addr}:#{p_port}" + end + else + unless proxy_uri = env[name] + if proxy_uri = env[name.upcase] + warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + end end end else diff --git a/lib/uri/rfc2396_parser.rb b/lib/uri/rfc2396_parser.rb index 1c9ce177bc3404..76a8f99fd48ccd 100644 --- a/lib/uri/rfc2396_parser.rb +++ b/lib/uri/rfc2396_parser.rb @@ -322,8 +322,14 @@ def unescape(str, escaped = @regexp[:ESCAPED]) end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private diff --git a/lib/uri/rfc3986_parser.rb b/lib/uri/rfc3986_parser.rb index 49a594c17d49a0..3e07de4805c374 100644 --- a/lib/uri/rfc3986_parser.rb +++ b/lib/uri/rfc3986_parser.rb @@ -79,8 +79,14 @@ def join(*uris) # :nodoc: end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private diff --git a/lib/uri/uri.gemspec b/lib/uri/uri.gemspec index c4a16f47eb6740..584a4faa112e95 100644 --- a/lib/uri/uri.gemspec +++ b/lib/uri/uri.gemspec @@ -15,6 +15,8 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/ruby/uri" spec.licenses = ["Ruby", "BSD-2-Clause"] + spec.required_ruby_version = '>= 2.4' + spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage diff --git a/memory_view.c b/memory_view.c index a1ab240656ae3b..ce2803717b4125 100644 --- a/memory_view.c +++ b/memory_view.c @@ -396,7 +396,7 @@ rb_memory_view_parse_item_format(const char *format, ssize_t max_alignment_size = 0; const char *p = format; - if (*p == '|') { // alginment specifier + if (*p == '|') { // alignment specifier alignment = true; ++format; ++p; diff --git a/misc/lldb_cruby.py b/misc/lldb_cruby.py index 83a28acbd7b66b..9e64957958d568 100755 --- a/misc/lldb_cruby.py +++ b/misc/lldb_cruby.py @@ -529,10 +529,68 @@ def dump_bits(target, result, page, object_address, end = "\n"): check_bits(page, "wb_unprotected_bits", bitmap_index, bitmap_bit, "U"), ), end=end, file=result) -def dump_page(debugger, command, result, internal_dict): +class HeapPageIter: + def __init__(self, page, target): + self.page = page + self.target = target + self.start = page.GetChildMemberWithName('start').GetValueAsUnsigned(); + self.num_slots = page.GetChildMemberWithName('total_slots').unsigned + self.counter = 0 + self.tRBasic = target.FindFirstType("struct RBasic") + self.tRValue = target.FindFirstType("struct RVALUE") + + def __iter__(self): + return self + + def __next__(self): + if self.counter < self.num_slots: + obj_addr_i = self.start + (self.counter * self.tRValue.GetByteSize()) + obj_addr = lldb.SBAddress(obj_addr_i, self.target) + slot_info = (self.counter, obj_addr_i, self.target.CreateValueFromAddress("object", obj_addr, self.tRBasic)) + self.counter += 1 + + return slot_info + else: + raise StopIteration + + +def dump_page_internal(page, target, process, thread, frame, result, debugger, highlight=None): if not ('RUBY_Qfalse' in globals()): lldb_init(debugger) + ruby_type_map = ruby_types(debugger) + + freelist = [] + fl_start = page.GetChildMemberWithName('freelist').GetValueAsUnsigned() + tRVALUE = target.FindFirstType("struct RVALUE") + + while fl_start > 0: + freelist.append(fl_start) + obj_addr = lldb.SBAddress(fl_start, target) + obj = target.CreateValueFromAddress("object", obj_addr, tRVALUE) + fl_start = obj.GetChildMemberWithName("as").GetChildMemberWithName("free").GetChildMemberWithName("next").GetValueAsUnsigned() + + for (page_index, obj_addr, obj) in HeapPageIter(page, target): + dump_bits(target, result, page, obj_addr, end= " ") + flags = obj.GetChildMemberWithName('flags').GetValueAsUnsigned() + flType = flags & RUBY_T_MASK + + flidx = ' ' + if flType == RUBY_T_NONE: + try: + flidx = "%3d" % freelist.index(obj_addr) + except ValueError: + flidx = ' ' + + result_str = "%s idx: [%3d] freelist_idx: {%s} Addr: %0#x (flags: %0#x)" % (rb_type(flags, ruby_type_map), page_index, flidx, obj_addr, flags) + + if highlight == obj_addr: + result_str = ' '.join([result_str, "<<<<<"]) + + print(result_str, file=result) + + +def dump_page(debugger, command, result, internal_dict): target = debugger.GetSelectedTarget() process = target.GetProcess() thread = process.GetSelectedThread() @@ -542,21 +600,23 @@ def dump_page(debugger, command, result, internal_dict): page = frame.EvaluateExpression(command) page = page.Cast(tHeapPageP) - tRBasic = target.FindFirstType("struct RBasic") - tRValue = target.FindFirstType("struct RVALUE") + dump_page_internal(page, target, process, thread, frame, result, debugger) - obj_address = page.GetChildMemberWithName('start').GetValueAsUnsigned(); - num_slots = page.GetChildMemberWithName('total_slots').unsigned - ruby_type_map = ruby_types(debugger) +def dump_page_rvalue(debugger, command, result, internal_dict): + target = debugger.GetSelectedTarget() + process = target.GetProcess() + thread = process.GetSelectedThread() + frame = thread.GetSelectedFrame() + + val = frame.EvaluateExpression(command) + page = get_page(lldb, target, val) + page_type = target.FindFirstType("struct heap_page").GetPointerType() + page.Cast(page_type) + + dump_page_internal(page, target, process, thread, frame, result, debugger, highlight=val.GetValueAsUnsigned()) + - for j in range(0, num_slots): - offset = obj_address + (j * tRValue.GetByteSize()) - obj_addr = lldb.SBAddress(offset, target) - p = target.CreateValueFromAddress("object", obj_addr, tRBasic) - dump_bits(target, result, page, offset, end = " ") - flags = p.GetChildMemberWithName('flags').GetValueAsUnsigned() - print("%s [%3d]: Addr: %0#x (flags: %0#x)" % (rb_type(flags, ruby_type_map), j, offset, flags), file=result) def rb_type(flags, ruby_types): flType = flags & RUBY_T_MASK @@ -588,6 +648,7 @@ def __lldb_init_module(debugger, internal_dict): debugger.HandleCommand("command script add -f lldb_cruby.heap_page_body heap_page_body") debugger.HandleCommand("command script add -f lldb_cruby.rb_backtrace rbbt") debugger.HandleCommand("command script add -f lldb_cruby.dump_page dump_page") + debugger.HandleCommand("command script add -f lldb_cruby.dump_page_rvalue dump_page_rvalue") lldb_init(debugger) print("lldb scripts for ruby has been installed.") diff --git a/parse.y b/parse.y index 909a4ec3b18287..6909e9c777e925 100644 --- a/parse.y +++ b/parse.y @@ -11072,6 +11072,7 @@ const_decl_path(struct parser_params *p, NODE **dest) path = rb_fstring(path); } *dest = n = NEW_LIT(path, loc); + RB_OBJ_WRITTEN(p->ast, Qnil, n->nd_lit); } return n; } @@ -11159,7 +11160,9 @@ shareable_literal_constant(struct parser_params *p, enum shareability shareable, case NODE_ZLIST: lit = rb_ary_new(); OBJ_FREEZE_RAW(lit); - return NEW_LIT(lit, loc); + NODE *n = NEW_LIT(lit, loc); + RB_OBJ_WRITTEN(p->ast, Qnil, n->nd_lit); + return n; case NODE_LIST: lit = rb_ary_new(); @@ -11245,6 +11248,7 @@ shareable_literal_constant(struct parser_params *p, enum shareability shareable, } else { value = NEW_LIT(rb_ractor_make_shareable(lit), loc); + RB_OBJ_WRITTEN(p->ast, Qnil, value->nd_lit); } return value; diff --git a/ractor.c b/ractor.c index e3cd602d59928d..cb86f667510550 100644 --- a/ractor.c +++ b/ractor.c @@ -559,7 +559,7 @@ wait_status_str(enum ractor_wait_status wait_status) case wait_taking|wait_yielding: return "taking|yielding"; case wait_receiving|wait_taking|wait_yielding: return "receiving|taking|yielding"; } - rb_bug("unrechable"); + rb_bug("unreachable"); } static const char * @@ -574,7 +574,7 @@ wakeup_status_str(enum ractor_wakeup_status wakeup_status) case wakeup_by_interrupt: return "by_interrupt"; case wakeup_by_retry: return "by_retry"; } - rb_bug("unrechable"); + rb_bug("unreachable"); } #endif // USE_RUBY_DEBUG_LOG diff --git a/ractor.rb b/ractor.rb index 2238e090c9725f..de7c9a93a7471e 100644 --- a/ractor.rb +++ b/ractor.rb @@ -147,7 +147,7 @@ # on a moved object. # # Besides frozen objects, there are shareable objects. Class and Module objects are shareable so -# the Class/Module definitons are shared between ractors. Ractor objects are also shareable objects. +# the Class/Module definitions are shared between ractors. Ractor objects are also shareable objects. # All operations for the shareable mutable objects are thread-safe, so the thread-safety property # will be kept. We can not define mutable shareable objects in Ruby, but C extensions can introduce them. # @@ -223,7 +223,7 @@ # # == Reference # -# See {Ractor desgin doc}[rdoc-ref:doc/ractor.md] for more details. +# See {Ractor design doc}[rdoc-ref:doc/ractor.md] for more details. # class Ractor # @@ -457,7 +457,7 @@ class << self # # If the block returns a truthy value, the message will be removed from the incoming queue # and returned. - # Otherwise, the messsage remains in the incoming queue and the following received + # Otherwise, the message remains in the incoming queue and the following received # messages are checked by the given block. # # If there are no messages left in the incoming queue, the method will diff --git a/spec/ruby/core/file/shared/read.rb b/spec/ruby/core/file/shared/read.rb index a2d479966d7a27..f2322352984c5b 100644 --- a/spec/ruby/core/file/shared/read.rb +++ b/spec/ruby/core/file/shared/read.rb @@ -1,13 +1,13 @@ require_relative '../../dir/fixtures/common' describe :file_read_directory, shared: true do - platform_is :darwin, :linux, :openbsd, :windows do + platform_is :darwin, :linux, :freebsd, :openbsd, :windows do it "raises an Errno::EISDIR when passed a path that is a directory" do -> { @object.send(@method, ".") }.should raise_error(Errno::EISDIR) end end - platform_is :freebsd, :netbsd do + platform_is :netbsd do it "does not raises any exception when passed a path that is a directory" do -> { @object.send(@method, ".") }.should_not raise_error end diff --git a/test/benchmark/test_benchmark.rb b/test/benchmark/test_benchmark.rb index 3030bc5dec012f..ddd4a591bb378a 100644 --- a/test/benchmark/test_benchmark.rb +++ b/test/benchmark/test_benchmark.rb @@ -71,7 +71,7 @@ def test_benchmark_does_not_print_any_space_if_the_given_caption_is_empty BENCH end - def test_benchmark_makes_extra_calcultations_with_an_Array_at_the_end_of_the_benchmark_and_show_the_result + def test_benchmark_makes_extra_calculations_with_an_Array_at_the_end_of_the_benchmark_and_show_the_result assert_equal(BENCHMARK_OUTPUT_WITH_TOTAL_AVG, capture_bench_output(:benchmark, Benchmark::CAPTION, 7, diff --git a/test/cgi/test_cgi_util.rb b/test/cgi/test_cgi_util.rb index b7bb7b8eae7bde..6ce8b42c20f5fb 100644 --- a/test/cgi/test_cgi_util.rb +++ b/test/cgi/test_cgi_util.rb @@ -36,9 +36,7 @@ def test_cgi_escape_with_unreserved_characters end def test_cgi_escape_with_invalid_byte_sequence - assert_nothing_raised(ArgumentError) do - assert_equal('%C0%3C%3C', CGI.escape("\xC0\<\<".dup.force_encoding("UTF-8"))) - end + assert_equal('%C0%3C%3C', CGI.escape("\xC0\<\<".dup.force_encoding("UTF-8"))) end def test_cgi_escape_preserve_encoding @@ -191,3 +189,32 @@ def test_cgi_unescapeElement assert_equal('<BR>', unescape_element(escapeHTML('
'), ["A", "IMG"])) end end + +class CGIUtilPureRubyTest < Test::Unit::TestCase + def setup + CGI::Escape.module_eval do + alias _escapeHTML escapeHTML + remove_method :escapeHTML + alias _unescapeHTML unescapeHTML + remove_method :unescapeHTML + end + end + + def teardown + CGI::Escape.module_eval do + alias escapeHTML _escapeHTML + remove_method :_escapeHTML + alias unescapeHTML _unescapeHTML + remove_method :_unescapeHTML + end + end + + def test_cgi_escapeHTML_with_invalid_byte_sequence + assert_equal("<\xA4??>", CGI.escapeHTML(%[<\xA4??>])) + end + + def test_cgi_unescapeHTML_with_invalid_byte_sequence + input = "\xFF&" + assert_equal(input, CGI.unescapeHTML(input)) + end +end diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index a28ae06117bbc7..ba99b1253f4e02 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -17,6 +17,27 @@ class TestColor < Test::Unit::TestCase MAGENTA = "\e[35m" CYAN = "\e[36m" + def test_colorize + text = "text" + { + [:BOLD] => "#{BOLD}#{text}#{CLEAR}", + [:UNDERLINE] => "#{UNDERLINE}#{text}#{CLEAR}", + [:REVERSE] => "#{REVERSE}#{text}#{CLEAR}", + [:RED] => "#{RED}#{text}#{CLEAR}", + [:GREEN] => "#{GREEN}#{text}#{CLEAR}", + [:YELLOW] => "#{YELLOW}#{text}#{CLEAR}", + [:BLUE] => "#{BLUE}#{text}#{CLEAR}", + [:MAGENTA] => "#{MAGENTA}#{text}#{CLEAR}", + [:CYAN] => "#{CYAN}#{text}#{CLEAR}", + }.each do |seq, result| + assert_equal_with_term(result, text, seq: seq) + + assert_equal_with_term(text, text, seq: seq, tty: false) + assert_equal_with_term(text, text, seq: seq, colorable: false) + assert_equal_with_term(result, text, seq: seq, tty: false, colorable: true) + end + end + def test_colorize_code # Common behaviors. Warn parser error, but do not warn compile error. tests = { @@ -105,14 +126,21 @@ def test_colorize_code tests.each do |code, result| if colorize_code_supported? - actual = with_term { IRB::Color.colorize_code(code, complete: true) } - assert_equal(result, actual, "Case: IRB::Color.colorize_code(#{code.dump}, complete: true)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: true) + assert_equal_with_term(result, code, complete: false) + + assert_equal_with_term(code, code, complete: true, tty: false) + assert_equal_with_term(code, code, complete: false, tty: false) - actual = with_term { IRB::Color.colorize_code(code, complete: false) } - assert_equal(result, actual, "Case: IRB::Color.colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(code, code, complete: true, colorable: false) + + assert_equal_with_term(code, code, complete: false, colorable: false) + + assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) + + assert_equal_with_term(result, code, complete: false, tty: false, colorable: true) else - actual = with_term { IRB::Color.colorize_code(code) } - assert_equal(code, actual) + assert_equal_with_term(code, code) end end end @@ -127,8 +155,13 @@ def test_colorize_code_complete_true "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}bar#{CLEAR}", "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}foo#{CLEAR}", }.each do |code, result| - actual = with_term { IRB::Color.colorize_code(code, complete: true) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: true)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: true) + + assert_equal_with_term(code, code, complete: true, tty: false) + + assert_equal_with_term(code, code, complete: true, colorable: false) + + assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) end end @@ -139,16 +172,25 @@ def test_colorize_code_complete_false "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}", }.each do |code, result| if colorize_code_supported? - actual = with_term { IRB::Color.colorize_code(code, complete: false) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: false) + + assert_equal_with_term(code, code, complete: false, tty: false) + + assert_equal_with_term(code, code, complete: false, colorable: false) + + assert_equal_with_term(result, code, complete: false, tty: false, colorable: true) unless complete_option_supported? - actual = with_term { IRB::Color.colorize_code(code, complete: true) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: true) + + assert_equal_with_term(code, code, complete: true, tty: false) + + assert_equal_with_term(code, code, complete: true, colorable: false) + + assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) end else - actual = with_term { IRB::Color.colorize_code(code) } - assert_equal(code, actual) + assert_equal_with_term(code, code) end end end @@ -185,10 +227,10 @@ def complete_option_supported? Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0') end - def with_term + def with_term(tty: true) stdout = $stdout io = StringIO.new - def io.tty?; true; end + def io.tty?; true; end if tty $stdout = io env = ENV.to_h.dup @@ -200,6 +242,23 @@ def io.tty?; true; end ENV.replace(env) if env end + def assert_equal_with_term(result, code, seq: nil, tty: true, **opts) + actual = with_term(tty: tty) do + if seq + IRB::Color.colorize(code, seq, **opts) + else + IRB::Color.colorize_code(code, **opts) + end + end + message = -> { + args = [code.dump] + args << seq.inspect if seq + opts.each {|kwd, val| args << "#{kwd}: #{val}"} + "Case: colorize#{seq ? "" : "_code"}(#{args.join(', ')})\nResult: #{humanized_literal(actual)}" + } + assert_equal(result, actual, message) + end + def humanized_literal(str) str .gsub(CLEAR, '@@@{CLEAR}') diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 2c50b5da3aef07..d57f0752bb7ffa 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -5,6 +5,19 @@ module TestIRB class TestInit < Test::Unit::TestCase + def setup + # IRBRC is for RVM... + @backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash| + hash[env] = ENV.delete(env) + end + ENV["HOME"] = @tmpdir = Dir.mktmpdir("test_irb_init_#{$$}") + end + + def teardown + ENV.update(@backup_env) + FileUtils.rm_rf(@tmpdir) + end + def test_setup_with_argv_preserves_global_argv argv = ["foo", "bar"] with_argv(argv) do @@ -20,12 +33,8 @@ def test_setup_with_minimum_argv_does_not_change_dollar0 end def test_rc_file - backup_irbrc = ENV.delete("IRBRC") # This is for RVM... - backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - backup_home = ENV["HOME"] - Dir.mktmpdir("test_irb_init_#{$$}") do |tmpdir| - ENV["HOME"] = tmpdir - + tmpdir = @tmpdir + Dir.chdir(tmpdir) do IRB.conf[:RC_NAME_GENERATOR] = nil assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) @@ -34,19 +43,11 @@ def test_rc_file assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) end - ensure - ENV["HOME"] = backup_home - ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home - ENV["IRBRC"] = backup_irbrc end def test_rc_file_in_subdir - backup_irbrc = ENV.delete("IRBRC") # This is for RVM... - backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - backup_home = ENV["HOME"] - Dir.mktmpdir("test_irb_init_#{$$}") do |tmpdir| - ENV["HOME"] = tmpdir - + tmpdir = @tmpdir + Dir.chdir(tmpdir) do FileUtils.mkdir_p("#{tmpdir}/mydir") Dir.chdir("#{tmpdir}/mydir") do IRB.conf[:RC_NAME_GENERATOR] = nil @@ -58,10 +59,6 @@ def test_rc_file_in_subdir assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) end end - ensure - ENV["HOME"] = backup_home - ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home - ENV["IRBRC"] = backup_irbrc end def test_recovery_sigint diff --git a/test/matrix/test_matrix.rb b/test/matrix/test_matrix.rb index 53887729eff549..25ad7b6b95466d 100644 --- a/test/matrix/test_matrix.rb +++ b/test/matrix/test_matrix.rb @@ -828,7 +828,7 @@ def test_ractor end.take assert_same obj1, obj2 RUBY - end + end if defined?(Ractor) def test_rotate_with_symbol assert_equal(Matrix[[4, 1], [5, 2], [6, 3]], @m1.rotate_entries) diff --git a/test/monitor/test_monitor.rb b/test/monitor/test_monitor.rb index 734b639d4cf731..0f17d58f712113 100644 --- a/test/monitor/test_monitor.rb +++ b/test/monitor/test_monitor.rb @@ -10,6 +10,13 @@ def setup @monitor = Monitor.new end + def test_enter_in_different_fibers + @monitor.enter + Fiber.new { + assert_equal false, @monitor.try_enter + }.resume + end + def test_enter ary = [] queue = Queue.new diff --git a/test/net/ftp/test_ftp.rb b/test/net/ftp/test_ftp.rb index 6cd32add9018f3..ee64290f2b292a 100644 --- a/test/net/ftp/test_ftp.rb +++ b/test/net/ftp/test_ftp.rb @@ -882,6 +882,40 @@ def test_getbinaryfile_with_filename_and_block end end + def test_getbinaryfile_error + commands = [] + server = create_ftp_server { |sock| + sock.print("220 (test_ftp).\r\n") + commands.push(sock.gets) + sock.print("331 Please specify the password.\r\n") + commands.push(sock.gets) + sock.print("230 Login successful.\r\n") + commands.push(sock.gets) + sock.print("200 Switching to Binary mode.\r\n") + line = sock.gets + commands.push(line) + sock.print("450 No Dice\r\n") + } + begin + begin + ftp = Net::FTP.new + ftp.passive = true + ftp.read_timeout *= 5 if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # for --jit-wait + ftp.connect(SERVER_ADDR, server.port) + ftp.login + assert_match(/\AUSER /, commands.shift) + assert_match(/\APASS /, commands.shift) + assert_equal("TYPE I\r\n", commands.shift) + assert_raise(Net::FTPTempError) {ftp.getbinaryfile("foo", nil)} + assert_match(/\A(PASV|EPSV)\r\n/, commands.shift) + ensure + ftp.close if ftp + end + ensure + server.close + end + end + def test_storbinary commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 @@ -1935,7 +1969,7 @@ def test_active_private_data_connection assert_equal(nil, commands.shift) # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. # See https://github.com/openssl/openssl/pull/5967 for details. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ + if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/ assert_equal(true, session_reused_for_data_connection) end ensure @@ -2019,7 +2053,7 @@ def test_passive_private_data_connection assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ + if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/ assert_equal(true, session_reused_for_data_connection) end ensure @@ -2393,7 +2427,7 @@ def test_putbinaryfile_command_injection File.binwrite("./|echo hello", binary_data) begin ftp = Net::FTP.new - ftp.read_timeout = RubyVM::JIT.enabled? ? 300 : 0.2 # use large timeout for --jit-wait + ftp.read_timeout = defined?(RubyVM::JIT) && RubyVM::JIT.enabled? ? 300 : 0.2 # use large timeout for --jit-wait ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) @@ -2474,6 +2508,23 @@ def test_puttextfile_command_injection end end + def test_time_parser + s = "20371231000000" + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0), + Net::FTP::TIME_PARSER[s]) + s = "20371231000000.123456" + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0, 123456), + Net::FTP::TIME_PARSER[s]) + s = "20371231000000." + "9" * 999999 + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0, + 99999999999999999r / 100000000000), + Net::FTP::TIME_PARSER[s]) + e = assert_raise(Net::FTPProtoError) { + Net::FTP::TIME_PARSER["x" * 999999] + } + assert_equal("invalid time-val: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...", e.message) + end + private def create_ftp_server(sleep_time = nil) diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index 8b924b524e9d4f..4fb9f744fcf102 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -578,23 +578,23 @@ def test_send_invalid_number begin imap = Net::IMAP.new(server_addr, :port => port) assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", -1) + imap.__send__(:send_command, "TEST", -1) end - imap.send(:send_command, "TEST", 0) - imap.send(:send_command, "TEST", 4294967295) + imap.__send__(:send_command, "TEST", 0) + imap.__send__(:send_command, "TEST", 4294967295) assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", 4294967296) + imap.__send__(:send_command, "TEST", 4294967296) end assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(-1)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(-1)) end assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(0)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(0)) end - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(1)) - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967295)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(1)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967295)) assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967296)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967296)) end imap.logout ensure @@ -628,7 +628,7 @@ def test_send_literal end begin imap = Net::IMAP.new(server_addr, :port => port) - imap.send(:send_command, "TEST", ["\xDE\xAD\xBE\xEF".b]) + imap.__send__(:send_command, "TEST", ["\xDE\xAD\xBE\xEF".b]) assert_equal(2, requests.length) assert_equal("RUBY0001 TEST ({4}\r\n", requests[0]) assert_equal("\xDE\xAD\xBE\xEF".b, literal) @@ -753,6 +753,55 @@ def test_append_fail end end + def test_id + server = create_tcp_server + port = server.addr[1] + requests = Queue.new + server_id = {"name" => "test server", "version" => "v0.1.0"} + server_id_str = '("name" "test server" "version" "v0.1.0")' + @threads << Thread.start do + sock = server.accept + begin + sock.print("* OK test server\r\n") + requests.push(sock.gets) + # RFC 2971 very clearly states (in section 3.2): + # "a server MUST send a tagged ID response to an ID command." + # And yet... some servers report ID capability but won't the response. + sock.print("RUBY0001 OK ID completed\r\n") + requests.push(sock.gets) + sock.print("* ID #{server_id_str}\r\n") + sock.print("RUBY0002 OK ID completed\r\n") + requests.push(sock.gets) + sock.print("* ID #{server_id_str}\r\n") + sock.print("RUBY0003 OK ID completed\r\n") + requests.push(sock.gets) + sock.print("* BYE terminating connection\r\n") + sock.print("RUBY0004 OK LOGOUT completed\r\n") + ensure + sock.close + server.close + end + end + + begin + imap = Net::IMAP.new(server_addr, :port => port) + resp = imap.id + assert_equal(nil, resp) + assert_equal("RUBY0001 ID NIL\r\n", requests.pop) + resp = imap.id({}) + assert_equal(server_id, resp) + assert_equal("RUBY0002 ID ()\r\n", requests.pop) + resp = imap.id("name" => "test client", "version" => "latest") + assert_equal(server_id, resp) + assert_equal("RUBY0003 ID (\"name\" \"test client\" \"version\" \"latest\")\r\n", + requests.pop) + imap.logout + assert_equal("RUBY0004 LOGOUT\r\n", requests.pop) + ensure + imap.disconnect if imap + end + end + private def imaps_test diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb index 4e470459c944e5..5b519edeff1b14 100644 --- a/test/net/imap/test_imap_response_parser.rb +++ b/test/net/imap/test_imap_response_parser.rb @@ -234,6 +234,27 @@ def test_capability response = parser.parse("* CAPABILITY st11p00mm-iscream009 1Q49 XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN \r\n") assert_equal("CAPABILITY", response.name) assert_equal("AUTH=PLAIN", response.data.last) + response = parser.parse("* OK [CAPABILITY IMAP4rev1 SASL-IR 1234 NIL THIS+THAT + AUTH=PLAIN ID] IMAP4rev1 Hello\r\n") + assert_equal("OK", response.name) + assert_equal("IMAP4rev1 Hello", response.data.text) + code = response.data.code + assert_equal("CAPABILITY", code.name) + assert_equal( + ["IMAP4REV1", "SASL-IR", "1234", "NIL", "THIS+THAT", "+", "AUTH=PLAIN", "ID"], + code.data + ) + end + + def test_id + parser = Net::IMAP::ResponseParser.new + response = parser.parse("* ID NIL\r\n") + assert_equal("ID", response.name) + assert_equal(nil, response.data) + response = parser.parse("* ID (\"name\" \"GImap\" \"vendor\" \"Google, Inc.\" \"support-url\" NIL)\r\n") + assert_equal("ID", response.name) + assert_equal("GImap", response.data["name"]) + assert_equal("Google, Inc.", response.data["vendor"]) + assert_equal(nil, response.data.fetch("support-url")) end def test_mixed_boundary @@ -301,6 +322,22 @@ def test_msg_att_modseq_data assert_equal(12345, response.data.attr["MODSEQ"]) end + def test_msg_rfc3501_response_text_with_T_LBRA + parser = Net::IMAP::ResponseParser.new + response = parser.parse("RUBY0004 OK [READ-WRITE] [Gmail]/Sent Mail selected. (Success)\r\n") + assert_equal("RUBY0004", response.tag) + assert_equal("READ-WRITE", response.data.code.name) + assert_equal("[Gmail]/Sent Mail selected. (Success)", response.data.text) + end + + def test_msg_rfc3501_response_text_with_BADCHARSET_astrings + parser = Net::IMAP::ResponseParser.new + response = parser.parse("t BAD [BADCHARSET (US-ASCII \"[astring with brackets]\")] unsupported charset foo.\r\n") + assert_equal("t", response.tag) + assert_equal("unsupported charset foo.", response.data.text) + assert_equal("BADCHARSET", response.data.code.name) + end + def test_continuation_request_without_response_text parser = Net::IMAP::ResponseParser.new response = parser.parse("+\r\n") @@ -308,4 +345,45 @@ def test_continuation_request_without_response_text assert_equal(nil, response.data.code) assert_equal("", response.data.text) end + + def test_ignored_response + parser = Net::IMAP::ResponseParser.new + response = nil + assert_nothing_raised do + response = parser.parse("* NOOP\r\n") + end + assert_instance_of(Net::IMAP::IgnoredResponse, response) + end + + def test_namespace + parser = Net::IMAP::ResponseParser.new + # RFC2342 Example 5.1 + response = parser.parse(%Q{* NAMESPACE (("" "/")) NIL NIL\r\n}) + assert_equal("NAMESPACE", response.name) + assert_equal([Net::IMAP::Namespace.new("", "/", {})], response.data.personal) + assert_equal([], response.data.other) + assert_equal([], response.data.shared) + # RFC2342 Example 5.4 + response = parser.parse(%Q{* NAMESPACE (("" "/")) (("~" "/")) (("#shared/" "/")} + + %Q{ ("#public/" "/") ("#ftp/" "/") ("#news." "."))\r\n}) + assert_equal("NAMESPACE", response.name) + assert_equal([Net::IMAP::Namespace.new("", "/", {})], response.data.personal) + assert_equal([Net::IMAP::Namespace.new("~", "/", {})], response.data.other) + assert_equal( + [ + Net::IMAP::Namespace.new("#shared/", "/", {}), + Net::IMAP::Namespace.new("#public/", "/", {}), + Net::IMAP::Namespace.new("#ftp/", "/", {}), + Net::IMAP::Namespace.new("#news.", ".", {}), + ], + response.data.shared + ) + # RFC2342 Example 5.6 + response = parser.parse(%Q{* NAMESPACE (("" "/") ("#mh/" "/" "X-PARAM" ("FLAG1" "FLAG2"))) NIL NIL\r\n}) + assert_equal("NAMESPACE", response.name) + namespace = response.data.personal.last + assert_equal("#mh/", namespace.prefix) + assert_equal("/", namespace.delim) + assert_equal({"X-PARAM" => ["FLAG1", "FLAG2"]}, namespace.extensions) + end end diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 97f3d60882361a..58e086c0a01681 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -168,7 +168,7 @@ def test_trace_object_allocations_stop_first assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; require "objspace" - # Make sure stoping before the tracepoints are initialized doesn't raise. See [Bug #17020] + # Make sure stopping before the tracepoints are initialized doesn't raise. See [Bug #17020] ObjectSpace.trace_object_allocations_stop end; end diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 9a52e7a2872151..51e8e503a00b33 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -581,7 +581,7 @@ def test_progress_chunked ) {|f| assert_equal(1, length.length) assert_equal(nil, length[0]) - assert(progress.length>1,"maybe test is worng") + assert(progress.length>1,"maybe test is wrong") assert(progress.sort == progress,"monotone increasing expected but was\n#{progress.inspect}") assert_equal(content.length, progress[-1]) assert_equal(content, f.read) diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 4a9863cc54a3a8..fb50f581fea93e 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -2741,9 +2741,9 @@ def test_zip_with_enumerator step = 0.step e = Enumerator.produce { step.next } a = %w(a b c) - assert_equal([["a", 0], ["b", 1], ["c", 2]], a.zip(e)) - assert_equal([["a", 3], ["b", 4], ["c", 5]], a.zip(e)) - assert_equal([["a", 6], ["b", 7], ["c", 8]], a.zip(e)) + assert_equal([["a", 0], ["b", 1], ["c", 2]], a.zip(e), bug17814) + assert_equal([["a", 3], ["b", 4], ["c", 5]], a.zip(e), bug17814) + assert_equal([["a", 6], ["b", 7], ["c", 8]], a.zip(e), bug17814) end def test_transpose diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb index 5a6ec97e67244d..41e8bffe82f36d 100644 --- a/test/ruby/test_assignment.rb +++ b/test/ruby/test_assignment.rb @@ -81,6 +81,64 @@ def test_massign_simple a,b,*c = [*[1,2]]; assert_equal([1,2,[]], [a,b,c]) end + def test_massign_order + order = [] + define_singleton_method(:x1){order << :x1; self} + define_singleton_method(:y1){order << :y1; self} + define_singleton_method(:z=){|x| order << [:z=, x]} + define_singleton_method(:x2){order << :x2; self} + define_singleton_method(:x3){order << :x3; self} + define_singleton_method(:x4){order << :x4; self} + define_singleton_method(:x5=){|x| order << [:x5=, x]; self} + define_singleton_method(:[]=){|*args| order << [:[]=, *args]} + define_singleton_method(:r1){order << :r1; :r1} + define_singleton_method(:r2){order << :r2; :r2} + + x1.y1.z, x2[1, 2, 3], self[4] = r1, 6, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, 6], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], self[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :x3, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4], x4.x5 = r1, 6, 7, r2, 8 + assert_equal([:x1, :y1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x2.x5), _a = [r1, r2], 7 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:x5=, :r2]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3] = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7, :r2, 8]]], order) + order.clear + + *x2[1, 2, 3], (x3[4], x4.x5) = 6, 7, [r2, 8] + assert_equal([:x2, :x3, :x4, :r2, [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], x3[4], x4.x5 = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], (x3[4], x4.x5) = [r1, 5], 6, 7, [r2, 8] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, x1.x5), _a), *x2[1, 2, 3], ((x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, *x1.x5), _a), *x2[1, 2, 3], ((*x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, [5]], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, [:r2]], [:x5=, 8]], order) + order.clear + end + def test_massign_splat a,b,*c = *[]; assert_equal([nil,nil,[]], [a,b,c]) a,b,*c = *[1]; assert_equal([1,nil,[]], [a,b,c]) diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb index a1ab1c8df6b8fd..1aad0de3474d7d 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -922,7 +922,7 @@ def test_newline_option end newlines.each do |nl| opts = {newline: :universal, nl => true} - ec2 = assert_warning(/:newline option preceds/, opts.inspect) do + ec2 = assert_warning(/:newline option precedes/, opts.inspect) do Encoding::Converter.new("", "", **opts) end assert_equal(ec1, ec2) diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 087bcda5acbfca..af29163ce3dd7f 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -78,6 +78,66 @@ def test_exception_ensure_2 # just duplication? assert(!bad) end + def test_exception_in_ensure_with_next + string = "[ruby-core:82936] [Bug #13930]" + assert_raise_with_message(RuntimeError, string) do + lambda do + next + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + + assert_raise_with_message(RuntimeError, string) do + flag = true + while flag + flag = false + begin + next + rescue + assert(false) + ensure + raise string + end + end + end + end + + def test_exception_in_ensure_with_redo + string = "[ruby-core:82936] [Bug #13930]" + + assert_raise_with_message(RuntimeError, string) do + i = 0 + lambda do + i += 1 + redo if i < 2 + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + end + + def test_exception_in_ensure_with_return + @string = "[ruby-core:97104] [Bug #16618]" + def self.meow + return if true # This if modifier suppresses "warning: statement not reached" + assert(false) + rescue + assert(false) + ensure + raise @string + end + assert_raise_with_message(RuntimeError, @string) do + meow + end + end + def test_errinfo_in_debug bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do def to_s diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index b2518c32f06b75..26f8268140a6b1 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -392,7 +392,7 @@ def test_fork_from_fiber xpid = fork do # enough to trigger GC on old root fiber count = 10000 - count = 1000 if /openbsd/i =~ RUBY_PLATFORM + count = 1000 if /solaris|openbsd/i =~ RUBY_PLATFORM count.times do Fiber.new {}.transfer Fiber.new { Fiber.yield } diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 1f75a34cacede7..f10946bd390fe9 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -494,4 +494,11 @@ def test_object_ids_never_repeat b = 1000.times.map { Object.new.object_id } assert_empty(a & b) end + + def test_ast_node_buffer + # https://github.com/ruby/ruby/pull/4416 + Module.new.class_eval do + eval((["# shareable_constant_value: literal"] + (0..100000).map {|i| "M#{ i } = {}" }).join("\n")) + end + end end diff --git a/test/ruby/test_jit.rb b/test/ruby/test_jit.rb index d7d4ee159c4cd8..55922b2d8c6dfe 100644 --- a/test/ruby/test_jit.rb +++ b/test/ruby/test_jit.rb @@ -318,10 +318,6 @@ def test_compile_insn_swap_topn assert_compile_once('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn]) end - def test_compile_insn_reverse - assert_compile_once('q, (w, e), r = 1, [2, 3], 4; [q, w, e, r]', result_inspect: '[1, 2, 3, 4]', insns: %i[reverse]) - end - def test_compile_insn_reput skip "write test" end diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 0bdcb79c6d4677..c8191a722c9c73 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -2411,6 +2411,13 @@ def baz(*args) args end + def empty_method + end + + def opt(arg = :opt) + arg + end + ruby2_keywords def foo_dbar(*args) dbar(*args) end @@ -2419,6 +2426,16 @@ def baz(*args) dbaz(*args) end + ruby2_keywords def clear_last_empty_method(*args) + args.last.clear + empty_method(*args) + end + + ruby2_keywords def clear_last_opt(*args) + args.last.clear + opt(*args) + end + define_method(:dbar) do |*args, **kw| [args, kw] end @@ -2653,6 +2670,9 @@ def method_missing(*args) assert_equal([[1, h1], {}], o.foo(:pass_bar, 1, :a=>1)) assert_equal([[1, h1], {}], o.foo(:pass_cfunc, 1, :a=>1)) + assert_equal(:opt, o.clear_last_opt(a: 1)) + assert_nothing_raised(ArgumentError) { o.clear_last_empty_method(a: 1) } + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or method does not accept argument splat\)/) do assert_nil(c.send(:ruby2_keywords, :bar)) end diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 3cd2f04eff4cdd..84e74693aa2a1c 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -2294,7 +2294,7 @@ def /(other) assert_equal(0, 1 / 2) end - def test_visibility_after_refine_and_visibility_change + def test_visibility_after_refine_and_visibility_change_with_origin_class m = Module.new c = Class.new do def x; :x end @@ -2317,6 +2317,114 @@ def x; :y end assert_equal(:x, o2.public_send(:x)) end + def test_visibility_after_multiple_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + Module.new do + refine sc do + def x; :z end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + def test_prepend_visibility bug8005 = '[ruby-core:53106] [Bug #8005]' c = Class.new do diff --git a/test/test_time.rb b/test/test_time.rb index ca20788aace140..b50d8417cf1785 100644 --- a/test/test_time.rb +++ b/test/test_time.rb @@ -62,6 +62,15 @@ def test_rfc2822 assert_equal(true, t.utc?) end + if defined?(Ractor) + def test_rfc2822_ractor + assert_ractor(<<~RUBY, require: 'time') + actual = Ractor.new { Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600") }.take + assert_equal(Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600, actual) + RUBY + end + end + def test_encode_rfc2822 t = Time.utc(1) assert_equal("Mon, 01 Jan 0001 00:00:00 -0000", t.rfc2822) diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb index b449a0a0d1fb6c..d122587031e073 100644 --- a/test/uri/test_generic.rb +++ b/test/uri/test_generic.rb @@ -799,8 +799,12 @@ def test_ipv6 u = URI("http://foo/bar") assert_equal("http://foo/bar", u.to_s) + u.hostname = "[::1]" + assert_equal("http://[::1]/bar", u.to_s) u.hostname = "::1" assert_equal("http://[::1]/bar", u.to_s) + u.hostname = "" + assert_equal("http:///bar", u.to_s) end def test_build diff --git a/test/uri/test_parser.rb b/test/uri/test_parser.rb index b13a26ca84edd2..03de137788b981 100644 --- a/test/uri/test_parser.rb +++ b/test/uri/test_parser.rb @@ -7,6 +7,11 @@ def uri_to_ary(uri) uri.class.component.collect {|c| uri.send(c)} end + def test_inspect + assert_match(/URI::RFC2396_Parser/, URI::Parser.new.inspect) + assert_match(/URI::RFC3986_Parser/, URI::RFC3986_Parser.new.inspect) + end + def test_compare url = 'http://a/b/c/d;p?q' u0 = URI.parse(url) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 3b8dd5474d05cf..ae13621f7eb174 100644 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -174,6 +174,7 @@ def sync_default_gems(gem) cp_r("#{upstream}/test/io/console", "test/io") mkdir_p("ext/io/console/lib") cp_r("#{upstream}/lib/io/console", "ext/io/console/lib") + rm_rf("ext/io/console/lib/console/ffi") cp_r("#{upstream}/io-console.gemspec", "ext/io/console") `git checkout ext/io/console/depend` when "io-nonblock" diff --git a/trace_point.rb b/trace_point.rb index d68eed42487be0..2e85369e65be00 100644 --- a/trace_point.rb +++ b/trace_point.rb @@ -309,15 +309,21 @@ def defined_class Primitive.tracepoint_attr_defined_class end - # Return the generated binding object from event + # Return the generated binding object from event. + # + # Note that for +c_call+ and +c_return+ events, the binding returned is the + # binding of the nearest Ruby method calling the C method, since C methods + # themselves do not have bindings. def binding Primitive.tracepoint_attr_binding end # Return the trace object during event # - # Same as TracePoint#binding: - # trace.binding.eval('self') + # Same as the following, except it returns the correct object (the method + # receiver) for +c_call+ and +c_return+ events: + # + # trace.binding.eval('self') def self Primitive.tracepoint_attr_self end diff --git a/transcode.c b/transcode.c index dcb85a532b8a99..505c8177fb3eb0 100644 --- a/transcode.c +++ b/transcode.c @@ -2514,7 +2514,7 @@ econv_opts(VALUE opt, int ecflags) break; case 3: - rb_warning(":newline option preceds other newline options"); + rb_warning(":newline option precedes other newline options"); break; } } diff --git a/version.h b/version.h index 3539a0260afcce..fe80071c4b2397 100644 --- a/version.h +++ b/version.h @@ -16,7 +16,7 @@ #define RUBY_RELEASE_YEAR 2021 #define RUBY_RELEASE_MONTH 4 -#define RUBY_RELEASE_DAY 21 +#define RUBY_RELEASE_DAY 28 #include "ruby/version.h" diff --git a/vm.c b/vm.c index 3edb174354cb59..79dacc1d8f3c8e 100644 --- a/vm.c +++ b/vm.c @@ -423,8 +423,6 @@ static const struct rb_callcache vm_empty_cc = { static void thread_free(void *ptr); -// - void rb_vm_inc_const_missing_count(void) { diff --git a/vm_insnhelper.c b/vm_insnhelper.c index a476a3a8ffee23..b10fa76b3304c2 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2269,11 +2269,6 @@ CALLER_REMOVE_EMPTY_KW_SPLAT(struct rb_control_frame_struct *restrict cfp, if (UNLIKELY(calling->kw_splat)) { /* This removes the last Hash object if it is empty. * So, vm_ci_flag(ci) & VM_CALL_KW_SPLAT is now inconsistent. - * However, you can use vm_ci_flag(ci) & VM_CALL_KW_SPLAT to - * determine whether a hash should be added back with - * warning (for backwards compatibility in cases where - * the method does not have the number of required - * arguments. */ if (RHASH_EMPTY_P(cfp->sp[-1])) { cfp->sp--; diff --git a/vm_method.c b/vm_method.c index fa00d4fd2e2a6c..1cf86fa6d5a2f5 100644 --- a/vm_method.c +++ b/vm_method.c @@ -997,7 +997,8 @@ search_method0(VALUE klass, ID id, VALUE *defined_class_ptr, bool skip_refined) for (; klass; klass = RCLASS_SUPER(klass)) { RB_DEBUG_COUNTER_INC(mc_search_super); if ((me = lookup_method_table(klass, id)) != 0) { - if (!skip_refined || me->def->type != VM_METHOD_TYPE_REFINED) { + if (!skip_refined || me->def->type != VM_METHOD_TYPE_REFINED || + me->def->body.refined.orig_me) { break; } } diff --git a/vm_trace.c b/vm_trace.c index 383f255799ae92..6e2c0587798b51 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -519,6 +519,10 @@ static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALU * line prog.rb:3 test Test * line prog.rb:4 test Test * return prog.rb:4 test Test + * + * Note that for +c-call+ and +c-return+ events, the binding returned is the + * binding of the nearest Ruby method calling the C method, since C methods + * themselves do not have bindings. */ static VALUE