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