diff options
76 files changed, 982 insertions, 825 deletions
@@ -185,19 +185,14 @@ define rp print (struct RBasic *)($arg0) else if ($flags & RUBY_T_MASK) == RUBY_T_DATA - if ($flags & RUBY_TYPED_FL_IS_TYPED_DATA) - set $data = (struct RTypedData *)($arg0) - set $type = (const rb_data_type_t *)($data->type & ~1) - printf "%sT_DATA%s(%s): ", $color_type, $color_end, $type->wrap_struct_name - print *$type - if ($data->type & 1) - print (void *)&$data->data - else - print $data - end + set $data = (struct RTypedData *)($arg0) + set $type = (const rb_data_type_t *)($data->type & ~1) + printf "%sT_DATA%s(%s): ", $color_type, $color_end, $type->wrap_struct_name + print *$type + if ($data->type & 1) + print (void *)&$data->data else - printf "%sT_DATA%s: ", $color_type, $color_end - print *(struct RData *)($arg0) + print $data end else if ($flags & RUBY_T_MASK) == RUBY_T_MATCH diff --git a/.github/workflows/check_sast.yml b/.github/workflows/check_sast.yml index 091f98ef34..6fd1be6542 100644 --- a/.github/workflows/check_sast.yml +++ b/.github/workflows/check_sast.yml @@ -92,7 +92,7 @@ jobs: output: sarif-results - name: filter-sarif - uses: advanced-security/filter-sarif@2da736ff05ef065cb2894ac6892e47b5eac2c3c0 # v1.1.0.1.1 + uses: advanced-security/filter-sarif@2da736ff05ef065cb2894ac6892e47b5eac2c3c0 # v1.1 with: patterns: | +**/*.rb @@ -100,6 +100,7 @@ jobs: -lib/uri/rfc3986_parser.rb:rb/overly-large-range -lib/bundler/vendor/uri/lib/uri/mailto.rb:rb/overly-large-range -lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb:rb/overly-large-range + -spec/ruby/core/regexp/timeout_spec.rb:rb/redos -test/ruby/test_io.rb:rb/non-constant-kernel-open -test/open-uri/test_open-uri.rb:rb/non-constant-kernel-open -test/open-uri/test_ssl.rb:rb/non-constant-kernel-open @@ -115,7 +116,7 @@ jobs: continue-on-error: true - name: filter-sarif - uses: advanced-security/filter-sarif@2da736ff05ef065cb2894ac6892e47b5eac2c3c0 # v1.1.0.1.1 + uses: advanced-security/filter-sarif@2da736ff05ef065cb2894ac6892e47b5eac2c3c0 # v1.1 with: patterns: | +**/*.c diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d355e4f4ab..501d35698b 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -26,25 +26,29 @@ jobs: matrix: include: - test_task: check - os: macos-14 + os: macos-26 - test_task: check - os: macos-14 + os: macos-15 configure_args: '--with-gcc=gcc-14' - test_task: check - os: macos-14 + os: macos-26 configure_args: '--with-jemalloc --with-opt-dir=$(brew --prefix jemalloc)' - test_task: check - os: macos-14 + os: macos-26 configure_args: '--with-gmp' - test_task: test-all test_opts: --repeat-count=2 - os: macos-14 + os: macos-26 - test_task: test-bundler-parallel - os: macos-14 + os: macos-26 - test_task: test-bundled-gems - os: macos-14 + os: macos-26 - test_task: check os: macos-15 + - test_task: check + os: macos-15-intel + - test_task: check + os: macos-14 fail-fast: false env: diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index f6aa445ad0..bd3c6ab575 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -28,7 +28,7 @@ jobs: - name: default - name: mmtk mmtk_build: release - os: [macos-latest, ubuntu-latest] + os: [macos-26, ubuntu-latest] include: - test_task: check fail-fast: false diff --git a/.github/workflows/tarball-macos.yml b/.github/workflows/tarball-macos.yml index 72c5a78c14..9bec94d528 100644 --- a/.github/workflows/tarball-macos.yml +++ b/.github/workflows/tarball-macos.yml @@ -12,13 +12,21 @@ on: required: false type: boolean default: false + secrets: + SIMPLER_ALERTS_URL: + required: false + SNAPSHOT_SLACK_WEBHOOK_URL: + required: false + +permissions: + contents: read jobs: macos: strategy: matrix: test_task: [check, test-bundled-gems, test-bundler-parallel] - os: [macos-14, macos-15] + os: [macos-26, macos-15, macos-14] include: - os: macos-15-intel test_task: check diff --git a/.github/workflows/tarball-non-development.yml b/.github/workflows/tarball-non-development.yml index 154d204a2c..db6230b301 100644 --- a/.github/workflows/tarball-non-development.yml +++ b/.github/workflows/tarball-non-development.yml @@ -3,6 +3,9 @@ name: tarball-non-development (reusable) on: workflow_call: {} +permissions: + contents: read + jobs: non_development: strategy: diff --git a/.github/workflows/tarball-test.yml b/.github/workflows/tarball-test.yml index e99c4515c8..52c4d31fc8 100644 --- a/.github/workflows/tarball-test.yml +++ b/.github/workflows/tarball-test.yml @@ -50,6 +50,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 # actions/checkout fetches all heads/tags unless > 0 + persist-credentials: false # tool/make-snapshot derives the branch name from HEAD and looks up # the upstream during ChangeLog generation. Detached checkouts # (pull_request, merge_group) lack a local branch with tracking, so @@ -73,7 +74,9 @@ jobs: with: archname: snapshot-${{ needs.tarball.outputs.branch }} notify-release-channel: ${{ github.event_name == 'workflow_dispatch' && inputs.notify-release-channel || false }} - secrets: inherit + secrets: + SIMPLER_ALERTS_URL: ${{ secrets.SIMPLER_ALERTS_URL }} + SNAPSHOT_SLACK_WEBHOOK_URL: ${{ secrets.SNAPSHOT_SLACK_WEBHOOK_URL }} macos: needs: tarball @@ -82,7 +85,9 @@ jobs: with: archname: snapshot-${{ needs.tarball.outputs.branch }} notify-release-channel: ${{ github.event_name == 'workflow_dispatch' && inputs.notify-release-channel || false }} - secrets: inherit + secrets: + SIMPLER_ALERTS_URL: ${{ secrets.SIMPLER_ALERTS_URL }} + SNAPSHOT_SLACK_WEBHOOK_URL: ${{ secrets.SNAPSHOT_SLACK_WEBHOOK_URL }} windows: needs: tarball @@ -91,9 +96,10 @@ jobs: with: archname: snapshot-${{ needs.tarball.outputs.branch }} notify-release-channel: ${{ github.event_name == 'workflow_dispatch' && inputs.notify-release-channel || false }} - secrets: inherit + secrets: + SIMPLER_ALERTS_URL: ${{ secrets.SIMPLER_ALERTS_URL }} + SNAPSHOT_SLACK_WEBHOOK_URL: ${{ secrets.SNAPSHOT_SLACK_WEBHOOK_URL }} non_development: needs: tarball uses: ./.github/workflows/tarball-non-development.yml - secrets: inherit diff --git a/.github/workflows/tarball-ubuntu.yml b/.github/workflows/tarball-ubuntu.yml index f0e773b526..03f2f946b5 100644 --- a/.github/workflows/tarball-ubuntu.yml +++ b/.github/workflows/tarball-ubuntu.yml @@ -12,6 +12,14 @@ on: required: false type: boolean default: false + secrets: + SIMPLER_ALERTS_URL: + required: false + SNAPSHOT_SLACK_WEBHOOK_URL: + required: false + +permissions: + contents: read jobs: ubuntu: diff --git a/.github/workflows/tarball-windows.yml b/.github/workflows/tarball-windows.yml index 1cd4ef454d..1ce95de6fc 100644 --- a/.github/workflows/tarball-windows.yml +++ b/.github/workflows/tarball-windows.yml @@ -12,6 +12,14 @@ on: required: false type: boolean default: false + secrets: + SIMPLER_ALERTS_URL: + required: false + SNAPSHOT_SLACK_WEBHOOK_URL: + required: false + +permissions: + contents: read jobs: windows: diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index 6e0827c8fe..b52c6355ad 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -30,7 +30,7 @@ jobs: cargo: name: cargo test - runs-on: macos-14 + runs-on: macos-26 if: >- ${{!(false @@ -74,7 +74,7 @@ jobs: RUN_OPTS: ${{ matrix.yjit_opts }} SPECOPTS: ${{ matrix.specopts }} - runs-on: macos-14 + runs-on: macos-26 if: >- ${{!(false @@ -134,7 +134,7 @@ jobs: id: launchable uses: ./.github/actions/launchable/setup with: - os: macos-14 + os: macos-26 test-opts: ${{ matrix.configure }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 50c7bc190a..f9282d64e8 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -58,7 +58,7 @@ jobs: RUST_BACKTRACE: 1 ZJIT_RB_BUG: 1 - runs-on: macos-14 + runs-on: macos-26 if: >- ${{!(false @@ -118,7 +118,7 @@ jobs: id: launchable uses: ./.github/actions/launchable/setup with: - os: macos-14 + os: macos-26 test-opts: ${{ matrix.configure }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build @@ -181,7 +181,7 @@ jobs: bench_opts: '--warmup=1 --bench=1 --excludes=shipit' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow - runs-on: macos-14 + runs-on: macos-26 if: >- ${{!(false diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 53112a2815..65b67fb6c8 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -13,6 +13,7 @@ rules: misfeature: ignore: - mingw.yml + - tarball-windows.yml - windows.yml unpinned-images: ignore: @@ -82,8 +82,8 @@ releases. * 6.0.1 to [v6.0.1.1][erb-v6.0.1.1], [v6.0.2][erb-v6.0.2], [v6.0.3][erb-v6.0.3], [v6.0.4][erb-v6.0.4] * ipaddr 1.2.9 * 1.2.8 to [v1.2.9][ipaddr-v1.2.9] -* json 2.19.6 - * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0], [v2.19.1][json-v2.19.1], [v2.19.2][json-v2.19.2], [v2.19.3][json-v2.19.3], [v2.19.4][json-v2.19.4], [v2.19.5][json-v2.19.5], [v2.19.6][json-v2.19.6] +* json 2.19.7 + * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0], [v2.19.1][json-v2.19.1], [v2.19.2][json-v2.19.2], [v2.19.3][json-v2.19.3], [v2.19.4][json-v2.19.4], [v2.19.5][json-v2.19.5], [v2.19.6][json-v2.19.6], [v2.19.7][json-v2.19.7] * openssl 4.0.2 * 4.0.0 to [v4.0.1][openssl-v4.0.1], [v4.0.2][openssl-v4.0.2] * prism 1.9.0 @@ -161,6 +161,19 @@ Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for det [[Feature #21861]] +### Removed APIs + +The following APIs, which have been deprecated for many years, are removed. +[[Feature #21768]] + +* old postponed job functions, +* untyped data object type/functions, +* old APIs to allocate a data object, +* taintedness/trustedness enums/macros, +* `rb_gc_force_recycle` function, +* `rb_iterate` function, +* and some functions and constants for internal use. + ## Implementation improvements ### Ractor @@ -172,6 +185,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #8948]: https://bugs.ruby-lang.org/issues/8948 [Feature #15330]: https://bugs.ruby-lang.org/issues/15330 [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 +[Feature #21768]: https://bugs.ruby-lang.org/issues/21768 [Feature #21785]: https://bugs.ruby-lang.org/issues/21785 [Feature #21796]: https://bugs.ruby-lang.org/issues/21796 [Feature #21853]: https://bugs.ruby-lang.org/issues/21853 @@ -209,6 +223,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [json-v2.19.4]: https://github.com/ruby/json/releases/tag/v2.19.4 [json-v2.19.5]: https://github.com/ruby/json/releases/tag/v2.19.5 [json-v2.19.6]: https://github.com/ruby/json/releases/tag/v2.19.6 +[json-v2.19.7]: https://github.com/ruby/json/releases/tag/v2.19.7 [openssl-v4.0.1]: https://github.com/ruby/openssl/releases/tag/v4.0.1 [openssl-v4.0.2]: https://github.com/ruby/openssl/releases/tag/v4.0.2 [prism-v1.8.0]: https://github.com/ruby/prism/releases/tag/v1.8.0 @@ -30,6 +30,7 @@ #include "ruby/thread.h" #include "ruby/util.h" #include "ruby/ractor.h" +#include "shape.h" #include "vm_core.h" #include "builtin.h" @@ -909,6 +910,7 @@ init_fake_ary_flags(void) struct RArray fake_ary = {0}; fake_ary.basic.flags = T_ARRAY; VALUE ary = (VALUE)&fake_ary; + RBASIC_SET_SHAPE_ID(ary, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); rb_ary_freeze(ary); return fake_ary.basic.flags; } @@ -585,7 +585,15 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) VALUE flags = type | FL_SHAREABLE; if (boxable) flags |= RCLASS_BOXABLE; - NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size); + shape_id_t shape_id = ROOT_SHAPE_ID; + if (boxable) { + shape_id |= SHAPE_ID_LAYOUT_OTHER; + } + else { + shape_id |= SHAPE_ID_LAYOUT_RCLASS; + } + + struct RClass *obj = (struct RClass *)rb_newobj(GET_EC(), klass, flags, shape_id, true, alloc_size); obj->object_id = 0; @@ -80,6 +80,7 @@ array.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h array.$(OBJEXT): $(top_srcdir)/internal/serial.h array.$(OBJEXT): $(top_srcdir)/internal/set_table.h array.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +array.$(OBJEXT): $(top_srcdir)/internal/struct.h array.$(OBJEXT): $(top_srcdir)/internal/variable.h array.$(OBJEXT): $(top_srcdir)/internal/vm.h array.$(OBJEXT): $(top_srcdir)/internal/warnings.h diff --git a/encoding.c b/encoding.c index 8bb393b471..c17f118eef 100644 --- a/encoding.c +++ b/encoding.c @@ -98,6 +98,7 @@ static rb_encoding *global_enc_ascii, *global_enc_us_ascii; static int filesystem_encindex = ENCINDEX_ASCII_8BIT; +static rb_atomic_t locale_alias_registered; #define GLOBAL_ENC_TABLE_LOCKING(tbl) \ for (struct enc_table *tbl = &global_enc_table, **locking = &tbl; \ @@ -126,7 +127,7 @@ static const rb_data_type_t encoding_data_type = { }; #define is_encoding_type(obj) (RTYPEDDATA_TYPE(obj) == &encoding_data_type) -#define is_data_encoding(obj) (rbimpl_rtypeddata_p(obj) && is_encoding_type(obj)) +#define is_data_encoding(obj) is_encoding_type(obj) #define is_obj_encoding(obj) (rbimpl_obj_typeddata_p(obj) && is_encoding_type(obj)) int @@ -1568,15 +1569,16 @@ rb_locale_encindex(void) if (idx < 0) idx = ENCINDEX_UTF_8; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (enc_registered(enc_table, "locale") < 0) { + if (!RUBY_ATOMIC_LOAD(locale_alias_registered)) { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (enc_registered(enc_table, "locale") < 0) { # if defined _WIN32 - void Init_w32_codepage(void); - Init_w32_codepage(); + void Init_w32_codepage(void); + Init_w32_codepage(); # endif - GLOBAL_ENC_TABLE_LOCKING(enc_table) { enc_alias_internal(enc_table, "locale", idx); } + RUBY_ATOMIC_SET(locale_alias_registered, 1); } } @@ -1364,8 +1364,7 @@ rb_check_type(VALUE x, int t) rb_bug(UNDEF_LEAKED); } - xt = TYPE(x); - if (xt != t || (xt == T_DATA && rbimpl_rtypeddata_p(x))) { + if (t == T_DATA) { /* * Typed data is not simple `T_DATA`, but in a sense an * extension of `struct RVALUE`, which are incompatible with @@ -1374,6 +1373,10 @@ rb_check_type(VALUE x, int t) * So it is not enough to just check `T_DATA`, it must be * identified by its `type` using `Check_TypedStruct` instead. */ + rb_unexpected_object_type(x, builtin_types[t]); + } + xt = TYPE(x); + if (xt != t) { unexpected_type(x, xt, t); } } diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 6295264a3d..72d697c8ea 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -27,7 +27,7 @@ static VALUE eDateError; static VALUE half_days_in_day, day_in_nanoseconds; static double positive_inf, negative_inf; -// used by deconstruct_keys +/* used by deconstruct_keys */ static VALUE sym_year, sym_month, sym_day, sym_yday, sym_wday; static VALUE sym_hour, sym_min, sym_sec, sym_sec_fraction, sym_zone; @@ -4528,6 +4528,7 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, rb_scan_args(argc, argv, "11", &vstr, &vfmt); StringValue(vstr); + if (argc > 1) StringValue(vfmt); if (!rb_enc_str_asciicompat_p(vstr)) rb_raise(rb_eArgError, "string should have ASCII compatible encoding"); @@ -4538,7 +4539,6 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, flen = strlen(default_fmt); } else { - StringValue(vfmt); if (!rb_enc_str_asciicompat_p(vfmt)) rb_raise(rb_eArgError, "format should have ASCII compatible encoding"); diff --git a/ext/date/date_strptime.c b/ext/date/date_strptime.c index f1c8201de8..1dde5fa3ec 100644 --- a/ext/date/date_strptime.c +++ b/ext/date/date_strptime.c @@ -661,6 +661,9 @@ date__strptime(const char *str, size_t slen, si = date__strptime_internal(str, slen, fmt, flen, hash); + if (fail_p()) + return Qnil; + if (slen > si) { VALUE s; @@ -668,9 +671,6 @@ date__strptime(const char *str, size_t slen, set_hash("leftover", s); } - if (fail_p()) - return Qnil; - cent = del_hash("_cent"); if (!NIL_P(cent)) { VALUE year; diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 9260712c9a..110b5f6b32 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1581,7 +1581,7 @@ static VALUE cState_max_nesting(VALUE self) static long long_config(VALUE num) { - return RTEST(num) ? FIX2LONG(num) : 0; + return RTEST(num) ? NUM2LONG(num) : 0; } // depth must never be negative; reject early with a clear error. diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 363f109d3a..503bed1fd4 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -385,6 +385,13 @@ static inline char peek(JSON_ParserState *state) static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out) { + JSON_ASSERT(state->cursor <= state->end); + + // Redundant but helpful for hardening + if (RB_UNLIKELY(state->cursor > state->end)) { + state->cursor = state->end; + } + const char *cursor = state->cursor; long column = 0; long line = 1; @@ -1022,6 +1029,13 @@ ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state) } state->cursor++; } + + // If the string ended with an unterminated escape sequence, we might + // have gone past the end. + if (RB_UNLIKELY(state->cursor > state->end)) { + state->cursor = state->end; + } + return false; } diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index b644c489b8..68ada01af3 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -573,7 +573,7 @@ dump_object(VALUE obj, struct dump_config *dc) break; case T_DATA: - if (RTYPEDDATA_P(obj)) { + { const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); dump_append(dc, ", \"struct\":\""); dump_append(dc, type->wrap_struct_name); diff --git a/ext/openssl/lib/openssl/digest.rb b/ext/openssl/lib/openssl/digest.rb index 46ddfd6021..4e6dea8d03 100644 --- a/ext/openssl/lib/openssl/digest.rb +++ b/ext/openssl/lib/openssl/digest.rb @@ -27,17 +27,21 @@ module OpenSSL end %w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512).each do |name| - klass = Class.new(self) { - define_method(:initialize, ->(data = nil) {super(name, data)}) - } - - singleton = (class << klass; self; end) - - singleton.class_eval{ - define_method(:digest) {|data| new.digest(data)} - define_method(:hexdigest) {|data| new.hexdigest(data)} - } + klass = Class.new(self) + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def initialize(data = nil) + super("#{name}", data) + end + RUBY + klass.singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def digest(data) + new.digest(data) + end + def hexdigest(data) + new.hexdigest(data) + end + RUBY const_set(name.tr('-', '_'), klass) end diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index f70b7f6cf9..99a3589b39 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -39,6 +39,10 @@ pbkdf2_hmac_nogvl(void *args_) * For more information about PBKDF2, see RFC 2898 Section 5.2 * (https://www.rfc-editor.org/rfc/rfc2898#section-5.2). * + * *NOTE*: This method cannot be interrupted by Timeout.timeout from the + * "timeout" library. Do not take parameters from untrusted sources without + * enforcing reasonable limits. + * * === Parameters * pass :: The password. * salt :: The salt. Salts prevent attacks based on dictionaries of common @@ -143,6 +147,10 @@ scrypt_nogvl(void *args_) * * See RFC 7914 (https://www.rfc-editor.org/rfc/rfc7914) for more information. * + * *NOTE*: This method cannot be interrupted by Timeout.timeout from the + * "timeout" library. Do not take parameters from untrusted sources without + * enforcing reasonable limits. + * * === Parameters * pass :: Passphrase. * salt :: Salt. diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c index 32b82a881c..a47c81354c 100644 --- a/ext/openssl/ossl_pkcs12.c +++ b/ext/openssl/ossl_pkcs12.c @@ -191,6 +191,7 @@ ossl_x509_sk2ary_i(VALUE arg) static VALUE ossl_pkcs12_initialize(int argc, VALUE *argv, VALUE self) { + PKCS12 *p12, *p12_orig = DATA_PTR(self); BIO *in; VALUE arg, pass, pkey, cert, ca; char *passphrase; @@ -198,17 +199,19 @@ ossl_pkcs12_initialize(int argc, VALUE *argv, VALUE self) X509 *x509; STACK_OF(X509) *x509s = NULL; int st = 0; - PKCS12 *pkcs = DATA_PTR(self); if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) return self; passphrase = NIL_P(pass) ? NULL : StringValueCStr(pass); in = ossl_obj2bio(&arg); - d2i_PKCS12_bio(in, &pkcs); - DATA_PTR(self) = pkcs; + p12 = d2i_PKCS12_bio(in, NULL); BIO_free(in); + if (!p12) + ossl_raise(ePKCS12Error, "d2i_PKCS12_bio"); + PKCS12_free(p12_orig); + RTYPEDDATA_DATA(self) = p12; pkey = cert = ca = Qnil; - if(!PKCS12_parse(pkcs, passphrase, &key, &x509, &x509s)) + if (!PKCS12_parse(p12, passphrase, &key, &x509, &x509s)) ossl_raise(ePKCS12Error, "PKCS12_parse"); if (key) { pkey = rb_protect(ossl_pkey_wrap_i, (VALUE)key, &st); diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 3cf5820c36..44e8cb305b 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -453,7 +453,7 @@ ossl_pkcs7_sym2typeid(VALUE sym) if(i == numberof(p7_type_tab)) ossl_raise(ePKCS7Error, "unknown type \"%"PRIsVALUE"\"", sym); if(strlen(p7_type_tab[i].name) != l) continue; - if(strcmp(p7_type_tab[i].name, s) == 0){ + if(memcmp(p7_type_tab[i].name, s, l) == 0){ ret = p7_type_tab[i].nid; break; } diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index 393e08acff..317f7786aa 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -168,7 +168,7 @@ ossl_ts_req_alloc(VALUE klass) static VALUE ossl_ts_req_initialize(int argc, VALUE *argv, VALUE self) { - TS_REQ *ts_req = DATA_PTR(self); + TS_REQ *req, *req_orig = DATA_PTR(self); BIO *in; VALUE arg; @@ -178,13 +178,14 @@ ossl_ts_req_initialize(int argc, VALUE *argv, VALUE self) arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); - ts_req = d2i_TS_REQ_bio(in, &ts_req); + req = d2i_TS_REQ_bio(in, NULL); BIO_free(in); - if (!ts_req) { - DATA_PTR(self) = NULL; - ossl_raise(eTimestampError, "Error when decoding the timestamp request"); + if (!req) { + ossl_raise(eTimestampError, + "Error when decoding the timestamp request"); } - DATA_PTR(self) = ts_req; + TS_REQ_free(req_orig); + RTYPEDDATA_DATA(self) = req; return self; } @@ -522,18 +523,19 @@ ossl_ts_resp_alloc(VALUE klass) static VALUE ossl_ts_resp_initialize(VALUE self, VALUE der) { - TS_RESP *ts_resp = DATA_PTR(self); + TS_RESP *resp, *resp_orig = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); - in = ossl_obj2bio(&der); - ts_resp = d2i_TS_RESP_bio(in, &ts_resp); + in = ossl_obj2bio(&der); + resp = d2i_TS_RESP_bio(in, NULL); BIO_free(in); - if (!ts_resp) { - DATA_PTR(self) = NULL; - ossl_raise(eTimestampError, "Error when decoding the timestamp response"); + if (!resp) { + ossl_raise(eTimestampError, + "Error when decoding the timestamp response"); } - DATA_PTR(self) = ts_resp; + TS_RESP_free(resp_orig); + RTYPEDDATA_DATA(self) = resp; return self; } @@ -876,18 +878,19 @@ ossl_ts_token_info_alloc(VALUE klass) static VALUE ossl_ts_token_info_initialize(VALUE self, VALUE der) { - TS_TST_INFO *info = DATA_PTR(self); + TS_TST_INFO *info, *info_orig = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); - in = ossl_obj2bio(&der); - info = d2i_TS_TST_INFO_bio(in, &info); + in = ossl_obj2bio(&der); + info = d2i_TS_TST_INFO_bio(in, NULL); BIO_free(in); if (!info) { - DATA_PTR(self) = NULL; - ossl_raise(eTimestampError, "Error when decoding the timestamp token info"); + ossl_raise(eTimestampError, + "Error when decoding the timestamp token info"); } - DATA_PTR(self) = info; + TS_TST_INFO_free(info_orig); + RTYPEDDATA_DATA(self) = info; return self; } @@ -4479,31 +4479,43 @@ rb_file_s_expand_path(int argc, const VALUE *argv) } /* + * :markup: markdown + * * call-seq: - * File.expand_path(file_name [, dir_string] ) -> abs_file_name + * File.expand_path(path, dirpath = '.') -> absolute_path * - * Converts a pathname to an absolute pathname. Relative paths are - * referenced from the current working directory of the process unless - * +dir_string+ is given, in which case it will be used as the - * starting point. The given pathname may start with a - * ``<code>~</code>'', which expands to the process owner's home - * directory (the environment variable +HOME+ must be set - * correctly). ``<code>~</code><i>user</i>'' expands to the named - * user's home directory. + * Returns the string absolute path for the given `path`. * - * File.expand_path("~oracle/bin") #=> "/home/oracle/bin" + * Evaluates a relative path with respect to the directory given by `dirpath`: * - * A simple example of using +dir_string+ is as follows. - * File.expand_path("ruby", "/usr/bin") #=> "/usr/bin/ruby" + * ```ruby + * Dir.chdir('/snap') + * # Default dirpath. + * File.expand_path('README') # => "/snap/README" + * File.expand_path('bin') # => "/snap/bin" + * File.expand_path('bin/../var') # => "/snap/var" # Cleaned. + * # Other dirpath. + * File.expand_path('../zip', '/usr/bin/ruby') # => "/usr/bin/zip" + * Dir.chdir('/usr/bin') + * File.expand_path('../../snap', __FILE__) # => "/usr/snap" + * ``` * - * A more complex example which also resolves parent directory is as follows. - * Suppose we are in bin/mygem and want the absolute path of lib/mygem.rb. + * Evaluates an absolute path without respect to `dirpath`: * - * File.expand_path("../../lib/mygem.rb", __FILE__) - * #=> ".../path/to/project/lib/mygem.rb" + * ```ruby + * File.expand_path('/snap') # => "/snap" + * File.expand_path('/snap', 'nosuch') # => "/snap" + * File.expand_path('/snap/../snap') # => "/snap" # Cleaned. + * ``` + * + * More examples: + * + * ``` + * Dir.chdir('/usr/bin') + * File.expand_path('../../snap', __FILE__) # => "/usr/snap" + * File.expand_path('../../snap') # => "/snap" + * ``` * - * So first it resolves the parent of __FILE__, that is bin/, then go to the - * parent, the root of the project and appends +lib/mygem.rb+. */ static VALUE @@ -5318,28 +5330,42 @@ ruby_enc_find_extname(const char *name, long *len, rb_encoding *enc) } /* + * :markup: markdown + * * call-seq: - * File.extname(path) -> string + * File.extname(path) -> extension + * + * Returns the filename extension -- + * usually the portion of the string `path` + * beginning from the last period: + * + * ```ruby + * File.extname('t.rb') # => ".rb" + * File.extname('foo.bar.t.rb') # => ".rb" + * File.extname('foo/bar/t.rb') # => ".rb" + * File.extname('nosuch.txt') # => ".txt" # Path need not exist. + * ``` + * + * Returns the entire string when there is no period: * - * Returns the extension (the portion of file name in +path+ - * starting from the last period). + * ```ruby + * Pathname('foo').extname # => "" + * ``` * - * If +path+ is a dotfile, or starts with a period, then the starting - * dot is not dealt with the start of the extension. + * Returns an empty string when the only period is the first character: * - * An empty string will also be returned when the period is the last character - * in +path+. + * ```ruby + * File.extname('.irbrc') # => "" + * ``` * - * On Windows, trailing dots are truncated. + * Returns an empty string or `'.'` when `path` ends with a period: * - * File.extname("test.rb") #=> ".rb" - * File.extname("a/b/d/test.rb") #=> ".rb" - * File.extname(".a/b/d/test.rb") #=> ".rb" - * File.extname("foo.") #=> "" on Windows - * File.extname("foo.") #=> "." on non-Windows - * File.extname("test") #=> "" - * File.extname(".profile") #=> "" - * File.extname(".profile.sh") #=> ".sh" + * ``` + * File.extname('foo.') # => "" # On Windows. + * File.extname('foo.') # => "." # Elsewhere. + * File.extname('foo....') # => "" # On Windows. + * File.extname('foo....') # => "." # Elsewhere. + * ``` * */ @@ -337,8 +337,10 @@ rb_gc_shutdown_call_finalizer_p(VALUE obj) { switch (BUILTIN_TYPE(obj)) { case T_DATA: - if (!ruby_free_at_exit_p() && !DATA_PTR(obj)) return false; - if (!ruby_free_at_exit_p() && !RTYPEDDATA_P(obj) && !RDATA(obj)->dfree) return false; + if (!ruby_free_at_exit_p()) { + if (!RDATA(obj)->type) return false; + if (!rbimpl_typeddata_embedded_p(obj) && !RTYPEDDATA(obj)->data) return false; + } if (rb_obj_is_thread(obj)) return false; if (rb_obj_is_mutex(obj)) return false; if (rb_obj_is_fiber(obj)) return false; @@ -374,7 +376,6 @@ void rb_vm_update_references(void *ptr); #define rb_setjmp(env) RUBY_SETJMP(env) #define rb_jmp_buf rb_jmpbuf_t -#undef rb_data_object_wrap #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) #define MAP_ANONYMOUS MAP_ANON @@ -1055,7 +1056,15 @@ rb_newobj(rb_execution_context_t *ec, VALUE klass, VALUE flags, shape_id_t shape VALUE rb_ec_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, size_t size) { - return rb_newobj(ec, klass, flags, ROOT_SHAPE_ID, true, size); + VALUE type = flags & T_MASK; + RUBY_ASSERT(type != T_OBJECT); + RUBY_ASSERT(type != T_DATA); + RUBY_ASSERT(type != T_CLASS); + RUBY_ASSERT(type != T_MODULE); + RUBY_ASSERT(type != T_ICLASS); + (void)type; + + return rb_newobj(ec, klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, true, size); } VALUE @@ -1067,13 +1076,13 @@ rb_newobj_of_with_shape(VALUE klass, VALUE flags, shape_id_t shape_id, size_t si VALUE rb_newobj_of(VALUE klass, VALUE flags, size_t size) { - return rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID, true, size); + return rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, true, size); } static VALUE class_allocate_complex_instance(VALUE klass, uint32_t capacity) { - shape_id_t initial_shape_id = rb_shape_root(rb_gc_heap_id_for_size(sizeof(struct RObject))); + shape_id_t initial_shape_id = rb_shape_id_with_robject_layout(rb_shape_root(rb_gc_heap_id_for_size(sizeof(struct RObject)))); VALUE obj = rb_newobj_of_with_shape(klass, T_OBJECT, initial_shape_id, sizeof(struct RObject)); rb_obj_init_complex(obj, rb_st_init_numtable_with_size(capacity)); return obj; @@ -1098,7 +1107,7 @@ rb_class_allocate_instance(VALUE klass) // There might be a NEWOBJ tracepoint callback, and it may set fields. // So the shape must be passed to `NEWOBJ_OF`. - obj = rb_newobj_of_with_shape(klass, T_OBJECT, rb_shape_root(rb_gc_heap_id_for_size(size)), size); + obj = rb_newobj_of_with_shape(klass, T_OBJECT, rb_shape_id_with_robject_layout(rb_shape_root(rb_gc_heap_id_for_size(size))), size); #if RUBY_DEBUG VALUE *ptr = ROBJECT_FIELDS(obj); @@ -1138,33 +1147,6 @@ rb_data_object_check(VALUE klass) } } -VALUE -rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree) -{ - RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1); - if (klass) rb_data_object_check(klass); - VALUE obj = rb_newobj(GET_EC(), klass, T_DATA, ROOT_SHAPE_ID, !dmark, sizeof(struct RData)); - - rb_gc_register_pinning_obj(obj); - - struct RData *data = (struct RData *)obj; - data->fields_obj = 0; - data->_reserved = 0; - data->data = datap; - data->dmark = dmark; - data->dfree = dfree; - - return obj; -} - -VALUE -rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree) -{ - VALUE obj = rb_data_object_wrap(klass, 0, dmark, dfree); - DATA_PTR(obj) = xcalloc(1, size); - return obj; -} - #define RTYPEDDATA_EMBEDDED_P rbimpl_typeddata_embedded_p #define RB_DATA_TYPE_EMBEDDABLE_P(type) ((type)->flags & RUBY_TYPED_EMBEDDABLE) #define RTYPEDDATA_EMBEDDABLE_P(obj) RB_DATA_TYPE_EMBEDDABLE_P(RTYPEDDATA_TYPE(obj)) @@ -1175,7 +1157,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_ RBIMPL_NONNULL_ARG(type); if (klass) rb_data_object_check(klass); bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark; - VALUE obj = rb_newobj(GET_EC(), klass, T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, ROOT_SHAPE_ID, wb_protected, size); + VALUE obj = rb_newobj(GET_EC(), klass, T_DATA, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_RDATA, wb_protected, size); rb_gc_register_pinning_obj(obj); @@ -1237,18 +1219,16 @@ static size_t rb_objspace_data_type_memsize(VALUE obj) { size_t size = 0; - if (RTYPEDDATA_P(obj)) { - const void *ptr = RTYPEDDATA_GET_DATA(obj); + const void *ptr = RTYPEDDATA_GET_DATA(obj); - if (ptr) { - if (RTYPEDDATA_EMBEDDABLE_P(obj) && !RTYPEDDATA_EMBEDDED_P(obj)) { - size += ruby_xmalloc_usable_size((void *)ptr); - } + if (ptr) { + if (RTYPEDDATA_EMBEDDABLE_P(obj) && !RTYPEDDATA_EMBEDDED_P(obj)) { + size += ruby_xmalloc_usable_size((void *)ptr); + } - const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); - if (type->function.dsize) { - size += type->function.dsize(ptr); - } + const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); + if (type->function.dsize) { + size += type->function.dsize(ptr); } } @@ -1258,12 +1238,7 @@ rb_objspace_data_type_memsize(VALUE obj) const char * rb_objspace_data_type_name(VALUE obj) { - if (RTYPEDDATA_P(obj)) { - return RTYPEDDATA_TYPE(obj)->wrap_struct_name; - } - else { - return 0; - } + return RTYPEDDATA_TYPE(obj)->wrap_struct_name; } void @@ -1285,7 +1260,7 @@ rb_gc_handle_weak_references(VALUE obj) { switch (BUILTIN_TYPE(obj)) { case T_DATA: - if (RTYPEDDATA_P(obj)) { + { const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); if (type->function.handle_weak_references) { @@ -1298,9 +1273,6 @@ rb_gc_handle_weak_references(VALUE obj) ); } } - else { - rb_bug("rb_gc_handle_weak_references: unknown T_DATA"); - } break; case T_IMEMO: { @@ -1423,7 +1395,7 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) return false; case T_DATA: - if (flags & RUBY_TYPED_FL_IS_TYPED_DATA) { + { uintptr_t type = (uintptr_t)RTYPEDDATA(obj)->type; if (type & TYPED_DATA_EMBEDDED) { RUBY_DATA_FUNC dfree = ((const rb_data_type_t *)(type & TYPED_DATA_PTR_MASK))->function.dfree; @@ -1485,22 +1457,17 @@ make_io_zombie(void *objspace, VALUE obj) static bool rb_data_free(void *objspace, VALUE obj) { - void *data = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj); + void *data = RTYPEDDATA_GET_DATA(obj); if (data) { int free_immediately = false; void (*dfree)(void *); - if (RTYPEDDATA_P(obj)) { - free_immediately = (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0; - dfree = RTYPEDDATA_TYPE(obj)->function.dfree; - } - else { - dfree = RDATA(obj)->dfree; - } + free_immediately = (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0; + dfree = RTYPEDDATA_TYPE(obj)->function.dfree; if (dfree) { if (dfree == RUBY_DEFAULT_FREE) { - if (!RTYPEDDATA_P(obj) || !RTYPEDDATA_EMBEDDED_P(obj)) { + if (!RTYPEDDATA_EMBEDDED_P(obj)) { xfree(data); RB_DEBUG_COUNTER_INC(obj_data_xfree); } @@ -3512,13 +3479,12 @@ rb_gc_mark_children(void *objspace, VALUE obj) break; case T_DATA: { - bool typed_data = RTYPEDDATA_P(obj); - void *const ptr = typed_data ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj); + void *const ptr = RTYPEDDATA_GET_DATA(obj); gc_mark_internal(RTYPEDDATA(obj)->fields_obj); if (ptr) { - if (typed_data && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) { + if (gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) { size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj); for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) { @@ -3526,9 +3492,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) } } else { - RUBY_DATA_FUNC mark_func = typed_data ? - RTYPEDDATA_TYPE(obj)->function.dmark : - RDATA(obj)->dmark; + RUBY_DATA_FUNC mark_func = RTYPEDDATA_TYPE(obj)->function.dmark; if (mark_func) (*mark_func)(ptr); } } @@ -4233,7 +4197,11 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data) break; case ST_DELETE: - RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID); + // When we're removing an object from the weak ref table, we need to + // set the shape on it so that the GC finalizer won't try to remove + // it again. A "root shape" indicates to the GC that this object + // has no fields on it, hence it won't be in the gen fields table. + RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); return ST_DELETE; case ST_REPLACE: { @@ -4460,13 +4428,12 @@ rb_gc_update_object_references(void *objspace, VALUE obj) case T_DATA: /* Call the compaction callback, if it exists */ { - bool typed_data = RTYPEDDATA_P(obj); - void *const ptr = typed_data ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj); + void *const ptr = RTYPEDDATA_GET_DATA(obj); UPDATE_IF_MOVED(objspace, RTYPEDDATA(obj)->fields_obj); if (ptr) { - if (typed_data && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) { + if (gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) { size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj); for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) { @@ -4474,7 +4441,7 @@ rb_gc_update_object_references(void *objspace, VALUE obj) *ref = gc_location_internal(objspace, *ref); } } - else if (typed_data) { + else { RUBY_DATA_FUNC compact_func = RTYPEDDATA_TYPE(obj)->function.dcompact; if (compact_func) (*compact_func)(ptr); } @@ -528,7 +528,7 @@ hash_st_table_init(VALUE hash, const struct st_hash_type *type, st_index_t size) RHASH_SET_ST_FLAG(hash); } -void +static void rb_hash_st_table_set(VALUE hash, st_table *st) { HASH_ASSERT(st != NULL); @@ -730,7 +730,7 @@ ar_force_convert_table(VALUE hash, const char *file, int line) st_init_existing_table_with_size(new_tab, &objhash, size); ar_each_key(ar, bound, ar_each_key_insert, NULL, new_tab, hashes); hash_ar_free_and_clear_table(hash); - RHASH_ST_TABLE_SET(hash, new_tab); + rb_hash_st_table_set(hash, new_tab); return RHASH_ST_TABLE(hash); } } @@ -1986,7 +1986,7 @@ rb_hash_rehash(VALUE hash) rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tmp); hash_st_free(hash); - RHASH_ST_TABLE_SET(hash, tbl); + rb_hash_st_table_set(hash, tbl); RHASH_ST_CLEAR(tmp); } hash_verify(hash); @@ -4669,7 +4669,7 @@ rb_hash_compare_by_id(VALUE hash) // We know for sure `identtable` is an st table, // so we can skip `ar_force_convert_table` here. - RHASH_ST_TABLE_SET(hash, identtable); + rb_hash_st_table_set(hash, identtable); RHASH_ST_CLEAR(tmp); } @@ -141,7 +141,11 @@ rb_imemo_fields_new(VALUE owner, shape_id_t shape_id, bool shareable) size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE); RUBY_ASSERT(rb_gc_size_allocatable_p(embedded_size)); VALUE fields = rb_imemo_new(imemo_fields, owner, embedded_size, shareable); - RBASIC_SET_SHAPE_ID(fields, shape_id); + // imemo fields objects should always have "RObject" layout. The + // layout in the shape describes the layout of the thing on which it is set. + // Imemo fields have the same layout as robject, therefore the layout + // should reflect that fact. + RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields)); return fields; } @@ -152,7 +156,11 @@ rb_imemo_fields_new_complex(VALUE owner, shape_id_t shape_id, size_t capa, bool VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa); FL_SET_RAW(fields, OBJ_FIELD_HEAP); - RBASIC_SET_SHAPE_ID(fields, shape_id); + // imemo fields objects should always have "RObject" layout. The + // layout in the shape describes the layout of the thing on which it is set. + // Imemo fields have the same layout as robject, therefore the layout + // should reflect that fact. + RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); return fields; } @@ -177,7 +185,11 @@ rb_imemo_fields_new_complex_tbl(VALUE owner, shape_id_t shape_id, st_table *tbl, VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl; FL_SET_RAW(fields, OBJ_FIELD_HEAP); - RBASIC_SET_SHAPE_ID(fields, shape_id); + // imemo fields objects should always have "RObject" layout. The + // layout in the shape describes the layout of the thing on which it is set. + // Imemo fields have the same layout as robject, therefore the layout + // should reflect that fact. + RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields); return fields; } diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index 0c99d93bf9..7ceb8c40b7 100644 --- a/include/ruby/internal/abi.h +++ b/include/ruby/internal/abi.h @@ -24,7 +24,7 @@ * In released versions of Ruby, this number is not defined since teeny * versions of Ruby should guarantee ABI compatibility. */ -#define RUBY_ABI_VERSION 2 +#define RUBY_ABI_VERSION 3 /* Windows does not support weak symbols so ruby_abi_version will not exist * in the shared library. */ diff --git a/include/ruby/internal/core/rdata.h b/include/ruby/internal/core/rdata.h index 5ebeb2473e..d0a4dc7c83 100644 --- a/include/ruby/internal/core/rdata.h +++ b/include/ruby/internal/core/rdata.h @@ -42,31 +42,9 @@ #endif #define RBIMPL_DATA_FUNC(f) RBIMPL_CAST((void (*)(void *))(f)) -#define RBIMPL_ATTRSET_UNTYPED_DATA_FUNC() \ - RBIMPL_ATTR_WARNING(("untyped Data is unsafe; use TypedData instead")) \ - RBIMPL_ATTR_DEPRECATED(("by TypedData")) - -#define RBIMPL_MACRO_SELECT(x, y) x ## y -#define RUBY_MACRO_SELECT(x, y) RBIMPL_MACRO_SELECT(x, y) /** @endcond */ /** - * Convenient casting macro. - * - * @param obj An object, which is in fact an ::RData. - * @return The passed object casted to ::RData. - */ -#define RDATA(obj) RBIMPL_CAST((struct RData *)(obj)) - -/** - * Convenient getter macro. - * - * @param obj An object, which is in fact an ::RData. - * @return The passed object's ::RData::data field. - */ -#define DATA_PTR(obj) RDATA(obj)->data - -/** * This is a value you can set to ::RData::dfree. Setting this means the data * was allocated using ::ruby_xmalloc() (or variants), and shall be freed using * ::ruby_xfree(). @@ -85,19 +63,6 @@ #define RUBY_NEVER_FREE RBIMPL_DATA_FUNC(0) /** - * @private - * - * @deprecated This macro once was a thing in the old days, but makes no sense - * any longer today. Exists here for backwards compatibility - * only. You can safely forget about it. - */ -#define RUBY_UNTYPED_DATA_FUNC(f) f RBIMPL_ATTRSET_UNTYPED_DATA_FUNC() - -/* -#define RUBY_DATA_FUNC(func) ((void (*)(void*))(func)) -*/ - -/** * This is the type of callbacks registered to ::RData. The argument is the * `data` field. */ @@ -106,264 +71,16 @@ typedef void (*RUBY_DATA_FUNC)(void*); /** * @deprecated * - * Old "untyped" user data. It has roughly the same usage as struct - * ::RTypedData, but lacked several features such as support for compaction GC. - * Use of this struct is not recommended any longer. If it is dead necessary, - * please inform the core devs about your usage. - * - * @internal - * - * @shyouhei tried to add RBIMPL_ATTR_DEPRECATED for this type but that yielded - * too many warnings in the core. Maybe we want to retry later... Just add - * deprecated document for now. - * - * RData shares its initial fields with struct ::RTypedData so the VM can handle - * per-object fields without checking whether a T_DATA object is typed or legacy. + * DO NOT USE: Obsolete "untyped" user data, that is remained only for + * the backward compatibility for some wrapper generator gems. */ struct RData { - - /** Basic part, including flags and class. */ struct RBasic basic; - - /** @internal Direct reference to the slots that hold instance variables, if any. */ VALUE fields_obj; - - /** @internal Padding where ::RTypedData stores its type, so both structs place data at the same offset. */ VALUE _reserved; - - /** Pointer to the actual C level struct that you want to wrap. */ void *data; - - /** - * This function is called when the object is experiencing GC marks. If it - * contains references to other Ruby objects, you need to mark them also. - * Otherwise GC will smash your data. - * - * @see rb_gc_mark() - * @warning This is called during GC runs. Object allocations are - * impossible at that moment (that is why GC runs). - */ RUBY_DATA_FUNC dmark; - - /** - * This function is called when the object is no longer used. You need to - * do whatever necessary to avoid memory leaks. - * - * @warning This is called during GC runs. Object allocations are - * impossible at that moment (that is why GC runs). - */ RUBY_DATA_FUNC dfree; }; -RBIMPL_SYMBOL_EXPORT_BEGIN() - -/** - * This is the primitive way to wrap an existing C struct into ::RData. - * - * @param[in] klass Ruby level class of the returning object. - * @param[in] datap Pointer to the target C struct. - * @param[in] dmark Mark function. - * @param[in] dfree Free function. - * @exception rb_eTypeError `klass` is not a class. - * @exception rb_eNoMemError Out of memory. - * @return An allocated object that wraps `datap`. - */ -VALUE rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree); - -/** - * Identical to rb_data_object_wrap(), except it allocates a new data region - * internally instead of taking an existing one. The allocation is done using - * ruby_calloc(). Hence it makes no sense to pass anything other than - * ::RUBY_DEFAULT_FREE to the last argument. - * - * @param[in] klass Ruby level class of the returning object. - * @param[in] size Requested size of memory to allocate. - * @param[in] dmark Mark function. - * @param[in] dfree Free function. - * @exception rb_eTypeError `klass` is not a class. - * @exception rb_eNoMemError Out of memory. - * @return An allocated object that wraps a new `size` byte region. - */ -VALUE rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree); - -RBIMPL_SYMBOL_EXPORT_END() - -/** - * Converts sval, a pointer to your struct, into a Ruby object. - * - * @param klass A ruby level class. - * @param mark Mark function. - * @param free Free function. - * @param sval A pointer to your struct. - * @exception rb_eTypeError `klass` is not a class. - * @exception rb_eNoMemError Out of memory. - * @return A created Ruby object. - */ -#define Data_Wrap_Struct(klass, mark, free, sval) \ - rb_data_object_wrap( \ - (klass), \ - (sval), \ - RBIMPL_DATA_FUNC(mark), \ - RBIMPL_DATA_FUNC(free)) - -/** - * @private - * - * This is an implementation detail of #Data_Make_Struct. People don't use it - * directly. - * - * @param result Variable name of created Ruby object. - * @param klass Ruby level class of the object. - * @param type Type name of the C struct. - * @param size Size of the C struct. - * @param mark Mark function. - * @param free Free function. - * @param sval Variable name of created C struct. - */ -#define Data_Make_Struct0(result, klass, type, size, mark, free, sval) \ - VALUE result = rb_data_object_zalloc( \ - (klass), \ - (size), \ - RBIMPL_DATA_FUNC(mark), \ - RBIMPL_DATA_FUNC(free)); \ - (sval) = RBIMPL_CAST((type *)DATA_PTR(result)); \ - RBIMPL_CAST(/*suppress unused variable warnings*/(void)(sval)) - -/** - * Identical to #Data_Wrap_Struct, except it allocates a new data region - * internally instead of taking an existing one. The allocation is done using - * ruby_calloc(). Hence it makes no sense to pass anything other than - * ::RUBY_DEFAULT_FREE to the `free` argument. - * - * @param klass Ruby level class of the returning object. - * @param type Type name of the C struct. - * @param mark Mark function. - * @param free Free function. - * @param sval Variable name of created C struct. - * @exception rb_eTypeError `klass` is not a class. - * @exception rb_eNoMemError Out of memory. - * @return A created Ruby object. - */ -#ifdef HAVE_STMT_AND_DECL_IN_EXPR -#define Data_Make_Struct(klass, type, mark, free, sval) \ - RB_GNUC_EXTENSION({ \ - Data_Make_Struct0( \ - data_struct_obj, \ - klass, \ - type, \ - sizeof(type), \ - mark, \ - free, \ - sval); \ - data_struct_obj; \ - }) -#else -#define Data_Make_Struct(klass, type, mark, free, sval) \ - rb_data_object_make( \ - (klass), \ - RBIMPL_DATA_FUNC(mark), \ - RBIMPL_DATA_FUNC(free), \ - RBIMPL_CAST((void **)&(sval)), \ - sizeof(type)) -#endif - -/** - * Obtains a C struct from inside of a wrapper Ruby object. - * - * @param obj An instance of ::RData. - * @param type Type name of the C struct. - * @param sval Variable name of obtained C struct. - * @return Unwrapped C struct that `obj` holds. - */ -#define Data_Get_Struct(obj, type, sval) \ - ((sval) = RBIMPL_CAST((type*)rb_data_object_get(obj))) - -RBIMPL_ATTRSET_UNTYPED_DATA_FUNC() -/** - * @private - * - * This is an implementation detail of rb_data_object_wrap(). People don't use - * it directly. - * - * @param[in] klass Ruby level class of the returning object. - * @param[in] ptr Pointer to the target C struct. - * @param[in] mark Mark function. - * @param[in] free Free function. - * @exception rb_eTypeError `klass` is not a class. - * @exception rb_eNoMemError Out of memory. - * @return An allocated object that wraps `datap`. - */ -static inline VALUE -rb_data_object_wrap_warning(VALUE klass, void *ptr, RUBY_DATA_FUNC mark, RUBY_DATA_FUNC free) -{ - return rb_data_object_wrap(klass, ptr, mark, free); -} - -/** - * @private - * - * This is an implementation detail of #Data_Get_Struct. People don't use it - * directly. - * - * @param[in] obj An instance of ::RData. - * @return Unwrapped C struct that `obj` holds. - */ -static inline void * -rb_data_object_get(VALUE obj) -{ - Check_Type(obj, RUBY_T_DATA); - return DATA_PTR(obj); -} - -RBIMPL_ATTRSET_UNTYPED_DATA_FUNC() -/** - * @private - * - * This is an implementation detail of #Data_Get_Struct. People don't use it - * directly. - * - * @param[in] obj An instance of ::RData. - * @return Unwrapped C struct that `obj` holds. - */ -static inline void * -rb_data_object_get_warning(VALUE obj) -{ - return rb_data_object_get(obj); -} - -/** - * This is an implementation detail of #Data_Make_Struct. People don't use it - * directly. - * - * @param[in] klass Ruby level class of the returning object. - * @param[in] mark_func Mark function. - * @param[in] free_func Free function. - * @param[in] datap Variable of created C struct. - * @param[in] size Requested size of allocation. - * @exception rb_eTypeError `klass` is not a class. - * @exception rb_eNoMemError Out of memory. - * @return A created Ruby object. - * @post `*datap` holds the created C struct. - */ -static inline VALUE -rb_data_object_make(VALUE klass, RUBY_DATA_FUNC mark_func, RUBY_DATA_FUNC free_func, void **datap, size_t size) -{ - Data_Make_Struct0(result, klass, void, size, mark_func, free_func, *datap); - return result; -} - -/** @cond INTERNAL_MACRO */ -#define rb_data_object_wrap_0 rb_data_object_wrap -#define rb_data_object_wrap_1 rb_data_object_wrap_warning -#define rb_data_object_wrap_2 rb_data_object_wrap_ /* Used here vvvv */ -#define rb_data_object_wrap RUBY_MACRO_SELECT(rb_data_object_wrap_2, RUBY_UNTYPED_DATA_WARNING) -#define rb_data_object_get_0 rb_data_object_get -#define rb_data_object_get_1 rb_data_object_get_warning -#define rb_data_object_get_2 rb_data_object_get_ /* Used here vvvv */ -#define rb_data_object_get RUBY_MACRO_SELECT(rb_data_object_get_2, RUBY_UNTYPED_DATA_WARNING) -#define rb_data_object_make_0 rb_data_object_make -#define rb_data_object_make_1 rb_data_object_make_warning -#define rb_data_object_make_2 rb_data_object_make_ /* Used here vvvv */ -#define rb_data_object_make RUBY_MACRO_SELECT(rb_data_object_make_2, RUBY_UNTYPED_DATA_WARNING) -/** @endcond */ #endif /* RBIMPL_RDATA_H */ diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 0b189c7ef8..eb8313f180 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -206,11 +206,6 @@ rbimpl_typeddata_flags { RUBY_TYPED_WB_PROTECTED = RUBY_FL_WB_PROTECTED, /* THIS FLAG DEPENDS ON Ruby version */ /** - * This flag is used to distinguish RTypedData from deprecated RData objects. - */ - RUBY_TYPED_FL_IS_TYPED_DATA = RUBY_FL_USERPRIV0, - - /** * This flag determines whether marking and compaction should be carried out * using the dmark/dcompact callback functions or whether we should mark * declaratively using a list of references defined inside the data struct we're wrapping @@ -409,6 +404,22 @@ RBIMPL_STATIC_ASSERT(fields_obj_in_rdata, offsetof(struct RData, fields_obj) == RBIMPL_STATIC_ASSERT(data_in_rtypeddata, offsetof(struct RData, data) == offsetof(struct RTypedData, data)); #endif +/** + * Convenient casting macro for backward compatibility. + * + * @param obj An object, which is in fact an ::RData. + * @return The passed object casted to ::RData. + */ +#define RDATA(obj) RTYPEDDATA(obj) + +/** + * Convenient casting macro for backward compatibility. + * + * @param obj An object, which is in fact an ::RData. + * @return The passed object's ::RTypedData::data field. + */ +#define DATA_PTR(obj) RTYPEDDATA_DATA(obj) + RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NONNULL((3)) /** @@ -621,27 +632,7 @@ RBIMPL_ATTR_ARTIFICIAL() /** * @private * - * This is an implementation detail of Check_Type(). People don't use it - * directly. - * - * @param[in] obj Object in question - * @retval true `obj` is an instance of ::RTypedData. - * @retval false `obj` is an instance of ::RData. - * @pre `obj` must be a Ruby object of ::RUBY_T_DATA. - */ -static inline bool -rbimpl_rtypeddata_p(VALUE obj) -{ - return FL_TEST_RAW(obj, RUBY_TYPED_FL_IS_TYPED_DATA); -} - -RBIMPL_ATTR_PURE() -RBIMPL_ATTR_ARTIFICIAL() -/** - * @private - * - * Identical to rbimpl_rtypeddata_p(), except it is allowed to call on non-data - * objects. + * Checks whether the passed object is ::RTypedData. * * This is an implementation detail of inline functions defined in this file. * People don't use it directly. @@ -653,17 +644,16 @@ RBIMPL_ATTR_ARTIFICIAL() static inline bool rbimpl_obj_typeddata_p(VALUE obj) { - return RB_TYPE_P(obj, RUBY_T_DATA) && rbimpl_rtypeddata_p(obj); + return RB_TYPE_P(obj, RUBY_T_DATA); } RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** - * Checks whether the passed object is ::RTypedData or ::RData. + * Checks whether the passed object is ::RTypedData. * * @param[in] obj Object in question - * @retval true `obj` is an instance of ::RTypedData. - * @retval false `obj` is an instance of ::RData. + * @retval true * @pre `obj` must be a Ruby object of ::RUBY_T_DATA. */ static inline bool @@ -671,7 +661,7 @@ RTYPEDDATA_P(VALUE obj) { RBIMPL_TYPEDDATA_PRECONDITION(obj, RBIMPL_UNREACHABLE_RETURN(false)); - return rbimpl_rtypeddata_p(obj); + return true; } RBIMPL_ATTR_PURE_UNLESS_DEBUG() @@ -752,7 +742,6 @@ static inline VALUE rbimpl_check_external_typeddata(VALUE obj) { RBIMPL_TYPEDDATA_PRECONDITION(obj, RBIMPL_UNREACHABLE_RETURN(false)); - RUBY_ASSERT(rbimpl_obj_typeddata_p(obj)); RUBY_ASSERT(!rbimpl_typeddata_embedded_p(obj)); return obj; } diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index e3878d9ed7..f7ec742422 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -205,11 +205,11 @@ ruby_fl_type { RUBY_FL_PROMOTED = (1<<5), /** - * This flag meaning is type dependent, currently only used by T_DATA. + * This flag is no longer in use * * @internal */ - RUBY_FL_USERPRIV0 = (1<<6), + RUBY_FL_UNUSED6 = (1<<6), /** * This flag has something to do with finalisers. A ruby object can have diff --git a/include/ruby/internal/value_type.h b/include/ruby/internal/value_type.h index b47d8afb97..77cb38bc7d 100644 --- a/include/ruby/internal/value_type.h +++ b/include/ruby/internal/value_type.h @@ -410,14 +410,6 @@ RB_TYPE_P(VALUE obj, enum ruby_value_type t) #endif /** @endcond */ -RBIMPL_ATTR_PURE() -RBIMPL_ATTR_ARTIFICIAL() -/** - * @private - * Defined in ruby/internal/core/rtypeddata.h - */ -static inline bool rbimpl_rtypeddata_p(VALUE obj); - RBIMPL_ATTR_ARTIFICIAL() /** * Identical to RB_TYPE_P(), except it raises exceptions on predication @@ -432,19 +424,11 @@ RBIMPL_ATTR_ARTIFICIAL() static inline void Check_Type(VALUE v, enum ruby_value_type t) { + /* Typed data is not simple `T_DATA`, see `rb_check_type` */ + RUBY_ASSERT_ALWAYS(t != RUBY_T_DATA); if (RB_UNLIKELY(! RB_TYPE_P(v, t))) { - goto unexpected_type; + rb_unexpected_type(v, RBIMPL_CAST((int)t)); } - else if (t == RUBY_T_DATA && rbimpl_rtypeddata_p(v)) { - /* Typed data is not simple `T_DATA`, see `rb_check_type` */ - goto unexpected_type; - } - else { - return; - } - - unexpected_type: - rb_unexpected_type(v, RBIMPL_CAST((int)t)); } #endif /* RBIMPL_VALUE_TYPE_H */ diff --git a/include/ruby/random.h b/include/ruby/random.h index 740be6bdad..3b63a23334 100644 --- a/include/ruby/random.h +++ b/include/ruby/random.h @@ -333,7 +333,6 @@ static inline const rb_random_interface_t * rb_rand_if(VALUE obj) { RBIMPL_ASSERT_OR_ASSUME(RB_TYPE_P(obj, T_DATA)); - RBIMPL_ASSERT_OR_ASSUME(RTYPEDDATA_P(obj)); RUBY_ASSERT(rb_typeddata_is_kind_of(obj, &rb_random_data_type)); const struct rb_data_type_struct *t = RTYPEDDATA_TYPE(obj); const void *ret = t->data; @@ -1099,10 +1099,25 @@ invokesuper // attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci); // attr bool zjit_profile = true; { - VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, true); - val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_super); + struct rb_callinfo adjusted_ci = VM_CI_ON_STACK(vm_ci_mid(cd->ci), + vm_ci_flag(cd->ci), + vm_ci_argc(cd->ci), + vm_ci_kwarg(cd->ci)); + const struct rb_callcache *original_cc = rbimpl_atomic_ptr_load((void **)&cd->cc, RBIMPL_ATOMIC_ACQUIRE); + struct rb_call_data adjusted_cd = { + .ci = &adjusted_ci, + .cc = original_cc, + }; + + VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), adjusted_cd.ci, blockiseq, true); + val = vm_sendish(ec, GET_CFP(), &adjusted_cd, bh, mexp_search_super); JIT_EXEC(ec, val); + if (original_cc != adjusted_cd.cc && vm_cc_markable(adjusted_cd.cc)) { + rbimpl_atomic_ptr_store((volatile void **)&cd->cc, (void *)adjusted_cd.cc, RBIMPL_ATOMIC_RELEASE); + RB_OBJ_WRITTEN(GET_ISEQ(), Qundef, adjusted_cd.cc); + } + if (UNDEF_P(val)) { RESTORE_REGS(); NEXT_INSN(); diff --git a/internal/gc.h b/internal/gc.h index 41675810c7..b78808370c 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -124,7 +124,7 @@ struct rb_objspace; /* in vm_core.h */ T *(var) = (T *)rb_ec_newobj_of((ec), (c), (f), s) #define NEWOBJ_OF(var, T, c, f, s) EC_NEWOBJ_OF(var, T, c, f, s, GET_EC()) #define UNPROTECTED_NEWOBJ_OF(var, T, c, f, s) \ - T *(var) = (T *)rb_newobj((GET_EC()), (c), (f), 0 /* ROOT_SHAPE_ID */, false, s) + T *(var) = (T *)rb_newobj((GET_EC()), (c), (f), ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, false, s) #ifndef RB_GC_OBJECT_METADATA_ENTRY_DEFINED # define RB_GC_OBJECT_METADATA_ENTRY_DEFINED diff --git a/internal/hash.h b/internal/hash.h index baf5af9abd..0386a5009c 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -70,7 +70,6 @@ struct RHash { #endif /* hash.c */ -void rb_hash_st_table_set(VALUE hash, st_table *st); VALUE rb_hash_default_value(VALUE hash, VALUE key); VALUE rb_hash_set_default(VALUE hash, VALUE ifnone); VALUE rb_hash_set_default_proc(VALUE hash, VALUE proc); @@ -197,7 +196,6 @@ RHASH_AR_TABLE_SIZE_RAW(VALUE h) ((unsigned int)((RBASIC(h)->flags >> RHASH_AR_TABLE_BOUND_SHIFT) & \ (RHASH_AR_TABLE_BOUND_MASK >> RHASH_AR_TABLE_BOUND_SHIFT))) -#define RHASH_ST_TABLE_SET(h, s) rb_hash_st_table_set(h, s) #define RHASH_TYPE(hash) (RHASH_AR_TABLE_P(hash) ? &objhash : RHASH_ST_TABLE(hash)->type) static inline unsigned int diff --git a/misc/lldb_rb/utils.py b/misc/lldb_rb/utils.py index e0cb609b1a..a2bcedc328 100644 --- a/misc/lldb_rb/utils.py +++ b/misc/lldb_rb/utils.py @@ -236,29 +236,20 @@ class RbInspector(LLDBInterface): elif rval.is_type("RUBY_T_DATA"): tRTypedData = self.target.FindFirstType("struct RTypedData").GetPointerType() val = val.Cast(tRTypedData) - is_typed_data = self.ruby_globals.get("RUBY_TYPED_FL_IS_TYPED_DATA", None) - if is_typed_data: - typed = rval.flags & is_typed_data - else: - typed = val.GetValueForExpressionPath("->typed_flag").GetValueAsUnsigned() == 1 - - if typed: - type = val.GetValueForExpressionPath("->type").GetValueAsUnsigned() - embed = (type & 1) - if embed: - flaginfo += "[EMBED] " - type = self.frame.EvaluateExpression("(rb_data_type_t *)%0#x" % (type & ~1)) - print("T_DATA: %s%s" % - (flaginfo, type.GetValueForExpressionPath("->wrap_struct_name")), - file=self.result) - print("%s", type.Dereference(), file=self.result) - ptr = val.GetValueForExpressionPath("->data") - if embed: - ptr = ptr.AddressOf() - self._append_expression("(void *)%0#x" % ptr.GetValueAsUnsigned()) - else: - print("T_DATA:", file=self.result) - self._append_expression("*(struct RData *) %0#x" % val.GetValueAsUnsigned()) + + type = val.GetValueForExpressionPath("->type").GetValueAsUnsigned() + embed = (type & 1) + if embed: + flaginfo += "[EMBED] " + type = self.frame.EvaluateExpression("(rb_data_type_t *)%0#x" % (type & ~1)) + print("T_DATA: %s%s" % + (flaginfo, type.GetValueForExpressionPath("->wrap_struct_name")), + file=self.result) + print("%s", type.Dereference(), file=self.result) + ptr = val.GetValueForExpressionPath("->data") + if embed: + ptr = ptr.AddressOf() + self._append_expression("(void *)%0#x" % ptr.GetValueAsUnsigned()) elif rval.is_type("RUBY_T_IMEMO"): imemo_type = ((rval.flags >> self.ruby_globals["RUBY_FL_USHIFT"]) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index ac436d4946..11ade220f0 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -557,19 +557,28 @@ class Pathname !absolute? end + # :markup: markdown + # + # call-seq: + # each_filename {|component| ... } -> nil + # each_filename -> new_enumerator # - # Iterates over each component of the path. + # With a block given, yields each component of the string path: # - # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... } - # # yields "usr", "bin", and "ruby". + # ```ruby + # Pathname('/foo/bar/baz').each_filename {|filename| p filename } + # => nil + # ``` # - # Returns an Enumerator if no block was given. + # Output: # - # enum = Pathname.new("/usr/bin/ruby").each_filename - # # ... do stuff ... - # enum.each { |e| ... } - # # yields "usr", "bin", and "ruby". + # ```text + # "foo" + # "bar" + # "baz" + # ``` # + # With no block given, returns a new Enumerator. def each_filename # :yield: filename return to_enum(__method__) unless block_given? _, names = split_names(@path) @@ -901,12 +910,34 @@ class Pathname end class Pathname # * File * + + # :markup: markdown + # + # call-seq: + # each_line(sep = $/, **opts) {|line| ... } → nil + # each_line(limit, **opts) {|line| ... } → nil + # each_line(sep, limit, **opts) {|line| ... } → nil + # each_line(...) → new_enumerator + # + # With a block given, calls the block with each line + # from the file represented by `self`; + # returns `nil`: # - # #each_line iterates over the line in the file. It yields a String object - # for each line. + # ```ruby + # lines = [] + # Pathname('COPYING').each_line {|line| lines << line } + # lines.take(3) + # # => + # # ["{日本語}[rdoc-ref:COPYING.ja]\n", + # # "\n", + # # "Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.\n"] + # ``` # - # This method has existed since 1.8.1. + # The lines are read using IO.foreach, + # all arguments and options are passed to that method; + # see details at IO.foreach. # + # With no block given, returns a new Enumerator. def each_line(...) # :yield: line File.foreach(@path, ...) end @@ -1391,10 +1422,81 @@ class Pathname # * File * # See <tt>File.dirname</tt>. Returns all but the last component of the path. def dirname() self.class.new(File.dirname(@path)) end - # See <tt>File.extname</tt>. Returns the file's extension. + # :markup: markdown + # + # call-seq: + # extname -> extension + # + # Returns the filename extension of `self` -- + # usually the portion of the string path beginning from the last period: + # + # ```ruby + # Pathname('t.rb').extname # => ".rb" + # Pathname('foo.bar.t.rb').extname # => ".rb" + # Pathname('foo/bar/t.rb').extname # => ".rb" + # Pathname('nosuch.txt').extname # => ".txt" # Path need not exist. + # ``` + # + # Returns the entire string when there is no period: + # + # ```ruby + # Pathname('foo').extname # => "" + # ``` + # + # Returns an empty string when the only period is the first character: + # + # ```ruby + # Pathname('.irbrc').extname # => "" + # ``` + # + # Returns an empty string or `'.'` when `path` ends with a period: + # + # ```ruby + # Pathname('foo.').extname # => "" # On Windows. + # Pathname('foo.').extname # => "." # Elsewhere. + # Pathname('foo....').extname # => "" # On Windows. + # Pathname('foo....').extname # => "." # Elsewhere. + # ``` + # def extname() File.extname(@path) end - # See <tt>File.expand_path</tt>. + # :markup: markdown + # + # call-seq: + # expand_path(dirpath = '.') -> new_pathname + # + # Returns a new pathname containing the absolute path for `self`. + # + # Evaluates a relative path with respect to the directory given by `dirpath`: + # + # ```ruby + # Dir.chdir('/snap') + # # Default dirpath. + # Pathname('README').expand_path # => #<Pathname:/snap/README> + # Pathname('bin').expand_path # => #<Pathname:/snap/bin> + # Pathname('bin/../var').expand_path # => #<Pathname:/snap/var> # Cleaned. + # # Other dirpath. + # Pathname('../zip').expand_path('/usr/bin/ruby') # => #<Pathname:/usr/bin/zip> + # Dir.chdir('/usr/bin') + # Pathname('../../snap').expand_path(__FILE__) # => #<Pathname:/usr/snap> + # ``` + # + # Evaluates an absolute path without respect to `dirpath`: + # + # ```ruby + # Pathname('/snap').expand_path # => #<Pathname:/snap> + # Pathname('/snap').expand_path.expand_path('nosuch') # => #<Pathname:/snap> + # Pathname('/snap/../snap').expand_path # => #<Pathname:/snap> # Cleaned. + # ``` + # + # More examples: + # + # ``` + # Dir.chdir('/usr/bin') + # Pathname('../../snap').expand_path(__FILE__) # => #<Pathname:/usr/snap> + # Pathname('../../snap').expand_path # => #<Pathname:/snap> + # ``` + # def expand_path(...) self.class.new(File.expand_path(@path, ...)) end # See <tt>File.split</tt>. Returns the #dirname and the #basename in an @@ -1694,8 +1796,24 @@ class Pathname # * Dir * alias pwd getwd end - # Return the entries (files and subdirectories) in the directory, each as a - # Pathname object. + # :markup: markdown + # + # call-seq: + # entries -> array_of_pathnames + # + # Returns an array of pathnames, + # one for each entry in the directory represented by `self`: + # + # ```ruby + # Pathname('.').entries.take(5) + # # => + # # [#<Pathname:.>, + # # #<Pathname:..>, + # # #<Pathname:gc.rb>, + # # #<Pathname:yjit.rb>, + # # #<Pathname:iseq.h>] + # ``` + # def entries() Dir.entries(@path).map {|f| self.class.new(f) } end # :markup: markdown @@ -1451,7 +1451,7 @@ allow_frozen_shareable_p(VALUE obj) if (!RB_TYPE_P(obj, T_DATA)) { return true; } - else if (RTYPEDDATA_P(obj)) { + else { const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE) { return true; @@ -2015,7 +2015,7 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data) else { VALUE type = RB_BUILTIN_TYPE(obj); size_t slot_size = rb_gc_obj_slot_size(obj); - VALUE moved = rb_newobj(GET_EC(), 0, type, 0, wb_protected_types[type], slot_size); + VALUE moved = rb_newobj(GET_EC(), 0, type, RBASIC_SHAPE_ID(obj), wb_protected_types[type], slot_size); MEMZERO(((struct RBasic *)moved) + 1, char, slot_size - sizeof(struct RBasic)); data->replacement = (VALUE)moved; return traverse_cont; @@ -409,10 +409,14 @@ rb_obj_shape_id(VALUE obj) if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + shape_id_t base = ROOT_SHAPE_ID; if (fields_obj) { - return RBASIC_SHAPE_ID(fields_obj); + // Remove the layout from the fields object. We want to + // combine the shape of the fields object with the layout of the + // class / module object. + base = RBASIC_SHAPE_ID(fields_obj) & ~SHAPE_ID_LAYOUT_MASK; } - return ROOT_SHAPE_ID; + return rb_shape_layout(RBASIC_SHAPE_ID(obj)) | base; } return RBASIC_SHAPE_ID(obj); } @@ -697,7 +701,7 @@ rb_shape_transition_object_id(shape_id_t original_shape_id) bool dont_care; rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), id_object_id, SHAPE_OBJ_ID, &dont_care, true); if (!shape) { - return ROOT_COMPLEX_WITH_OBJ_ID | RSHAPE_FLAGS(original_shape_id); + return rb_shape_layout(original_shape_id) | ROOT_COMPLEX_WITH_OBJ_ID | RSHAPE_FLAGS(original_shape_id); } RUBY_ASSERT(shape); @@ -1225,17 +1229,55 @@ rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_ } #if RUBY_DEBUG +/* + * Get the layout of this object. The "layout" indicates what strategy + * we should use for fetching instance variables from `obj`. It's based + * on the C struct layout for each particular object. + * + * TODO: make Struct have a similar layout to RDATA + */ +static shape_id_t +rb_shape_expected_layout(VALUE obj) +{ + switch (BUILTIN_TYPE(obj)) { + case T_OBJECT: + return SHAPE_ID_LAYOUT_ROBJECT; + case T_CLASS: + case T_MODULE: + if (FL_TEST_RAW(obj, RCLASS_BOXABLE)) { + return SHAPE_ID_LAYOUT_OTHER; + } + return SHAPE_ID_LAYOUT_RCLASS; + case T_DATA: + return SHAPE_ID_LAYOUT_RDATA; + case T_IMEMO: + if (IMEMO_TYPE_P(obj, imemo_fields)) { + return SHAPE_ID_LAYOUT_ROBJECT; + } + return SHAPE_ID_LAYOUT_OTHER; + default: + return SHAPE_ID_LAYOUT_OTHER; + } +} + bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) { - if (shape_id == ROOT_SHAPE_ID) { - return true; - } - if (shape_id == INVALID_SHAPE_ID) { rb_bug("Can't set INVALID_SHAPE_ID on an object"); } + shape_id_t actual_layout = rb_shape_layout(rb_obj_shape_id(obj)); + shape_id_t expected_layout = rb_shape_expected_layout(obj); + if (actual_layout != expected_layout) { + rb_bug("shape_id layout mismatch: expected=%x actual=%x shape_id=%u obj=%s", + expected_layout, actual_layout, shape_id, rb_obj_info(obj)); + } + + if (shape_id == ROOT_SHAPE_ID) { + return true; + } + rb_shape_t *shape = RSHAPE(shape_id); bool has_object_id = false; @@ -1249,13 +1291,11 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) if (rb_shape_has_object_id(shape_id)) { if (!has_object_id) { - rb_p(obj); rb_bug("shape_id claim having obj_id but doesn't shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); } } else { if (has_object_id) { - rb_p(obj); rb_bug("shape_id claim not having obj_id but it does shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); } } @@ -1330,6 +1370,25 @@ shape_has_object_id_p(VALUE self) } static VALUE +shape_layout(VALUE self) +{ + shape_id_t shape_id = NUM2UINT(rb_struct_getmember(self, rb_intern("id"))); + + switch (rb_shape_layout(shape_id)) { + case SHAPE_ID_LAYOUT_ROBJECT: + return ID2SYM(rb_intern("robject")); + case SHAPE_ID_LAYOUT_RCLASS: + return ID2SYM(rb_intern("rclass")); + case SHAPE_ID_LAYOUT_RDATA: + return ID2SYM(rb_intern("rdata")); + case SHAPE_ID_LAYOUT_OTHER: + return ID2SYM(rb_intern("other")); + default: + rb_bug("unknown shape layout: %u", rb_shape_layout(shape_id)); + } +} + +static VALUE parse_key(ID key) { if (is_instance_id(key)) { @@ -1628,6 +1687,7 @@ Init_shape(void) rb_define_method(rb_cShape, "complex?", shape_complex, 0); rb_define_method(rb_cShape, "shape_frozen?", shape_frozen, 0); rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id_p, 0); + rb_define_method(rb_cShape, "layout", shape_layout, 0); rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT)); rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR)); @@ -27,12 +27,14 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_ // 19-22 SHAPE_ID_HEAP_INDEX_MASK // index in rb_shape_tree.capacities. Allow to access slot size. // Currently always 0 except for T_OBJECT. -// 23 SHAPE_ID_FL_FROZEN +// 23 SHAPE_ID_FL_COMPLEX +// The object is backed by a `st_table`. +// 24 SHAPE_ID_FL_FROZEN // Whether the object is frozen or not. -// 24 SHAPE_ID_FL_HAS_OBJECT_ID +// 25 SHAPE_ID_FL_HAS_OBJECT_ID // Whether the object has an `SHAPE_OBJ_ID` transition. -// 25 SHAPE_ID_FL_COMPLEX -// The object is backed by a `st_table`. +// 26-27 SHAPE_ID_LAYOUT_MASK +// The object's physical field layout. enum shape_id_fl_type { #define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n)) @@ -43,8 +45,26 @@ enum shape_id_fl_type { SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(1), SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(2), + // Means IVs are found at an offset from the object's addr, or in a + // malloc allocated side table + SHAPE_ID_LAYOUT_ROBJECT = 0, + + // Means this object is a class/module that is NOT RCLASS_BOXABLE, and IV's + // are found in the fields_obj found on the rclass struct + SHAPE_ID_LAYOUT_RCLASS = RBIMPL_SHAPE_ID_FL(3), + + // Means this object is an RData or RTypedData and IVs are found in the + // fields_obj found on the RData/RTypedData struct + SHAPE_ID_LAYOUT_RDATA = RBIMPL_SHAPE_ID_FL(4), + + // Means this is a complicated object: boxable classes, structs, objects + // that store IVs on the geniv table + SHAPE_ID_LAYOUT_OTHER = SHAPE_ID_LAYOUT_RCLASS | SHAPE_ID_LAYOUT_RDATA, + + SHAPE_ID_LAYOUT_MASK = SHAPE_ID_LAYOUT_OTHER, + SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID, - SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX, + SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX | SHAPE_ID_LAYOUT_MASK, #undef RBIMPL_SHAPE_ID_FL }; @@ -55,12 +75,13 @@ enum shape_id_mask { SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1), }; -// The interpreter doesn't care about frozen status, slot size or object id when reading ivars. +// The interpreter doesn't care about frozen status, slot size, or object id, and +// has its own checks for physical field layout when reading ivars. // So we normalize shape_id by clearing these bits to improve cache hits. // JITs however might care about some of it. -#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID)) +#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK)) // For write it's the same idea, but here we do care about frozen status. -#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID)) +#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK)) typedef uint32_t redblack_id_t; @@ -153,11 +174,18 @@ RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) #endif } +static inline shape_id_t +rb_shape_layout(shape_id_t shape_id) +{ + return shape_id & SHAPE_ID_LAYOUT_MASK; +} + static inline void RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) { RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields)); + RUBY_ASSERT(!IMEMO_TYPE_P(obj, imemo_fields) || rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); @@ -232,6 +260,12 @@ rb_shape_canonical_p(shape_id_t shape_id) return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK); } +static inline shape_id_t +rb_shape_id_with_robject_layout(shape_id_t shape_id) +{ + return (shape_id & ~SHAPE_ID_LAYOUT_MASK) | SHAPE_ID_LAYOUT_ROBJECT; +} + static inline uint8_t rb_shape_heap_index(shape_id_t shape_id) { @@ -450,10 +484,10 @@ rb_shape_transition_frozen(shape_id_t shape_id) static inline shape_id_t rb_shape_transition_complex(shape_id_t shape_id) { - shape_id_t next_shape_id = ROOT_COMPLEX_SHAPE_ID; + shape_id_t next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_SHAPE_ID; if (rb_shape_has_object_id(shape_id)) { - next_shape_id = ROOT_COMPLEX_WITH_OBJ_ID; + next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_WITH_OBJ_ID; } uint8_t heap_index = rb_shape_heap_index(shape_id); diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 877deca663..27ddc6a771 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -38,7 +38,7 @@ require_relative "support/indexes" require_relative "support/matchers" require_relative "support/permissions" require_relative "support/platforms" -require_relative "support/windows_tag_group" +require_relative "support/shards" begin raise LoadError if File.exist?(File.expand_path("../../lib/bundler/bundler.gemspec", __dir__)) @@ -88,7 +88,7 @@ RSpec.configure do |config| config.include Spec::Path config.include Spec::Platforms config.include Spec::Permissions - config.include Spec::WindowsTagGroup + config.include Spec::Shards # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" @@ -175,7 +175,7 @@ RSpec.configure do |config| reset! end - Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.each do |tag, file_paths| + Spec::Shards::EXAMPLE_MAPPINGS.each do |tag, file_paths| file_pattern = Regexp.union(file_paths.map {|path| Regexp.new(Regexp.escape(path) + "$") }) config.define_derived_metadata(file_path: file_pattern) do |metadata| @@ -185,8 +185,8 @@ RSpec.configure do |config| config.before(:context) do |example| metadata = example.class.metadata - if metadata[:type] != :aruba && !metadata[:realworld] && metadata.keys.none? {|k| Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.keys.include?(k) } - warn "#{metadata[:file_path]} is not assigned to any Windows runner group. see spec/support/windows_tag_group.rb for details." + if metadata[:type] != :aruba && !metadata[:realworld] && metadata.keys.none? {|k| Spec::Shards::EXAMPLE_MAPPINGS.keys.include?(k) } + warn "#{metadata[:file_path]} is not assigned to any shard. see spec/support/shards.rb for details." end end unless Spec::Path.ruby_core? end diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index e61fe921ec..e684aa8628 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -2,7 +2,7 @@ require_relative "endpoint" -$LOAD_PATH.unshift Dir[Spec::Path.scoped_base_system_gem_path.join("gems/compact_index*/lib")].first.to_s +$LOAD_PATH.unshift Spec::Path.tmp_root.join("compact_index/lib").to_s require "compact_index" require "digest" @@ -90,13 +90,17 @@ class CompactIndexAPI < Endpoint rescue StandardError checksum = nil end - CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, - deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s) + build_gem_version(spec, deps, checksum) end CompactIndex::Gem.new(name, gem_versions) end end end + + def build_gem_version(spec, deps, checksum) + CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, + deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s) + end end get "/names" do diff --git a/spec/bundler/support/windows_tag_group.rb b/spec/bundler/support/shards.rb index fb9c081149..580997eb72 100644 --- a/spec/bundler/support/windows_tag_group.rb +++ b/spec/bundler/support/shards.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -# This group classifies test files into 4 groups by running `bin/rspec --profile 10000` +# This classifies test files into 4 shards by running `bin/rspec --profile 10000` # to ensure balanced execution times. When adding new test files, it is recommended to -# re-aggregate and adjust the groups to keep them balanced. -# For now, please add new files to group 'windows_d'. +# re-aggregate and adjust the shards to keep them balanced. +# For now, please add new files to shard 'shard_d'. module Spec - module WindowsTagGroup + module Shards EXAMPLE_MAPPINGS = { - windows_a: [ + shard_a: [ "spec/runtime/setup_spec.rb", "spec/commands/install_spec.rb", "spec/commands/add_spec.rb", @@ -53,7 +53,7 @@ module Spec "spec/bundler/plugin/source_list_spec.rb", "spec/bundler/source/path_spec.rb", ], - windows_b: [ + shard_b: [ "spec/install/gemfile/git_spec.rb", "spec/install/gems/standalone_spec.rb", "spec/commands/lock_spec.rb", @@ -97,7 +97,7 @@ module Spec "spec/bundler/index_spec.rb", "spec/other/cli_man_pages_spec.rb", ], - windows_c: [ + shard_c: [ "spec/commands/newgem_spec.rb", "spec/commands/exec_spec.rb", "spec/commands/clean_spec.rb", @@ -142,7 +142,7 @@ module Spec "spec/bundler/cli_common_spec.rb", "spec/bundler/ci_detector_spec.rb", ], - windows_d: [ + shard_d: [ "spec/commands/outdated_spec.rb", "spec/commands/update_spec.rb", "spec/lock/lockfile_spec.rb", diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h index 7107bead90..5a92645785 100644 --- a/spec/ruby/optional/capi/ext/rubyspec.h +++ b/spec/ruby/optional/capi/ext/rubyspec.h @@ -35,6 +35,10 @@ (RUBY_API_VERSION_MAJOR == (major) && RUBY_API_VERSION_MINOR < (minor))) #define RUBY_VERSION_SINCE(major,minor) (!RUBY_VERSION_BEFORE(major, minor)) +#if RUBY_VERSION_SINCE(4, 1) +#define RUBY_VERSION_IS_4_1 +#endif + #if RUBY_VERSION_SINCE(4, 0) #define RUBY_VERSION_IS_4_0 #endif diff --git a/spec/ruby/optional/capi/ext/typed_data_spec.c b/spec/ruby/optional/capi/ext/typed_data_spec.c index 221f1c8ac4..c6fcfa3bc8 100644 --- a/spec/ruby/optional/capi/ext/typed_data_spec.c +++ b/spec/ruby/optional/capi/ext/typed_data_spec.c @@ -106,6 +106,7 @@ VALUE sws_typed_wrap_struct(VALUE self, VALUE val) { return TypedData_Wrap_Struct(rb_cObject, &sample_typed_wrapped_struct_data_type, bar); } +#ifndef RUBY_VERSION_IS_4_1 #undef RUBY_UNTYPED_DATA_WARNING #define RUBY_UNTYPED_DATA_WARNING 0 VALUE sws_untyped_wrap_struct(VALUE self, VALUE val) { @@ -113,6 +114,7 @@ VALUE sws_untyped_wrap_struct(VALUE self, VALUE val) { *data = FIX2INT(val); return Data_Wrap_Struct(rb_cObject, NULL, free, data); } +#endif VALUE sws_typed_get_struct(VALUE self, VALUE obj) { struct sample_typed_wrapped_struct* bar; @@ -173,9 +175,11 @@ VALUE sws_typed_rb_check_typeddata_different_type(VALUE self, VALUE obj) { return rb_check_typeddata(obj, &sample_typed_wrapped_struct_other_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; } +#ifndef RUBY_VERSION_IS_4_1 VALUE sws_typed_RTYPEDDATA_P(VALUE self, VALUE obj) { return RTYPEDDATA_P(obj) ? Qtrue : Qfalse; } +#endif void Init_typed_data_spec(void) { VALUE cls = rb_define_class("CApiAllocTypedSpecs", rb_cObject); @@ -183,7 +187,9 @@ void Init_typed_data_spec(void) { rb_define_method(cls, "typed_wrapped_data", sdaf_typed_get_struct, 0); cls = rb_define_class("CApiWrappedTypedStructSpecs", rb_cObject); rb_define_method(cls, "typed_wrap_struct", sws_typed_wrap_struct, 1); +#ifndef RUBY_VERSION_IS_4_1 rb_define_method(cls, "untyped_wrap_struct", sws_untyped_wrap_struct, 1); +#endif rb_define_method(cls, "typed_get_struct", sws_typed_get_struct, 1); rb_define_method(cls, "typed_get_struct_other", sws_typed_get_struct_different_type, 1); rb_define_method(cls, "typed_get_struct_parent", sws_typed_get_struct_parent_type, 1); @@ -194,10 +200,11 @@ void Init_typed_data_spec(void) { rb_define_method(cls, "rb_check_typeddata_same_type", sws_typed_rb_check_typeddata_same_type, 1); rb_define_method(cls, "rb_check_typeddata_same_type_parent", sws_typed_rb_check_typeddata_same_type_parent, 1); rb_define_method(cls, "rb_check_typeddata_different_type", sws_typed_rb_check_typeddata_different_type, 1); +#ifndef RUBY_VERSION_IS_4_1 rb_define_method(cls, "RTYPEDDATA_P", sws_typed_RTYPEDDATA_P, 1); +#endif } #ifdef __cplusplus } #endif - diff --git a/spec/ruby/optional/capi/typed_data_spec.rb b/spec/ruby/optional/capi/typed_data_spec.rb index 8eaf7751ba..376cfe417f 100644 --- a/spec/ruby/optional/capi/typed_data_spec.rb +++ b/spec/ruby/optional/capi/typed_data_spec.rb @@ -86,15 +86,17 @@ describe "CApiWrappedTypedStruct" do end end - describe "RTYPEDDATA_P" do - it "returns true for a typed data" do - a = @s.typed_wrap_struct(1024) - @s.RTYPEDDATA_P(a).should == true - end + ruby_version_is ""..."4.1" do + describe "RTYPEDDATA_P" do + it "returns true for a typed data" do + a = @s.typed_wrap_struct(1024) + @s.RTYPEDDATA_P(a).should == true + end - it "returns false for an untyped data object" do - a = @s.untyped_wrap_struct(1024) - @s.RTYPEDDATA_P(a).should == false + it "returns false for an untyped data object" do + a = @s.untyped_wrap_struct(1024) + @s.RTYPEDDATA_P(a).should == false + end end end end @@ -630,7 +630,7 @@ static VALUE setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx) { fake_str->basic.flags = T_STRING|RSTRING_NOEMBED|STR_NOFREE|STR_FAKESTR; - RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID); + RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); if (!name) { RUBY_ASSERT_ALWAYS(len == 0); diff --git a/test/date/test_date_strptime.rb b/test/date/test_date_strptime.rb index 4efe1a47d0..6aa7db292d 100644 --- a/test/date/test_date_strptime.rb +++ b/test/date/test_date_strptime.rb @@ -517,7 +517,20 @@ class TestDateStrptime < Test::Unit::TestCase d = DateTime.strptime('9000 +0200', '%Q %z') assert_equal([1970, 1, 1, 2, 0, 9], [d.year, d.mon, d.mday, d.hour, d.min, d.sec]) assert_equal(Rational(2, 24), d.offset) - end + def test_format_modified + str = " " * 100 + fmt = Struct.new(:str) { + def to_str + str << "2026-06-01" << " "*100 + " %F " + end + }.new(str) + d = Date._strptime(str, fmt) + assert_not_nil(d) + assert_equal(2026, d[:year]) + assert_equal(6, d[:mon]) + assert_equal(1, d[:mday]) + end end diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 87d9cd7f7d..753ee0fbdf 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -601,6 +601,8 @@ class JSONGeneratorTest < Test::Unit::TestCase assert_equal too_deep, ok ok = generate too_deep_ary, :max_nesting => 0 assert_equal too_deep, ok + + assert_raise(TypeError) { generate too_deep_ary, max_nesting: "garbage" } end def test_backslash diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 1969e79b31..292ca1a670 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -545,22 +545,26 @@ class JSONParserTest < Test::Unit::TestCase end def test_backslash + assert_raise(JSON::ParserError) do + JSON.parse('"\\') + end + data = [ '\\.(?i:gif|jpe?g|png)$' ] json = '["\\\\.(?i:gif|jpe?g|png)$"]' assert_equal data, parse(json) - # + data = [ '\\"' ] json = '["\\\\\""]' assert_equal data, parse(json) - # + json = '["/"]' data = [ '/' ] assert_equal data, parse(json) - # + json = '["\""]' data = ['"'] assert_equal data, parse(json) - # + json = '["\\/"]' data = ["/"] assert_equal data, parse(json) diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb index 91ed247414..bc1f680df5 100644 --- a/test/openssl/test_digest.rb +++ b/test/openssl/test_digest.rb @@ -155,6 +155,22 @@ class OpenSSL::TestDigest < OpenSSL::TestCase assert_include digests, "sha256" assert_include digests, "sha512" end + + if respond_to?(:ractor) && defined?(Ractor.shareable_proc) + ractor + + def test_ractor + assert_nothing_raised do + Ractor.new { + [ + OpenSSL::Digest::SHA256.new(""), + OpenSSL::Digest::SHA256.hexdigest(""), + OpenSSL::Digest::SHA256.digest(""), + ] + }.value + end + end + end end end diff --git a/test/openssl/test_kdf.rb b/test/openssl/test_kdf.rb index 6a12a25aa8..708d1883af 100644 --- a/test/openssl/test_kdf.rb +++ b/test/openssl/test_kdf.rb @@ -5,64 +5,31 @@ if defined?(OpenSSL) class OpenSSL::TestKDF < OpenSSL::TestCase def test_pkcs5_pbkdf2_hmac_compatibility - expected = OpenSSL::KDF.pbkdf2_hmac("password", salt: "salt", iterations: 1, length: 20, hash: "sha1") - assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("password", "salt", 1, 20, "sha1")) - assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "salt", 1, 20)) + # PBKDF2 salt >= 16 bytes (128 bits) and iterations >= 1000 are required in + # FIPS. + # SP 800-132. + # https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf + # * 5.1 The Salt (S) + # * 5.2 The Iteration Count (C) + # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L235-L240 + # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L247-L252 + # Use the same parameters with test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25. + expected = OpenSSL::KDF.pbkdf2_hmac("passwordPASSWORDpassword", + salt: "saltSALTsaltSALTsaltSALTsaltSALTsalt", + iterations: 4096, + length: 25, + hash: "sha1") + assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + 25, + "sha1")) + assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + 25)) end - def test_pbkdf2_hmac_sha1_rfc6070_c_1_len_20 - p ="password" - s = "salt" - c = 1 - dk_len = 20 - raw = %w{ 0c 60 c8 0f 96 1f 0e 71 - f3 a9 b5 24 af 60 12 06 - 2f e0 37 a6 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha1_rfc6070_c_2_len_20 - p ="password" - s = "salt" - c = 2 - dk_len = 20 - raw = %w{ ea 6c 01 4d c7 2d 6f 8c - cd 1e d9 2a ce 1d 41 f0 - d8 de 89 57 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_20 - p ="password" - s = "salt" - c = 4096 - dk_len = 20 - raw = %w{ 4b 00 79 01 b7 65 48 9a - be ad 49 d9 26 f7 21 d0 - 65 a4 29 c1 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - -# takes too long! -# def test_pbkdf2_hmac_sha1_rfc6070_c_16777216_len_20 -# p ="password" -# s = "salt" -# c = 16777216 -# dk_len = 20 -# raw = %w{ ee fe 3d 61 cd 4d a4 e4 -# e9 94 5b 3d 6b a2 15 8c -# 26 34 e9 84 } -# expected = [raw.join('')].pack('H*') -# value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") -# assert_equal(expected, value) -# end - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25 p ="passwordPASSWORDpassword" s = "saltSALTsaltSALTsaltSALTsaltSALTsalt" @@ -78,18 +45,6 @@ class OpenSSL::TestKDF < OpenSSL::TestCase assert_equal(expected, value) end - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_16 - p ="pass\0word" - s = "sa\0lt" - c = 4096 - dk_len = 16 - raw = %w{ 56 fa 6a a7 55 48 09 9d - cc 37 d7 f0 34 25 e0 c3 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - def test_pbkdf2_hmac_sha256_c_20000_len_32 #unfortunately no official test vectors available yet for SHA-2 p ="password" @@ -103,6 +58,11 @@ class OpenSSL::TestKDF < OpenSSL::TestCase def test_scrypt_rfc7914_first pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 + # scrypt is not available in FIPS. + # EVP_KDF_fetch(ctx, OSSL_KDF_NAME_SCRYPT, propq) returns NULL in FIPS. + # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/crypto/evp/pbe_scrypt.c#L67-L71 + omit_on_fips + pass = "" salt = "" n = 16 @@ -118,6 +78,9 @@ class OpenSSL::TestKDF < OpenSSL::TestCase def test_scrypt_rfc7914_second pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 + # scrypt is not available in FIPS. + omit_on_fips + pass = "password" salt = "NaCl" n = 1024 @@ -131,6 +94,7 @@ class OpenSSL::TestKDF < OpenSSL::TestCase assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen)) end + # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.1 def test_hkdf_rfc5869_test_case_1 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") @@ -144,6 +108,7 @@ class OpenSSL::TestKDF < OpenSSL::TestCase assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end + # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.3 def test_hkdf_rfc5869_test_case_3 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") @@ -157,16 +122,32 @@ class OpenSSL::TestKDF < OpenSSL::TestCase assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end - def test_hkdf_rfc5869_test_case_4 + # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.5 + def test_hkdf_rfc5869_test_case_5 hash = "sha1" - ikm = B("0b0b0b0b0b0b0b0b0b0b0b") - salt = B("000102030405060708090a0b0c") - info = B("f0f1f2f3f4f5f6f7f8f9") - l = 42 - - okm = B("085a01ea1b10f36933068b56efa5ad81" \ - "a4f14b822f5b091568a9cdd4f155fda2" \ - "c22e422478d305f3f896") + ikm = B("000102030405060708090a0b0c0d0e0f" \ + "101112131415161718191a1b1c1d1e1f" \ + "202122232425262728292a2b2c2d2e2f" \ + "303132333435363738393a3b3c3d3e3f" \ + "404142434445464748494a4b4c4d4e4f") + salt = B("606162636465666768696a6b6c6d6e6f" \ + "707172737475767778797a7b7c7d7e7f" \ + "808182838485868788898a8b8c8d8e8f" \ + "909192939495969798999a9b9c9d9e9f" \ + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf") + info = B("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" \ + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" \ + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" \ + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" \ + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") + l = 82 + + okm = B("0bd770a74d1160f7c9f12cd5912a06eb" \ + "ff6adcae899d92191fe4305673ba2ffe" \ + "8fa3f1a4e5ad79f3f334b3b202b2173c" \ + "486ea37ce3d397ed034c7f9dfeb15c5e" \ + "927336d0441f4c4300e2cff0d0900b52" \ + "d3b4") assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 1adf47ac51..a78527d40e 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -1393,10 +1393,6 @@ class TestIO < Test::Unit::TestCase args = ['-e', '$>.write($<.read)'] if args.empty? ruby = EnvUtil.rubybin opts = {} - if defined?(Process::RLIMIT_NPROC) - lim = Process.getrlimit(Process::RLIMIT_NPROC)[1] - opts[:rlimit_nproc] = [lim, 2048].min - end f = IO.popen([ruby] + args, 'r+', opts) pid = f.pid yield(f) diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index fabb414e14..611b3b7715 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -42,6 +42,59 @@ class TestRactor < Test::Unit::TestCase =end end + def test_shareable_proc_define_method_super_method_missing + assert_ractor(<<~'RUBY', timeout: 30) + iterations = 1_000_000 + + class SuperFromShareableProcMethodMissingBase + def method_missing(mid, *) = mid + end + + class SuperFromShareableProcMethodMissingChild < SuperFromShareableProcMethodMissingBase + BODY = Ractor.shareable_proc { super() } + define_method(:foo, &BODY) + define_method(:bar, &BODY) + end + + [:foo, :bar].map do |mid| + Ractor.new(mid, iterations) do |mid, iterations| + obj = SuperFromShareableProcMethodMissingChild.new + iterations.times do + got = obj.__send__(mid) + raise "#{mid} returned #{got.inspect}" unless got == mid + end + end + end.each(&:value) + RUBY + end + + def test_shareable_proc_define_method_super_method_entry + assert_ractor(<<~'RUBY', timeout: 30) + iterations = 1_000_000 + + class SuperFromShareableProcBase + def foo = :foo + def bar = :bar + end + + class SuperFromShareableProcChild < SuperFromShareableProcBase + BODY = Ractor.shareable_proc { super() } + define_method(:foo, &BODY) + define_method(:bar, &BODY) + end + + [:foo, :bar].map do |mid| + Ractor.new(mid, iterations) do |mid, iterations| + obj = SuperFromShareableProcChild.new + iterations.times do + got = obj.__send__(mid) + raise "#{mid} returned #{got.inspect}" unless got == mid + end + end + end.each(&:value) + RUBY + end + def test_shareability_error_uses_inspect x = (+"").instance_exec { method(:to_s) } def x.to_s diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index ef5dbd9fb1..bace69658a 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -1067,6 +1067,37 @@ class TestShapes < Test::Unit::TestCase assert_nil shape.parent end + def test_shape_layout + assert_equal :robject, RubyVM::Shape.of(TestObject.new).layout + + if ENV["RUBY_BOX"] + assert_equal :other, RubyVM::Shape.of(Kernel).layout + assert_equal :other, RubyVM::Shape.of(String).layout + else + assert_equal :rclass, RubyVM::Shape.of(Kernel).layout + assert_equal :rclass, RubyVM::Shape.of(String).layout + end + + assert_equal :rclass, RubyVM::Shape.of(Class.new).layout + assert_equal :rclass, RubyVM::Shape.of(Module.new).layout + + klass = Class.new + assert_equal :rclass, RubyVM::Shape.of(klass).layout + klass.instance_variable_set(:@a, 123) + assert_equal :rclass, RubyVM::Shape.of(klass).layout + + assert_equal :rdata, RubyVM::Shape.of(Thread.current).layout + assert_equal :rdata, RubyVM::Shape.of(lambda {}).layout + + assert_equal :other, RubyVM::Shape.of(Struct.new(:x).new(1)).layout + assert_equal :other, RubyVM::Shape.of([]).layout + assert_equal :other, RubyVM::Shape.of("hello").layout + assert_equal :other, RubyVM::Shape.of(/foo/).layout + assert_equal :other, RubyVM::Shape.of(2..3).layout + assert_equal :other, RubyVM::Shape.of(2**67).layout + assert_equal :other, RubyVM::Shape.of(:"aaroniscool#{123}").layout + end + def test_str_has_root_shape assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of("")) end diff --git a/test/rubygems/test_gem_commands_cert_command.rb b/test/rubygems/test_gem_commands_cert_command.rb index 39fda73eba..ed1a1c8627 100644 --- a/test/rubygems/test_gem_commands_cert_command.rb +++ b/test/rubygems/test_gem_commands_cert_command.rb @@ -31,14 +31,6 @@ class TestGemCommandsCertCommand < Gem::TestCase @cmd = Gem::Commands::CertCommand.new @trust_dir = Gem::Security.trust_dir - - @cleanup = [] - end - - def teardown - FileUtils.rm_f(@cleanup) - - super end def test_certificates_matching @@ -661,8 +653,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis assert_equal "/CN=nobody/DC=example", EXPIRED_PUBLIC_CERT.issuer.to_s - tmp_expired_cert_file = File.join(Dir.tmpdir, File.basename(EXPIRED_PUBLIC_CERT_FILE)) - @cleanup << tmp_expired_cert_file + tmp_expired_cert_file = File.join(@tempdir, File.basename(EXPIRED_PUBLIC_CERT_FILE)) File.write(tmp_expired_cert_file, File.read(EXPIRED_PUBLIC_CERT_FILE)) @cmd.handle_options %W[ @@ -694,8 +685,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis assert_equal "/CN=nobody/DC=example", EXPIRED_PUBLIC_CERT.issuer.to_s - tmp_expired_cert_file = File.join(Dir.tmpdir, File.basename(EXPIRED_PUBLIC_CERT_FILE)) - @cleanup << tmp_expired_cert_file + tmp_expired_cert_file = File.join(@tempdir, File.basename(EXPIRED_PUBLIC_CERT_FILE)) File.write(tmp_expired_cert_file, File.read(EXPIRED_PUBLIC_CERT_FILE)) @cmd.handle_options %W[ diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index 384ff85d1f..71230c32b7 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -4,7 +4,6 @@ source "https://rubygems.org" gem "rack", "~> 3.1" gem "rack-test", "~> 2.1" -gem "compact_index", "~> 0.15.0" gem "sinatra", "~> 4.1" gem "rake", "~> 13.1" gem "builder", "~> 3.2" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index a8352f3070..0b9ac34162 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -57,7 +57,6 @@ PLATFORMS DEPENDENCIES builder (~> 3.2) - compact_index (~> 0.15.0) concurrent-ruby etc fiddle diff --git a/variable.c b/variable.c index 857d870413..687fa03631 100644 --- a/variable.c +++ b/variable.c @@ -1343,7 +1343,7 @@ rb_free_generic_ivar(VALUE obj) } } } - RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | ROOT_SHAPE_ID); } } @@ -1395,7 +1395,7 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie } } - RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(fields_obj)); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | RBASIC_SHAPE_ID(fields_obj)); } void @@ -1710,6 +1710,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) size_t trailing_fields = new_fields_count - removed_index; MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields); + RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); if (FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP)) { @@ -1732,7 +1733,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) } } - RBASIC_SET_SHAPE_ID(obj, next_shape_id); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | next_shape_id); if (fields_obj != original_fields_obj) { switch (type) { case T_OBJECT: @@ -1843,7 +1844,7 @@ imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID f RUBY_ASSERT(field_name); st_insert(table, (st_data_t)field_name, (st_data_t)val); RB_OBJ_WRITTEN(fields_obj, Qundef, val); - RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(target_shape_id)); } else { attr_index_t index = RSHAPE_INDEX(target_shape_id); @@ -1855,7 +1856,7 @@ imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID f RB_OBJ_WRITE(fields_obj, &table[index], val); if (index >= RSHAPE_LEN(current_shape_id)) { - RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(target_shape_id)); } } @@ -2010,11 +2011,12 @@ rb_obj_freeze_inline(VALUE x) RB_FL_UNSET_RAW(x, FL_USER2 | FL_USER3); // STR_CHILLED } + // rb_obj_freeze_inline(String) shape_id_t shape_id = rb_obj_shape_transition_frozen(x); switch (BUILTIN_TYPE(x)) { case T_CLASS: case T_MODULE: - RBASIC_SET_SHAPE_ID(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(x), shape_id); + rb_obj_freeze_inline(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(x)); // FIXME: How to do multi-shape? RBASIC_SET_SHAPE_ID(x, shape_id); break; @@ -2303,7 +2305,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) } if (!RSHAPE_LEN(dest_shape_id)) { - RBASIC_SET_SHAPE_ID(dest, dest_shape_id); + RBASIC_SET_SHAPE_ID(dest, rb_shape_layout(RBASIC_SHAPE_ID(dest)) | dest_shape_id); return; } @@ -4636,6 +4638,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc } if (new_ivar) { + RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } } @@ -4658,6 +4661,7 @@ complex: RB_OBJ_WRITTEN(fields_obj, Qundef, val); if (fields_obj != original_fields_obj) { + RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } } @@ -4684,7 +4688,7 @@ class_ivar_set(VALUE obj, ID id, VALUE val, bool *new_ivar) // TODO: What should we set as the T_CLASS shape_id? // In most case we can replicate the single `fields_obj` shape // but in namespaced case? Perhaps INVALID_SHAPE_ID? - RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | RBASIC_SHAPE_ID(new_fields_obj)); return index; } @@ -4709,7 +4713,7 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src); if (fields_obj) { RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj)); - RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src)); + RBASIC_SET_SHAPE_ID(dst, rb_shape_layout(RBASIC_SHAPE_ID(dst)) | RBASIC_SHAPE_ID(src)); } } @@ -574,13 +574,6 @@ jit_exec(rb_execution_context_t *ec) rb_jit_func_t func = zjit_compile(ec); if (func) { VALUE result = ((rb_zjit_func_t)zjit_entry)(ec, ec->cfp, func); - // Materialize any remaining lightweight ZJIT frames on side exit. - // This is done here (once per JIT entry) instead of in each side exit - // to reduce generated code size. - if (UNDEF_P(result)) { - ec->cfp->jit_return = 0; // exit code already cleared most fields except jit_return - rb_zjit_materialize_frames(ec, ec->cfp); - } return result; } } @@ -189,10 +189,6 @@ default: \ rb_jit_func_t func = zjit_compile(ec); \ if (func) { \ val = zjit_entry(ec, ec->cfp, func); \ - if (UNDEF_P(val)) { \ - ec->cfp->jit_return = 0; \ - rb_zjit_materialize_frames(ec, ec->cfp); \ - } \ } \ } \ } \ diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 75d5023566..f515662bf0 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1412,7 +1412,8 @@ vm_setivar_class(VALUE obj, VALUE val, rb_setivar_cache cache) RB_OBJ_WRITE(fields_obj, &rb_imemo_fields_ptr(fields_obj)[cache.index], val); if (shape_id != dest_shape_id) { - RBASIC_SET_SHAPE_ID(obj, dest_shape_id); + // The dest_shape_id comes from the fields_obj + RBASIC_SET_SHAPE_ID(obj, SHAPE_ID_LAYOUT_RCLASS | (dest_shape_id & ~SHAPE_ID_LAYOUT_MASK)); RBASIC_SET_SHAPE_ID(fields_obj, dest_shape_id); } @@ -1437,7 +1438,9 @@ vm_setivar_default(VALUE obj, ID id, VALUE val, rb_setivar_cache cache) if (shape_id != dest_shape_id) { RBASIC_SET_SHAPE_ID(obj, dest_shape_id); - RBASIC_SET_SHAPE_ID(fields_obj, dest_shape_id); + // The dest_shape_id comes from the owner, but fields_obj must always + // have layout RObject, so give the fields_object the right layout. + RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(dest_shape_id)); } RB_DEBUG_COUNTER_INC(ivar_set_ic_hit); @@ -6080,8 +6083,23 @@ rb_vm_invokesuper(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, CALL_ { stack_check(ec); - VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, true); - VALUE val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_super); + struct rb_callinfo adjusted_ci = VM_CI_ON_STACK(vm_ci_mid(cd->ci), + vm_ci_flag(cd->ci), + vm_ci_argc(cd->ci), + vm_ci_kwarg(cd->ci)); + const struct rb_callcache *original_cc = rbimpl_atomic_ptr_load((void **)&cd->cc, RBIMPL_ATOMIC_ACQUIRE); + struct rb_call_data adjusted_cd = { + .ci = &adjusted_ci, + .cc = original_cc, + }; + + VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), adjusted_cd.ci, blockiseq, true); + VALUE val = vm_sendish(ec, GET_CFP(), &adjusted_cd, bh, mexp_search_super); + + if (original_cc != adjusted_cd.cc && vm_cc_markable(adjusted_cd.cc)) { + rbimpl_atomic_ptr_store((volatile void **)&cd->cc, (void *)adjusted_cd.cc, RBIMPL_ATOMIC_RELEASE); + RB_OBJ_WRITTEN(CFP_ISEQ(GET_CFP()), Qundef, adjusted_cd.cc); + } VM_EXEC(ec, val); return val; diff --git a/win32/win32.c b/win32/win32.c index f25139ca8b..e3a3df71f6 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -6603,7 +6603,7 @@ rb_w32_pipe(int fds[2]) memcpy(name, prefix, width_of_prefix); snprintf(name + width_of_prefix, width_of_ids, "%.*"PRI_PIDT_PREFIX"x-%.*lx", - width_of_pid, rb_w32_getpid(), width_of_serial, InterlockedIncrement(&serial)-1); + width_of_pid, rb_w32_getpid(), width_of_serial, (unsigned long)(InterlockedIncrement(&serial)-1)); sec.nLength = sizeof(sec); sec.lpSecurityDescriptor = NULL; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index ae372711d7..272c10fde9 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -223,7 +223,7 @@ pub const RUBY_FL_USHIFT: ruby_fl_ushift = 12; pub type ruby_fl_ushift = u32; pub const RUBY_FL_WB_PROTECTED: ruby_fl_type = 32; pub const RUBY_FL_PROMOTED: ruby_fl_type = 32; -pub const RUBY_FL_USERPRIV0: ruby_fl_type = 64; +pub const RUBY_FL_UNUSED6: ruby_fl_type = 64; pub const RUBY_FL_FINALIZE: ruby_fl_type = 128; pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 25fa0cc151..a417df300a 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -4,7 +4,7 @@ use std::mem::take; use std::rc::Rc; use crate::bitset::BitSet; use crate::codegen::{local_size_and_idx_to_ep_offset, perf_symbol_range_start, perf_symbol_range_end}; -use crate::cruby::{IseqPtr, Qundef, RUBY_OFFSET_CFP_ISEQ, RUBY_OFFSET_CFP_JIT_RETURN, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; +use crate::cruby::{IseqPtr, RUBY_OFFSET_CFP_ISEQ, RUBY_OFFSET_CFP_JIT_RETURN, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; use crate::hir::{Invariant, SideExitReason}; use crate::hir; use crate::options::{TraceExits, PerfMap, get_option}; @@ -13,7 +13,7 @@ use crate::payload::IseqVersionRef; use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; -use crate::state::rb_zjit_record_exit_stack; +use crate::state::{ZJITState, rb_zjit_record_exit_stack}; /// LIR Block ID. Unique ID for each block, and also defined in LIR so /// we can differentiate it from HIR block ids. @@ -1562,23 +1562,6 @@ impl Assembler iter } - /// Return an operand for a basic block argument at a given index. - /// To simplify the implementation, we allocate a fixed register or a stack slot - /// for each basic block argument. - pub fn param_opnd(idx: usize) -> Opnd { - use crate::backend::current::ALLOC_REGS; - use crate::cruby::SIZEOF_VALUE_I32; - - if idx < ALLOC_REGS.len() { - Opnd::Reg(ALLOC_REGS[idx]) - } else { - // With FrameSetup, the address that NATIVE_BASE_PTR points to stores an old value in the register. - // To avoid clobbering it, we need to start from the next slot, and we also reserve one space for - // JITFrame, hence `+ 2` for the index. - Opnd::mem(64, NATIVE_BASE_PTR, (idx - ALLOC_REGS.len() + 2) as i32 * -SIZEOF_VALUE_I32) - } - } - pub fn linearize_instructions(&self) -> Vec<Insn> { // Emit instructions with labels, expanding branch parameters let mut insns = Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY); @@ -2040,11 +2023,25 @@ impl Assembler if self.basic_blocks[block_id.0].is_dummy() { continue; } let params = self.basic_blocks[block_id.0].parameters.clone(); + // JIT-to-JIT entries that would need more argument registers should + // be unreachable because can_direct_send() refuses to call them. + // Keep compiling the function body, but make the unsupported entry + // abort if control ever reaches it. TODO: Remove this (Shopify/ruby#916) + if params.len() > C_ARG_OPNDS.len() { + let insert_pos = self.basic_blocks[block_id.0].insns.iter() + .position(|insn| matches!(insn, Insn::FrameSetup { .. })) + .or_else(|| self.basic_blocks[block_id.0].insns.iter().position(|insn| matches!(insn, Insn::Label(_))).map(|idx| idx + 1)) + .unwrap_or(0); + self.basic_blocks[block_id.0].insns.insert(insert_pos, Insn::Abort); + self.basic_blocks[block_id.0].insn_ids.insert(insert_pos, None); + continue; + } + // Rewrite VRegs to physical registers before sequentialization // so the parcopy algorithm can detect physical register conflicts. let reg_copies: Vec<parcopy::RegisterCopy<Opnd>> = params.iter().enumerate() .map(|(i, param)| parcopy::RegisterCopy::<Opnd> { - source: Assembler::param_opnd(i), + source: C_ARG_OPNDS[i], destination: Self::rewritten_opnd(*param, assignments), }) .filter(|copy| copy.source != copy.destination) @@ -2380,7 +2377,7 @@ impl Assembler asm_comment!(asm, "save cfp->iseq"); asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_ISEQ), VALUE::from(*iseq).into()); - // cfp->block_code and cfp->jit_return are cleared by the caller jit_exec() or JIT_EXEC() + // cfp->block_code and cfp->jit_return are cleared by the materialize_exit trampoline if !stack.is_empty() { asm_comment!(asm, "write stack slots: {}", join_opnds(&stack, ", ")); @@ -2400,8 +2397,7 @@ impl Assembler /// Tear down the JIT frame and return to the interpreter. fn compile_exit_return(asm: &mut Assembler) { asm_comment!(asm, "exit to the interpreter"); - asm.frame_teardown(&[]); // matching the setup in gen_entry_point() - asm.cret(Opnd::UImm(Qundef.as_u64())); + asm.jmp(Target::CodePtr(ZJITState::get_materialize_exit_trampoline())); } fn compile_exit_recompile(asm: &mut Assembler, exit: &SideExit) { @@ -2434,7 +2430,7 @@ impl Assembler compile_exit_save_state(asm, exit); if trace_reason.is_some() || exit.recompile.is_some() { // Clear cfp->jit_return to prepare for a C call. Normally, cfp->jit_return - // is cleared by the caller jit_exec() or JIT_EXEC(), but if we're about to + // is cleared by the materialize_exit trampoline, but if we're about to // make a C call, we need to clear any stale JITFrame. asm_comment!(asm, "clear cfp->jit_return"); asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_JIT_RETURN), 0.into()); @@ -4217,8 +4213,8 @@ mod tests { let (assignments, _) = asm.linear_scan(intervals.clone(), 5, &preferred_registers); // Entry block b1 has parameters [v0, v1]. - // With 5 registers: v0 -> Reg(0) = regs[0], arrival = param_opnd(0) = regs[0] -> self-move, filtered - // v1 -> Reg(1) = regs[1], arrival = param_opnd(1) = regs[1] -> self-move, filtered + // With 5 registers: v0 -> Reg(0) = regs[0], arrival = C_ARG_OPNDS[0] = regs[0] -> self-move, filtered + // v1 -> Reg(1) = regs[1], arrival = C_ARG_OPNDS[1] = regs[1] -> self-move, filtered // Before resolve_ssa, b1 has: [Label, Jmp] = 2 insns assert_eq!(asm.basic_blocks[b1.0].insns.len(), 2); @@ -4243,6 +4239,31 @@ mod tests { } } + #[test] + fn test_resolve_ssa_entry_params_too_many_abort() { + let mut asm = Assembler::new(); + let block = asm.new_block(hir::BlockId(0), true, 0); + asm.set_current_block(block); + let label = asm.new_label("bb0"); + asm.write_label(label); + + for _ in 0..=C_ARG_OPNDS.len() { + let param = asm.new_vreg(64); + asm.basic_blocks[block.0].add_parameter(param); + } + asm.basic_blocks[block.0].push_insn(Insn::CRet(Opnd::UImm(0))); + + let live_in = asm.analyze_liveness(); + asm.number_instructions(0); + let intervals = asm.build_intervals(live_in); + let preferred_registers = asm.preferred_register_assignments(&intervals); + let (assignments, _) = asm.linear_scan(intervals.clone(), 5, &preferred_registers); + + asm.resolve_ssa(&intervals, &assignments); + + assert!(matches!(asm.basic_blocks[block.0].insns[1], Insn::Abort)); + } + fn build_critical_edge() -> (Assembler, Opnd, Opnd, Opnd, Opnd, Opnd, BlockId, BlockId, BlockId) { let mut asm = Assembler::new(); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7c893a72fe..d5d381acfa 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2551,25 +2551,6 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard asm.cmp(klass, Opnd::Value(expected_class)); asm.jne(jit, side_exit); - } else if guard_type.is_subtype(types::TData) { - let side = side_exit(jit, state, GuardType(guard_type)); - - // Check special constant - asm.test(val, Opnd::UImm(RUBY_IMMEDIATE_MASK as u64)); - asm.jnz(jit, side.clone()); - - // Check false - asm.cmp(val, Qfalse.into()); - asm.je(jit, side.clone()); - - // Check the T_DATA builtin type. - let val = asm.load_mem(val); - let flags = asm.load(Opnd::mem(VALUE_BITS, val, RUBY_OFFSET_RBASIC_FLAGS)); - let mask = RUBY_T_MASK.to_usize(); - let expected = RUBY_T_DATA.to_usize(); - let masked = asm.and(flags, mask.into()); - asm.cmp(masked, expected.into()); - asm.jne(jit, side); } else if let Some(builtin_type) = guard_type.builtin_type_equivalent() { let side = side_exit(jit, state, GuardType(guard_type)); @@ -3098,7 +3079,7 @@ c_callable! { // If we side-exit from function_stub_hit (before JIT code runs), we need to set them here. fn prepare_for_exit(iseq: IseqPtr, cfp: CfpPtr, sp: *mut VALUE, argc: u16, num_opts_filled: u16, compile_error: &CompileError) { unsafe { - // Caller frames are materialized by jit_exec() after the entry trampoline returns. + // Caller frames are materialized by the materialize_exit trampoline before unwinding native frames. // The current frame's pc and iseq are already set by function_stub_hit before this point. // Set SP which gen_push_frame() doesn't set @@ -3186,7 +3167,7 @@ c_callable! { unsafe { Rc::increment_strong_count(iseq_call_ptr as *const IseqCall); } prepare_for_exit(iseq, cfp, sp, argc, num_opts_filled, compile_error); - return ZJITState::get_exit_trampoline_with_counter().raw_ptr(cb); + return ZJITState::get_materialize_exit_trampoline_with_counter().raw_ptr(cb); } // Otherwise, attempt to compile the ISEQ. We have to mark_all_executable() beyond this point. @@ -3201,7 +3182,7 @@ c_callable! { unsafe { Rc::increment_strong_count(iseq_call_ptr as *const IseqCall); } prepare_for_exit(iseq, cfp, sp, argc, num_opts_filled, &compile_error); - ZJITState::get_exit_trampoline_with_counter() + ZJITState::get_materialize_exit_trampoline_with_counter() }); cb.mark_all_executable(); code_ptr.raw_ptr(cb) @@ -3332,14 +3313,34 @@ pub fn gen_exit_trampoline(cb: &mut CodeBlock) -> Result<CodePtr, CompileError> }) } -/// Generate a trampoline that increments exit_compilation_failure and jumps to exit_trampoline. -pub fn gen_exit_trampoline_with_counter(cb: &mut CodeBlock, exit_trampoline: CodePtr) -> Result<CodePtr, CompileError> { +/// Generate a trampoline that materializes ZJIT frames before unwinding native frames. +pub fn gen_materialize_exit_trampoline(cb: &mut CodeBlock, exit_trampoline: CodePtr) -> Result<CodePtr, CompileError> { + unsafe extern "C" { + fn rb_zjit_materialize_frames(ec: EcPtr, cfp: CfpPtr); + } + let mut asm = Assembler::new(); - asm.new_block_without_id("exit_trampoline_with_counter"); + asm.new_block_without_id("materialize_exit_trampoline"); + + asm_comment!(asm, "materialize ZJIT frames"); + asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_JIT_RETURN), 0.into()); + asm_ccall!(asm, rb_zjit_materialize_frames, EC, CFP); + asm.jmp(Target::CodePtr(exit_trampoline)); + + asm.compile(cb).map(|(code_ptr, gc_offsets)| { + assert_eq!(gc_offsets.len(), 0); + code_ptr + }) +} + +/// Generate a trampoline that increments exit_compilation_failure and jumps to materialize_exit_trampoline. +pub fn gen_materialize_exit_trampoline_with_counter(cb: &mut CodeBlock, materialize_exit_trampoline: CodePtr) -> Result<CodePtr, CompileError> { + let mut asm = Assembler::new(); + asm.new_block_without_id("materialize_exit_trampoline_with_counter"); asm_comment!(asm, "function stub exit trampoline"); gen_incr_counter(&mut asm, exit_compile_error); - asm.jmp(Target::CodePtr(exit_trampoline)); + asm.jmp(Target::CodePtr(materialize_exit_trampoline)); asm.compile(cb).map(|(code_ptr, gc_offsets)| { assert_eq!(gc_offsets.len(), 0); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index dad29087be..08c502b0d8 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -286,7 +286,7 @@ pub const RUBY_FL_USHIFT: ruby_fl_ushift = 12; pub type ruby_fl_ushift = u32; pub const RUBY_FL_WB_PROTECTED: ruby_fl_type = 32; pub const RUBY_FL_PROMOTED: ruby_fl_type = 32; -pub const RUBY_FL_USERPRIV0: ruby_fl_type = 64; +pub const RUBY_FL_UNUSED6: ruby_fl_type = 64; pub const RUBY_FL_FINALIZE: ruby_fl_type = 128; pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; @@ -347,7 +347,6 @@ pub const RUBY_TYPED_FREE_IMMEDIATELY: rbimpl_typeddata_flags = 1; pub const RUBY_TYPED_EMBEDDABLE: rbimpl_typeddata_flags = 2; pub const RUBY_TYPED_FROZEN_SHAREABLE: rbimpl_typeddata_flags = 256; pub const RUBY_TYPED_WB_PROTECTED: rbimpl_typeddata_flags = 32; -pub const RUBY_TYPED_FL_IS_TYPED_DATA: rbimpl_typeddata_flags = 64; pub const RUBY_TYPED_DECL_MARKING: rbimpl_typeddata_flags = 16384; pub type rbimpl_typeddata_flags = u32; pub type rb_event_flag_t = u32; @@ -1492,8 +1491,13 @@ pub const SHAPE_ID_HEAP_INDEX_MASK: shape_id_fl_type = 7864320; pub const SHAPE_ID_FL_COMPLEX: shape_id_fl_type = 8388608; pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 16777216; pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 33554432; +pub const SHAPE_ID_LAYOUT_ROBJECT: shape_id_fl_type = 0; +pub const SHAPE_ID_LAYOUT_RCLASS: shape_id_fl_type = 67108864; +pub const SHAPE_ID_LAYOUT_RDATA: shape_id_fl_type = 134217728; +pub const SHAPE_ID_LAYOUT_OTHER: shape_id_fl_type = 201326592; +pub const SHAPE_ID_LAYOUT_MASK: shape_id_fl_type = 201326592; pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 50331648; -pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 66584576; +pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 267911168; pub type shape_id_fl_type = u32; pub const CONST_DEPRECATED: rb_const_flag_t = 256; pub const CONST_VISIBILITY_MASK: rb_const_flag_t = 255; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 86078f4ac8..6911129ad3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1984,9 +1984,10 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::FixnumAref { recv, index } => write!(f, "FixnumAref {recv}, {index}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::CondBranch { val, if_true, if_false } => { write!(f, "CondBranch {val}, {if_true}, {if_false}") }, - Insn::SendDirect { recv, cd, iseq, args, block, .. } => { + Insn::SendDirect { recv, cme, iseq, args, block, .. } => { let blockiseq = block.map(|bh| match bh { BlockHandler::BlockIseq(iseq) => iseq, BlockHandler::BlockArg => unreachable!() }); - write!(f, "SendDirect {recv}, {:p}, :{} ({:?})", self.ptr_map.map_ptr(&blockiseq), ruby_call_method_name(*cd), self.ptr_map.map_ptr(iseq))?; + let method_name = unsafe { (**cme).called_id }; + write!(f, "SendDirect {recv}, {:p}, :{} ({:?})", self.ptr_map.map_ptr(&blockiseq), method_name, self.ptr_map.map_ptr(iseq))?; for arg in args { write!(f, ", {arg}")?; } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 6aa10dea55..d7327975ce 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -444,8 +444,9 @@ impl Type { Some(cruby::RUBY_T_STRING) } else if self.bit_equal(types::Hash) { Some(cruby::RUBY_T_HASH) + } else if self.bit_equal(types::TData) { + Some(cruby::RUBY_T_DATA) } else { - // T_DATA uses a specialized guard, so not here. None } } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 2dbc2140e1..da09d09314 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -1,6 +1,6 @@ //! Runtime state of ZJIT. -use crate::codegen::{gen_entry_trampoline, gen_exit_trampoline, gen_exit_trampoline_with_counter, gen_function_stub_hit_trampoline}; +use crate::codegen::{gen_entry_trampoline, gen_exit_trampoline, gen_function_stub_hit_trampoline, gen_materialize_exit_trampoline, gen_materialize_exit_trampoline_with_counter}; use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, src_loc, EcPtr, Qnil, Qtrue, rb_profile_frames, rb_profile_frame_full_label, rb_profile_frame_absolute_path, rb_profile_frame_path, VALUE, VM_INSTRUCTION_SIZE, with_vm_lock, rust_str_to_id, rb_funcallv, rb_const_get, rb_cRubyVM}; use crate::cruby_methods; use cruby::{ID, rb_callable_method_entry, get_def_method_serial, rb_gc_register_mark_object, ruby_str_to_rust_string_result}; @@ -51,8 +51,11 @@ pub struct ZJITState { /// Trampoline to side-exit without restoring PC or the stack exit_trampoline: CodePtr, - /// Trampoline to side-exit and increment exit_compilation_failure - exit_trampoline_with_counter: CodePtr, + /// Trampoline to materialize JIT frames before side-exiting + materialize_exit_trampoline: CodePtr, + + /// Trampoline to materialize JIT frames and increment exit_compilation_failure + materialize_exit_trampoline_with_counter: CodePtr, /// Trampoline to call function_stub_hit function_stub_hit_trampoline: CodePtr, @@ -126,6 +129,7 @@ impl ZJITState { let entry_trampoline = gen_entry_trampoline(&mut cb).unwrap().raw_ptr(&cb); let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap(); + let materialize_exit_trampoline = gen_materialize_exit_trampoline(&mut cb, exit_trampoline).unwrap(); let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap(); let perfetto_tracer = if get_option!(trace_side_exits).is_some() || get_option!(trace_compiles) || get_option!(trace_invalidation) { @@ -144,8 +148,9 @@ impl ZJITState { assert_compiles: false, method_annotations, exit_trampoline, + materialize_exit_trampoline, + materialize_exit_trampoline_with_counter: materialize_exit_trampoline, function_stub_hit_trampoline, - exit_trampoline_with_counter: exit_trampoline, full_frame_cfunc_counter_pointers: HashMap::new(), not_annotated_frame_cfunc_counter_pointers: HashMap::new(), ccall_counter_pointers: HashMap::new(), @@ -160,8 +165,8 @@ impl ZJITState { // on the counter, so ZJIT_STATE needs to be initialized first. if get_option!(stats) { let cb = ZJITState::get_code_block(); - let code_ptr = gen_exit_trampoline_with_counter(cb, exit_trampoline).unwrap(); - ZJITState::get_instance().exit_trampoline_with_counter = code_ptr; + let code_ptr = gen_materialize_exit_trampoline_with_counter(cb, materialize_exit_trampoline).unwrap(); + ZJITState::get_instance().materialize_exit_trampoline_with_counter = code_ptr; } entry_trampoline @@ -288,9 +293,14 @@ impl ZJITState { ZJITState::get_instance().exit_trampoline } - /// Return a code pointer to the exit trampoline for function stubs - pub fn get_exit_trampoline_with_counter() -> CodePtr { - ZJITState::get_instance().exit_trampoline_with_counter + /// Return a code pointer to the materialize_exit trampoline + pub fn get_materialize_exit_trampoline() -> CodePtr { + ZJITState::get_instance().materialize_exit_trampoline + } + + /// Return a code pointer to the materialize_exit trampoline for function stubs + pub fn get_materialize_exit_trampoline_with_counter() -> CodePtr { + ZJITState::get_instance().materialize_exit_trampoline_with_counter } /// Return a code pointer to the function stub hit trampoline |
