diff options
Diffstat (limited to 'ext')
171 files changed, 8980 insertions, 8226 deletions
diff --git a/ext/-test-/asan/asan.c b/ext/-test-/asan/asan.c deleted file mode 100644 index 45b6253fda..0000000000 --- a/ext/-test-/asan/asan.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "ruby/ruby.h" - -static VALUE -asan_enabled_p(VALUE self) -{ -#if defined(__has_feature) - /* clang uses __has_feature for determining asan */ - return __has_feature(address_sanitizer) ? Qtrue : Qfalse; -#elif defined(__SANITIZE_ADDRESS__) - /* GCC sets __SANITIZE_ADDRESS__ for determining asan */ - return Qtrue; -#else - return Qfalse; -#endif -} - -void -Init_asan(void) -{ - VALUE m = rb_define_module("Test"); - VALUE c = rb_define_class_under(m, "ASAN", rb_cObject); - rb_define_singleton_method(c, "enabled?", asan_enabled_p, 0); -} - diff --git a/ext/-test-/asan/depend b/ext/-test-/asan/depend deleted file mode 100644 index 93cdc739ec..0000000000 --- a/ext/-test-/asan/depend +++ /dev/null @@ -1,162 +0,0 @@ -# AUTOGENERATED DEPENDENCIES START -asan.o: $(RUBY_EXTCONF_H) -asan.o: $(arch_hdrdir)/ruby/config.h -asan.o: $(hdrdir)/ruby/assert.h -asan.o: $(hdrdir)/ruby/backward.h -asan.o: $(hdrdir)/ruby/backward/2/assume.h -asan.o: $(hdrdir)/ruby/backward/2/attributes.h -asan.o: $(hdrdir)/ruby/backward/2/bool.h -asan.o: $(hdrdir)/ruby/backward/2/inttypes.h -asan.o: $(hdrdir)/ruby/backward/2/limits.h -asan.o: $(hdrdir)/ruby/backward/2/long_long.h -asan.o: $(hdrdir)/ruby/backward/2/stdalign.h -asan.o: $(hdrdir)/ruby/backward/2/stdarg.h -asan.o: $(hdrdir)/ruby/defines.h -asan.o: $(hdrdir)/ruby/intern.h -asan.o: $(hdrdir)/ruby/internal/abi.h -asan.o: $(hdrdir)/ruby/internal/anyargs.h -asan.o: $(hdrdir)/ruby/internal/arithmetic.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/char.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/double.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/int.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/long.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/short.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h -asan.o: $(hdrdir)/ruby/internal/assume.h -asan.o: $(hdrdir)/ruby/internal/attr/alloc_size.h -asan.o: $(hdrdir)/ruby/internal/attr/artificial.h -asan.o: $(hdrdir)/ruby/internal/attr/cold.h -asan.o: $(hdrdir)/ruby/internal/attr/const.h -asan.o: $(hdrdir)/ruby/internal/attr/constexpr.h -asan.o: $(hdrdir)/ruby/internal/attr/deprecated.h -asan.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h -asan.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h -asan.o: $(hdrdir)/ruby/internal/attr/error.h -asan.o: $(hdrdir)/ruby/internal/attr/flag_enum.h -asan.o: $(hdrdir)/ruby/internal/attr/forceinline.h -asan.o: $(hdrdir)/ruby/internal/attr/format.h -asan.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h -asan.o: $(hdrdir)/ruby/internal/attr/noalias.h -asan.o: $(hdrdir)/ruby/internal/attr/nodiscard.h -asan.o: $(hdrdir)/ruby/internal/attr/noexcept.h -asan.o: $(hdrdir)/ruby/internal/attr/noinline.h -asan.o: $(hdrdir)/ruby/internal/attr/nonnull.h -asan.o: $(hdrdir)/ruby/internal/attr/noreturn.h -asan.o: $(hdrdir)/ruby/internal/attr/packed_struct.h -asan.o: $(hdrdir)/ruby/internal/attr/pure.h -asan.o: $(hdrdir)/ruby/internal/attr/restrict.h -asan.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h -asan.o: $(hdrdir)/ruby/internal/attr/warning.h -asan.o: $(hdrdir)/ruby/internal/attr/weakref.h -asan.o: $(hdrdir)/ruby/internal/cast.h -asan.o: $(hdrdir)/ruby/internal/compiler_is.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/apple.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/clang.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/intel.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h -asan.o: $(hdrdir)/ruby/internal/compiler_since.h -asan.o: $(hdrdir)/ruby/internal/config.h -asan.o: $(hdrdir)/ruby/internal/constant_p.h -asan.o: $(hdrdir)/ruby/internal/core.h -asan.o: $(hdrdir)/ruby/internal/core/rarray.h -asan.o: $(hdrdir)/ruby/internal/core/rbasic.h -asan.o: $(hdrdir)/ruby/internal/core/rbignum.h -asan.o: $(hdrdir)/ruby/internal/core/rclass.h -asan.o: $(hdrdir)/ruby/internal/core/rdata.h -asan.o: $(hdrdir)/ruby/internal/core/rfile.h -asan.o: $(hdrdir)/ruby/internal/core/rhash.h -asan.o: $(hdrdir)/ruby/internal/core/robject.h -asan.o: $(hdrdir)/ruby/internal/core/rregexp.h -asan.o: $(hdrdir)/ruby/internal/core/rstring.h -asan.o: $(hdrdir)/ruby/internal/core/rstruct.h -asan.o: $(hdrdir)/ruby/internal/core/rtypeddata.h -asan.o: $(hdrdir)/ruby/internal/ctype.h -asan.o: $(hdrdir)/ruby/internal/dllexport.h -asan.o: $(hdrdir)/ruby/internal/dosish.h -asan.o: $(hdrdir)/ruby/internal/error.h -asan.o: $(hdrdir)/ruby/internal/eval.h -asan.o: $(hdrdir)/ruby/internal/event.h -asan.o: $(hdrdir)/ruby/internal/fl_type.h -asan.o: $(hdrdir)/ruby/internal/gc.h -asan.o: $(hdrdir)/ruby/internal/glob.h -asan.o: $(hdrdir)/ruby/internal/globals.h -asan.o: $(hdrdir)/ruby/internal/has/attribute.h -asan.o: $(hdrdir)/ruby/internal/has/builtin.h -asan.o: $(hdrdir)/ruby/internal/has/c_attribute.h -asan.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h -asan.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h -asan.o: $(hdrdir)/ruby/internal/has/extension.h -asan.o: $(hdrdir)/ruby/internal/has/feature.h -asan.o: $(hdrdir)/ruby/internal/has/warning.h -asan.o: $(hdrdir)/ruby/internal/intern/array.h -asan.o: $(hdrdir)/ruby/internal/intern/bignum.h -asan.o: $(hdrdir)/ruby/internal/intern/class.h -asan.o: $(hdrdir)/ruby/internal/intern/compar.h -asan.o: $(hdrdir)/ruby/internal/intern/complex.h -asan.o: $(hdrdir)/ruby/internal/intern/cont.h -asan.o: $(hdrdir)/ruby/internal/intern/dir.h -asan.o: $(hdrdir)/ruby/internal/intern/enum.h -asan.o: $(hdrdir)/ruby/internal/intern/enumerator.h -asan.o: $(hdrdir)/ruby/internal/intern/error.h -asan.o: $(hdrdir)/ruby/internal/intern/eval.h -asan.o: $(hdrdir)/ruby/internal/intern/file.h -asan.o: $(hdrdir)/ruby/internal/intern/hash.h -asan.o: $(hdrdir)/ruby/internal/intern/io.h -asan.o: $(hdrdir)/ruby/internal/intern/load.h -asan.o: $(hdrdir)/ruby/internal/intern/marshal.h -asan.o: $(hdrdir)/ruby/internal/intern/numeric.h -asan.o: $(hdrdir)/ruby/internal/intern/object.h -asan.o: $(hdrdir)/ruby/internal/intern/parse.h -asan.o: $(hdrdir)/ruby/internal/intern/proc.h -asan.o: $(hdrdir)/ruby/internal/intern/process.h -asan.o: $(hdrdir)/ruby/internal/intern/random.h -asan.o: $(hdrdir)/ruby/internal/intern/range.h -asan.o: $(hdrdir)/ruby/internal/intern/rational.h -asan.o: $(hdrdir)/ruby/internal/intern/re.h -asan.o: $(hdrdir)/ruby/internal/intern/ruby.h -asan.o: $(hdrdir)/ruby/internal/intern/select.h -asan.o: $(hdrdir)/ruby/internal/intern/select/largesize.h -asan.o: $(hdrdir)/ruby/internal/intern/set.h -asan.o: $(hdrdir)/ruby/internal/intern/signal.h -asan.o: $(hdrdir)/ruby/internal/intern/sprintf.h -asan.o: $(hdrdir)/ruby/internal/intern/string.h -asan.o: $(hdrdir)/ruby/internal/intern/struct.h -asan.o: $(hdrdir)/ruby/internal/intern/thread.h -asan.o: $(hdrdir)/ruby/internal/intern/time.h -asan.o: $(hdrdir)/ruby/internal/intern/variable.h -asan.o: $(hdrdir)/ruby/internal/intern/vm.h -asan.o: $(hdrdir)/ruby/internal/interpreter.h -asan.o: $(hdrdir)/ruby/internal/iterator.h -asan.o: $(hdrdir)/ruby/internal/memory.h -asan.o: $(hdrdir)/ruby/internal/method.h -asan.o: $(hdrdir)/ruby/internal/module.h -asan.o: $(hdrdir)/ruby/internal/newobj.h -asan.o: $(hdrdir)/ruby/internal/scan_args.h -asan.o: $(hdrdir)/ruby/internal/special_consts.h -asan.o: $(hdrdir)/ruby/internal/static_assert.h -asan.o: $(hdrdir)/ruby/internal/stdalign.h -asan.o: $(hdrdir)/ruby/internal/stdbool.h -asan.o: $(hdrdir)/ruby/internal/stdckdint.h -asan.o: $(hdrdir)/ruby/internal/symbol.h -asan.o: $(hdrdir)/ruby/internal/value.h -asan.o: $(hdrdir)/ruby/internal/value_type.h -asan.o: $(hdrdir)/ruby/internal/variable.h -asan.o: $(hdrdir)/ruby/internal/warning_push.h -asan.o: $(hdrdir)/ruby/internal/xmalloc.h -asan.o: $(hdrdir)/ruby/missing.h -asan.o: $(hdrdir)/ruby/ruby.h -asan.o: $(hdrdir)/ruby/st.h -asan.o: $(hdrdir)/ruby/subst.h -asan.o: asan.c -# AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/asan/extconf.rb b/ext/-test-/asan/extconf.rb deleted file mode 100644 index ec02742b81..0000000000 --- a/ext/-test-/asan/extconf.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'mkmf' -create_makefile('-test-/asan') diff --git a/ext/-test-/bignum/depend b/ext/-test-/bignum/depend index 049f0c7b52..82972f1032 100644 --- a/ext/-test-/bignum/depend +++ b/ext/-test-/bignum/depend @@ -6,6 +6,7 @@ big2str.o: $(hdrdir)/ruby/backward.h big2str.o: $(hdrdir)/ruby/backward/2/assume.h big2str.o: $(hdrdir)/ruby/backward/2/attributes.h big2str.o: $(hdrdir)/ruby/backward/2/bool.h +big2str.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h big2str.o: $(hdrdir)/ruby/backward/2/inttypes.h big2str.o: $(hdrdir)/ruby/backward/2/limits.h big2str.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -159,6 +160,7 @@ big2str.o: $(hdrdir)/ruby/ruby.h big2str.o: $(hdrdir)/ruby/st.h big2str.o: $(hdrdir)/ruby/subst.h big2str.o: $(top_srcdir)/internal/bignum.h +big2str.o: $(top_srcdir)/internal/compilers.h big2str.o: big2str.c bigzero.o: $(RUBY_EXTCONF_H) bigzero.o: $(arch_hdrdir)/ruby/config.h @@ -167,6 +169,7 @@ bigzero.o: $(hdrdir)/ruby/backward.h bigzero.o: $(hdrdir)/ruby/backward/2/assume.h bigzero.o: $(hdrdir)/ruby/backward/2/attributes.h bigzero.o: $(hdrdir)/ruby/backward/2/bool.h +bigzero.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h bigzero.o: $(hdrdir)/ruby/backward/2/inttypes.h bigzero.o: $(hdrdir)/ruby/backward/2/limits.h bigzero.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -320,6 +323,7 @@ bigzero.o: $(hdrdir)/ruby/ruby.h bigzero.o: $(hdrdir)/ruby/st.h bigzero.o: $(hdrdir)/ruby/subst.h bigzero.o: $(top_srcdir)/internal/bignum.h +bigzero.o: $(top_srcdir)/internal/compilers.h bigzero.o: bigzero.c div.o: $(RUBY_EXTCONF_H) div.o: $(arch_hdrdir)/ruby/config.h @@ -328,6 +332,7 @@ div.o: $(hdrdir)/ruby/backward.h div.o: $(hdrdir)/ruby/backward/2/assume.h div.o: $(hdrdir)/ruby/backward/2/attributes.h div.o: $(hdrdir)/ruby/backward/2/bool.h +div.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h div.o: $(hdrdir)/ruby/backward/2/inttypes.h div.o: $(hdrdir)/ruby/backward/2/limits.h div.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -481,6 +486,7 @@ div.o: $(hdrdir)/ruby/ruby.h div.o: $(hdrdir)/ruby/st.h div.o: $(hdrdir)/ruby/subst.h div.o: $(top_srcdir)/internal/bignum.h +div.o: $(top_srcdir)/internal/compilers.h div.o: div.c init.o: $(RUBY_EXTCONF_H) init.o: $(arch_hdrdir)/ruby/config.h @@ -650,6 +656,7 @@ intpack.o: $(hdrdir)/ruby/backward.h intpack.o: $(hdrdir)/ruby/backward/2/assume.h intpack.o: $(hdrdir)/ruby/backward/2/attributes.h intpack.o: $(hdrdir)/ruby/backward/2/bool.h +intpack.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h intpack.o: $(hdrdir)/ruby/backward/2/inttypes.h intpack.o: $(hdrdir)/ruby/backward/2/limits.h intpack.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -803,6 +810,7 @@ intpack.o: $(hdrdir)/ruby/ruby.h intpack.o: $(hdrdir)/ruby/st.h intpack.o: $(hdrdir)/ruby/subst.h intpack.o: $(top_srcdir)/internal/bignum.h +intpack.o: $(top_srcdir)/internal/compilers.h intpack.o: intpack.c mul.o: $(RUBY_EXTCONF_H) mul.o: $(arch_hdrdir)/ruby/config.h @@ -811,6 +819,7 @@ mul.o: $(hdrdir)/ruby/backward.h mul.o: $(hdrdir)/ruby/backward/2/assume.h mul.o: $(hdrdir)/ruby/backward/2/attributes.h mul.o: $(hdrdir)/ruby/backward/2/bool.h +mul.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h mul.o: $(hdrdir)/ruby/backward/2/inttypes.h mul.o: $(hdrdir)/ruby/backward/2/limits.h mul.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -964,6 +973,7 @@ mul.o: $(hdrdir)/ruby/ruby.h mul.o: $(hdrdir)/ruby/st.h mul.o: $(hdrdir)/ruby/subst.h mul.o: $(top_srcdir)/internal/bignum.h +mul.o: $(top_srcdir)/internal/compilers.h mul.o: mul.c str2big.o: $(RUBY_EXTCONF_H) str2big.o: $(arch_hdrdir)/ruby/config.h @@ -972,6 +982,7 @@ str2big.o: $(hdrdir)/ruby/backward.h str2big.o: $(hdrdir)/ruby/backward/2/assume.h str2big.o: $(hdrdir)/ruby/backward/2/attributes.h str2big.o: $(hdrdir)/ruby/backward/2/bool.h +str2big.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h str2big.o: $(hdrdir)/ruby/backward/2/inttypes.h str2big.o: $(hdrdir)/ruby/backward/2/limits.h str2big.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -1125,5 +1136,6 @@ str2big.o: $(hdrdir)/ruby/ruby.h str2big.o: $(hdrdir)/ruby/st.h str2big.o: $(hdrdir)/ruby/subst.h str2big.o: $(top_srcdir)/internal/bignum.h +str2big.o: $(top_srcdir)/internal/compilers.h str2big.o: str2big.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/box/yay1/extconf.rb b/ext/-test-/box/yay1/extconf.rb new file mode 100644 index 0000000000..54387cedf1 --- /dev/null +++ b/ext/-test-/box/yay1/extconf.rb @@ -0,0 +1 @@ +create_makefile('-test-/box/yay1') diff --git a/ext/-test-/namespace/yay1/yay1.c b/ext/-test-/box/yay1/yay1.c index 564a221c8c..564a221c8c 100644 --- a/ext/-test-/namespace/yay1/yay1.c +++ b/ext/-test-/box/yay1/yay1.c diff --git a/ext/-test-/namespace/yay1/yay1.def b/ext/-test-/box/yay1/yay1.def index 510fbe7017..510fbe7017 100644 --- a/ext/-test-/namespace/yay1/yay1.def +++ b/ext/-test-/box/yay1/yay1.def diff --git a/ext/-test-/namespace/yay1/yay1.h b/ext/-test-/box/yay1/yay1.h index c4dade928a..c4dade928a 100644 --- a/ext/-test-/namespace/yay1/yay1.h +++ b/ext/-test-/box/yay1/yay1.h diff --git a/ext/-test-/box/yay2/extconf.rb b/ext/-test-/box/yay2/extconf.rb new file mode 100644 index 0000000000..850ef3edc9 --- /dev/null +++ b/ext/-test-/box/yay2/extconf.rb @@ -0,0 +1 @@ +create_makefile('-test-/box/yay2') diff --git a/ext/-test-/namespace/yay2/yay2.c b/ext/-test-/box/yay2/yay2.c index b632ae8495..b632ae8495 100644 --- a/ext/-test-/namespace/yay2/yay2.c +++ b/ext/-test-/box/yay2/yay2.c diff --git a/ext/-test-/namespace/yay2/yay2.def b/ext/-test-/box/yay2/yay2.def index 163fc44c04..163fc44c04 100644 --- a/ext/-test-/namespace/yay2/yay2.def +++ b/ext/-test-/box/yay2/yay2.def diff --git a/ext/-test-/namespace/yay2/yay2.h b/ext/-test-/box/yay2/yay2.h index c4dade928a..c4dade928a 100644 --- a/ext/-test-/namespace/yay2/yay2.h +++ b/ext/-test-/box/yay2/yay2.h diff --git a/ext/-test-/cxxanyargs/cxxanyargs.cpp b/ext/-test-/cxxanyargs/cxxanyargs.cpp index eded13e2ee..c7df7f9038 100644 --- a/ext/-test-/cxxanyargs/cxxanyargs.cpp +++ b/ext/-test-/cxxanyargs/cxxanyargs.cpp @@ -97,31 +97,6 @@ struct test_rb_define_hooked_variable { }; VALUE test_rb_define_hooked_variable::v = Qundef; -namespace test_rb_iterate { - VALUE - iter(VALUE self) - { - return rb_funcall(self, rb_intern("yield"), 0); - } - - VALUE - block(RB_BLOCK_CALL_FUNC_ARGLIST(arg, param)) - { - return rb_funcall(arg, rb_intern("=="), 1, param); - } - - VALUE - test(VALUE self) - { -#ifdef HAVE_NULLPTR - rb_iterate(iter, self, nullptr, self); -#endif - - rb_iterate(iter, self, RUBY_METHOD_FUNC(block), self); // old - return rb_iterate(iter, self, block, self); // new - } -} - namespace test_rb_block_call { VALUE block(RB_BLOCK_CALL_FUNC_ARGLIST(arg, param)) @@ -936,7 +911,6 @@ Init_cxxanyargs(void) test(rb_define_virtual_variable); test(rb_define_hooked_variable); - test(rb_iterate); test(rb_block_call); test(rb_rescue); test(rb_rescue2); diff --git a/ext/-test-/fatal/invalid.c b/ext/-test-/fatal/invalid.c index 393465416a..6fd970b181 100644 --- a/ext/-test-/fatal/invalid.c +++ b/ext/-test-/fatal/invalid.c @@ -1,11 +1,5 @@ #include <ruby.h> -#if SIZEOF_LONG == SIZEOF_VOIDP -# define NUM2PTR(x) NUM2ULONG(x) -#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP -# define NUM2PTR(x) NUM2ULL(x) -#endif - static VALUE invalid_call(VALUE obj, VALUE address) { diff --git a/ext/-test-/integer/depend b/ext/-test-/integer/depend index 0ea007e814..d0589b5e5d 100644 --- a/ext/-test-/integer/depend +++ b/ext/-test-/integer/depend @@ -159,8 +159,11 @@ core_ext.o: $(hdrdir)/ruby/missing.h core_ext.o: $(hdrdir)/ruby/ruby.h core_ext.o: $(hdrdir)/ruby/st.h core_ext.o: $(hdrdir)/ruby/subst.h +core_ext.o: $(top_srcdir)/internal.h +core_ext.o: $(top_srcdir)/internal/basic_operators.h core_ext.o: $(top_srcdir)/internal/bignum.h core_ext.o: $(top_srcdir)/internal/bits.h +core_ext.o: $(top_srcdir)/internal/compar.h core_ext.o: $(top_srcdir)/internal/compilers.h core_ext.o: $(top_srcdir)/internal/fixnum.h core_ext.o: $(top_srcdir)/internal/numeric.h diff --git a/ext/-test-/namespace/yay1/extconf.rb b/ext/-test-/namespace/yay1/extconf.rb deleted file mode 100644 index 539e99ab09..0000000000 --- a/ext/-test-/namespace/yay1/extconf.rb +++ /dev/null @@ -1 +0,0 @@ -create_makefile('-test-/namespace/yay1') diff --git a/ext/-test-/namespace/yay2/extconf.rb b/ext/-test-/namespace/yay2/extconf.rb deleted file mode 100644 index 2027a42860..0000000000 --- a/ext/-test-/namespace/yay2/extconf.rb +++ /dev/null @@ -1 +0,0 @@ -create_makefile('-test-/namespace/yay2') diff --git a/ext/-test-/postponed_job/postponed_job.c b/ext/-test-/postponed_job/postponed_job.c index 9ac866ae77..4426fc3104 100644 --- a/ext/-test-/postponed_job/postponed_job.c +++ b/ext/-test-/postponed_job/postponed_job.c @@ -36,38 +36,6 @@ pjob_callback(void *data) } static VALUE -pjob_register(VALUE self, VALUE obj) -{ - counter = 0; - rb_postponed_job_register(0, pjob_callback, (void *)obj); - rb_gc_start(); - counter++; - rb_gc_start(); - counter++; - rb_gc_start(); - counter++; - return self; -} - -static void -pjob_one_callback(void *data) -{ - VALUE ary = (VALUE)data; - Check_Type(ary, T_ARRAY); - - rb_ary_push(ary, INT2FIX(1)); -} - -static VALUE -pjob_register_one(VALUE self, VALUE obj) -{ - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - return self; -} - -static VALUE pjob_call_direct(VALUE self, VALUE obj) { counter = 0; @@ -83,48 +51,6 @@ pjob_call_direct(VALUE self, VALUE obj) static void pjob_noop_callback(void *data) { } -static VALUE -pjob_register_one_same(VALUE self) -{ - rb_gc_start(); - int r1 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); - int r2 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); - int r3 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); - VALUE ary = rb_ary_new(); - rb_ary_push(ary, INT2FIX(r1)); - rb_ary_push(ary, INT2FIX(r2)); - rb_ary_push(ary, INT2FIX(r3)); - return ary; -} - -#ifdef HAVE_PTHREAD_H -#include <pthread.h> - -static void * -pjob_register_in_c_thread_i(void *obj) -{ - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - return NULL; -} - -static VALUE -pjob_register_in_c_thread(VALUE self, VALUE obj) -{ - pthread_t thread; - if (pthread_create(&thread, NULL, pjob_register_in_c_thread_i, (void *)obj)) { - return Qfalse; - } - - if (pthread_join(thread, NULL)) { - return Qfalse; - } - - return Qtrue; -} -#endif - static void pjob_preregistered_callback(void *data) { @@ -216,13 +142,7 @@ void Init_postponed_job(VALUE self) { VALUE mBug = rb_define_module("Bug"); - rb_define_module_function(mBug, "postponed_job_register", pjob_register, 1); - rb_define_module_function(mBug, "postponed_job_register_one", pjob_register_one, 1); rb_define_module_function(mBug, "postponed_job_call_direct", pjob_call_direct, 1); - rb_define_module_function(mBug, "postponed_job_register_one_same", pjob_register_one_same, 0); -#ifdef HAVE_PTHREAD_H - rb_define_module_function(mBug, "postponed_job_register_in_c_thread", pjob_register_in_c_thread, 1); -#endif rb_define_module_function(mBug, "postponed_job_preregister_and_call_with_sleep", pjob_preregister_and_call_with_sleep, 1); rb_define_module_function(mBug, "postponed_job_preregister_and_call_without_sleep", pjob_preregister_and_call_without_sleep, 1); rb_define_module_function(mBug, "postponed_job_preregister_multiple_times", pjob_preregister_multiple_times, 0); diff --git a/ext/-test-/rational/depend b/ext/-test-/rational/depend index 56d6ab77d6..d949fca66b 100644 --- a/ext/-test-/rational/depend +++ b/ext/-test-/rational/depend @@ -163,8 +163,11 @@ rat.o: $(hdrdir)/ruby/missing.h rat.o: $(hdrdir)/ruby/ruby.h rat.o: $(hdrdir)/ruby/st.h rat.o: $(hdrdir)/ruby/subst.h +rat.o: $(top_srcdir)/internal.h +rat.o: $(top_srcdir)/internal/basic_operators.h rat.o: $(top_srcdir)/internal/bignum.h rat.o: $(top_srcdir)/internal/bits.h +rat.o: $(top_srcdir)/internal/compar.h rat.o: $(top_srcdir)/internal/compilers.h rat.o: $(top_srcdir)/internal/fixnum.h rat.o: $(top_srcdir)/internal/gc.h diff --git a/ext/-test-/sanitizers/depend b/ext/-test-/sanitizers/depend new file mode 100644 index 0000000000..0e6e632803 --- /dev/null +++ b/ext/-test-/sanitizers/depend @@ -0,0 +1,162 @@ +# AUTOGENERATED DEPENDENCIES START +sanitizers.o: $(RUBY_EXTCONF_H) +sanitizers.o: $(arch_hdrdir)/ruby/config.h +sanitizers.o: $(hdrdir)/ruby/assert.h +sanitizers.o: $(hdrdir)/ruby/backward.h +sanitizers.o: $(hdrdir)/ruby/backward/2/assume.h +sanitizers.o: $(hdrdir)/ruby/backward/2/attributes.h +sanitizers.o: $(hdrdir)/ruby/backward/2/bool.h +sanitizers.o: $(hdrdir)/ruby/backward/2/inttypes.h +sanitizers.o: $(hdrdir)/ruby/backward/2/limits.h +sanitizers.o: $(hdrdir)/ruby/backward/2/long_long.h +sanitizers.o: $(hdrdir)/ruby/backward/2/stdalign.h +sanitizers.o: $(hdrdir)/ruby/backward/2/stdarg.h +sanitizers.o: $(hdrdir)/ruby/defines.h +sanitizers.o: $(hdrdir)/ruby/intern.h +sanitizers.o: $(hdrdir)/ruby/internal/abi.h +sanitizers.o: $(hdrdir)/ruby/internal/anyargs.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/char.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/double.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/int.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/long.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/short.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +sanitizers.o: $(hdrdir)/ruby/internal/assume.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/artificial.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/cold.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/const.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/constexpr.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/deprecated.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/error.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/forceinline.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/format.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noalias.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noexcept.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noinline.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/nonnull.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noreturn.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/packed_struct.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/pure.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/restrict.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/warning.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/weakref.h +sanitizers.o: $(hdrdir)/ruby/internal/cast.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_since.h +sanitizers.o: $(hdrdir)/ruby/internal/config.h +sanitizers.o: $(hdrdir)/ruby/internal/constant_p.h +sanitizers.o: $(hdrdir)/ruby/internal/core.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rarray.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rbasic.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rbignum.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rclass.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rdata.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rfile.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rhash.h +sanitizers.o: $(hdrdir)/ruby/internal/core/robject.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rregexp.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rstring.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rstruct.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +sanitizers.o: $(hdrdir)/ruby/internal/ctype.h +sanitizers.o: $(hdrdir)/ruby/internal/dllexport.h +sanitizers.o: $(hdrdir)/ruby/internal/dosish.h +sanitizers.o: $(hdrdir)/ruby/internal/error.h +sanitizers.o: $(hdrdir)/ruby/internal/eval.h +sanitizers.o: $(hdrdir)/ruby/internal/event.h +sanitizers.o: $(hdrdir)/ruby/internal/fl_type.h +sanitizers.o: $(hdrdir)/ruby/internal/gc.h +sanitizers.o: $(hdrdir)/ruby/internal/glob.h +sanitizers.o: $(hdrdir)/ruby/internal/globals.h +sanitizers.o: $(hdrdir)/ruby/internal/has/attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/builtin.h +sanitizers.o: $(hdrdir)/ruby/internal/has/c_attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/extension.h +sanitizers.o: $(hdrdir)/ruby/internal/has/feature.h +sanitizers.o: $(hdrdir)/ruby/internal/has/warning.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/array.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/bignum.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/class.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/compar.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/complex.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/cont.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/dir.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/enum.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/enumerator.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/error.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/eval.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/file.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/hash.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/io.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/load.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/marshal.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/numeric.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/object.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/parse.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/proc.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/process.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/random.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/range.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/rational.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/re.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/ruby.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/select.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/set.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/signal.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/sprintf.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/string.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/struct.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/thread.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/time.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/variable.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/vm.h +sanitizers.o: $(hdrdir)/ruby/internal/interpreter.h +sanitizers.o: $(hdrdir)/ruby/internal/iterator.h +sanitizers.o: $(hdrdir)/ruby/internal/memory.h +sanitizers.o: $(hdrdir)/ruby/internal/method.h +sanitizers.o: $(hdrdir)/ruby/internal/module.h +sanitizers.o: $(hdrdir)/ruby/internal/newobj.h +sanitizers.o: $(hdrdir)/ruby/internal/scan_args.h +sanitizers.o: $(hdrdir)/ruby/internal/special_consts.h +sanitizers.o: $(hdrdir)/ruby/internal/static_assert.h +sanitizers.o: $(hdrdir)/ruby/internal/stdalign.h +sanitizers.o: $(hdrdir)/ruby/internal/stdbool.h +sanitizers.o: $(hdrdir)/ruby/internal/stdckdint.h +sanitizers.o: $(hdrdir)/ruby/internal/symbol.h +sanitizers.o: $(hdrdir)/ruby/internal/value.h +sanitizers.o: $(hdrdir)/ruby/internal/value_type.h +sanitizers.o: $(hdrdir)/ruby/internal/variable.h +sanitizers.o: $(hdrdir)/ruby/internal/warning_push.h +sanitizers.o: $(hdrdir)/ruby/internal/xmalloc.h +sanitizers.o: $(hdrdir)/ruby/missing.h +sanitizers.o: $(hdrdir)/ruby/ruby.h +sanitizers.o: $(hdrdir)/ruby/st.h +sanitizers.o: $(hdrdir)/ruby/subst.h +sanitizers.o: sanitizers.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/sanitizers/extconf.rb b/ext/-test-/sanitizers/extconf.rb new file mode 100644 index 0000000000..c94a96de6c --- /dev/null +++ b/ext/-test-/sanitizers/extconf.rb @@ -0,0 +1,2 @@ +require 'mkmf' +create_makefile('-test-/sanitizers') diff --git a/ext/-test-/sanitizers/sanitizers.c b/ext/-test-/sanitizers/sanitizers.c new file mode 100644 index 0000000000..97a85b26ef --- /dev/null +++ b/ext/-test-/sanitizers/sanitizers.c @@ -0,0 +1,36 @@ +#include "ruby/ruby.h" + +static VALUE +asan_enabled_p(VALUE self) +{ +#if defined(__has_feature) + /* clang uses __has_feature for determining asan */ + return __has_feature(address_sanitizer) ? Qtrue : Qfalse; +#elif defined(__SANITIZE_ADDRESS__) + /* GCC sets __SANITIZE_ADDRESS__ for determining asan */ + return Qtrue; +#else + return Qfalse; +#endif +} + +static VALUE +lsan_enabled_p(VALUE self) +{ +#if defined(__has_feature) + /* clang uses __has_feature for determining LSAN */ + return __has_feature(leak_sanitizer) ? Qtrue : Qfalse; +#else + return Qfalse; +#endif +} + +void +Init_sanitizers(void) +{ + VALUE m = rb_define_module("Test"); + VALUE c = rb_define_class_under(m, "Sanitizers", rb_cObject); + rb_define_singleton_method(c, "asan_enabled?", asan_enabled_p, 0); + rb_define_singleton_method(c, "lsan_enabled?", lsan_enabled_p, 0); +} + diff --git a/ext/-test-/scheduler/extconf.rb b/ext/-test-/scheduler/extconf.rb new file mode 100644 index 0000000000..159699bd8e --- /dev/null +++ b/ext/-test-/scheduler/extconf.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: false +create_makefile("-test-/scheduler") diff --git a/ext/-test-/scheduler/scheduler.c b/ext/-test-/scheduler/scheduler.c new file mode 100644 index 0000000000..b742a5573b --- /dev/null +++ b/ext/-test-/scheduler/scheduler.c @@ -0,0 +1,92 @@ +#include "ruby/ruby.h" +#include "ruby/thread.h" +#include "ruby/io.h" +#include "ruby/fiber/scheduler.h" + +/* + * Test extension for reproducing the gRPC interrupt handling bug. + * + * This reproduces the exact issue from grpc/grpc commit 69f229e (June 2025): + * https://github.com/grpc/grpc/commit/69f229edd1d79ab7a7dfda98e3aef6fd807adcad + * + * The bug occurs when: + * 1. A fiber scheduler uses Thread.handle_interrupt(::SignalException => :never) + * (like Async::Scheduler does) + * 2. Native code uses rb_thread_call_without_gvl in a retry loop that checks + * the interrupted flag and retries (like gRPC's completion queue) + * 3. A signal (SIGINT/SIGTERM) is sent + * 4. The unblock_func sets interrupted=1, but Thread.handle_interrupt defers the signal + * 5. The loop sees interrupted=1 and retries without yielding to the scheduler + * 6. The deferred interrupt never gets processed -> infinite hang + * + * The fix is in vm_check_ints_blocking() in thread.c, which should yield to + * the fiber scheduler when interrupts are pending, allowing the scheduler to + * detect Thread.pending_interrupt? and exit its run loop. + */ + +struct blocking_state { + int notify_descriptor; + volatile int interrupted; +}; + +static void +unblock_callback(void *argument) +{ + struct blocking_state *blocking_state = (struct blocking_state *)argument; + blocking_state->interrupted = 1; +} + +static void * +blocking_operation(void *argument) +{ + struct blocking_state *blocking_state = (struct blocking_state *)argument; + + ssize_t ret = write(blocking_state->notify_descriptor, "x", 1); + (void)ret; // ignore the result for now + + while (!blocking_state->interrupted) { + struct timeval tv = {1, 0}; // 1 second timeout. + int result = select(0, NULL, NULL, NULL, &tv); + + if (result == -1 && errno == EINTR) { + blocking_state->interrupted = 1; + } + + // Otherwise, timeout -> loop again. + } + + return NULL; +} + +static VALUE +scheduler_blocking_loop(VALUE self, VALUE notify) +{ + struct blocking_state blocking_state = { + .notify_descriptor = rb_io_descriptor(notify), + .interrupted = 0, + }; + + while (true) { + blocking_state.interrupted = 0; + + rb_thread_call_without_gvl( + blocking_operation, &blocking_state, + unblock_callback, &blocking_state + ); + + // The bug: When interrupted, loop retries without yielding to scheduler. + // With Thread.handle_interrupt(:never), this causes an infinite hang, + // because the deferred interrupt never gets a chance to be processed. + } while (blocking_state.interrupted); + + return Qnil; +} + +void +Init_scheduler(void) +{ + VALUE mBug = rb_define_module("Bug"); + VALUE mScheduler = rb_define_module_under(mBug, "Scheduler"); + + rb_define_module_function(mScheduler, "blocking_loop", scheduler_blocking_loop, 1); +} diff --git a/ext/-test-/st/foreach/foreach.c b/ext/-test-/st/foreach/foreach.c index 7fbf064694..5c1bfd1631 100644 --- a/ext/-test-/st/foreach/foreach.c +++ b/ext/-test-/st/foreach/foreach.c @@ -14,13 +14,9 @@ force_unpack_check(struct checker *c, st_data_t key, st_data_t val) if (c->nr == 0) { st_data_t i; - if (c->tbl->bins != NULL) rb_bug("should be packed"); - /* force unpacking during iteration: */ for (i = 1; i < expect_size; i++) st_add_direct(c->tbl, i, i); - - if (c->tbl->bins == NULL) rb_bug("should be unpacked"); } if (key != c->nr) { @@ -84,8 +80,6 @@ unp_fec(VALUE self, VALUE test) st_add_direct(tbl, 0, 0); - if (tbl->bins != NULL) rb_bug("should still be packed"); - st_foreach_check(tbl, unp_fec_i, (st_data_t)&c, -1); if (c.test == ID2SYM(rb_intern("delete2"))) { @@ -98,8 +92,6 @@ unp_fec(VALUE self, VALUE test) (VALUE)c.nr, (VALUE)expect_size); } - if (tbl->bins == NULL) rb_bug("should be unpacked"); - st_free_table(tbl); return Qnil; @@ -145,8 +137,6 @@ unp_fe(VALUE self, VALUE test) st_add_direct(tbl, 0, 0); - if (tbl->bins != NULL) rb_bug("should still be packed"); - st_foreach(tbl, unp_fe_i, (st_data_t)&c); if (c.test == ID2SYM(rb_intern("unpack_delete"))) { @@ -159,8 +149,6 @@ unp_fe(VALUE self, VALUE test) (VALUE)c.nr, (VALUE)expect_size); } - if (tbl->bins == NULL) rb_bug("should be unpacked"); - st_free_table(tbl); return Qnil; diff --git a/ext/-test-/stack/depend b/ext/-test-/stack/depend index 31571c882e..77e93bb201 100644 --- a/ext/-test-/stack/depend +++ b/ext/-test-/stack/depend @@ -172,6 +172,7 @@ stack.o: $(hdrdir)/ruby/oniguruma.h stack.o: $(hdrdir)/ruby/ruby.h stack.o: $(hdrdir)/ruby/st.h stack.o: $(hdrdir)/ruby/subst.h +stack.o: $(top_srcdir)/encindex.h stack.o: $(top_srcdir)/internal/compilers.h stack.o: $(top_srcdir)/internal/string.h stack.o: stack.c diff --git a/ext/-test-/stack/stack.c b/ext/-test-/stack/stack.c index 8ff32f9737..f0e65e74b2 100644 --- a/ext/-test-/stack/stack.c +++ b/ext/-test-/stack/stack.c @@ -2,7 +2,7 @@ #include "internal/string.h" static VALUE -stack_alloca_overflow(VALUE self) +stack_overflow(VALUE self) { size_t i = 0; @@ -30,6 +30,6 @@ asan_p(VALUE klass) void Init_stack(VALUE klass) { - rb_define_singleton_method(rb_cThread, "alloca_overflow", stack_alloca_overflow, 0); + rb_define_singleton_method(rb_cThread, "stack_overflow", stack_overflow, 0); rb_define_singleton_method(rb_cThread, "asan?", asan_p, 0); } diff --git a/ext/-test-/string/cstr.c b/ext/-test-/string/cstr.c index b0b1ef5374..931220b46b 100644 --- a/ext/-test-/string/cstr.c +++ b/ext/-test-/string/cstr.c @@ -111,9 +111,10 @@ bug_str_s_cstr_noembed(VALUE self, VALUE str) FL_SET((str2), STR_NOEMBED); memcpy(buf, RSTRING_PTR(str), capacity); RBASIC(str2)->flags &= ~(STR_SHARED | FL_USER5 | FL_USER6); - RSTRING(str2)->as.heap.aux.capa = capacity; + RSTRING(str2)->as.heap.aux.capa = RSTRING_LEN(str); RSTRING(str2)->as.heap.ptr = buf; RSTRING(str2)->len = RSTRING_LEN(str); + TERM_FILL(RSTRING_END(str2), TERM_LEN(str)); return str2; } diff --git a/ext/-test-/string/depend b/ext/-test-/string/depend index de6e775acc..478ae3b82b 100644 --- a/ext/-test-/string/depend +++ b/ext/-test-/string/depend @@ -172,6 +172,7 @@ capacity.o: $(hdrdir)/ruby/oniguruma.h capacity.o: $(hdrdir)/ruby/ruby.h capacity.o: $(hdrdir)/ruby/st.h capacity.o: $(hdrdir)/ruby/subst.h +capacity.o: $(top_srcdir)/encindex.h capacity.o: $(top_srcdir)/internal/compilers.h capacity.o: $(top_srcdir)/internal/string.h capacity.o: capacity.c @@ -679,6 +680,7 @@ cstr.o: $(hdrdir)/ruby/oniguruma.h cstr.o: $(hdrdir)/ruby/ruby.h cstr.o: $(hdrdir)/ruby/st.h cstr.o: $(hdrdir)/ruby/subst.h +cstr.o: $(top_srcdir)/encindex.h cstr.o: $(top_srcdir)/internal.h cstr.o: $(top_srcdir)/internal/compilers.h cstr.o: $(top_srcdir)/internal/string.h @@ -1535,6 +1537,7 @@ fstring.o: $(hdrdir)/ruby/oniguruma.h fstring.o: $(hdrdir)/ruby/ruby.h fstring.o: $(hdrdir)/ruby/st.h fstring.o: $(hdrdir)/ruby/subst.h +fstring.o: $(top_srcdir)/encindex.h fstring.o: $(top_srcdir)/internal/compilers.h fstring.o: $(top_srcdir)/internal/string.h fstring.o: fstring.c diff --git a/ext/-test-/string/fstring.c b/ext/-test-/string/fstring.c index 71c4b7f97e..0b5940f28c 100644 --- a/ext/-test-/string/fstring.c +++ b/ext/-test-/string/fstring.c @@ -12,20 +12,20 @@ VALUE bug_s_fstring_fake_str(VALUE self) { static const char literal[] = "abcdefghijklmnopqrstuvwxyz"; - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return rb_str_to_interned_str(rb_setup_fake_str(&fake_str, literal, sizeof(literal) - 1, 0)); } VALUE bug_s_rb_enc_interned_str(VALUE self, VALUE encoding) { - return rb_enc_interned_str("foo", 3, NIL_P(encoding) ? NULL : RDATA(encoding)->data); + return rb_enc_interned_str("foo", 3, NIL_P(encoding) ? NULL : RTYPEDDATA_GET_DATA(encoding)); } VALUE bug_s_rb_enc_str_new(VALUE self, VALUE encoding) { - return rb_enc_str_new("foo", 3, NIL_P(encoding) ? NULL : RDATA(encoding)->data); + return rb_enc_str_new("foo", 3, NIL_P(encoding) ? NULL : RTYPEDDATA_GET_DATA(encoding)); } void diff --git a/ext/-test-/time/leap_second.c b/ext/-test-/time/leap_second.c deleted file mode 100644 index ee7011fa97..0000000000 --- a/ext/-test-/time/leap_second.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "ruby.h" -#include "internal/time.h" - -static VALUE -bug_time_s_reset_leap_second_info(VALUE klass) -{ - ruby_reset_leap_second_info(); - return Qnil; -} - -void -Init_time_leap_second(VALUE klass) -{ - rb_define_singleton_method(klass, "reset_leap_second_info", bug_time_s_reset_leap_second_info, 0); -} diff --git a/ext/-test-/tracepoint/gc_hook.c b/ext/-test-/tracepoint/gc_hook.c index 54c06c54a5..525be6da63 100644 --- a/ext/-test-/tracepoint/gc_hook.c +++ b/ext/-test-/tracepoint/gc_hook.c @@ -2,6 +2,7 @@ #include "ruby/debug.h" static int invoking; /* TODO: should not be global variable */ +extern VALUE tp_mBug; static VALUE invoke_proc_ensure(VALUE _) @@ -17,9 +18,9 @@ invoke_proc_begin(VALUE proc) } static void -invoke_proc(void *data) +invoke_proc(void *ivar_name) { - VALUE proc = (VALUE)data; + VALUE proc = rb_ivar_get(tp_mBug, rb_intern(ivar_name)); invoking += 1; rb_ensure(invoke_proc_begin, proc, invoke_proc_ensure, 0); } @@ -40,16 +41,16 @@ gc_start_end_i(VALUE tpval, void *data) } static VALUE -set_gc_hook(VALUE module, VALUE proc, rb_event_flag_t event, const char *tp_str, const char *proc_str) +set_gc_hook(VALUE proc, rb_event_flag_t event, const char *tp_str, const char *proc_str) { VALUE tpval; ID tp_key = rb_intern(tp_str); /* disable previous keys */ - if (rb_ivar_defined(module, tp_key) != 0 && - RTEST(tpval = rb_ivar_get(module, tp_key))) { + if (rb_ivar_defined(tp_mBug, tp_key) != 0 && + RTEST(tpval = rb_ivar_get(tp_mBug, tp_key))) { rb_tracepoint_disable(tpval); - rb_ivar_set(module, tp_key, Qnil); + rb_ivar_set(tp_mBug, tp_key, Qnil); } if (RTEST(proc)) { @@ -57,8 +58,9 @@ set_gc_hook(VALUE module, VALUE proc, rb_event_flag_t event, const char *tp_str, rb_raise(rb_eTypeError, "trace_func needs to be Proc"); } - tpval = rb_tracepoint_new(0, event, gc_start_end_i, (void *)proc); - rb_ivar_set(module, tp_key, tpval); + rb_ivar_set(tp_mBug, rb_intern(proc_str), proc); + tpval = rb_tracepoint_new(0, event, gc_start_end_i, (void *)proc_str); + rb_ivar_set(tp_mBug, tp_key, tpval); rb_tracepoint_enable(tpval); } @@ -66,16 +68,16 @@ set_gc_hook(VALUE module, VALUE proc, rb_event_flag_t event, const char *tp_str, } static VALUE -set_after_gc_start(VALUE module, VALUE proc) +set_after_gc_start(VALUE _self, VALUE proc) { - return set_gc_hook(module, proc, RUBY_INTERNAL_EVENT_GC_START, + return set_gc_hook(proc, RUBY_INTERNAL_EVENT_GC_START, "__set_after_gc_start_tpval__", "__set_after_gc_start_proc__"); } static VALUE -start_after_gc_exit(VALUE module, VALUE proc) +start_after_gc_exit(VALUE _self, VALUE proc) { - return set_gc_hook(module, proc, RUBY_INTERNAL_EVENT_GC_EXIT, + return set_gc_hook(proc, RUBY_INTERNAL_EVENT_GC_EXIT, "__set_after_gc_exit_tpval__", "__set_after_gc_exit_proc__"); } diff --git a/ext/-test-/tracepoint/tracepoint.c b/ext/-test-/tracepoint/tracepoint.c index 2826cc038c..001d9513b2 100644 --- a/ext/-test-/tracepoint/tracepoint.c +++ b/ext/-test-/tracepoint/tracepoint.c @@ -1,6 +1,8 @@ #include "ruby/ruby.h" #include "ruby/debug.h" +VALUE tp_mBug; + struct tracepoint_track { size_t newobj_count; size_t free_count; @@ -89,8 +91,8 @@ void Init_gc_hook(VALUE); void Init_tracepoint(void) { - VALUE mBug = rb_define_module("Bug"); - Init_gc_hook(mBug); - rb_define_module_function(mBug, "tracepoint_track_objspace_events", tracepoint_track_objspace_events, 0); - rb_define_module_function(mBug, "tracepoint_specify_normal_and_internal_events", tracepoint_specify_normal_and_internal_events, 0); + tp_mBug = rb_define_module("Bug"); // GC root + Init_gc_hook(tp_mBug); + rb_define_module_function(tp_mBug, "tracepoint_track_objspace_events", tracepoint_track_objspace_events, 0); + rb_define_module_function(tp_mBug, "tracepoint_specify_normal_and_internal_events", tracepoint_specify_normal_and_internal_events, 0); } diff --git a/ext/.document b/ext/.document index 0d6c97ff73..d75c5c3d35 100644 --- a/ext/.document +++ b/ext/.document @@ -1,7 +1,5 @@ # Add files to this as they become documented -bigdecimal/bigdecimal.c -bigdecimal/lib cgi/escape/escape.c continuation/continuation.c coverage/coverage.c @@ -21,24 +19,12 @@ digest/sha2/sha2init.c digest/sha2/lib etc/etc.c fcntl/fcntl.c -fiddle/closure.c -fiddle/conversions.c -fiddle/fiddle.c -fiddle/function.c -fiddle/pinned.c -fiddle/pointer.c -fiddle/handle.c -fiddle/lib io/console/ io/nonblock/nonblock.c io/wait/wait.c json/generator/generator.c json/lib json/parser/parser.c -monitor/lib -monitor/monitor.c -nkf/lib -nkf/nkf.c objspace/objspace.c objspace/objspace_dump.c objspace/object_tracing.c @@ -84,17 +70,12 @@ psych/psych_to_ruby.c psych/psych_yaml_tree.c pty/lib pty/pty.c -racc/cparse/cparse.c rbconfig/sizeof/*.c -readline/readline.c ripper/lib socket stringio/stringio.c +strscan/lib strscan/strscan.c -syslog/syslog.c -syslog/lib win32/lib win32/resolv/*.c -win32ole/lib -win32ole/*.c zlib/zlib.c @@ -1,39 +1,34 @@ #option nodynamic -#bigdecimal #cgi/escape #continuation #coverage #date -#digest/bubblebabble #digest +#digest/bubblebabble #digest/md5 #digest/rmd160 #digest/sha1 #digest/sha2 +#erb/escape #etc #fcntl -#fiddle #io/console #io/nonblock #io/wait #json #json/generator #json/parser -#nkf #objspace #openssl -#pathname #psych #pty -#racc/cparse #rbconfig/sizeof -#readline #ripper +#rubyvm #socket #stringio #strscan -#syslog #win32 -#win32ole +#win32/resolv #zlib diff --git a/ext/Setup.atheos b/ext/Setup.atheos index 91f73f32f9..73c8d5d5a8 100644 --- a/ext/Setup.atheos +++ b/ext/Setup.atheos @@ -1,24 +1,23 @@ option nodynamic -bigdecimal cgi/escape -digest -digest/md5 -digest/rmd160 -digest/sha1 -digest/sha2 +continuation +coverage +date +digest/* +erb/escape etc fcntl -io/wait -nkf +io/* +json +json/* +objspace #openssl pty -#racc/cparse -readline +rbconfig/sizeof ripper socket stringio strscan syslog -#win32ole zlib diff --git a/ext/Setup.nt b/ext/Setup.nt index 1278f183e4..a9e87249d3 100644 --- a/ext/Setup.nt +++ b/ext/Setup.nt @@ -1,25 +1,23 @@ #option platform cygwin|mingw|mswin #option nodynamic -bigdecimal cgi/escape -digest -digest/md5 -digest/rmd160 -digest/sha1 -digest/sha2 +continuation +coverage +date +digest/* +erb/escape etc fcntl -#io/wait -nkf +io/* +json +json/* +objspace #openssl -#pty -#racc/cparse -#readline +rbconfig/sizeof #ripper socket stringio strscan #syslog -win32ole #zlib diff --git a/ext/cgi/escape/escape.c b/ext/cgi/escape/escape.c index 6b00bc37c1..4773186603 100644 --- a/ext/cgi/escape/escape.c +++ b/ext/cgi/escape/escape.c @@ -45,6 +45,7 @@ escaped_length(VALUE str) static VALUE optimized_escape_html(VALUE str) { + VALUE escaped; VALUE vbuf; char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); const char *cstr = RSTRING_PTR(str); @@ -63,7 +64,6 @@ optimized_escape_html(VALUE str) } } - VALUE escaped; if (RSTRING_LEN(str) < (dest - buf)) { escaped = rb_str_new(buf, dest - buf); preserve_original_state(str, escaped); diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c index 1519b559cd..1b0280b460 100644 --- a/ext/coverage/coverage.c +++ b/ext/coverage/coverage.c @@ -52,17 +52,29 @@ rb_coverage_supported(VALUE self, VALUE _mode) /* * call-seq: - * Coverage.setup => nil - * Coverage.setup(:all) => nil - * Coverage.setup(lines: bool, branches: bool, methods: bool, eval: bool) => nil - * Coverage.setup(oneshot_lines: true) => nil + * Coverage.setup -> nil + * Coverage.setup(type) -> nil + * Coverage.setup(lines: false, branches: false, methods: false, eval: false, oneshot_lines: false) -> nil * - * Set up the coverage measurement. + * Performs setup for coverage measurement, but does not start coverage measurement. + * To start coverage measurement, use Coverage.resume. * - * Note that this method does not start the measurement itself. - * Use Coverage.resume to start the measurement. + * To perform both setup and start coverage measurement, Coverage.start can be used. * - * You may want to use Coverage.start to setup and then start the measurement. + * With argument +type+ given and +type+ is symbol +:all+, enables all types of coverage + * (lines, branches, methods, and eval). + * + * Keyword arguments or hash +type+ can be given with each of the following keys: + * + * - +lines+: Enables line coverage that records the number of times each line was executed. + * If +lines+ is enabled, +oneshot_lines+ cannot be enabled. + * See {Lines Coverage}[rdoc-ref:Coverage@Lines+Coverage]. + * - +branches+: Enables branch coverage that records the number of times each + * branch in each conditional was executed. See {Branches Coverage}[rdoc-ref:Coverage@Branches+Coverage]. + * - +methods+: Enables method coverage that records the number of times each method was exectued. + * See {Methods Coverage}[rdoc-ref:Coverage@Methods+Coverage]. + * - +eval+: Enables coverage for evaluations (e.g. Kernel#eval, Module#class_eval). + * See {Eval Coverage}[rdoc-ref:Coverage@Eval+Coverage]. */ static VALUE rb_coverage_setup(int argc, VALUE *argv, VALUE klass) @@ -152,14 +164,14 @@ rb_coverage_resume(VALUE klass) /* * call-seq: - * Coverage.start => nil - * Coverage.start(:all) => nil - * Coverage.start(lines: bool, branches: bool, methods: bool, eval: bool) => nil - * Coverage.start(oneshot_lines: true) => nil - * - * Enables the coverage measurement. - * See the documentation of Coverage class in detail. - * This is equivalent to Coverage.setup and Coverage.resume. + * Coverage.start -> nil + * Coverage.start(type) -> nil + * Coverage.start(lines: false, branches: false, methods: false, eval: false, oneshot_lines: false) -> nil + * + * Enables coverage measurement. + * This method is equivalent to calling Coverage.setup with the arguments provided, + * and then calling Coverage.resume. See their respective documentation for more + * details. */ static VALUE rb_coverage_start(int argc, VALUE *argv, VALUE klass) @@ -337,7 +349,7 @@ coverage_peek_result_i(st_data_t key, st_data_t val, st_data_t h) * Coverage.peek_result => hash * * Returns a hash that contains filename as key and coverage array as value. - * This is the same as `Coverage.result(stop: false, clear: false)`. + * This is the same as <tt>Coverage.result(stop: false, clear: false)</tt>. * * { * "file.rb" => [1, 2, nil], @@ -352,7 +364,6 @@ rb_coverage_peek_result(VALUE klass) if (!RTEST(coverages)) { rb_raise(rb_eRuntimeError, "coverage measurement is not enabled"); } - OBJ_WB_UNPROTECT(coverages); rb_hash_foreach(coverages, coverage_peek_result_i, ncoverages); @@ -468,151 +479,217 @@ rb_coverage_running(VALUE klass) return current_state == RUNNING ? Qtrue : Qfalse; } -/* Coverage provides coverage measurement feature for Ruby. - * This feature is experimental, so these APIs may be changed in future. +/* \Coverage provides coverage measurement feature for Ruby. * - * Caveat: Currently, only process-global coverage measurement is supported. - * You cannot measure per-thread coverage. + * Only process-global coverage measurement is supported, meaning + * that coverage cannot be measure on a per-thread basis. * - * = Usage + * = Quick Start * - * 1. require "coverage" - * 2. do Coverage.start - * 3. require or load Ruby source file - * 4. Coverage.result will return a hash that contains filename as key and - * coverage array as value. A coverage array gives, for each line, the - * number of line execution by the interpreter. A +nil+ value means - * coverage is disabled for this line (lines like +else+ and +end+). + * 1. Load coverage using <tt>require "coverage"</tt>. + * 2. Call Coverage.start to set up and begin coverage measurement. + * 3. All Ruby code loaded following the call to Coverage.start will have + * coverage measurement. + * 4. Coverage results can be fetched by calling Coverage.result, which returns a + * hash that contains filenames as the keys and coverage arrays as the values. + * Each element of the coverage array gives the number of times each line was + * executed. A +nil+ value means coverage was disabled for that line (e.g. + * lines like +else+ and +end+). * * = Examples * - * [foo.rb] - * s = 0 - * 10.times do |x| - * s += x - * end + * In file +fib.rb+: * - * if s == 45 - * p :ok - * else - * p :ng + * def fibonacci(n) + * if n == 0 + * 0 + * elsif n == 1 + * 1 + * else + * fibonacci(n - 1) + fibonacci(n - 2) + * end * end - * [EOF] + * + * puts fibonacci(10) + * + * In another file, coverage can be measured: * * require "coverage" * Coverage.start - * require "foo.rb" - * p Coverage.result #=> {"foo.rb"=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil]} + * require "fib.rb" + * Coverage.result # => {"fib.rb" => [1, 177, 34, 143, 55, nil, 88, nil, nil, nil, 1]} * - * == Lines Coverage + * == Lines \Coverage * - * If a coverage mode is not explicitly specified when starting coverage, lines - * coverage is what will run. It reports the number of line executions for each - * line. + * Lines coverage reports the number of line executions for each line. + * If the coverage mode is not explicitly specified when starting coverage, + * lines coverage is used as the default. * * require "coverage" * Coverage.start(lines: true) - * require "foo.rb" - * p Coverage.result #=> {"foo.rb"=>{:lines=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil]}} + * require "fib" + * Coverage.result # => {"fib.rb" => {lines: [1, 177, 34, 143, 55, nil, 88, nil, nil, nil, 1]}} + * + * The returned hash differs depending on how Coverage.setup or Coverage.start + * was executed. + * + * If Coverage.start or Coverage.setup was called with no arguments, it returns a + * hash which contains filenames as the keys and coverage arrays as the values. * - * The value of the lines coverage result is an array containing how many times - * each line was executed. Order in this array is important. For example, the - * first item in this array, at index 0, reports how many times line 1 of this - * file was executed while coverage was run (which, in this example, is one - * time). + * If Coverage.start or Coverage.setup was called with <tt>line: true</tt>, it + * returns a hash which contains filenames as the keys and hashes as the values. + * The value hash has a key +:lines+ where the value is a coverage array. * - * A +nil+ value means coverage is disabled for this line (lines like +else+ - * and +end+). + * Each element of the coverage array gives the number of times the line was + * executed. A +nil+ value in the coverage array means coverage was disabled + * for that line (e.g. lines like +else+ and +end+). * - * == Oneshot Lines Coverage + * == Oneshot Lines \Coverage * - * Oneshot lines coverage tracks and reports on the executed lines while - * coverage is running. It will not report how many times a line was executed, - * only that it was executed. + * Oneshot lines coverage is similar to lines coverage, but instead of reporting + * the number of times a line was executed, it only reports the lines that were + * executed. * * require "coverage" * Coverage.start(oneshot_lines: true) - * require "foo.rb" - * p Coverage.result #=> {"foo.rb"=>{:oneshot_lines=>[1, 2, 3, 6, 7]}} + * require "fib" + * Coverage.result # => {"fib.rb" => {oneshot_lines: [1, 11, 2, 4, 7, 5, 3]}} * * The value of the oneshot lines coverage result is an array containing the * line numbers that were executed. * - * == Branches Coverage + * == Branches \Coverage * - * Branches coverage reports how many times each branch within each conditional + * Branches coverage reports the number of times each branch within each conditional * was executed. * * require "coverage" * Coverage.start(branches: true) - * require "foo.rb" - * p Coverage.result #=> {"foo.rb"=>{:branches=>{[:if, 0, 6, 0, 10, 3]=>{[:then, 1, 7, 2, 7, 7]=>1, [:else, 2, 9, 2, 9, 7]=>0}}}} + * require "fib" + * Coverage.result + * # => {"fib.rb" => { + * # branches: { + * # [:if, 0, 2, 2, 8, 5] => { + * # [:then, 1, 3, 4, 3, 5] => 34, + * # [:else, 2, 4, 2, 8, 5] => 143}, + * # [:if, 3, 4, 2, 8, 5] => { + * # [:then, 4, 5, 4, 5, 5] => 55, + * # [:else, 5, 7, 4, 7, 39] => 88}}}} * * Each entry within the branches hash is a conditional, the value of which is - * another hash where each entry is a branch in that conditional. The values - * are the number of times the method was executed, and the keys are identifying - * information about the branch. + * another hash where each entry is a branch in that conditional. The keys are + * arrays containing information about the branch and the values are the number + * of times the branch was executed. * - * The information that makes up each key identifying branches or conditionals - * is the following, from left to right: + * The information that makes up the array that are the keys for conditional or + * branches are the following, from left to right: * - * 1. A label for the type of branch or conditional. + * 1. A label for the type of branch or conditional (e.g. +:if+, +:then+, +:else+). * 2. A unique identifier. - * 3. The starting line number it appears on in the file. - * 4. The starting column number it appears on in the file. - * 5. The ending line number it appears on in the file. - * 6. The ending column number it appears on in the file. + * 3. Starting line number. + * 4. Starting column number. + * 5. Ending line number. + * 6. Ending column number. * - * == Methods Coverage + * == Methods \Coverage * * Methods coverage reports how many times each method was executed. * - * [foo_method.rb] - * class Greeter - * def greet - * "welcome!" - * end - * end - * - * def hello - * "Hi" - * end - * - * hello() - * Greeter.new.greet() - * [EOF] - * * require "coverage" * Coverage.start(methods: true) - * require "foo_method.rb" - * p Coverage.result #=> {"foo_method.rb"=>{:methods=>{[Object, :hello, 7, 0, 9, 3]=>1, [Greeter, :greet, 2, 2, 4, 5]=>1}}} + * require "fib" + * p Coverage.result #=> {"fib.rb" => {methods: {[Object, :fibonacci, 1, 0, 9, 3] => 177}}} * - * Each entry within the methods hash represents a method. The values in this - * hash are the number of times the method was executed, and the keys are + * Each entry within the methods hash represents a method. The keys are arrays + * containing hash are the number of times the method was executed, and the keys are * identifying information about the method. * * The information that makes up each key identifying a method is the following, * from left to right: * - * 1. The class. - * 2. The method name. - * 3. The starting line number the method appears on in the file. - * 4. The starting column number the method appears on in the file. - * 5. The ending line number the method appears on in the file. - * 6. The ending column number the method appears on in the file. + * 1. Class that the method was defined in. + * 2. Method name as a Symbol. + * 3. Starting line number of the method. + * 4. Starting column number of the method. + * 5. Ending line number of the method. + * 6. Ending column number of the method. * - * == All Coverage Modes + * == Eval \Coverage * - * You can also run all modes of coverage simultaneously with this shortcut. - * Note that running all coverage modes does not run both lines and oneshot - * lines. Those modes cannot be run simultaneously. Lines coverage is run in - * this case, because you can still use it to determine whether or not a line - * was executed. + * Eval coverage can be combined with the coverage types above to track + * coverage for eval. + * + * require "coverage" + * Coverage.start(eval: true, lines: true) + * + * eval(<<~RUBY, nil, "eval 1") + * ary = [] + * 10.times do |i| + * ary << "hello" * i + * end + * RUBY + * + * Coverage.result # => {"eval 1" => {lines: [1, 1, 10, nil]}} + * + * Note that the eval must have a filename assigned, otherwise coverage + * will not be measured. + * + * require "coverage" + * Coverage.start(eval: true, lines: true) + * + * eval(<<~RUBY) + * ary = [] + * 10.times do |i| + * ary << "hello" * i + * end + * RUBY + * + * Coverage.result # => {"(eval)" => {lines: [nil, nil, nil, nil]}} + * + * Also note that if a line number is assigned to the eval and it is not 1, + * then the resulting coverage will be padded with +nil+ if the line number is + * greater than 1, and truncated if the line number is less than 1. + * + * require "coverage" + * Coverage.start(eval: true, lines: true) + * + * eval(<<~RUBY, nil, "eval 1", 3) + * ary = [] + * 10.times do |i| + * ary << "hello" * i + * end + * RUBY + * + * eval(<<~RUBY, nil, "eval 2", -1) + * ary = [] + * 10.times do |i| + * ary << "hello" * i + * end + * RUBY + * + * Coverage.result + * # => {"eval 1" => {lines: [nil, nil, 1, 1, 10, nil]}, "eval 2" => {lines: [10, nil]}} + * + * == All \Coverage Modes + * + * All modes of coverage can be enabled simultaneously using the Symbol +:all+. + * However, note that this mode runs lines coverage and not oneshot lines since + * they cannot be ran simultaneously. * * require "coverage" * Coverage.start(:all) - * require "foo.rb" - * p Coverage.result #=> {"foo.rb"=>{:lines=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil], :branches=>{[:if, 0, 6, 0, 10, 3]=>{[:then, 1, 7, 2, 7, 7]=>1, [:else, 2, 9, 2, 9, 7]=>0}}, :methods=>{}}} + * require "fib" + * Coverage.result + * # => {"fib.rb" => { + * # lines: [1, 177, 34, 143, 55, nil, 88, nil, nil, nil, 1], + * # branches: { + * # [:if, 0, 2, 2, 8, 5] => { + * # [:then, 1, 3, 4, 3, 5] => 34, + * # [:else, 2, 4, 2, 8, 5] => 143}, + * # [:if, 3, 4, 2, 8, 5] => { + * # [:then, 4, 5, 4, 5, 5] => 55, + * # [:else, 5, 7, 4, 7, 39] => 88}}}}, + * # methods: {[Object, :fibonacci, 1, 0, 9, 3] => 177}}} */ void Init_coverage(void) diff --git a/ext/coverage/depend b/ext/coverage/depend index e042bac7c4..fb7f9a95af 100644 --- a/ext/coverage/depend +++ b/ext/coverage/depend @@ -183,11 +183,11 @@ coverage.o: $(top_srcdir)/id_table.h coverage.o: $(top_srcdir)/internal.h coverage.o: $(top_srcdir)/internal/array.h coverage.o: $(top_srcdir)/internal/basic_operators.h +coverage.o: $(top_srcdir)/internal/box.h coverage.o: $(top_srcdir)/internal/compilers.h coverage.o: $(top_srcdir)/internal/gc.h coverage.o: $(top_srcdir)/internal/hash.h coverage.o: $(top_srcdir)/internal/imemo.h -coverage.o: $(top_srcdir)/internal/namespace.h coverage.o: $(top_srcdir)/internal/sanitizers.h coverage.o: $(top_srcdir)/internal/serial.h coverage.o: $(top_srcdir)/internal/set_table.h diff --git a/ext/coverage/lib/coverage.rb b/ext/coverage/lib/coverage.rb index f1923ef366..4bd20e22cb 100644 --- a/ext/coverage/lib/coverage.rb +++ b/ext/coverage/lib/coverage.rb @@ -1,6 +1,11 @@ require "coverage.so" module Coverage + # call-seq: + # line_stub(file) -> array + # + # A simple helper function that creates the "stub" of line coverage + # from a given source code. def self.line_stub(file) lines = File.foreach(file).map { nil } iseqs = [RubyVM::InstructionSequence.compile_file(file)] diff --git a/ext/date/date_core.c b/ext/date/date_core.c index dbee067f6b..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; @@ -452,11 +452,43 @@ do {\ static int c_valid_civil_p(int, int, int, double, int *, int *, int *, int *); +/* Check if using pure Gregorian calendar (sg == -Infinity) */ +#define c_gregorian_only_p(sg) (isinf(sg) && (sg) < 0) + +/* + * Fast path macros for pure Gregorian calendar. + * Sets *rjd to the JD value, *ns to 1 (new style), and returns. + */ +#define GREGORIAN_JD_FAST_PATH_RET(sg, jd_expr, rjd, ns) \ + if (c_gregorian_only_p(sg)) { \ + *(rjd) = (jd_expr); \ + *(ns) = 1; \ + return 1; \ + } + +#define GREGORIAN_JD_FAST_PATH(sg, jd_expr, rjd, ns) \ + if (c_gregorian_only_p(sg)) { \ + *(rjd) = (jd_expr); \ + *(ns) = 1; \ + return; \ + } + +/* Forward declarations for Neri-Schneider optimized functions */ +static int c_gregorian_civil_to_jd(int y, int m, int d); +static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd); +static int c_gregorian_fdoy(int y); +static int c_gregorian_ldoy(int y); +static int c_gregorian_ldom_jd(int y, int m); +static int ns_jd_in_range(int jd); + static int c_find_fdoy(int y, double sg, int *rjd, int *ns) { int d, rm, rd; + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_fdoy(y), rjd, ns); + + /* Keep existing loop for Julian/reform period */ for (d = 1; d < 31; d++) if (c_valid_civil_p(y, 1, d, sg, &rm, &rd, rjd, ns)) return 1; @@ -468,6 +500,9 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns) { int i, rm, rd; + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldoy(y), rjd, ns); + + /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) if (c_valid_civil_p(y, 12, 31 - i, sg, &rm, &rd, rjd, ns)) return 1; @@ -493,6 +528,9 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) { int i, rm, rd; + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldom_jd(y, m), rjd, ns); + + /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) if (c_valid_civil_p(y, m, 31 - i, sg, &rm, &rd, rjd, ns)) return 1; @@ -502,55 +540,69 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) static void c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) { - double a, b, jd; + int jd; + + GREGORIAN_JD_FAST_PATH(sg, c_gregorian_civil_to_jd(y, m, d), rjd, ns); + + /* Calculate Gregorian JD using optimized algorithm */ + jd = c_gregorian_civil_to_jd(y, m, d); - if (m <= 2) { - y -= 1; - m += 12; - } - a = floor(y / 100.0); - b = 2 - a + floor(a / 4.0); - jd = floor(365.25 * (y + 4716)) + - floor(30.6001 * (m + 1)) + - d + b - 1524; if (jd < sg) { - jd -= b; + /* Before Gregorian switchover - use Julian calendar */ + int y2 = y, m2 = m; + if (m2 <= 2) { + y2 -= 1; + m2 += 12; + } + jd = (int)(floor(365.25 * (y2 + 4716)) + + floor(30.6001 * (m2 + 1)) + + d - 1524); *ns = 0; } - else + else { *ns = 1; + } - *rjd = (int)jd; + *rjd = jd; } static void c_jd_to_civil(int jd, double sg, int *ry, int *rm, int *rdom) { - double x, a, b, c, d, e, y, m, dom; - - if (jd < sg) - a = jd; - else { - x = floor((jd - 1867216.25) / 36524.25); - a = jd + 1 + x - floor(x / 4.0); - } - b = a + 1524; - c = floor((b - 122.1) / 365.25); - d = floor(365.25 * c); - e = floor((b - d) / 30.6001); - dom = b - d - floor(30.6001 * e); - if (e <= 13) { - m = e - 1; - y = c - 4716; - } - else { - m = e - 13; - y = c - 4715; + /* Fast path: pure Gregorian or date after switchover, within safe range */ + if ((c_gregorian_only_p(sg) || jd >= sg) && ns_jd_in_range(jd)) { + c_gregorian_jd_to_civil(jd, ry, rm, rdom); + return; } - *ry = (int)y; - *rm = (int)m; - *rdom = (int)dom; + /* Original algorithm for Julian calendar or extreme dates */ + { + double x, a, b, c, d, e, y, m, dom; + + if (jd < sg) + a = jd; + else { + x = floor((jd - 1867216.25) / 36524.25); + a = jd + 1 + x - floor(x / 4.0); + } + b = a + 1524; + c = floor((b - 122.1) / 365.25); + d = floor(365.25 * c); + e = floor((b - d) / 30.6001); + dom = b - d - floor(30.6001 * e); + if (e <= 13) { + m = e - 1; + y = c - 4716; + } + else { + m = e - 13; + y = c - 4715; + } + + *ry = (int)y; + *rm = (int)m; + *rdom = (int)dom; + } } static void @@ -725,6 +777,147 @@ c_gregorian_last_day_of_month(int y, int m) return monthtab[c_gregorian_leap_p(y) ? 1 : 0][m]; } +/* + * Neri-Schneider algorithm for optimized Gregorian date conversion. + * Reference: Neri & Schneider, "Euclidean Affine Functions and Applications + * to Calendar Algorithms", Software: Practice and Experience, 2023. + * https://arxiv.org/abs/2102.06959 + * + * This algorithm provides ~2-3x speedup over traditional floating-point + * implementations by using pure integer arithmetic with multiplication + * and bit-shifts instead of expensive division operations. + */ + +/* JDN of March 1, Year 0 in proleptic Gregorian calendar */ +#define NS_EPOCH 1721120 + +/* Days in a 4-year cycle (3 normal years + 1 leap year) */ +#define NS_DAYS_IN_4_YEARS 1461 + +/* Days in a 400-year Gregorian cycle (97 leap years in 400 years) */ +#define NS_DAYS_IN_400_YEARS 146097 + +/* Years per century */ +#define NS_YEARS_PER_CENTURY 100 + +/* + * Multiplier for extracting year within century using fixed-point arithmetic. + * This is ceil(2^32 / NS_DAYS_IN_4_YEARS) for the Euclidean affine function. + */ +#define NS_YEAR_MULTIPLIER 2939745 + +/* + * Coefficients for month calculation from day-of-year. + * Maps day-of-year to month using: month = (NS_MONTH_COEFF * doy + NS_MONTH_OFFSET) >> 16 + */ +#define NS_MONTH_COEFF 2141 +#define NS_MONTH_OFFSET 197913 + +/* + * Coefficients for civil date to JDN month contribution. + * Maps month to accumulated days: days = (NS_CIVIL_MONTH_COEFF * m - NS_CIVIL_MONTH_OFFSET) / 32 + */ +#define NS_CIVIL_MONTH_COEFF 979 +#define NS_CIVIL_MONTH_OFFSET 2919 +#define NS_CIVIL_MONTH_DIVISOR 32 + +/* Days from March 1 to December 31 (for Jan/Feb year adjustment) */ +#define NS_DAYS_BEFORE_NEW_YEAR 306 + +/* + * Safe bounds for Neri-Schneider algorithm to avoid integer overflow. + * These correspond to approximately years -1,000,000 to +1,000,000. + */ +#define NS_JD_MIN -364000000 +#define NS_JD_MAX 538000000 + +inline static int +ns_jd_in_range(int jd) +{ + return jd >= NS_JD_MIN && jd <= NS_JD_MAX; +} + +/* Optimized: Gregorian date -> Julian Day Number */ +static int +c_gregorian_civil_to_jd(int y, int m, int d) +{ + /* Shift epoch to March 1 of year 0 (Jan/Feb belong to previous year) */ + int j = (m < 3) ? 1 : 0; + int y0 = y - j; + int m0 = j ? m + 12 : m; + int d0 = d - 1; + + /* Calculate year contribution with leap year correction */ + int q1 = DIV(y0, NS_YEARS_PER_CENTURY); + int yc = DIV(NS_DAYS_IN_4_YEARS * y0, 4) - q1 + DIV(q1, 4); + + /* Calculate month contribution using integer arithmetic */ + int mc = (NS_CIVIL_MONTH_COEFF * m0 - NS_CIVIL_MONTH_OFFSET) / NS_CIVIL_MONTH_DIVISOR; + + /* Combine and add epoch offset to get JDN */ + return yc + mc + d0 + NS_EPOCH; +} + +/* Optimized: Julian Day Number -> Gregorian date */ +static void +c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd) +{ + int r0, n1, q1, r1, n2, q2, r2, n3, q3, r3, y0, j; + uint64_t u2; + + /* Convert JDN to rata die (March 1, Year 0 epoch) */ + r0 = jd - NS_EPOCH; + + /* Extract century and day within 400-year cycle */ + /* Use Euclidean (floor) division for negative values */ + n1 = 4 * r0 + 3; + q1 = DIV(n1, NS_DAYS_IN_400_YEARS); + r1 = MOD(n1, NS_DAYS_IN_400_YEARS) / 4; + + /* Calculate year within century and day of year */ + n2 = 4 * r1 + 3; + /* Use 64-bit arithmetic to avoid overflow */ + u2 = (uint64_t)NS_YEAR_MULTIPLIER * (uint64_t)n2; + q2 = (int)(u2 >> 32); + r2 = (int)((uint32_t)u2 / NS_YEAR_MULTIPLIER / 4); + + /* Calculate month and day using integer arithmetic */ + n3 = NS_MONTH_COEFF * r2 + NS_MONTH_OFFSET; + q3 = n3 >> 16; + r3 = (n3 & 0xFFFF) / NS_MONTH_COEFF; + + /* Combine century and year */ + y0 = NS_YEARS_PER_CENTURY * q1 + q2; + + /* Adjust for January/February (shift from fiscal year) */ + j = (r2 >= NS_DAYS_BEFORE_NEW_YEAR) ? 1 : 0; + + *ry = y0 + j; + *rm = j ? q3 - 12 : q3; + *rd = r3 + 1; +} + +/* O(1) first day of year for Gregorian calendar */ +inline static int +c_gregorian_fdoy(int y) +{ + return c_gregorian_civil_to_jd(y, 1, 1); +} + +/* O(1) last day of year for Gregorian calendar */ +inline static int +c_gregorian_ldoy(int y) +{ + return c_gregorian_civil_to_jd(y, 12, 31); +} + +/* O(1) last day of month (JDN) for Gregorian calendar */ +inline static int +c_gregorian_ldom_jd(int y, int m) +{ + return c_gregorian_civil_to_jd(y, m, c_gregorian_last_day_of_month(y, m)); +} + static int c_valid_julian_p(int y, int m, int d, int *rm, int *rd) { @@ -2498,7 +2691,7 @@ date_s__valid_jd_p(int argc, VALUE *argv, VALUE klass) * * Date.valid_jd?(2451944) # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd. */ @@ -2592,7 +2785,7 @@ date_s__valid_civil_p(int argc, VALUE *argv, VALUE klass) * Date.valid_date?(2001, 2, 29) # => false * Date.valid_date?(2001, 2, -1) # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.new. */ @@ -2680,7 +2873,7 @@ date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass) * Date.valid_ordinal?(2001, 34) # => true * Date.valid_ordinal?(2001, 366) # => false * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.ordinal. */ @@ -2770,7 +2963,7 @@ date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass) * * See Date.commercial. * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.commercial. */ @@ -3350,7 +3543,7 @@ static VALUE d_lite_plus(VALUE, VALUE); * * Date.jd(Date::ITALY - 1).julian? # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.new. */ @@ -3415,7 +3608,7 @@ date_s_jd(int argc, VALUE *argv, VALUE klass) * * Raises an exception if +yday+ is zero or out of range. * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.new. */ @@ -3492,7 +3685,7 @@ date_s_civil(int argc, VALUE *argv, VALUE klass) * where +n+ is the number of days in the month; * when the argument is negative, counts backward from the end of the month. * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd. */ @@ -3598,7 +3791,7 @@ date_initialize(int argc, VALUE *argv, VALUE self) * Date.commercial(2020, 1, 1).to_s # => "2019-12-30" Date.commercial(2020, 1, 7).to_s # => "2020-01-05" * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.new, Date.ordinal. */ @@ -3783,7 +3976,7 @@ static void set_sg(union DateData *, double); * * Date.today.to_s # => "2022-07-06" * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * */ static VALUE @@ -3974,7 +4167,7 @@ rt_complete_frags(VALUE klass, VALUE hash) rb_gc_register_mark_object(tab); } - k = Qnil; + k = a = Qnil; { long i, eno = 0; @@ -4335,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"); @@ -4345,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"); @@ -4383,7 +4576,7 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, * Date._strptime('2001-02-03', '%Y-%m-%d') # => {:year=>2001, :mon=>2, :mday=>3} * * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. * (Unlike Date.strftime, does not support flags and width.) * * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. @@ -4412,10 +4605,10 @@ date_s__strptime(int argc, VALUE *argv, VALUE klass) * Date.strptime('sat3feb01', '%a%d%b%y') # => #<Date: 2001-02-03> * * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. * (Unlike Date.strftime, does not support flags and width.) * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. * @@ -4464,11 +4657,10 @@ get_limit(VALUE opt) #define rb_category_warn(category, fmt) rb_warn(fmt) #endif -static void +static VALUE check_limit(VALUE str, VALUE opt) { size_t slen, limit; - if (NIL_P(str)) return; StringValue(str); slen = RSTRING_LEN(str); limit = get_limit(opt); @@ -4476,6 +4668,7 @@ check_limit(VALUE str, VALUE opt) rb_raise(rb_eArgError, "string length (%"PRI_SIZE_PREFIX"u) exceeds the limit %"PRI_SIZE_PREFIX"u", slen, limit); } + return str; } static VALUE @@ -4484,8 +4677,7 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) VALUE vstr, vcomp, hash, opt; argc = rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt); - check_limit(vstr, opt); - StringValue(vstr); + vstr = check_limit(vstr, opt); if (!rb_enc_str_asciicompat_p(vstr)) rb_raise(rb_eArgError, "string should have ASCII compatible encoding"); @@ -4505,7 +4697,7 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) * This method recognizes many forms in +string+, * but it is not a validator. * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * * If +string+ does not specify a valid date, * the result is unpredictable; @@ -4540,7 +4732,7 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) * This method recognizes many forms in +string+, * but it is not a validator. * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * If +string+ does not specify a valid date, * the result is unpredictable; * consider using Date._strptime instead. @@ -4560,7 +4752,7 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._parse (returns a hash). @@ -4604,7 +4796,7 @@ VALUE date__jisx0301(VALUE); * Date._iso8601(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should contain - * an {ISO 8601 formatted date}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: * * d = Date.new(2001, 2, 3) * s = d.iso8601 # => "2001-02-03" @@ -4620,7 +4812,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__iso8601(str); } @@ -4631,7 +4823,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should contain - * an {ISO 8601 formatted date}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: * * d = Date.new(2001, 2, 3) * s = d.iso8601 # => "2001-02-03" @@ -4639,7 +4831,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._iso8601 (returns a hash). @@ -4673,7 +4865,7 @@ date_s_iso8601(int argc, VALUE *argv, VALUE klass) * Date._rfc3339(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 3339 format}[rdoc-ref:strftime_formatting.rdoc@RFC+3339+Format]: + * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" @@ -4690,7 +4882,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__rfc3339(str); } @@ -4701,7 +4893,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {RFC 3339 format}[rdoc-ref:strftime_formatting.rdoc@RFC+3339+Format]: + * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" @@ -4709,7 +4901,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._rfc3339 (returns a hash). @@ -4759,7 +4951,7 @@ date_s__xmlschema(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__xmlschema(str); } @@ -4777,7 +4969,7 @@ date_s__xmlschema(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._xmlschema (returns a hash). @@ -4811,7 +5003,7 @@ date_s_xmlschema(int argc, VALUE *argv, VALUE klass) * Date._rfc2822(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 2822 date format}[rdoc-ref:strftime_formatting.rdoc@RFC+2822+Format]: + * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" @@ -4828,7 +5020,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__rfc2822(str); } @@ -4839,7 +5031,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {RFC 2822 date format}[rdoc-ref:strftime_formatting.rdoc@RFC+2822+Format]: + * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" @@ -4847,7 +5039,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._rfc2822 (returns a hash). @@ -4881,7 +5073,7 @@ date_s_rfc2822(int argc, VALUE *argv, VALUE klass) * Date._httpdate(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {HTTP date format}[rdoc-ref:strftime_formatting.rdoc@HTTP+Format]: + * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: * * d = Date.new(2001, 2, 3) * s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" @@ -4896,7 +5088,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__httpdate(str); } @@ -4907,7 +5099,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {HTTP date format}[rdoc-ref:strftime_formatting.rdoc@HTTP+Format]: + * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: * * d = Date.new(2001, 2, 3) s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" @@ -4915,7 +5107,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._httpdate (returns a hash). @@ -4949,7 +5141,7 @@ date_s_httpdate(int argc, VALUE *argv, VALUE klass) * Date._jisx0301(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {JIS X 0301 date format}[rdoc-ref:strftime_formatting.rdoc@JIS+X+0301+Format]: + * {JIS X 0301 date format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: * * d = Date.new(2001, 2, 3) * s = d.jisx0301 # => "H13.02.03" @@ -4965,7 +5157,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__jisx0301(str); } @@ -4975,7 +5167,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) * Date.jisx0301(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date * * Returns a new \Date object with values parsed from +string+, - * which should be a valid {JIS X 0301 format}[rdoc-ref:strftime_formatting.rdoc@JIS+X+0301+Format]: + * which should be a valid {JIS X 0301 format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: * * d = Date.new(2001, 2, 3) * s = d.jisx0301 # => "H13.02.03" @@ -4987,7 +5179,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._jisx0301 (returns a hash). @@ -5745,7 +5937,7 @@ d_lite_leap_p(VALUE self) * Date.new(2001, 2, 3, Date::GREGORIAN).start # => -Infinity * Date.new(2001, 2, 3, Date::JULIAN).start # => Infinity * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * */ static VALUE @@ -5811,7 +6003,7 @@ dup_obj_with_new_start(VALUE obj, double sg) /* * call-seq: - * new_start(start = Date::ITALY]) -> new_date + * new_start(start = Date::ITALY) -> new_date * * Returns a copy of +self+ with the given +start+ value: * @@ -5820,7 +6012,7 @@ dup_obj_with_new_start(VALUE obj, double sg) * d1 = d0.new_start(Date::JULIAN) * d1.julian? # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * */ static VALUE @@ -6968,7 +7160,7 @@ static VALUE strftimev(const char *, VALUE, * to_s -> string * * Returns a string representation of the date in +self+ - * in {ISO 8601 extended date format}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications] + * in {ISO 8601 extended date format}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications] * (<tt>'%Y-%m-%d'</tt>): * * Date.new(2001, 2, 3).to_s # => "2001-02-03" @@ -7249,7 +7441,7 @@ date_strftime_internal(int argc, VALUE *argv, VALUE self, * Date.new(2001, 2, 3).strftime # => "2001-02-03" * * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. * */ static VALUE @@ -7281,12 +7473,12 @@ strftimev(const char *fmt, VALUE self, * asctime -> string * * Equivalent to #strftime with argument <tt>'%a %b %e %T %Y'</tt> - * (or its {shorthand form}[rdoc-ref:strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] * <tt>'%c'</tt>): * * Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" * - * See {asctime}[https://linux.die.net/man/3/asctime]. + * See {asctime}[https://man7.org/linux/man-pages/man3/asctime.3p.html]. * */ static VALUE @@ -7300,7 +7492,7 @@ d_lite_asctime(VALUE self) * iso8601 -> string * * Equivalent to #strftime with argument <tt>'%Y-%m-%d'</tt> - * (or its {shorthand form}[rdoc-ref:strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] * <tt>'%F'</tt>); * * Date.new(2001, 2, 3).iso8601 # => "2001-02-03" @@ -7317,7 +7509,7 @@ d_lite_iso8601(VALUE self) * rfc3339 -> string * * Equivalent to #strftime with argument <tt>'%FT%T%:z'</tt>; - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).rfc3339 # => "2001-02-03T00:00:00+00:00" * @@ -7333,7 +7525,7 @@ d_lite_rfc3339(VALUE self) * rfc2822 -> string * * Equivalent to #strftime with argument <tt>'%a, %-d %b %Y %T %z'</tt>; - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" * @@ -7349,7 +7541,7 @@ d_lite_rfc2822(VALUE self) * httpdate -> string * * Equivalent to #strftime with argument <tt>'%a, %d %b %Y %T GMT'</tt>; - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" * @@ -8718,7 +8910,7 @@ dt_lite_to_s(VALUE self) * DateTime.now.strftime # => "2022-07-01T11:03:19-05:00" * * For other formats, - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * */ static VALUE @@ -9497,6 +9689,7 @@ Init_date_core(void) sym_zone = ID2SYM(rb_intern_const("zone")); half_days_in_day = rb_rational_new2(INT2FIX(1), INT2FIX(2)); + rb_gc_register_mark_object(half_days_in_day); #if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS day_in_nanoseconds = LONG2NUM((long)DAY_IN_SECONDS * @@ -9508,8 +9701,6 @@ Init_date_core(void) day_in_nanoseconds = f_mul(INT2FIX(DAY_IN_SECONDS), INT2FIX(SECOND_IN_NANOSECONDS)); #endif - - rb_gc_register_mark_object(half_days_in_day); rb_gc_register_mark_object(day_in_nanoseconds); positive_inf = +INFINITY; @@ -9525,7 +9716,7 @@ Init_date_core(void) * * - You need both dates and times; \Date handles only dates. * - You need only Gregorian dates (and not Julian dates); - * see {Julian and Gregorian Calendars}[rdoc-ref:date/calendars.rdoc]. + * see {Julian and Gregorian Calendars}[rdoc-ref:language/calendars.rdoc]. * * A \Date object, once created, is immutable, and cannot be modified. * @@ -9572,7 +9763,7 @@ Init_date_core(void) * Date.strptime('fri31dec99', '%a%d%b%y') # => #<Date: 1999-12-31> * * See also the specialized methods in - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * * == Argument +limit+ * 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/date/lib/date.rb b/ext/date/lib/date.rb index aa630eb6d1..0cb763017f 100644 --- a/ext/date/lib/date.rb +++ b/ext/date/lib/date.rb @@ -4,7 +4,7 @@ require 'date_core' class Date - VERSION = "3.4.1" # :nodoc: + VERSION = "3.5.1" # :nodoc: # call-seq: # infinite? -> false diff --git a/ext/digest/digest.c b/ext/digest/digest.c index bd8d3e815f..28f6022754 100644 --- a/ext/digest/digest.c +++ b/ext/digest/digest.c @@ -571,7 +571,7 @@ static rb_digest_metadata_t * get_digest_base_metadata(VALUE klass) { VALUE p; - VALUE obj; + VALUE obj = Qnil; rb_digest_metadata_t *algo; for (p = klass; !NIL_P(p); p = rb_class_superclass(p)) { diff --git a/ext/digest/lib/digest/version.rb b/ext/digest/lib/digest/version.rb index 854106165c..a56e80c54e 100644 --- a/ext/digest/lib/digest/version.rb +++ b/ext/digest/lib/digest/version.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true module Digest - VERSION = "3.2.0" + # The version string + VERSION = "3.2.1" end diff --git a/ext/digest/sha1/sha1.c b/ext/digest/sha1/sha1.c index ce200270b7..003c87d224 100644 --- a/ext/digest/sha1/sha1.c +++ b/ext/digest/sha1/sha1.c @@ -220,14 +220,16 @@ int SHA1_Init(SHA1_CTX *context) */ void SHA1_Update(SHA1_CTX *context, const uint8_t *data, size_t len) { - uint32_t i, j; + size_t i; + uint32_t j; _DIAGASSERT(context != 0); _DIAGASSERT(data != 0); j = context->count[0]; if ((context->count[0] += len << 3) < j) - context->count[1] += (len>>29)+1; + context->count[1]++; + context->count[1] += (uint32_t)(len >> 29); j = (j >> 3) & 63; if ((j + len) > 63) { (void)memcpy(&context->buffer[j], data, (i = 64-j)); diff --git a/ext/erb/escape/escape.c b/ext/erb/escape/escape.c index 2a5903c3b7..1794fc30eb 100644 --- a/ext/erb/escape/escape.c +++ b/ext/erb/escape/escape.c @@ -39,29 +39,41 @@ static VALUE optimized_escape_html(VALUE str) { VALUE vbuf; - char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); + char *buf = NULL; const char *cstr = RSTRING_PTR(str); const char *end = cstr + RSTRING_LEN(str); - char *dest = buf; + const char *segment_start = cstr; + char *dest = NULL; while (cstr < end) { const unsigned char c = *cstr++; uint8_t len = html_escape_table[c].len; if (len) { + size_t segment_len = cstr - segment_start - 1; + if (!buf) { + buf = ALLOCV_N(char, vbuf, escaped_length(str)); + dest = buf; + } + if (segment_len) { + memcpy(dest, segment_start, segment_len); + dest += segment_len; + } + segment_start = cstr; memcpy(dest, html_escape_table[c].str, len); dest += len; } - else { - *dest++ = c; - } } - VALUE escaped = str; - if (RSTRING_LEN(str) < (dest - buf)) { + if (buf) { + size_t segment_len = cstr - segment_start; + if (segment_len) { + memcpy(dest, segment_start, segment_len); + dest += segment_len; + } escaped = rb_str_new(buf, dest - buf); preserve_original_state(str, escaped); + ALLOCV_END(vbuf); } - ALLOCV_END(vbuf); return escaped; } diff --git a/ext/etc/etc.c b/ext/etc/etc.c index 66ef8fc9a4..8d50a96a62 100644 --- a/ext/etc/etc.c +++ b/ext/etc/etc.c @@ -832,11 +832,7 @@ etc_uname(VALUE obj) rb_w32_conv_from_wchar(v.szCSDVersion, rb_utf8_encoding())); rb_hash_aset(result, SYMBOL_LIT("version"), version); -# if defined _MSC_VER && _MSC_VER < 1300 -# define GET_COMPUTER_NAME(ptr, plen) GetComputerNameW(ptr, plen) -# else # define GET_COMPUTER_NAME(ptr, plen) GetComputerNameExW(ComputerNameDnsFullyQualified, ptr, plen) -# endif GET_COMPUTER_NAME(NULL, &len); buf = ALLOCV_N(WCHAR, vbuf, len); if (GET_COMPUTER_NAME(buf, &len)) { diff --git a/ext/etc/etc.gemspec b/ext/etc/etc.gemspec index 3facc74866..0e9803dc62 100644 --- a/ext/etc/etc.gemspec +++ b/ext/etc/etc.gemspec @@ -40,5 +40,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.extensions = %w{ext/etc/extconf.rb} - spec.required_ruby_version = ">= 2.6.0" + spec.required_ruby_version = ">= 2.7.0" end diff --git a/ext/extmk.rb b/ext/extmk.rb index 39cbce1bc9..f5244f72c8 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -8,8 +8,6 @@ module Gem RbConfig::CONFIG end end -# only needs Gem::Platform -require 'rubygems/platform' # :stopdoc: $extension = nil @@ -36,13 +34,16 @@ DUMMY_SIGNATURE = "***DUMMY MAKEFILE***" srcdir = File.dirname(File.dirname(__FILE__)) unless defined?(CROSS_COMPILING) and CROSS_COMPILING - $:.replace([File.expand_path("lib", srcdir), Dir.pwd]) + $:.replace([Dir.pwd, File.expand_path("lib", srcdir)]) end -$:.unshift(srcdir) require 'rbconfig' +# only needs Gem::Platform +require 'rubygems/platform' + $topdir = "." $top_srcdir = srcdir +$extmk = true inplace = File.identical?($top_srcdir, $topdir) $" << "mkmf.rb" @@ -138,14 +139,6 @@ def extract_makefile(makefile, keep = true) true end -def create_makefile(target, srcprefix = nil) - if $static and target.include?("/") - base = File.basename(target) - $defs << "-DInit_#{base}=Init_#{target.tr('/', '_')}" - end - super -end - def extmake(target, basedir = 'ext', maybestatic = true) FileUtils.mkpath target unless File.directory?(target) begin @@ -566,6 +559,7 @@ extend Module.new { if $static and (target = args.first).include?("/") base = File.basename(target) $defs << "-DInit_#{base}=Init_#{target.tr('/', '_')}" + $defs << "-DInitVM_#{base}=InitVM_#{target.tr('/', '_')}" end return super end @@ -598,6 +592,7 @@ gem = #{@gemname} build_complete = $(TARGET_GEM_DIR)/gem.build_complete install-so: build_complete clean-so:: clean-build_complete +$(build_complete) $(OBJS): $(TARGET_SO_DIR_TIMESTAMP) build_complete: $(build_complete) $(build_complete): $(TARGET_SO) diff --git a/ext/fcntl/fcntl.c b/ext/fcntl/fcntl.c index 86bee5fb45..b987e237dd 100644 --- a/ext/fcntl/fcntl.c +++ b/ext/fcntl/fcntl.c @@ -65,7 +65,7 @@ pack up your own arguments to pass as args for locking functions, etc. * */ -#define FCNTL_VERSION "1.2.0" +#define FCNTL_VERSION "1.3.0" void Init_fcntl(void) @@ -251,4 +251,50 @@ Init_fcntl(void) */ rb_define_const(mFcntl, "F_DUP2FD_CLOEXEC", INT2NUM(F_DUP2FD_CLOEXEC)); #endif + +#ifdef F_PREALLOCATE + /* + * macOS specific flag used for preallocating file space. + */ + rb_define_const(mFcntl, "F_PREALLOCATE", INT2NUM(F_PREALLOCATE)); +#endif + +#ifdef F_ALLOCATECONTIG + /* + * macOS specific flag used with F_PREALLOCATE for allocating contiguous + * space. + */ + rb_define_const(mFcntl, "F_ALLOCATECONTIG", INT2NUM(F_ALLOCATECONTIG)); +#endif + +#ifdef F_ALLOCATEALL + /* + * macOS specific flag used with F_PREALLOCATE for allocating all contiguous + * space or no space. + */ + rb_define_const(mFcntl, "F_ALLOCATEALL", INT2NUM(F_ALLOCATEALL)); +#endif + +#ifdef F_ALLOCATEPERSIST + /* + * macOS specific flag used with F_PREALLOCATE. Allocate space that is not + * freed when close is called. + */ + rb_define_const(mFcntl, "F_ALLOCATEPERSIST", INT2NUM(F_ALLOCATEPERSIST)); +#endif + +#ifdef F_PEOFPOSMODE + /* + * macOS specific flag used with F_PREALLOCATE. Allocate from the physical + * end of file + */ + rb_define_const(mFcntl, "F_PEOFPOSMODE", INT2NUM(F_PEOFPOSMODE)); +#endif + +#ifdef F_VOLPOSMODE + /* + * macOS specific flag used with F_PREALLOCATE. Allocate from the volume offset. + */ + rb_define_const(mFcntl, "F_VOLPOSMODE", INT2NUM(F_VOLPOSMODE)); +#endif } diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 3c8bb82083..5f3c9478f7 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -4,7 +4,7 @@ */ static const char *const -IO_CONSOLE_VERSION = "0.8.1"; +IO_CONSOLE_VERSION = "0.8.2"; #include "ruby.h" #include "ruby/io.h" @@ -840,6 +840,9 @@ console_winsize(VALUE io) return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws))); } +static VALUE console_scroll(VALUE io, int line); +static VALUE console_goto(VALUE io, VALUE y, VALUE x); + /* * call-seq: * io.winsize = [rows, columns] @@ -856,7 +859,8 @@ console_set_winsize(VALUE io, VALUE size) #if defined _WIN32 HANDLE wh; int newrow, newcol; - BOOL ret; + COORD oldsize; + SMALL_RECT oldwindow; #endif VALUE row, col, xpixel, ypixel; const VALUE *sz; @@ -891,18 +895,62 @@ console_set_winsize(VALUE io, VALUE size) if (!GetConsoleScreenBufferInfo(wh, &ws)) { rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo"); } + oldsize = ws.dwSize; + oldwindow = ws.srWindow; + if (ws.srWindow.Right + 1 < newcol) { + ws.dwSize.X = newcol; + } + if (ws.dwSize.Y < newrow) { + ws.dwSize.Y = newrow; + } + /* expand screen buffer first if needed */ + if (!SetConsoleScreenBufferSize(wh, ws.dwSize)) { + rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); + } + /* refresh ws for new dwMaximumWindowSize */ + if (!GetConsoleScreenBufferInfo(wh, &ws)) { + rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo"); + } + /* check new size before modifying buffer content */ + if (newrow <= 0 || newcol <= 0 || + newrow > ws.dwMaximumWindowSize.Y || + newcol > ws.dwMaximumWindowSize.X) { + SetConsoleScreenBufferSize(wh, oldsize); + /* remove scrollbar if possible */ + SetConsoleWindowInfo(wh, TRUE, &oldwindow); + rb_raise(rb_eArgError, "out of range winsize: [%d, %d]", newrow, newcol); + } + /* shrink screen buffer width */ ws.dwSize.X = newcol; - ret = SetConsoleScreenBufferSize(wh, ws.dwSize); + /* shrink screen buffer height if window height were the same */ + if (oldsize.Y == ws.srWindow.Bottom - ws.srWindow.Top + 1) { + ws.dwSize.Y = newrow; + } ws.srWindow.Left = 0; - ws.srWindow.Top = 0; - ws.srWindow.Right = newcol-1; - ws.srWindow.Bottom = newrow-1; + ws.srWindow.Right = newcol - 1; + ws.srWindow.Bottom = ws.srWindow.Top + newrow -1; + if (ws.dwCursorPosition.Y > ws.srWindow.Bottom) { + console_scroll(io, ws.dwCursorPosition.Y - ws.srWindow.Bottom); + ws.dwCursorPosition.Y = ws.srWindow.Bottom; + console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); + } + if (ws.srWindow.Bottom > ws.dwSize.Y - 1) { + console_scroll(io, ws.srWindow.Bottom - (ws.dwSize.Y - 1)); + ws.dwCursorPosition.Y -= ws.srWindow.Bottom - (ws.dwSize.Y - 1); + console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); + ws.srWindow.Bottom = ws.dwSize.Y - 1; + } + ws.srWindow.Top = ws.srWindow.Bottom - (newrow - 1); + /* perform changes to winsize */ if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { - rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo"); + int last_error = LAST_ERROR; + SetConsoleScreenBufferSize(wh, oldsize); + rb_syserr_fail(last_error, "SetConsoleWindowInfo"); } - /* retry when shrinking buffer after shrunk window */ - if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) { - rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); + /* perform screen buffer shrinking if necessary */ + if ((ws.dwSize.Y < oldsize.Y || ws.dwSize.X < oldsize.X) && + !SetConsoleScreenBufferSize(wh, ws.dwSize)) { + rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); } /* remove scrollbar if possible */ if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { @@ -1221,7 +1269,7 @@ console_cursor_pos(VALUE io) if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { rb_syserr_fail(LAST_ERROR, 0); } - return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); + return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y - ws.srWindow.Top), UINT2NUM(ws.dwCursorPosition.X)); #else static const struct query_args query = {"\033[6n", 0}; VALUE resp = console_vt_response(0, 0, io, &query); @@ -1254,11 +1302,17 @@ static VALUE console_goto(VALUE io, VALUE y, VALUE x) { #ifdef _WIN32 - COORD pos; - int fd = GetWriteFD(io); - pos.X = NUM2UINT(x); - pos.Y = NUM2UINT(y); - if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X = NUM2UINT(x); + pos->Y = ws.srWindow.Top + NUM2UINT(y); + if (!SetConsoleCursorPosition(h, *pos)) { rb_syserr_fail(LAST_ERROR, 0); } #else @@ -1651,13 +1705,11 @@ console_dev(int argc, VALUE *argv, VALUE klass) VALUE con = 0; VALUE sym = 0; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - if (argc) { Check_Type(sym = argv[0], T_SYMBOL); } - // Force the class to be File. + /* Force the class to be File. */ if (klass == rb_cIO) klass = rb_cFile; if (console_dev_get(klass, &con)) { @@ -1948,14 +2000,13 @@ InitVM_console(void) rb_define_method(rb_cIO, "ttyname", console_ttyname, 0); rb_define_singleton_method(rb_cIO, "console", console_dev, -1); { - /* :stopdoc: */ + /* :nodoc: */ VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable"); - /* :startdoc: */ rb_define_method(mReadable, "getch", io_getch, -1); rb_define_method(mReadable, "getpass", io_getpass, -1); } { - /* :stopdoc: */ + /* :nodoc: */ cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject); rb_define_const(cConmode, "VERSION", rb_obj_freeze(rb_str_new_cstr(IO_CONSOLE_VERSION))); rb_define_alloc_func(cConmode, conmode_alloc); @@ -1964,6 +2015,5 @@ InitVM_console(void) rb_define_method(cConmode, "echo=", conmode_set_echo, 1); rb_define_method(cConmode, "raw!", conmode_set_raw, -1); rb_define_method(cConmode, "raw", conmode_raw_new, -1); - /* :startdoc: */ } } diff --git a/ext/io/wait/extconf.rb b/ext/io/wait/extconf.rb index e97efa179d..00c455a45c 100644 --- a/ext/io/wait/extconf.rb +++ b/ext/io/wait/extconf.rb @@ -1,21 +1,4 @@ # frozen_string_literal: false require 'mkmf' -target = "io/wait" -have_func("rb_io_wait", "ruby/io.h") -have_func("rb_io_descriptor", "ruby/io.h") -unless macro_defined?("DOSISH", "#include <ruby.h>") - have_header(ioctl_h = "sys/ioctl.h") or ioctl_h = nil - fionread = %w[sys/ioctl.h sys/filio.h sys/socket.h].find do |h| - have_macro("FIONREAD", [h, ioctl_h].compact) - end - if fionread - $defs << "-DFIONREAD_HEADER=\"<#{fionread}>\"" - create_makefile(target) - end -else - if have_func("rb_w32_ioctlsocket", "ruby.h") - have_func("rb_w32_is_socket", "ruby.h") - create_makefile(target) - end -end +create_makefile("io/wait") diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index 1554dcdb30..c1c6172589 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,4 +1,4 @@ -_VERSION = "0.3.2" +_VERSION = "0.4.0" Gem::Specification.new do |spec| spec.name = "io-wait" @@ -10,25 +10,25 @@ Gem::Specification.new do |spec| spec.description = %q{Waits until IO is readable or writable without blocking.} spec.homepage = "https://github.com/ruby/io-wait" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = Gem::Requirement.new(">= 3.0") + spec.required_ruby_version = Gem::Requirement.new(">= 3.2") spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject do |f| - File.identical?(f, __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)}) - end - end + jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby' + dir, gemspec = File.split(__FILE__) + excludes = [ + *%w[:^/.git* :^/Gemfile* :^/Rakefile* :^/bin/ :^/test/ :^/rakelib/ :^*.java], + *(jruby ? %w[:^/ext/io] : %w[:^/ext/java]), + ":(exclude,literal,top)#{gemspec}" + ] + files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0") + + spec.files = files spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] - jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby' - spec.files.delete_if do |f| - f.end_with?(".java") or - f.start_with?("ext/") && (jruby ^ f.start_with?("ext/java/")) - end if jruby spec.platform = 'java' spec.files << "lib/io/wait.jar" diff --git a/ext/io/wait/wait.c b/ext/io/wait/wait.c index 88e6dd2af1..f7575191fe 100644 --- a/ext/io/wait/wait.c +++ b/ext/io/wait/wait.c @@ -11,427 +11,13 @@ **********************************************************************/ -#include "ruby.h" -#include "ruby/io.h" - -#include <sys/types.h> -#if defined(HAVE_UNISTD_H) && (defined(__sun)) -#include <unistd.h> -#endif -#if defined(HAVE_SYS_IOCTL_H) -#include <sys/ioctl.h> -#endif -#if defined(FIONREAD_HEADER) -#include FIONREAD_HEADER -#endif - -#ifdef HAVE_RB_W32_IOCTLSOCKET -#define ioctl ioctlsocket -#define ioctl_arg u_long -#define ioctl_arg2num(i) ULONG2NUM(i) -#else -#define ioctl_arg int -#define ioctl_arg2num(i) INT2NUM(i) -#endif - -#ifdef HAVE_RB_W32_IS_SOCKET -#define FIONREAD_POSSIBLE_P(fd) rb_w32_is_socket(fd) -#else -#define FIONREAD_POSSIBLE_P(fd) ((void)(fd),Qtrue) -#endif - -#ifndef HAVE_RB_IO_WAIT -static struct timeval * -get_timeout(int argc, VALUE *argv, struct timeval *timerec) -{ - VALUE timeout = Qnil; - rb_check_arity(argc, 0, 1); - if (!argc || NIL_P(timeout = argv[0])) { - return NULL; - } - else { - *timerec = rb_time_interval(timeout); - return timerec; - } -} - -static int -wait_for_single_fd(rb_io_t *fptr, int events, struct timeval *tv) -{ - int i = rb_wait_for_single_fd(fptr->fd, events, tv); - if (i < 0) - rb_sys_fail(0); - rb_io_check_closed(fptr); - return (i & events); -} -#endif - -/* - * call-seq: - * io.nread -> int - * - * Returns number of bytes that can be read without blocking. - * Returns zero if no information available. - * - * You must require 'io/wait' to use this method. - */ - -static VALUE -io_nread(VALUE io) -{ - rb_io_t *fptr; - int len; - ioctl_arg n; - - GetOpenFile(io, fptr); - rb_io_check_readable(fptr); - len = rb_io_read_pending(fptr); - if (len > 0) return INT2FIX(len); - -#ifdef HAVE_RB_IO_DESCRIPTOR - int fd = rb_io_descriptor(io); -#else - int fd = fptr->fd; -#endif - - if (!FIONREAD_POSSIBLE_P(fd)) return INT2FIX(0); - if (ioctl(fd, FIONREAD, &n)) return INT2FIX(0); - if (n > 0) return ioctl_arg2num(n); - return INT2FIX(0); -} - -#ifdef HAVE_RB_IO_WAIT -static VALUE -io_wait_event(VALUE io, int event, VALUE timeout, int return_io) -{ - VALUE result = rb_io_wait(io, RB_INT2NUM(event), timeout); - - if (!RB_TEST(result)) { - return Qnil; - } - - int mask = RB_NUM2INT(result); - - if (mask & event) { - if (return_io) - return io; - else - return result; - } - else { - return Qfalse; - } -} -#endif - -/* - * call-seq: - * io.ready? -> truthy or falsy - * - * Returns a truthy value if input available without blocking, or a - * falsy value. - * - * You must require 'io/wait' to use this method. - */ - -static VALUE -io_ready_p(VALUE io) -{ - rb_io_t *fptr; -#ifndef HAVE_RB_IO_WAIT - struct timeval tv = {0, 0}; -#endif - - GetOpenFile(io, fptr); - rb_io_check_readable(fptr); - if (rb_io_read_pending(fptr)) return Qtrue; - -#ifndef HAVE_RB_IO_WAIT - return wait_for_single_fd(fptr, RB_WAITFD_IN, &tv) ? Qtrue : Qfalse; -#else - return io_wait_event(io, RUBY_IO_READABLE, RB_INT2NUM(0), 1); -#endif -} - -/* Ruby 3.2+ can define these methods. This macro indicates that case. */ -#ifndef RUBY_IO_WAIT_METHODS - -/* - * call-seq: - * io.wait_readable -> truthy or falsy - * io.wait_readable(timeout) -> truthy or falsy - * - * Waits until IO is readable and returns a truthy value, or a falsy - * value when times out. Returns a truthy value immediately when - * buffered data is available. - * - * You must require 'io/wait' to use this method. - */ - -static VALUE -io_wait_readable(int argc, VALUE *argv, VALUE io) -{ - rb_io_t *fptr; -#ifndef HAVE_RB_IO_WAIT - struct timeval timerec; - struct timeval *tv; -#endif - - GetOpenFile(io, fptr); - rb_io_check_readable(fptr); - -#ifndef HAVE_RB_IO_WAIT - tv = get_timeout(argc, argv, &timerec); -#endif - if (rb_io_read_pending(fptr)) return Qtrue; - -#ifndef HAVE_RB_IO_WAIT - if (wait_for_single_fd(fptr, RB_WAITFD_IN, tv)) { - return io; - } - return Qnil; -#else - rb_check_arity(argc, 0, 1); - VALUE timeout = (argc == 1 ? argv[0] : Qnil); - - return io_wait_event(io, RUBY_IO_READABLE, timeout, 1); -#endif -} +#include "ruby.h" /* abi_version */ /* - * call-seq: - * io.wait_writable -> truthy or falsy - * io.wait_writable(timeout) -> truthy or falsy - * - * Waits until IO is writable and returns a truthy value or a falsy - * value when times out. - * - * You must require 'io/wait' to use this method. - */ -static VALUE -io_wait_writable(int argc, VALUE *argv, VALUE io) -{ - rb_io_t *fptr; -#ifndef HAVE_RB_IO_WAIT - struct timeval timerec; - struct timeval *tv; -#endif - - GetOpenFile(io, fptr); - rb_io_check_writable(fptr); - -#ifndef HAVE_RB_IO_WAIT - tv = get_timeout(argc, argv, &timerec); - if (wait_for_single_fd(fptr, RB_WAITFD_OUT, tv)) { - return io; - } - return Qnil; -#else - rb_check_arity(argc, 0, 1); - VALUE timeout = (argc == 1 ? argv[0] : Qnil); - - return io_wait_event(io, RUBY_IO_WRITABLE, timeout, 1); -#endif -} - -#ifdef HAVE_RB_IO_WAIT -/* - * call-seq: - * io.wait_priority -> truthy or falsy - * io.wait_priority(timeout) -> truthy or falsy - * - * Waits until IO is priority and returns a truthy value or a falsy - * value when times out. Priority data is sent and received using - * the Socket::MSG_OOB flag and is typically limited to streams. - * - * You must require 'io/wait' to use this method. - */ -static VALUE -io_wait_priority(int argc, VALUE *argv, VALUE io) -{ - rb_io_t *fptr = NULL; - - RB_IO_POINTER(io, fptr); - rb_io_check_readable(fptr); - - if (rb_io_read_pending(fptr)) return Qtrue; - - rb_check_arity(argc, 0, 1); - VALUE timeout = argc == 1 ? argv[0] : Qnil; - - return io_wait_event(io, RUBY_IO_PRIORITY, timeout, 1); -} -#endif - -static int -wait_mode_sym(VALUE mode) -{ - if (mode == ID2SYM(rb_intern("r"))) { - return RB_WAITFD_IN; - } - if (mode == ID2SYM(rb_intern("read"))) { - return RB_WAITFD_IN; - } - if (mode == ID2SYM(rb_intern("readable"))) { - return RB_WAITFD_IN; - } - if (mode == ID2SYM(rb_intern("w"))) { - return RB_WAITFD_OUT; - } - if (mode == ID2SYM(rb_intern("write"))) { - return RB_WAITFD_OUT; - } - if (mode == ID2SYM(rb_intern("writable"))) { - return RB_WAITFD_OUT; - } - if (mode == ID2SYM(rb_intern("rw"))) { - return RB_WAITFD_IN|RB_WAITFD_OUT; - } - if (mode == ID2SYM(rb_intern("read_write"))) { - return RB_WAITFD_IN|RB_WAITFD_OUT; - } - if (mode == ID2SYM(rb_intern("readable_writable"))) { - return RB_WAITFD_IN|RB_WAITFD_OUT; - } - rb_raise(rb_eArgError, "unsupported mode: %"PRIsVALUE, mode); - return 0; -} - -#ifdef HAVE_RB_IO_WAIT -static inline rb_io_event_t -io_event_from_value(VALUE value) -{ - int events = RB_NUM2INT(value); - - if (events <= 0) rb_raise(rb_eArgError, "Events must be positive integer!"); - - return events; -} -#endif - -/* - * call-seq: - * io.wait(events, timeout) -> event mask, false or nil - * io.wait(*event_symbols[, timeout]) -> self, true, or false - * - * Waits until the IO becomes ready for the specified events and returns the - * subset of events that become ready, or a falsy value when times out. - * - * The events can be a bit mask of +IO::READABLE+, +IO::WRITABLE+ or - * +IO::PRIORITY+. - * - * Returns an event mask (truthy value) immediately when buffered data is - * available. - * - * The second form: if one or more event symbols (+:read+, +:write+, or - * +:read_write+) are passed, the event mask is the bit OR of the bitmask - * corresponding to those symbols. In this form, +timeout+ is optional, the - * order of the arguments is arbitrary, and returns +io+ if any of the - * events is ready. - * - * You must require 'io/wait' to use this method. - */ - -static VALUE -io_wait(int argc, VALUE *argv, VALUE io) -{ -#ifndef HAVE_RB_IO_WAIT - rb_io_t *fptr; - struct timeval timerec; - struct timeval *tv = NULL; - int event = 0; - int i; - - GetOpenFile(io, fptr); - for (i = 0; i < argc; ++i) { - if (SYMBOL_P(argv[i])) { - event |= wait_mode_sym(argv[i]); - } - else { - *(tv = &timerec) = rb_time_interval(argv[i]); - } - } - /* rb_time_interval() and might_mode() might convert the argument */ - rb_io_check_closed(fptr); - if (!event) event = RB_WAITFD_IN; - if ((event & RB_WAITFD_IN) && rb_io_read_pending(fptr)) - return Qtrue; - if (wait_for_single_fd(fptr, event, tv)) - return io; - return Qnil; -#else - VALUE timeout = Qundef; - rb_io_event_t events = 0; - int i, return_io = 0; - - if (argc != 2 || (RB_SYMBOL_P(argv[0]) || RB_SYMBOL_P(argv[1]))) { - /* We'd prefer to return the actual mask, but this form would return the io itself: */ - return_io = 1; - - /* Slow/messy path: */ - for (i = 0; i < argc; i += 1) { - if (RB_SYMBOL_P(argv[i])) { - events |= wait_mode_sym(argv[i]); - } - else if (timeout == Qundef) { - rb_time_interval(timeout = argv[i]); - } - else { - rb_raise(rb_eArgError, "timeout given more than once"); - } - } - - if (timeout == Qundef) timeout = Qnil; - - if (events == 0) { - events = RUBY_IO_READABLE; - } - } - else /* argc == 2 and neither are symbols */ { - /* This is the fast path: */ - events = io_event_from_value(argv[0]); - timeout = argv[1]; - } - - if (events & RUBY_IO_READABLE) { - rb_io_t *fptr = NULL; - RB_IO_POINTER(io, fptr); - - if (rb_io_read_pending(fptr)) { - /* This was the original behaviour: */ - if (return_io) return Qtrue; - /* New behaviour always returns an event mask: */ - else return RB_INT2NUM(RUBY_IO_READABLE); - } - } - - return io_wait_event(io, events, timeout, return_io); -#endif -} - -#endif /* RUBY_IO_WAIT_METHODS */ - -/* - * IO wait methods + * IO wait methods are built in ruby now, just for backward compatibility. */ void Init_wait(void) { -#ifdef HAVE_RB_EXT_RACTOR_SAFE - RB_EXT_RACTOR_SAFE(true); -#endif - - rb_define_method(rb_cIO, "nread", io_nread, 0); - rb_define_method(rb_cIO, "ready?", io_ready_p, 0); - -#ifndef RUBY_IO_WAIT_METHODS - rb_define_method(rb_cIO, "wait", io_wait, -1); - - rb_define_method(rb_cIO, "wait_readable", io_wait_readable, -1); - rb_define_method(rb_cIO, "wait_writable", io_wait_writable, -1); -#ifdef HAVE_RB_IO_WAIT - rb_define_method(rb_cIO, "wait_priority", io_wait_priority, -1); -#endif -#endif } diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index d32371476c..b4f5266ca5 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -1,47 +1,9 @@ #ifndef _FBUFFER_H_ #define _FBUFFER_H_ -#include "ruby.h" -#include "ruby/encoding.h" +#include "../json.h" #include "../vendor/jeaiii-ltoa.h" -/* shims */ -/* This is the fallback definition from Ruby 3.4 */ - -#ifndef RBIMPL_STDBOOL_H -#if defined(__cplusplus) -# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) -# include <cstdbool> -# endif -#elif defined(HAVE_STDBOOL_H) -# include <stdbool.h> -#elif !defined(HAVE__BOOL) -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)+0) -# define __bool_true_false_are_defined -#endif -#endif - -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(expr) expr -#endif - -#ifndef RB_LIKELY -#define RB_LIKELY(expr) expr -#endif - -#ifndef MAYBE_UNUSED -# define MAYBE_UNUSED(x) x -#endif - -#ifdef RUBY_DEBUG -#ifndef JSON_DEBUG -#define JSON_DEBUG RUBY_DEBUG -#endif -#endif - enum fbuffer_type { FBUFFER_HEAP_ALLOCATED = 0, FBUFFER_STACK_ALLOCATED = 1, @@ -49,11 +11,11 @@ enum fbuffer_type { typedef struct FBufferStruct { enum fbuffer_type type; - unsigned long initial_length; - unsigned long len; - unsigned long capa; -#ifdef JSON_DEBUG - unsigned long requested; + size_t initial_length; + size_t len; + size_t capa; +#if JSON_DEBUG + size_t requested; #endif char *ptr; VALUE io; @@ -70,12 +32,12 @@ typedef struct FBufferStruct { static void fbuffer_free(FBuffer *fb); static void fbuffer_clear(FBuffer *fb); -static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len); +static void fbuffer_append(FBuffer *fb, const char *newstr, size_t len); static void fbuffer_append_long(FBuffer *fb, long number); static inline void fbuffer_append_char(FBuffer *fb, char newchr); static VALUE fbuffer_finalize(FBuffer *fb); -static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size) +static void fbuffer_stack_init(FBuffer *fb, size_t initial_length, char *stack_buffer, size_t stack_buffer_size) { fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT; if (stack_buffer) { @@ -83,14 +45,14 @@ static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char * fb->ptr = stack_buffer; fb->capa = stack_buffer_size; } -#ifdef JSON_DEBUG +#if JSON_DEBUG fb->requested = 0; #endif } -static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed) +static inline void fbuffer_consumed(FBuffer *fb, size_t consumed) { -#ifdef JSON_DEBUG +#if JSON_DEBUG if (consumed > fb->requested) { rb_bug("fbuffer: Out of bound write"); } @@ -102,7 +64,7 @@ static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed) static void fbuffer_free(FBuffer *fb) { if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) { - ruby_xfree(fb->ptr); + JSON_SIZED_FREE_N(fb->ptr, fb->capa); } } @@ -117,7 +79,7 @@ static void fbuffer_flush(FBuffer *fb) fbuffer_clear(fb); } -static void fbuffer_realloc(FBuffer *fb, unsigned long required) +static void fbuffer_realloc(FBuffer *fb, size_t required) { if (required > fb->capa) { if (fb->type == FBUFFER_STACK_ALLOCATED) { @@ -126,13 +88,13 @@ static void fbuffer_realloc(FBuffer *fb, unsigned long required) fb->type = FBUFFER_HEAP_ALLOCATED; MEMCPY(fb->ptr, old_buffer, char, fb->len); } else { - REALLOC_N(fb->ptr, char, required); + JSON_SIZED_REALLOC_N(fb->ptr, char, required, fb->capa); } fb->capa = required; } } -static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) +static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested) { if (RB_UNLIKELY(fb->io)) { if (fb->capa < FBUFFER_IO_BUFFER_SIZE) { @@ -146,7 +108,7 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) } } - unsigned long required; + size_t required; if (RB_UNLIKELY(!fb->ptr)) { fb->ptr = ALLOC_N(char, fb->initial_length); @@ -158,9 +120,9 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) fbuffer_realloc(fb, required); } -static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) +static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested) { -#ifdef JSON_DEBUG +#if JSON_DEBUG fb->requested = requested; #endif @@ -169,19 +131,33 @@ static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) } } -static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len) +static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b) +{ + size_t result = a * b; + if (RB_UNLIKELY(a != 0 && (result / a) != b)) { + rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated"); + } + return result; +} + +static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len) +{ + MEMCPY(fb->ptr + fb->len, newstr, char, len); + fbuffer_consumed(fb, len); +} + +static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len) { if (len > 0) { fbuffer_inc_capa(fb, len); - MEMCPY(fb->ptr + fb->len, newstr, char, len); - fbuffer_consumed(fb, len); + fbuffer_append_reserved(fb, newstr, len); } } /* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr) { -#ifdef JSON_DEBUG +#if JSON_DEBUG if (fb->requested < 1) { rb_bug("fbuffer: unreserved write"); } @@ -194,12 +170,29 @@ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr) static void fbuffer_append_str(FBuffer *fb, VALUE str) { - const char *newstr = StringValuePtr(str); - unsigned long len = RSTRING_LEN(str); + const char *ptr; + size_t len; + RSTRING_GETMEM(str, ptr, len); + fbuffer_append(fb, ptr, len); RB_GC_GUARD(str); +} - fbuffer_append(fb, newstr, len); +static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat) +{ + const char *ptr; + size_t len; + RSTRING_GETMEM(str, ptr, len); + + fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len)); + while (repeat) { +#if JSON_DEBUG + fb->requested = len; +#endif + fbuffer_append_reserved(fb, ptr, len); + repeat--; + } + RB_GC_GUARD(str); } static inline void fbuffer_append_char(FBuffer *fb, char newchr) @@ -257,14 +250,11 @@ static VALUE fbuffer_finalize(FBuffer *fb) { if (fb->io) { fbuffer_flush(fb); - fbuffer_free(fb); rb_io_flush(fb->io); return fb->io; } else { - VALUE result = rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); - fbuffer_free(fb); - return result; + return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); } } -#endif +#endif // _FBUFFER_H_ diff --git a/ext/json/generator/depend b/ext/json/generator/depend index aee4ab94eb..3ba4acfdd2 100644 --- a/ext/json/generator/depend +++ b/ext/json/generator/depend @@ -178,6 +178,7 @@ generator.o: $(hdrdir)/ruby/ruby.h generator.o: $(hdrdir)/ruby/st.h generator.o: $(hdrdir)/ruby/subst.h generator.o: $(srcdir)/../fbuffer/fbuffer.h +generator.o: $(srcdir)/../json.h generator.o: $(srcdir)/../simd/simd.h generator.o: $(srcdir)/../vendor/fpconv.c generator.o: $(srcdir)/../vendor/jeaiii-ltoa.h diff --git a/ext/json/generator/extconf.rb b/ext/json/generator/extconf.rb index fb9afd07f7..33af03ea30 100644 --- a/ext/json/generator/extconf.rb +++ b/ext/json/generator/extconf.rb @@ -5,8 +5,11 @@ if RUBY_ENGINE == 'truffleruby' File.write('Makefile', dummy_makefile("").join) else append_cflags("-std=c99") + have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 + have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1 + $defs << "-DJSON_GENERATOR" - $defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] + $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0" if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) load __dir__ + "/../simd/conf.rb" diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 9c6ed93049..82853633ba 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1,4 +1,4 @@ -#include "ruby.h" +#include "../json.h" #include "../fbuffer/fbuffer.h" #include "../vendor/fpconv.c" @@ -9,6 +9,12 @@ /* ruby api and some helpers */ +enum duplicate_key_action { + JSON_DEPRECATED = 0, + JSON_IGNORE, + JSON_RAISE, +}; + typedef struct JSON_Generator_StateStruct { VALUE indent; VALUE space; @@ -21,20 +27,19 @@ typedef struct JSON_Generator_StateStruct { long depth; long buffer_initial_length; + enum duplicate_key_action on_duplicate_key; + + bool as_json_single_arg; bool allow_nan; bool ascii_only; bool script_safe; bool strict; } JSON_Generator_State; -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(cond) (cond) -#endif - static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8; -static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode; -static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, +static ID i_to_s, i_to_json, i_new, i_encode; +static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, sym_allow_duplicate_key, sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json; @@ -55,8 +60,11 @@ struct generate_json_data { JSON_Generator_State *state; VALUE obj; generator_func func; + long depth; }; +static SIMD_Implementation simd_impl; + static VALUE cState_from_state_s(VALUE self, VALUE opts); static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func, VALUE io); static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj); @@ -66,9 +74,6 @@ static void generate_json_string(FBuffer *buffer, struct generate_json_data *dat static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, VALUE obj); -#ifdef RUBY_INTEGER_UNIFICATION -static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, VALUE obj); -#endif static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj); @@ -76,23 +81,18 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d static int usascii_encindex, utf8_encindex, binary_encindex; -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_generator_error_str(VALUE invalid_object, VALUE str) +NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str) { + rb_enc_associate_index(str, utf8_encindex); VALUE exc = rb_exc_new_str(eGeneratorError, str); rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object); rb_exc_raise(exc); } -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif #ifdef RBIMPL_ATTR_FORMAT RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) #endif -static void raise_generator_error(VALUE invalid_object, const char *fmt, ...) +NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -127,13 +127,7 @@ typedef struct _search_state { #endif /* HAVE_SIMD */ } search_state; -#if (defined(__GNUC__ ) || defined(__clang__)) -#define FORCE_INLINE __attribute__((always_inline)) -#else -#define FORCE_INLINE -#endif - -static inline FORCE_INLINE void search_flush(search_state *search) +ALWAYS_INLINE(static) void search_flush(search_state *search) { // Do not remove this conditional without profiling, specifically escape-heavy text. // escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush). @@ -160,8 +154,6 @@ static const unsigned char escape_table_basic[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -static unsigned char (*search_escape_basic_impl)(search_state *); - static inline unsigned char search_escape_basic(search_state *search) { while (search->ptr < search->end) { @@ -176,7 +168,7 @@ static inline unsigned char search_escape_basic(search_state *search) return 0; } -static inline FORCE_INLINE void escape_UTF8_char_basic(search_state *search) +ALWAYS_INLINE(static) void escape_UTF8_char_basic(search_state *search) { const unsigned char ch = (unsigned char)*search->ptr; switch (ch) { @@ -217,11 +209,39 @@ static inline FORCE_INLINE void escape_UTF8_char_basic(search_state *search) * Everything else (should be UTF-8) is just passed through and * appended to the result. */ + + +#if defined(HAVE_SIMD_NEON) +static inline unsigned char search_escape_basic_neon(search_state *search); +#elif defined(HAVE_SIMD_SSE2) +static inline unsigned char search_escape_basic_sse2(search_state *search); +#endif + +static inline unsigned char search_escape_basic(search_state *search); + static inline void convert_UTF8_to_JSON(search_state *search) { - while (search_escape_basic_impl(search)) { +#ifdef HAVE_SIMD +#if defined(HAVE_SIMD_NEON) + while (search_escape_basic_neon(search)) { + escape_UTF8_char_basic(search); + } +#elif defined(HAVE_SIMD_SSE2) + if (simd_impl == SIMD_SSE2) { + while (search_escape_basic_sse2(search)) { + escape_UTF8_char_basic(search); + } + return; + } + while (search_escape_basic(search)) { + escape_UTF8_char_basic(search); + } +#endif +#else + while (search_escape_basic(search)) { escape_UTF8_char_basic(search); } +#endif /* HAVE_SIMD */ } static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) @@ -263,8 +283,10 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) #ifdef HAVE_SIMD -static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) +ALWAYS_INLINE(static) char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) { + RBIMPL_ASSERT_OR_ASSUME(len < vec_len); + // Flush the buffer so everything up until the last 'len' characters are unflushed. search_flush(search); @@ -274,19 +296,25 @@ static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsi char *s = (buf->ptr + buf->len); // Pad the buffer with dummy characters that won't need escaping. - // This seem wateful at first sight, but memset of vector length is very fast. - memset(s, 'X', vec_len); + // This seem wasteful at first sight, but memset of vector length is very fast. + // This is a space as it can be directly represented as an immediate on AArch64. + memset(s, ' ', vec_len); // Optimistically copy the remaining 'len' characters to the output FBuffer. If there are no characters // to escape, then everything ends up in the correct spot. Otherwise it was convenient temporary storage. - MEMCPY(s, search->ptr, char, len); + if (vec_len == 16) { + RBIMPL_ASSERT_OR_ASSUME(len >= SIMD_MINIMUM_THRESHOLD); + json_fast_memcpy16(s, search->ptr, len); + } else { + MEMCPY(s, search->ptr, char, len); + } return s; } #ifdef HAVE_SIMD_NEON -static inline FORCE_INLINE unsigned char neon_next_match(search_state *search) +ALWAYS_INLINE(static) unsigned char neon_next_match(search_state *search) { uint64_t mask = search->matches_mask; uint32_t index = trailing_zeros64(mask) >> 2; @@ -400,7 +428,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search) #ifdef HAVE_SIMD_SSE2 -static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search) +ALWAYS_INLINE(static) unsigned char sse2_next_match(search_state *search) { int mask = search->matches_mask; int index = trailing_zeros(mask); @@ -424,7 +452,7 @@ static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search) #define TARGET_SSE2 #endif -static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(search_state *search) +ALWAYS_INLINE(static) TARGET_SSE2 unsigned char search_escape_basic_sse2(search_state *search) { if (RB_UNLIKELY(search->has_matches)) { // There are more matches if search->matches_mask > 0. @@ -672,233 +700,6 @@ static void convert_UTF8_to_ASCII_only_JSON(search_state *search, const unsigned } } -/* - * Document-module: JSON::Ext::Generator - * - * This is the JSON generator implemented as a C extension. It can be - * configured to be used by setting - * - * JSON.generator = JSON::Ext::Generator - * - * with the method generator= in JSON. - * - */ - -/* Explanation of the following: that's the only way to not pollute - * standard library's docs with GeneratorMethods::<ClassName> which - * are uninformative and take a large place in a list of classes - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Array - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Bignum - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::FalseClass - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Fixnum - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Float - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Hash - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Integer - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::NilClass - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Object - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::String - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::String::Extend - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::TrueClass - * :nodoc: - */ - -/* - * call-seq: to_json(state = nil) - * - * Returns a JSON string containing a JSON object, that is generated from - * this Hash instance. - * _state_ is a JSON::State object, that can also be used to configure the - * produced JSON string output further. - */ -static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_object, Qfalse); -} - -/* - * call-seq: to_json(state = nil) - * - * Returns a JSON string containing a JSON array, that is generated from - * this Array instance. - * _state_ is a JSON::State object, that can also be used to configure the - * produced JSON string output further. - */ -static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_array, Qfalse); -} - -#ifdef RUBY_INTEGER_UNIFICATION -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Integer number. - */ -static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_integer, Qfalse); -} - -#else -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Integer number. - */ -static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_fixnum, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Integer number. - */ -static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_bignum, Qfalse); -} -#endif - -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Float number. - */ -static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_float, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * This string should be encoded with UTF-8 A call to this method - * returns a JSON string encoded with UTF16 big endian characters as - * \u????. - */ -static VALUE mString_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_string, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for true: 'true'. - */ -static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("true", 4); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for false: 'false'. - */ -static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("false", 5); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for nil: 'null'. - */ -static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("null", 4); -} - -/* - * call-seq: to_json(*) - * - * Converts this object to a string (calling #to_s), converts - * it to a JSON string, and returns the result. This is a fallback, if no - * special method #to_json was defined for some object. - */ -static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self) -{ - VALUE state; - VALUE string = rb_funcall(self, i_to_s, 0); - rb_scan_args(argc, argv, "01", &state); - Check_Type(string, T_STRING); - state = cState_from_state_s(cState, state); - return cState_partial_generate(state, string, generate_json_string, Qfalse); -} - static void State_mark(void *ptr) { JSON_Generator_State *state = ptr; @@ -921,32 +722,24 @@ static void State_compact(void *ptr) state->as_json = rb_gc_location(state->as_json); } -static void State_free(void *ptr) -{ - JSON_Generator_State *state = ptr; - ruby_xfree(state); -} - static size_t State_memsize(const void *ptr) { +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + return 0; +#else return sizeof(JSON_Generator_State); -} - -#ifndef HAVE_RB_EXT_RACTOR_SAFE -# undef RUBY_TYPED_FROZEN_SHAREABLE -# define RUBY_TYPED_FROZEN_SHAREABLE 0 #endif +} static const rb_data_type_t JSON_Generator_State_type = { - "JSON/Generator/State", - { + .wrap_struct_name = "JSON/Generator/State", + .function = { .dmark = State_mark, - .dfree = State_free, + .dfree = RUBY_DEFAULT_FREE, .dsize = State_memsize, .dcompact = State_compact, }, - 0, 0, - RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE, + .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, }; static void state_init(JSON_Generator_State *state) @@ -978,18 +771,24 @@ static void vstate_spill(struct generate_json_data *data) RB_OBJ_WRITTEN(vstate, Qundef, state->as_json); } -static inline VALUE vstate_get(struct generate_json_data *data) +static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj) { if (RB_UNLIKELY(!data->vstate)) { vstate_spill(data); } - return data->vstate; + GET_STATE(data->vstate); + state->depth = data->depth; + VALUE tmp = rb_funcall(obj, i_to_json, 1, data->vstate); + // no need to restore state->depth, vstate is just a temporary State + return tmp; } -struct hash_foreach_arg { - struct generate_json_data *data; - int iter; -}; +static VALUE +json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key) +{ + VALUE proc_args[2] = {object, is_key}; + return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil); +} static VALUE convert_string_subclass(VALUE key) @@ -1006,6 +805,159 @@ convert_string_subclass(VALUE key) return key_to_s; } +static bool enc_utf8_compatible_p(int enc_idx) +{ + if (enc_idx == usascii_encindex) return true; + if (enc_idx == utf8_encindex) return true; + return false; +} + +static VALUE encode_json_string_try(VALUE str) +{ + return rb_funcall(str, i_encode, 1, Encoding_UTF_8); +} + +static VALUE encode_json_string_rescue(VALUE str, VALUE exception) +{ + raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); + return Qundef; +} + +static inline int json_str_coderange(VALUE str) { + int coderange = RB_ENC_CODERANGE(str); + if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) { + coderange = rb_enc_str_coderange(str); + } + return coderange; +} + +static inline bool valid_json_string_p(VALUE str) +{ + int coderange = json_str_coderange(str); + + if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) { + return true; + } + + if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) { + return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str)); + } + + return false; +} + +NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key) +{ + if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) { + VALUE coerced_str = json_call_as_json(data->state, str, Qfalse); + if (coerced_str != str) { + if (RB_TYPE_P(coerced_str, T_STRING)) { + if (!valid_json_string_p(coerced_str)) { + raise_generator_error(str, "source sequence is illegal/malformed utf-8"); + } + } else { + // as_json could return another type than T_STRING + if (is_key) { + raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str)); + } + } + + return coerced_str; + } + } + + if (RB_ENCODING_GET_INLINED(str) == binary_encindex) { + VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex); + switch (rb_enc_str_coderange(utf8_string)) { + case ENC_CODERANGE_7BIT: + return utf8_string; + case ENC_CODERANGE_VALID: + // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. + // TODO: Raise in 3.0.0 + rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); + return utf8_string; + break; + } + } + + return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); +} + +ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key) +{ + if (RB_LIKELY(valid_json_string_p(str))) { + return str; + } + else { + return convert_invalid_encoding(data, str, as_json_called, is_key); + } +} + +static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + fbuffer_append_char(buffer, '"'); + + long len; + search_state search; + search.buffer = buffer; + RSTRING_GETMEM(obj, search.ptr, len); + search.cursor = search.ptr; + search.end = search.ptr + len; + +#ifdef HAVE_SIMD + search.matches_mask = 0; + search.has_matches = false; + search.chunk_base = NULL; + search.chunk_end = NULL; +#endif /* HAVE_SIMD */ + + switch (json_str_coderange(obj)) { + case ENC_CODERANGE_7BIT: + case ENC_CODERANGE_VALID: + if (RB_UNLIKELY(data->state->ascii_only)) { + convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table); + } else if (RB_UNLIKELY(data->state->script_safe)) { + convert_UTF8_to_script_safe_JSON(&search); + } else { + convert_UTF8_to_JSON(&search); + } + break; + default: + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + break; + } + fbuffer_append_char(buffer, '"'); +} + +static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + obj = ensure_valid_encoding(data, obj, false, false); + raw_generate_json_string(buffer, data, obj); +} + +struct hash_foreach_arg { + VALUE hash; + struct generate_json_data *data; + int first_key_type; + bool first; + bool mixed_keys_encountered; +}; + +NOINLINE(static) void +json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) +{ + if (arg->mixed_keys_encountered) { + return; + } + arg->mixed_keys_encountered = true; + + JSON_Generator_State *state = arg->data->state; + if (state->on_duplicate_key != JSON_IGNORE) { + VALUE do_raise = state->on_duplicate_key == JSON_RAISE ? Qtrue : Qfalse; + rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 2, arg->hash, do_raise); + } +} + static int json_object_i(VALUE key, VALUE val, VALUE _arg) { @@ -1015,22 +967,34 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) FBuffer *buffer = data->buffer; JSON_Generator_State *state = data->state; - long depth = state->depth; - int j; + long depth = data->depth; + int key_type = rb_type(key); + + if (arg->first) { + arg->first = false; + arg->first_key_type = key_type; + } + else { + fbuffer_append_char(buffer, ','); + } - if (arg->iter > 0) fbuffer_append_char(buffer, ','); if (RB_UNLIKELY(data->state->object_nl)) { fbuffer_append_str(buffer, data->state->object_nl); } if (RB_UNLIKELY(data->state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, data->state->indent); - } + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } VALUE key_to_s; - switch (rb_type(key)) { + bool as_json_called = false; + + start: + switch (key_type) { case T_STRING: + if (RB_UNLIKELY(arg->first_key_type != T_STRING)) { + json_inspect_hash_with_mixed_keys(arg); + } + if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) { key_to_s = key; } else { @@ -1038,15 +1002,31 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) } break; case T_SYMBOL: + if (RB_UNLIKELY(arg->first_key_type != T_SYMBOL)) { + json_inspect_hash_with_mixed_keys(arg); + } + key_to_s = rb_sym2str(key); break; default: + if (data->state->strict) { + if (RTEST(data->state->as_json) && !as_json_called) { + key = json_call_as_json(data->state, key, Qtrue); + key_type = rb_type(key); + as_json_called = true; + goto start; + } else { + raise_generator_error(key, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(key)); + } + } key_to_s = rb_convert_type(key, T_STRING, "String", "to_s"); break; } + key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true); + if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) { - generate_json_string(buffer, data, key_to_s); + raw_generate_json_string(buffer, data, key_to_s); } else { generate_json(buffer, data, key_to_s); } @@ -1055,46 +1035,43 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, data->state->space); generate_json(buffer, data, val); - arg->iter++; return ST_CONTINUE; } static inline long increase_depth(struct generate_json_data *data) { JSON_Generator_State *state = data->state; - long depth = ++state->depth; + long depth = ++data->depth; if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) { - rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth); + rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --data->depth); } return depth; } static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { - int j; long depth = increase_depth(data); if (RHASH_SIZE(obj) == 0) { fbuffer_append(buffer, "{}", 2); - --data->state->depth; + --data->depth; return; } fbuffer_append_char(buffer, '{'); struct hash_foreach_arg arg = { + .hash = obj, .data = data, - .iter = 0, + .first = true, }; rb_hash_foreach(obj, json_object_i, (VALUE)&arg); - depth = --data->state->depth; + depth = --data->depth; if (RB_UNLIKELY(data->state->object_nl)) { fbuffer_append_str(buffer, data->state->object_nl); if (RB_UNLIKELY(data->state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, data->state->indent); - } + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } } fbuffer_append_char(buffer, '}'); @@ -1102,125 +1079,41 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { - int i, j; long depth = increase_depth(data); if (RARRAY_LEN(obj) == 0) { fbuffer_append(buffer, "[]", 2); - --data->state->depth; + --data->depth; return; } fbuffer_append_char(buffer, '['); if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl); - for (i = 0; i < RARRAY_LEN(obj); i++) { + for (int i = 0; i < RARRAY_LEN(obj); i++) { if (i > 0) { fbuffer_append_char(buffer, ','); if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl); } if (RB_UNLIKELY(data->state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, data->state->indent); - } + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } generate_json(buffer, data, RARRAY_AREF(obj, i)); } - data->state->depth = --depth; + data->depth = --depth; if (RB_UNLIKELY(data->state->array_nl)) { fbuffer_append_str(buffer, data->state->array_nl); if (RB_UNLIKELY(data->state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, data->state->indent); - } + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } } fbuffer_append_char(buffer, ']'); } -static inline int enc_utf8_compatible_p(int enc_idx) -{ - if (enc_idx == usascii_encindex) return 1; - if (enc_idx == utf8_encindex) return 1; - return 0; -} - -static VALUE encode_json_string_try(VALUE str) -{ - return rb_funcall(str, i_encode, 1, Encoding_UTF_8); -} - -static VALUE encode_json_string_rescue(VALUE str, VALUE exception) -{ - raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); - return Qundef; -} - -static inline VALUE ensure_valid_encoding(VALUE str) -{ - int encindex = RB_ENCODING_GET(str); - VALUE utf8_string; - if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) { - if (encindex == binary_encindex) { - utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex); - switch (rb_enc_str_coderange(utf8_string)) { - case ENC_CODERANGE_7BIT: - return utf8_string; - case ENC_CODERANGE_VALID: - // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. - // TODO: Raise in 3.0.0 - rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); - return utf8_string; - break; - } - } - - str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); - } - return str; -} - -static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) -{ - obj = ensure_valid_encoding(obj); - - fbuffer_append_char(buffer, '"'); - - long len; - search_state search; - search.buffer = buffer; - RSTRING_GETMEM(obj, search.ptr, len); - search.cursor = search.ptr; - search.end = search.ptr + len; - -#ifdef HAVE_SIMD - search.matches_mask = 0; - search.has_matches = false; - search.chunk_base = NULL; -#endif /* HAVE_SIMD */ - - switch (rb_enc_str_coderange(obj)) { - case ENC_CODERANGE_7BIT: - case ENC_CODERANGE_VALID: - if (RB_UNLIKELY(data->state->ascii_only)) { - convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table); - } else if (RB_UNLIKELY(data->state->script_safe)) { - convert_UTF8_to_script_safe_JSON(&search); - } else { - convert_UTF8_to_JSON(&search); - } - break; - default: - raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); - break; - } - fbuffer_append_char(buffer, '"'); -} - static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { VALUE tmp; if (rb_respond_to(obj, i_to_json)) { - tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data)); + tmp = json_call_to_json(data, obj); Check_Type(tmp, T_STRING); fbuffer_append_str(buffer, tmp); } else { @@ -1262,19 +1155,9 @@ static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *dat static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { VALUE tmp = rb_funcall(obj, i_to_s, 0); - fbuffer_append_str(buffer, tmp); + fbuffer_append_str(buffer, StringValue(tmp)); } -#ifdef RUBY_INTEGER_UNIFICATION -static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, VALUE obj) -{ - if (FIXNUM_P(obj)) - generate_json_fixnum(buffer, data, obj); - else - generate_json_bignum(buffer, data, obj); -} -#endif - static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { double value = RFLOAT_VALUE(obj); @@ -1283,11 +1166,11 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data /* for NaN and Infinity values we either raise an error or rely on Float#to_s. */ if (!allow_nan) { if (data->state->strict && data->state->as_json) { - VALUE casted_obj = rb_proc_call_with_block(data->state->as_json, 1, &obj, Qnil); + VALUE casted_obj = json_call_as_json(data->state, obj, Qfalse); if (casted_obj != obj) { increase_depth(data); generate_json(buffer, data, casted_obj); - data->state->depth--; + data->depth--; return; } } @@ -1300,12 +1183,11 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data } /* This implementation writes directly into the buffer. We reserve - * the 28 characters that fpconv_dtoa states as its maximum. + * the 32 characters that fpconv_dtoa states as its maximum. */ - fbuffer_inc_capa(buffer, 28); + fbuffer_inc_capa(buffer, 32); char* d = buffer->ptr + buffer->len; int len = fpconv_dtoa(value, d); - /* fpconv_dtoa converts a float to its shortest string representation, * but it adds a ".0" if this is a plain integer. */ @@ -1319,7 +1201,7 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d fbuffer_append_str(buffer, fragment); } -static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +static inline void generate_json_general(FBuffer *buffer, struct generate_json_data *data, VALUE obj, bool fallback) { bool as_json_called = false; start: @@ -1346,22 +1228,31 @@ start: generate_json_bignum(buffer, data, obj); break; case T_HASH: - if (klass != rb_cHash) goto general; + if (fallback && klass != rb_cHash) goto general; generate_json_object(buffer, data, obj); break; case T_ARRAY: - if (klass != rb_cArray) goto general; + if (fallback && klass != rb_cArray) goto general; generate_json_array(buffer, data, obj); break; case T_STRING: - if (klass != rb_cString) goto general; - generate_json_string(buffer, data, obj); + if (fallback && klass != rb_cString) goto general; + + if (RB_LIKELY(valid_json_string_p(obj))) { + raw_generate_json_string(buffer, data, obj); + } else if (as_json_called) { + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + } else { + obj = ensure_valid_encoding(data, obj, false, false); + as_json_called = true; + goto start; + } break; case T_SYMBOL: generate_json_symbol(buffer, data, obj); break; case T_FLOAT: - if (klass != rb_cFloat) goto general; + if (fallback && klass != rb_cFloat) goto general; generate_json_float(buffer, data, obj); break; case T_STRUCT: @@ -1372,7 +1263,7 @@ start: general: if (data->state->strict) { if (RTEST(data->state->as_json) && !as_json_called) { - obj = rb_proc_call_with_block(data->state->as_json, 1, &obj, Qnil); + obj = json_call_as_json(data->state, obj, Qfalse); as_json_called = true; goto start; } else { @@ -1385,26 +1276,34 @@ start: } } +static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + generate_json_general(buffer, data, obj, true); +} + +static void generate_json_no_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + generate_json_general(buffer, data, obj, false); +} + static VALUE generate_json_try(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; data->func(data->buffer, data, data->obj); - return Qnil; + return fbuffer_finalize(data->buffer); } -static VALUE generate_json_rescue(VALUE d, VALUE exc) +static VALUE generate_json_ensure(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; fbuffer_free(data->buffer); - rb_exc_raise(exc); - return Qundef; } -static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) +static inline VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) { GET_STATE(self); @@ -1416,14 +1315,13 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, struct generate_json_data data = { .buffer = &buffer, - .vstate = self, + .vstate = Qfalse, // don't use self as it may be frozen and its depth is mutated when calling to_json .state = state, + .depth = state->depth, .obj = obj, .func = func }; - rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); - - return fbuffer_finalize(&buffer); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } /* call-seq: @@ -1439,10 +1337,16 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 1, 2); VALUE obj = argv[0]; VALUE io = argc > 1 ? argv[1] : Qnil; - VALUE result = cState_partial_generate(self, obj, generate_json, io); - GET_STATE(self); - (void)state; - return result; + return cState_partial_generate(self, obj, generate_json, io); +} + +/* :nodoc: */ +static VALUE cState_generate_no_fallback(int argc, VALUE *argv, VALUE self) +{ + rb_check_arity(argc, 1, 2); + VALUE obj = argv[0]; + VALUE io = argc > 1 ? argv[1] : Qnil; + return cState_partial_generate(self, obj, generate_json_no_fallback, io); } static VALUE cState_initialize(int argc, VALUE *argv, VALUE self) @@ -1467,12 +1371,14 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig) if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State"); MEMCPY(objState, origState, JSON_Generator_State, 1); - objState->indent = origState->indent; - objState->space = origState->space; - objState->space_before = origState->space_before; - objState->object_nl = origState->object_nl; - objState->array_nl = origState->array_nl; - objState->as_json = origState->as_json; + + RB_OBJ_WRITTEN(obj, Qundef, objState->indent); + RB_OBJ_WRITTEN(obj, Qundef, objState->space); + RB_OBJ_WRITTEN(obj, Qundef, objState->space_before); + RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl); + RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl); + RB_OBJ_WRITTEN(obj, Qundef, objState->as_json); + return obj; } @@ -1523,6 +1429,7 @@ static VALUE string_config(VALUE config) */ static VALUE cState_indent_set(VALUE self, VALUE indent) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->indent, string_config(indent)); return Qnil; @@ -1548,6 +1455,7 @@ static VALUE cState_space(VALUE self) */ static VALUE cState_space_set(VALUE self, VALUE space) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->space, string_config(space)); return Qnil; @@ -1571,6 +1479,7 @@ static VALUE cState_space_before(VALUE self) */ static VALUE cState_space_before_set(VALUE self, VALUE space_before) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->space_before, string_config(space_before)); return Qnil; @@ -1596,6 +1505,7 @@ static VALUE cState_object_nl(VALUE self) */ static VALUE cState_object_nl_set(VALUE self, VALUE object_nl) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->object_nl, string_config(object_nl)); return Qnil; @@ -1619,6 +1529,7 @@ static VALUE cState_array_nl(VALUE self) */ static VALUE cState_array_nl_set(VALUE self, VALUE array_nl) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->array_nl, string_config(array_nl)); return Qnil; @@ -1642,6 +1553,7 @@ static VALUE cState_as_json(VALUE self) */ static VALUE cState_as_json_set(VALUE self, VALUE as_json) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc")); return Qnil; @@ -1673,7 +1585,21 @@ 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. +static long depth_config(VALUE num) +{ + if (!RTEST(num)) return 0; + long d = NUM2LONG(num); + if (RB_UNLIKELY(d < 0)) { + rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d); + } + if (RB_UNLIKELY(d > INT_MAX)) { + rb_raise(rb_eArgError, "depth is too large (got %ld)", d); + } + return d; } /* @@ -1684,6 +1610,7 @@ static long long_config(VALUE num) */ static VALUE cState_max_nesting_set(VALUE self, VALUE depth) { + rb_check_frozen(self); GET_STATE(self); state->max_nesting = long_config(depth); return Qnil; @@ -1709,6 +1636,7 @@ static VALUE cState_script_safe(VALUE self) */ static VALUE cState_script_safe_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->script_safe = RTEST(enable); return Qnil; @@ -1740,6 +1668,7 @@ static VALUE cState_strict(VALUE self) */ static VALUE cState_strict_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->strict = RTEST(enable); return Qnil; @@ -1764,6 +1693,7 @@ static VALUE cState_allow_nan_p(VALUE self) */ static VALUE cState_allow_nan_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->allow_nan = RTEST(enable); return Qnil; @@ -1788,11 +1718,25 @@ static VALUE cState_ascii_only_p(VALUE self) */ static VALUE cState_ascii_only_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->ascii_only = RTEST(enable); return Qnil; } +static VALUE cState_allow_duplicate_key_p(VALUE self) +{ + GET_STATE(self); + switch (state->on_duplicate_key) { + case JSON_IGNORE: + return Qtrue; + case JSON_DEPRECATED: + return Qnil; + default: + return Qfalse; + } +} + /* * call-seq: depth * @@ -1812,8 +1756,9 @@ static VALUE cState_depth(VALUE self) */ static VALUE cState_depth_set(VALUE self, VALUE depth) { + rb_check_frozen(self); GET_STATE(self); - state->depth = long_config(depth); + state->depth = depth_config(depth); return Qnil; } @@ -1845,6 +1790,7 @@ static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_ */ static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length) { + rb_check_frozen(self); GET_STATE(self); buffer_initial_length_set(state, buffer_initial_length); return Qnil; @@ -1877,13 +1823,15 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg) else if (key == sym_max_nesting) { state->max_nesting = long_config(val); } else if (key == sym_allow_nan) { state->allow_nan = RTEST(val); } else if (key == sym_ascii_only) { state->ascii_only = RTEST(val); } - else if (key == sym_depth) { state->depth = long_config(val); } + else if (key == sym_depth) { state->depth = depth_config(val); } else if (key == sym_buffer_initial_length) { buffer_initial_length_set(state, val); } else if (key == sym_script_safe) { state->script_safe = RTEST(val); } else if (key == sym_escape_slash) { state->script_safe = RTEST(val); } else if (key == sym_strict) { state->strict = RTEST(val); } + else if (key == sym_allow_duplicate_key) { state->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } else if (key == sym_as_json) { VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse; + state->as_json_single_arg = proc && rb_proc_arity(proc) == 1; state_write_value(data, &state->as_json, proc); } return ST_CONTINUE; @@ -1909,12 +1857,13 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con static VALUE cState_configure(VALUE self, VALUE opts) { + rb_check_frozen(self); GET_STATE(self); configure_state(state, self, opts); return self; } -static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) +static VALUE cState_m_do_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io, generator_func func) { JSON_Generator_State state = {0}; state_init(&state); @@ -1930,17 +1879,23 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) .buffer = &buffer, .vstate = Qfalse, .state = &state, + .depth = state.depth, .obj = obj, - .func = generate_json, + .func = func, }; - rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); +} - return fbuffer_finalize(&buffer); +static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) +{ + return cState_m_do_generate(klass, obj, opts, io, generate_json); +} + +static VALUE cState_m_generate_no_fallback(VALUE klass, VALUE obj, VALUE opts, VALUE io) +{ + return cState_m_do_generate(klass, obj, opts, io, generate_json_no_fallback); } -/* - * - */ void Init_generator(void) { #ifdef HAVE_RB_EXT_RACTOR_SAFE @@ -2005,45 +1960,12 @@ void Init_generator(void) rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0); rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1); rb_define_method(cState, "generate", cState_generate, -1); - rb_define_alias(cState, "generate_new", "generate"); // :nodoc: + rb_define_method(cState, "_generate_no_fallback", cState_generate_no_fallback, -1); - rb_define_singleton_method(cState, "generate", cState_m_generate, 3); - - VALUE mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods"); - - VALUE mObject = rb_define_module_under(mGeneratorMethods, "Object"); - rb_define_method(mObject, "to_json", mObject_to_json, -1); - - VALUE mHash = rb_define_module_under(mGeneratorMethods, "Hash"); - rb_define_method(mHash, "to_json", mHash_to_json, -1); - - VALUE mArray = rb_define_module_under(mGeneratorMethods, "Array"); - rb_define_method(mArray, "to_json", mArray_to_json, -1); - -#ifdef RUBY_INTEGER_UNIFICATION - VALUE mInteger = rb_define_module_under(mGeneratorMethods, "Integer"); - rb_define_method(mInteger, "to_json", mInteger_to_json, -1); -#else - VALUE mFixnum = rb_define_module_under(mGeneratorMethods, "Fixnum"); - rb_define_method(mFixnum, "to_json", mFixnum_to_json, -1); + rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0); - VALUE mBignum = rb_define_module_under(mGeneratorMethods, "Bignum"); - rb_define_method(mBignum, "to_json", mBignum_to_json, -1); -#endif - VALUE mFloat = rb_define_module_under(mGeneratorMethods, "Float"); - rb_define_method(mFloat, "to_json", mFloat_to_json, -1); - - VALUE mString = rb_define_module_under(mGeneratorMethods, "String"); - rb_define_method(mString, "to_json", mString_to_json, -1); - - VALUE mTrueClass = rb_define_module_under(mGeneratorMethods, "TrueClass"); - rb_define_method(mTrueClass, "to_json", mTrueClass_to_json, -1); - - VALUE mFalseClass = rb_define_module_under(mGeneratorMethods, "FalseClass"); - rb_define_method(mFalseClass, "to_json", mFalseClass_to_json, -1); - - VALUE mNilClass = rb_define_module_under(mGeneratorMethods, "NilClass"); - rb_define_method(mNilClass, "to_json", mNilClass_to_json, -1); + rb_define_singleton_method(cState, "generate", cState_m_generate, 3); + rb_define_singleton_method(cState, "_generate_no_fallback", cState_m_generate_no_fallback, 3); rb_global_variable(&Encoding_UTF_8); Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8")); @@ -2051,10 +1973,6 @@ void Init_generator(void) i_to_s = rb_intern("to_s"); i_to_json = rb_intern("to_json"); i_new = rb_intern("new"); - i_pack = rb_intern("pack"); - i_unpack = rb_intern("unpack"); - i_create_id = rb_intern("create_id"); - i_extend = rb_intern("extend"); i_encode = rb_intern("encode"); sym_indent = ID2SYM(rb_intern("indent")); @@ -2071,6 +1989,7 @@ void Init_generator(void) sym_escape_slash = ID2SYM(rb_intern("escape_slash")); sym_strict = ID2SYM(rb_intern("strict")); sym_as_json = ID2SYM(rb_intern("as_json")); + sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key")); usascii_encindex = rb_usascii_encindex(); utf8_encindex = rb_utf8_encindex(); @@ -2078,22 +1997,5 @@ void Init_generator(void) rb_require("json/ext/generator/state"); - - switch (find_simd_implementation()) { -#ifdef HAVE_SIMD -#ifdef HAVE_SIMD_NEON - case SIMD_NEON: - search_escape_basic_impl = search_escape_basic_neon; - break; -#endif /* HAVE_SIMD_NEON */ -#ifdef HAVE_SIMD_SSE2 - case SIMD_SSE2: - search_escape_basic_impl = search_escape_basic_sse2; - break; -#endif /* HAVE_SIMD_SSE2 */ -#endif /* HAVE_SIMD */ - default: - search_escape_basic_impl = search_escape_basic; - break; - } + simd_impl = find_simd_implementation(); } diff --git a/ext/json/json.h b/ext/json/json.h new file mode 100644 index 0000000000..cf9420d4dd --- /dev/null +++ b/ext/json/json.h @@ -0,0 +1,134 @@ +#ifndef _JSON_H_ +#define _JSON_H_ + +#include "ruby.h" +#include "ruby/encoding.h" +#include <stdint.h> + +#ifndef RBIMPL_ASSERT_OR_ASSUME +# define RBIMPL_ASSERT_OR_ASSUME(x) +#endif + +#if defined(RUBY_DEBUG) && RUBY_DEBUG +# define JSON_ASSERT RUBY_ASSERT +#else +# ifdef JSON_DEBUG +# include <assert.h> +# define JSON_ASSERT(x) assert(x) +# else +# define JSON_ASSERT(x) +# endif +#endif + +/* shims */ + +#if SIZEOF_UINT64_T == SIZEOF_LONG_LONG +# define INT64T2NUM(x) LL2NUM(x) +# define UINT64T2NUM(x) ULL2NUM(x) +#elif SIZEOF_UINT64_T == SIZEOF_LONG +# define INT64T2NUM(x) LONG2NUM(x) +# define UINT64T2NUM(x) ULONG2NUM(x) +#else +# error No uint64_t conversion +#endif + +/* This is the fallback definition from Ruby 3.4 */ +#ifndef RBIMPL_STDBOOL_H +#if defined(__cplusplus) +# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) +# include <cstdbool> +# endif +#elif defined(HAVE_STDBOOL_H) +# include <stdbool.h> +#elif !defined(HAVE__BOOL) +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)+0) +# define __bool_true_false_are_defined +#endif +#endif + +#ifndef HAVE_RUBY_XFREE_SIZED +static inline void ruby_xfree_sized(void *ptr, size_t oldsize) +{ + ruby_xfree(ptr); +} + +static inline void *ruby_xrealloc2_sized(void *ptr, size_t new_elems, size_t elem_size, size_t old_elems) +{ + return ruby_xrealloc2(ptr, new_elems, elem_size); +} +#endif + +# define JSON_SIZED_REALLOC_N(v, T, m, n) \ + ((v) = (T *)ruby_xrealloc2_sized((void *)(v), (m), sizeof(T), (n))) + +# define JSON_SIZED_FREE(v) ruby_xfree_sized((void *)(v), sizeof(*(v))) +# define JSON_SIZED_FREE_N(v, n) ruby_xfree_sized((void *)(v), sizeof(*(v)) * (n)) + +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# undef RUBY_TYPED_FROZEN_SHAREABLE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + +#ifdef RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +#else +# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE +# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +# else +# define RUBY_TYPED_EMBEDDABLE 0 +# endif +#endif + +#ifndef NORETURN +#if defined(__has_attribute) && __has_attribute(noreturn) +#define NORETURN(x) __attribute__((noreturn)) x +#else +#define NORETURN(x) x +#endif +#endif + +#ifndef NOINLINE +#if defined(__has_attribute) && __has_attribute(noinline) +#define NOINLINE(x) __attribute__((noinline)) x +#else +#define NOINLINE(x) x +#endif +#endif + +#ifndef ALWAYS_INLINE +#if defined(__has_attribute) && __has_attribute(always_inline) +#define ALWAYS_INLINE(x) inline __attribute__((always_inline)) x +#else +#define ALWAYS_INLINE(x) inline x +#endif +#endif + +#ifndef RB_UNLIKELY +#define RB_UNLIKELY(expr) expr +#endif + +#ifndef RB_LIKELY +#define RB_LIKELY(expr) expr +#endif + +#ifndef MAYBE_UNUSED +# define MAYBE_UNUSED(x) x +#endif + +#ifdef RUBY_DEBUG +#ifndef JSON_DEBUG +#define JSON_DEBUG RUBY_DEBUG +#endif +#endif + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && INTPTR_MAX == INT64_MAX +#define JSON_CPU_LITTLE_ENDIAN_64BITS 1 +#else +#define JSON_CPU_LITTLE_ENDIAN_64BITS 0 +#endif + +#endif // _JSON_H_ diff --git a/ext/json/lib/json.rb b/ext/json/lib/json.rb index 0ebff2f948..26d601926f 100644 --- a/ext/json/lib/json.rb +++ b/ext/json/lib/json.rb @@ -6,6 +6,15 @@ require 'json/common' # # \JSON is a lightweight data-interchange format. # +# \JSON is easy for us humans to read and write, +# and equally simple for machines to read (parse) and write (generate). +# +# \JSON is language-independent, making it an ideal interchange format +# for applications in differing programming languages +# and on differing operating systems. +# +# == \JSON Values +# # A \JSON value is one of the following: # - Double-quoted text: <tt>"foo"</tt>. # - Number: +1+, +1.0+, +2.0e2+. @@ -173,6 +182,30 @@ require 'json/common' # When enabled: # JSON.parse('[1,]', allow_trailing_comma: true) # => [1] # +# --- +# +# Option +allow_control_characters+ (boolean) specifies whether to allow +# unescaped ASCII control characters, such as newlines, in strings; +# defaults to +false+. +# +# With the default, +false+: +# JSON.parse(%{"Hello\nWorld"}) # invalid ASCII control character in string (JSON::ParserError) +# +# When enabled: +# JSON.parse(%{"Hello\nWorld"}, allow_control_characters: true) # => "Hello\nWorld" +# +# --- +# +# Option +allow_invalid_escape+ (boolean) specifies whether to ignore backslahes that are followed +# by an invalid escape character in strings; +# defaults to +false+. +# +# With the default, +false+: +# JSON.parse('"Hell\o"') # invalid escape character in string (JSON::ParserError) +# +# When enabled: +# JSON.parse('"Hell\o"', allow_invalid_escape: true) # => "Hello" +# # ====== Output Options # # Option +freeze+ (boolean) specifies whether the returned objects will be frozen; @@ -302,8 +335,27 @@ require 'json/common' # JSON.generate(JSON::MinusInfinity) # # Allow: -# ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity] -# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]' +# ruby = [Float::NAN, Float::INFINITY, JSON::NaN, JSON::Infinity, JSON::MinusInfinity] +# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,NaN,Infinity,-Infinity]' +# +# --- +# +# Option +allow_duplicate_key+ (boolean) specifies whether +# hashes with duplicate keys should be allowed or produce an error. +# defaults to emit a deprecation warning. +# +# With the default, (not set): +# Warning[:deprecated] = true +# JSON.generate({ foo: 1, "foo" => 2 }) +# # warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}. +# # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true` +# # => '{"foo":1,"foo":2}' +# +# With <tt>false</tt> +# JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false) +# # detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError) +# +# In version 3.0, <tt>false</tt> will become the default. # # --- # @@ -384,6 +436,9 @@ require 'json/common' # # == \JSON Additions # +# Note that JSON Additions must only be used with trusted data, and is +# deprecated. +# # When you "round trip" a non-\String object from Ruby to \JSON and back, # you have a new \String, instead of the object you began with: # ruby0 = Range.new(0, 2) diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index e99d152a88..230bf08012 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -71,9 +71,14 @@ module JSON end when object_class if opts[:create_additions] != false - if class_name = object[JSON.create_id] - klass = JSON.deep_const_get(class_name) - if (klass.respond_to?(:json_creatable?) && klass.json_creatable?) || klass.respond_to?(:json_create) + if class_path = object[JSON.create_id] + klass = begin + Object.const_get(class_path) + rescue NameError => e + raise ArgumentError, "can't get const #{class_path}: #{e}" + end + + if klass.respond_to?(:json_creatable?) ? klass.json_creatable? : klass.respond_to?(:json_create) create_additions_warning if create_additions.nil? object = klass.json_create(object) end @@ -97,7 +102,7 @@ module JSON class << self def deprecation_warning(message, uplevel = 3) # :nodoc: - gem_root = File.expand_path("../../../", __FILE__) + "/" + gem_root = File.expand_path("..", __dir__) + "/" caller_locations(uplevel, 10).each do |frame| if frame.path.nil? || frame.path.start_with?(gem_root) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c") uplevel += 1 @@ -147,29 +152,21 @@ module JSON const_set :Parser, parser end - # Return the constant located at _path_. The format of _path_ has to be - # either ::A::B::C or A::B::C. In any case, A has to be located at the top - # level (absolute namespace path?). If there doesn't exist a constant at - # the given path, an ArgumentError is raised. - def deep_const_get(path) # :nodoc: - Object.const_get(path) - rescue NameError => e - raise ArgumentError, "can't get const #{path}: #{e}" - end - # Set the module _generator_ to be used by JSON. def generator=(generator) # :nodoc: old, $VERBOSE = $VERBOSE, nil @generator = generator - generator_methods = generator::GeneratorMethods - for const in generator_methods.constants - klass = const_get(const) - modul = generator_methods.const_get(const) - klass.class_eval do - instance_methods(false).each do |m| - m.to_s == 'to_json' and remove_method m + if generator.const_defined?(:GeneratorMethods) + generator_methods = generator::GeneratorMethods + for const in generator_methods.constants + klass = const_get(const) + modul = generator_methods.const_get(const) + klass.class_eval do + instance_methods(false).each do |m| + m.to_s == 'to_json' and remove_method m + end + include modul end - include modul end end self.state = generator::State @@ -186,6 +183,25 @@ module JSON private + # Called from the extension when a hash has both string and symbol keys + def on_mixed_keys_hash(hash, do_raise) + set = {} + hash.each_key do |key| + key_str = key.to_s + + if set[key_str] + message = "detected duplicate key #{key_str.inspect} in #{hash.inspect}" + if do_raise + raise GeneratorError, message + else + deprecation_warning("#{message}.\nThis will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`") + end + else + set[key_str] = true + end + end + end + def deprecated_singleton_attr_accessor(*attrs) args = RUBY_VERSION >= "3.0" ? ", category: :deprecated" : "" attrs.each do |attr| @@ -391,7 +407,7 @@ module JSON # # Returns a \String containing the generated \JSON data. # - # See also JSON.fast_generate, JSON.pretty_generate. + # See also JSON.pretty_generate. # # Argument +obj+ is the Ruby object to be converted to \JSON. # @@ -536,6 +552,7 @@ module JSON :create_additions => nil, } # :call-seq: + # JSON.unsafe_load(source, options = {}) -> object # JSON.unsafe_load(source, proc = nil, options = {}) -> object # # Returns the Ruby objects created by parsing the given +source+. @@ -643,6 +660,7 @@ module JSON # when Array # obj.map! {|v| deserialize_obj v } # end + # obj # }) # pp ruby # Output: @@ -666,7 +684,12 @@ module JSON # def unsafe_load(source, proc = nil, options = nil) opts = if options.nil? - _unsafe_load_default_options + if proc && proc.is_a?(Hash) + options, proc = proc, nil + options + else + _unsafe_load_default_options + end else _unsafe_load_default_options.merge(options) end @@ -684,12 +707,17 @@ module JSON if opts[:allow_blank] && (source.nil? || source.empty?) source = 'null' end - result = parse(source, opts) - recurse_proc(result, &proc) if proc - result + + if proc + opts = opts.dup + opts[:on_load] = proc.to_proc + end + + parse(source, opts) end # :call-seq: + # JSON.load(source, options = {}) -> object # JSON.load(source, proc = nil, options = {}) -> object # # Returns the Ruby objects created by parsing the given +source+. @@ -803,6 +831,7 @@ module JSON # when Array # obj.map! {|v| deserialize_obj v } # end + # obj # }) # pp ruby # Output: @@ -825,8 +854,18 @@ module JSON # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>} # def load(source, proc = nil, options = nil) + if proc && options.nil? && proc.is_a?(Hash) + options = proc + proc = nil + end + opts = if options.nil? - _load_default_options + if proc && proc.is_a?(Hash) + options, proc = proc, nil + options + else + _load_default_options + end else _load_default_options.merge(options) end @@ -841,7 +880,7 @@ module JSON end end - if opts[:allow_blank] && (source.nil? || source.empty?) + if opts[:allow_blank] && (source.nil? || (String === source && source.empty?)) source = 'null' end @@ -999,7 +1038,8 @@ module JSON # JSON.new(options = nil, &block) # # Argument +options+, if given, contains a \Hash of options for both parsing and generating. - # See {Parsing Options}[#module-JSON-label-Parsing+Options], and {Generating Options}[#module-JSON-label-Generating+Options]. + # See {Parsing Options}[rdoc-ref:JSON@Parsing+Options], + # and {Generating Options}[rdoc-ref:JSON@Generating+Options]. # # For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is # encountered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native @@ -1028,7 +1068,7 @@ module JSON options[:as_json] = as_json if as_json @state = State.new(options).freeze - @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)) + @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)).freeze end # call-seq: @@ -1037,7 +1077,7 @@ module JSON # # Serialize the given object into a \JSON document. def dump(object, io = nil) - @state.generate_new(object, io) + @state.generate(object, io) end alias_method :generate, :dump @@ -1058,6 +1098,30 @@ module JSON load(File.read(path, encoding: Encoding::UTF_8)) end end + + module GeneratorMethods + # call-seq: to_json(*) + # + # Converts this object into a JSON string. + # If this object doesn't directly maps to a JSON native type, + # first convert it to a string (calling #to_s), then converts + # it to a JSON string, and returns the result. + # This is a fallback, if no special method #to_json was defined for some object. + def to_json(state = nil, *) + obj = case self + when nil, false, true, Integer, Float, Array, Hash + self + else + "#{self}" + end + + if state.nil? + JSON::State._generate_no_fallback(obj, nil, nil) + else + JSON::State.from_state(state)._generate_no_fallback(obj) + end + end + end end module ::Kernel @@ -1103,3 +1167,7 @@ module ::Kernel JSON[object, opts] end end + +class Object + include JSON::GeneratorMethods +end diff --git a/ext/json/lib/json/ext/generator/state.rb b/ext/json/lib/json/ext/generator/state.rb index d40c3b5ec3..e4f425af6a 100644 --- a/ext/json/lib/json/ext/generator/state.rb +++ b/ext/json/lib/json/ext/generator/state.rb @@ -8,20 +8,8 @@ module JSON # # Instantiates a new State object, configured by _opts_. # - # _opts_ can have the following keys: - # - # * *indent*: a string used to indent levels (default: ''), - # * *space*: a string that is put after, a : or , delimiter (default: ''), - # * *space_before*: a string that is put before a : pair delimiter (default: ''), - # * *object_nl*: a string that is put at the end of a JSON object (default: ''), - # * *array_nl*: a string that is put at the end of a JSON array (default: ''), - # * *allow_nan*: true if NaN, Infinity, and -Infinity should be - # generated, otherwise an exception is thrown, if these values are - # encountered. This options defaults to false. - # * *ascii_only*: true if only ASCII characters should be generated. This - # option defaults to false. - # * *buffer_initial_length*: sets the initial length of the generator's - # internal buffer. + # Argument +opts+, if given, contains a \Hash of options for the generation. + # See {Generating Options}[rdoc-ref:JSON@Generating+Options]. def initialize(opts = nil) if opts && !opts.empty? configure(opts) @@ -68,6 +56,11 @@ module JSON buffer_initial_length: buffer_initial_length, } + allow_duplicate_key = allow_duplicate_key? + unless allow_duplicate_key.nil? + result[:allow_duplicate_key] = allow_duplicate_key + end + instance_variables.each do |iv| iv = iv.to_s[1..-1] result[iv.to_sym] = self[iv] @@ -82,6 +75,8 @@ module JSON # # Returns the value returned by method +name+. def [](name) + ::JSON.deprecation_warning("JSON::State#[] is deprecated and will be removed in json 3.0.0") + if respond_to?(name) __send__(name) else @@ -94,6 +89,8 @@ module JSON # # Sets the attribute name to value. def []=(name, value) + ::JSON.deprecation_warning("JSON::State#[]= is deprecated and will be removed in json 3.0.0") + if respond_to?(name_writer = "#{name}=") __send__ name_writer, value else diff --git a/ext/json/lib/json/generic_object.rb b/ext/json/lib/json/generic_object.rb index ec5aa9dcb2..5c8ace354b 100644 --- a/ext/json/lib/json/generic_object.rb +++ b/ext/json/lib/json/generic_object.rb @@ -52,14 +52,6 @@ module JSON table end - def [](name) - __send__(name) - end unless method_defined?(:[]) - - def []=(name, value) - __send__("#{name}=", value) - end unless method_defined?(:[]=) - def |(other) self.class[other.to_hash.merge(to_hash)] end diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index f9ac3e17a9..30c0a71d2f 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.13.2' + VERSION = '2.19.8' end diff --git a/ext/json/parser/depend b/ext/json/parser/depend index 1bb03d3517..d4737b1dfb 100644 --- a/ext/json/parser/depend +++ b/ext/json/parser/depend @@ -175,6 +175,8 @@ parser.o: $(hdrdir)/ruby/ruby.h parser.o: $(hdrdir)/ruby/st.h parser.o: $(hdrdir)/ruby/subst.h parser.o: $(srcdir)/../fbuffer/fbuffer.h +parser.o: $(srcdir)/../json.h parser.o: $(srcdir)/../simd/simd.h +parser.o: $(srcdir)/../vendor/ryu.h parser.o: parser.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index de5d5758b4..a9d740c755 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -1,10 +1,16 @@ # frozen_string_literal: true require 'mkmf' +$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0" have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 +have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby -have_func("strnlen", "string.h") # Missing on Solaris 10 +have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1 + +if RUBY_ENGINE == "ruby" + have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 +end append_cflags("-std=c99") diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 1e6ee753f0..c0631728c3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1,50 +1,22 @@ -#include "ruby.h" -#include "ruby/encoding.h" - -/* shims */ -/* This is the fallback definition from Ruby 3.4 */ - -#ifndef RBIMPL_STDBOOL_H -#if defined(__cplusplus) -# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) -# include <cstdbool> -# endif -#elif defined(HAVE_STDBOOL_H) -# include <stdbool.h> -#elif !defined(HAVE__BOOL) -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)+0) -# define __bool_true_false_are_defined -#endif -#endif - +#include "../json.h" +#include "../vendor/ryu.h" #include "../simd/simd.h" -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(expr) expr -#endif - -#ifndef RB_LIKELY -#define RB_LIKELY(expr) expr -#endif - static VALUE mJSON, eNestingError, Encoding_UTF_8; static VALUE CNaN, CInfinity, CMinusInfinity; -static ID i_chr, i_aset, i_aref, - i_leftshift, i_new, i_try_convert, i_uminus, i_encode; +static ID i_new, i_try_convert, i_uminus, i_encode; -static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_symbolize_names, sym_freeze, - sym_decimal_class, sym_on_load, sym_allow_duplicate_key; +static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_allow_control_characters, + sym_allow_invalid_escape, sym_symbolize_names, sym_freeze, sym_decimal_class, sym_on_load, + sym_allow_duplicate_key; static int binary_encindex; static int utf8_encindex; #ifndef HAVE_RB_HASH_BULK_INSERT // For TruffleRuby -void +static void rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) { long index = 0; @@ -61,6 +33,12 @@ rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) #define rb_hash_new_capa(n) rb_hash_new() #endif +#ifndef HAVE_RB_STR_TO_INTERNED_STR +static VALUE rb_str_to_interned_str(VALUE str) +{ + return rb_funcall(rb_str_freeze(str), i_uminus, 0); +} +#endif /* name cache */ @@ -106,116 +84,104 @@ static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring cache->entries[index] = rstring; } -static inline int rstring_cache_cmp(const char *str, const long length, VALUE rstring) +#define rstring_cache_memcmp memcmp + +#if JSON_CPU_LITTLE_ENDIAN_64BITS +#if __has_builtin(__builtin_bswap64) +#undef rstring_cache_memcmp +ALWAYS_INLINE(static) int rstring_cache_memcmp(const char *str, const char *rptr, const long length) { - long rstring_length = RSTRING_LEN(rstring); - if (length == rstring_length) { - return memcmp(str, RSTRING_PTR(rstring), length); - } else { - return (int)(length - rstring_length); + // The libc memcmp has numerous complex optimizations, but in this particular case, + // we know the string is small (JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH), so being able to + // inline a simpler memcmp outperforms calling the libc version. + long i = 0; + + for (; i + 8 <= length; i += 8) { + uint64_t a, b; + memcpy(&a, str + i, 8); + memcpy(&b, rptr + i, 8); + if (a != b) { + a = __builtin_bswap64(a); + b = __builtin_bswap64(b); + return (a < b) ? -1 : 1; + } } + + for (; i < length; i++) { + if (str[i] != rptr[i]) { + return (str[i] < rptr[i]) ? -1 : 1; + } + } + + return 0; } +#endif +#endif -static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) +ALWAYS_INLINE(static) int rstring_cache_cmp(const char *str, const long length, VALUE rstring) { - if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) { - // Common names aren't likely to be very long. So we just don't - // cache names above an arbitrary threshold. - return Qfalse; - } + const char *rstring_ptr; + long rstring_length; - if (RB_UNLIKELY(!isalpha((unsigned char)str[0]))) { - // Simple heuristic, if the first character isn't a letter, - // we're much less likely to see this string again. - // We mostly want to cache strings that are likely to be repeated. - return Qfalse; + RSTRING_GETMEM(rstring, rstring_ptr, rstring_length); + + if (length == rstring_length) { + return rstring_cache_memcmp(str, rstring_ptr, length); + } else { + return (int)(length - rstring_length); } +} +ALWAYS_INLINE(static) VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) +{ int low = 0; int high = cache->length - 1; - int mid = 0; - int last_cmp = 0; while (low <= high) { - mid = (high + low) >> 1; + int mid = (high + low) >> 1; VALUE entry = cache->entries[mid]; - last_cmp = rstring_cache_cmp(str, length, entry); + int cmp = rstring_cache_cmp(str, length, entry); - if (last_cmp == 0) { + if (cmp == 0) { return entry; - } else if (last_cmp > 0) { + } else if (cmp > 0) { low = mid + 1; } else { high = mid - 1; } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rstring = build_interned_string(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { - if (last_cmp > 0) { - mid += 1; - } - - rvalue_cache_insert_at(cache, mid, rstring); + rvalue_cache_insert_at(cache, low, rstring); } return rstring; } static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const long length) { - if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) { - // Common names aren't likely to be very long. So we just don't - // cache names above an arbitrary threshold. - return Qfalse; - } - - if (RB_UNLIKELY(!isalpha((unsigned char)str[0]))) { - // Simple heuristic, if the first character isn't a letter, - // we're much less likely to see this string again. - // We mostly want to cache strings that are likely to be repeated. - return Qfalse; - } - int low = 0; int high = cache->length - 1; - int mid = 0; - int last_cmp = 0; while (low <= high) { - mid = (high + low) >> 1; + int mid = (high + low) >> 1; VALUE entry = cache->entries[mid]; - last_cmp = rstring_cache_cmp(str, length, rb_sym2str(entry)); + int cmp = rstring_cache_cmp(str, length, rb_sym2str(entry)); - if (last_cmp == 0) { + if (cmp == 0) { return entry; - } else if (last_cmp > 0) { + } else if (cmp > 0) { low = mid + 1; } else { high = mid - 1; } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rsymbol = build_symbol(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { - if (last_cmp > 0) { - mid += 1; - } - - rvalue_cache_insert_at(cache, mid, rsymbol); + rvalue_cache_insert_at(cache, low, rsymbol); } return rsymbol; } @@ -245,7 +211,7 @@ static rvalue_stack *rvalue_stack_grow(rvalue_stack *stack, VALUE *handle, rvalu if (stack->type == RVALUE_STACK_STACK_ALLOCATED) { stack = rvalue_stack_spill(stack, handle, stack_ref); } else { - REALLOC_N(stack->ptr, VALUE, required); + JSON_SIZED_REALLOC_N(stack->ptr, VALUE, required, stack->capa); stack->capa = required; } return stack; @@ -275,35 +241,62 @@ static void rvalue_stack_mark(void *ptr) { rvalue_stack *stack = (rvalue_stack *)ptr; long index; - for (index = 0; index < stack->head; index++) { - rb_gc_mark(stack->ptr[index]); + if (stack && stack->ptr) { + for (index = 0; index < stack->head; index++) { + rb_gc_mark_movable(stack->ptr[index]); + } } } +static void rvalue_stack_free_buffer(rvalue_stack *stack) +{ + JSON_SIZED_FREE_N(stack->ptr, stack->capa); + stack->ptr = NULL; +} + static void rvalue_stack_free(void *ptr) { rvalue_stack *stack = (rvalue_stack *)ptr; if (stack) { - ruby_xfree(stack->ptr); - ruby_xfree(stack); + rvalue_stack_free_buffer(stack); +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + JSON_SIZED_FREE(stack); +#endif } } static size_t rvalue_stack_memsize(const void *ptr) { const rvalue_stack *stack = (const rvalue_stack *)ptr; - return sizeof(rvalue_stack) + sizeof(VALUE) * stack->capa; + size_t memsize = sizeof(VALUE) * stack->capa; +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + memsize += sizeof(rvalue_stack); +#endif + return memsize; +} + +static void rvalue_stack_compact(void *ptr) +{ + rvalue_stack *stack = (rvalue_stack *)ptr; + long index; + if (stack && stack->ptr) { + for (index = 0; index < stack->head; index++) { + stack->ptr[index] = rb_gc_location(stack->ptr[index]); + } + } } static const rb_data_type_t JSON_Parser_rvalue_stack_type = { - "JSON::Ext::Parser/rvalue_stack", - { + .wrap_struct_name = "JSON::Ext::Parser/rvalue_stack", + .function = { .dmark = rvalue_stack_mark, .dfree = rvalue_stack_free, .dsize = rvalue_stack_memsize, + .dcompact = rvalue_stack_compact, }, - 0, 0, - RUBY_TYPED_FREE_IMMEDIATELY, + // We deliberately don't declare rvalue_stack as RUBY_TYPED_WB_PROTECTED + // because it churns a lot of values so trigering write barriers every time is very costly. + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE, }; static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref) @@ -325,19 +318,206 @@ static void rvalue_stack_eagerly_release(VALUE handle) if (handle) { rvalue_stack *stack; TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack); - RTYPEDDATA_DATA(handle) = NULL; +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + rvalue_stack_free_buffer(stack); +#else rvalue_stack_free(stack); + RTYPEDDATA_DATA(handle) = NULL; +#endif + } +} + +/* frame stack */ + +// Iterative (non-recursive) parsing keeps an explicit stack of the containers +// currently being built, instead of relying on the C call stack. Each frame +// only needs enough bookkeeping to close its container: which kind it is, the +// rvalue_stack position where its children start (so we know how many to pop), +// and the cursor at its opening brace (used to rewind for duplicate key +// errors). Frames hold no VALUEs, so this stack needs no GC marking; it reuses +// the same stack-allocated-with-heap-spill strategy as the rvalue_stack so that +// it's freed even if parsing raises. +// +// The lifecycle helpers below (grow/push/peek/pop/spill/free/eagerly_release +// and the rb_data_type_t) deliberately mirror their rvalue_stack counterparts +// -- the element type and the absence of a mark function are the only real +// differences. Keep the two in sync: a fix to the spill/release or +// HAVE_RUBY_TYPED_EMBEDDABLE handling in one almost certainly belongs in the +// other. +#define JSON_FRAME_STACK_INITIAL_CAPA 32 + +enum json_frame_type { + JSON_FRAME_ROOT, // == JSON_PHASE_DONE + JSON_FRAME_ARRAY, // == JSON_PHASE_ARRAY_COMMA + JSON_FRAME_OBJECT, // = JSON_PHASE_OBJECT_COMMA +}; + +// Where a frame is within its container's grammar. This is the entirety of the +// parser's "what to do next" state: json_parse_any dispatches on the top +// frame's phase and holds no resume state in C locals, so a parse can stop at +// any value boundary and be resumed purely from the (persistable) frame stack. +// +// The first three phases are deliberately equal to the corresponding json_frame_type +// to simplify the transition of phase in json_value_completed. +enum json_frame_phase { + JSON_PHASE_DONE = JSON_FRAME_ROOT, // root only: the document value has been parsed + JSON_PHASE_ARRAY_COMMA = JSON_FRAME_ARRAY, // after a value: expecting ',' or the closing ']' + JSON_PHASE_OBJECT_COMMA = JSON_FRAME_OBJECT, // after a value: expecting ',' or the closing '}' + JSON_PHASE_VALUE, // expecting a value (document root, array element, or object value after ':') + JSON_PHASE_OBJECT_KEY, // expecting a '"' key (after '{' or ',') + JSON_PHASE_OBJECT_COLON, // object only: after a key, expecting ':' +}; + +typedef struct json_frame_struct { + enum json_frame_type type; + enum json_frame_phase phase; + long value_stack_head; // rvalue_stack->head when this container opened + const char *start_cursor; // object frames only (the '{'); NULL otherwise +} json_frame; + +typedef struct json_frame_stack_struct { + enum rvalue_stack_type type; // shared with rvalue_stack: is ptr stack- or heap-allocated + long capa; + long head; + json_frame *ptr; +} json_frame_stack; + +enum duplicate_key_action { + JSON_DEPRECATED = 0, + JSON_IGNORE, + JSON_RAISE, +}; + +typedef struct JSON_ParserStruct { + VALUE on_load_proc; + VALUE decimal_class; + ID decimal_method_id; + enum duplicate_key_action on_duplicate_key; + int max_nesting; + bool allow_nan; + bool allow_trailing_comma; + bool allow_control_characters; + bool allow_invalid_escape; + bool symbolize_names; + bool freeze; +} JSON_ParserConfig; + +typedef struct JSON_ParserStateStruct { + VALUE *value_stack_handle; + VALUE *frame_stack_handle; + const char *start; + const char *cursor; + const char *end; + rvalue_stack *value_stack; + json_frame_stack *frames; + rvalue_cache name_cache; + int in_array; + int current_nesting; + unsigned int emitted_deprecations; +} JSON_ParserState; + +static json_frame_stack *json_frame_stack_spill(json_frame_stack *old_stack, VALUE *handle, json_frame_stack **stack_ref); + +static json_frame_stack *json_frame_stack_grow(json_frame_stack *stack, VALUE *handle, json_frame_stack **stack_ref) +{ + long required = stack->capa * 2; + + if (stack->type == RVALUE_STACK_STACK_ALLOCATED) { + stack = json_frame_stack_spill(stack, handle, stack_ref); + } else { + JSON_SIZED_REALLOC_N(stack->ptr, json_frame, required, stack->capa); + stack->capa = required; } + return stack; } +static json_frame *json_frame_stack_push(JSON_ParserState *state, json_frame frame) +{ + json_frame_stack *stack = state->frames; + if (RB_UNLIKELY(stack->head >= stack->capa)) { + stack = json_frame_stack_grow(stack, state->frame_stack_handle, &state->frames); + } + + json_frame *frame_ptr = &stack->ptr[stack->head++]; + *frame_ptr = frame; + return frame_ptr; +} -#ifndef HAVE_STRNLEN -static size_t strnlen(const char *s, size_t maxlen) +static inline json_frame *json_frame_stack_peek(json_frame_stack *stack) { - char *p; - return ((p = memchr(s, '\0', maxlen)) ? p - s : maxlen); + return &stack->ptr[stack->head - 1]; } + +static inline void json_frame_stack_pop(json_frame_stack *stack) +{ + stack->head--; +} + +static void json_frame_stack_free_buffer(json_frame_stack *stack) +{ + JSON_SIZED_FREE_N(stack->ptr, stack->capa); + stack->ptr = NULL; +} + +static void json_frame_stack_free(void *ptr) +{ + json_frame_stack *stack = (json_frame_stack *)ptr; + if (stack) { + json_frame_stack_free_buffer(stack); +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + JSON_SIZED_FREE(stack); #endif + } +} + +static size_t json_frame_stack_memsize(const void *ptr) +{ + const json_frame_stack *stack = (const json_frame_stack *)ptr; + + size_t memsize = sizeof(json_frame) * stack->capa; +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + memsize += sizeof(json_frame_stack); +#endif + return memsize; +} + +static const rb_data_type_t JSON_Parser_frame_stack_type = { + .wrap_struct_name = "JSON::Ext::Parser/frame_stack", + .function = { + .dmark = NULL, + .dfree = json_frame_stack_free, + .dsize = json_frame_stack_memsize, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE, +}; + +static json_frame_stack *json_frame_stack_spill(json_frame_stack *old_stack, VALUE *handle, json_frame_stack **stack_ref) +{ + json_frame_stack *stack; + *handle = TypedData_Make_Struct(0, json_frame_stack, &JSON_Parser_frame_stack_type, stack); + *stack_ref = stack; + MEMCPY(stack, old_stack, json_frame_stack, 1); + + stack->capa = old_stack->capa << 1; + stack->ptr = ALLOC_N(json_frame, stack->capa); + stack->type = RVALUE_STACK_HEAP_ALLOCATED; + MEMCPY(stack->ptr, old_stack->ptr, json_frame, old_stack->head); + return stack; +} + +static void json_frame_stack_eagerly_release(VALUE handle) +{ + if (handle) { + json_frame_stack *stack; + TypedData_Get_Struct(handle, json_frame_stack, &JSON_Parser_frame_stack_type, stack); +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + json_frame_stack_free_buffer(stack); +#else + json_frame_stack_free(stack); + RTYPEDDATA_DATA(handle) = NULL; +#endif + } +} static int convert_UTF32_to_UTF8(char *buf, uint32_t ch) { @@ -365,38 +545,31 @@ static int convert_UTF32_to_UTF8(char *buf, uint32_t ch) return len; } -enum duplicate_key_action { - JSON_DEPRECATED = 0, - JSON_IGNORE, - JSON_RAISE, -}; +static inline size_t rest(JSON_ParserState *state) { + return state->end - state->cursor; +} -typedef struct JSON_ParserStruct { - VALUE on_load_proc; - VALUE decimal_class; - ID decimal_method_id; - enum duplicate_key_action on_duplicate_key; - int max_nesting; - bool allow_nan; - bool allow_trailing_comma; - bool parsing_name; - bool symbolize_names; - bool freeze; -} JSON_ParserConfig; +static inline bool eos(JSON_ParserState *state) { + return state->cursor >= state->end; +} -typedef struct JSON_ParserStateStruct { - VALUE stack_handle; - const char *start; - const char *cursor; - const char *end; - rvalue_stack *stack; - rvalue_cache name_cache; - int in_array; - int current_nesting; -} JSON_ParserState; +static inline char peek(JSON_ParserState *state) +{ + if (RB_UNLIKELY(eos(state))) { + return 0; + } + return *state->cursor; +} 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; @@ -428,14 +601,9 @@ static void emit_parse_warning(const char *message, JSON_ParserState *state) #define PARSE_ERROR_FRAGMENT_LEN 32 -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_parse_error(const char *format, JSON_ParserState *state) +static VALUE build_parse_error_message(const char *format, JSON_ParserState *state, long line, long column) { unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3]; - long line, column; - cursor_position(state, &line, &column); const char *ptr = "EOF"; if (state->cursor && state->cursor < state->end) { @@ -467,20 +635,28 @@ static void raise_parse_error(const char *format, JSON_ParserState *state) } } - VALUE msg = rb_sprintf(format, ptr); - VALUE message = rb_enc_sprintf(enc_utf8, "%s at line %ld column %ld", RSTRING_PTR(msg), line, column); - RB_GC_GUARD(msg); + VALUE message = rb_enc_sprintf(enc_utf8, format, ptr); + rb_str_catf(message, " at line %ld column %ld", line, column); + return message; +} +static VALUE parse_error_new(VALUE message, long line, long column) +{ VALUE exc = rb_exc_new_str(rb_path2class("JSON::ParserError"), message); rb_ivar_set(exc, rb_intern("@line"), LONG2NUM(line)); rb_ivar_set(exc, rb_intern("@column"), LONG2NUM(column)); - rb_exc_raise(exc); + return exc; } -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_parse_error_at(const char *format, JSON_ParserState *state, const char *at) +NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state) +{ + long line, column; + cursor_position(state, &line, &column); + VALUE message = build_parse_error_message(format, state, line, column); + rb_exc_raise(parse_error_new(message, line, column)); +} + +NORETURN(static) void raise_parse_error_at(const char *format, JSON_ParserState *state, const char *at) { state->cursor = at; raise_parse_error(format, state); @@ -505,23 +681,24 @@ static const signed char digit_values[256] = { -1, -1, -1, -1, -1, -1, -1 }; -static uint32_t unescape_unicode(JSON_ParserState *state, const unsigned char *p) -{ - signed char b; - uint32_t result = 0; - b = digit_values[p[0]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - b = digit_values[p[1]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - b = digit_values[p[2]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - b = digit_values[p[3]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - return result; +static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const char *spe) +{ + if (RB_UNLIKELY(sp > spe - 4)) { + raise_parse_error_at("incomplete unicode character escape sequence at %s", state, sp - 2); + } + + const unsigned char *p = (const unsigned char *)sp; + + const signed char b0 = digit_values[p[0]]; + const signed char b1 = digit_values[p[1]]; + const signed char b2 = digit_values[p[2]]; + const signed char b3 = digit_values[p[3]]; + + if (RB_UNLIKELY((signed char)(b0 | b1 | b2 | b3) < 0)) { + raise_parse_error_at("incomplete unicode character escape sequence at %s", state, sp - 2); + } + + return ((uint32_t)b0 << 12) | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 4) | (uint32_t)b3; } #define GET_PARSER_CONFIG \ @@ -530,61 +707,82 @@ static uint32_t unescape_unicode(JSON_ParserState *state, const unsigned char *p static const rb_data_type_t JSON_ParserConfig_type; -static const bool whitespace[256] = { - [' '] = 1, - ['\t'] = 1, - ['\n'] = 1, - ['\r'] = 1, - ['/'] = 1, -}; - -static void +NOINLINE(static) void json_eat_comments(JSON_ParserState *state) { - if (state->cursor + 1 < state->end) { - switch (state->cursor[1]) { - case '/': { - state->cursor = memchr(state->cursor, '\n', state->end - state->cursor); - if (!state->cursor) { - state->cursor = state->end; - } else { - state->cursor++; - } - break; + const char *start = state->cursor; + state->cursor++; + + switch (peek(state)) { + case '/': { + state->cursor = memchr(state->cursor, '\n', state->end - state->cursor); + if (!state->cursor) { + state->cursor = state->end; + } else { + state->cursor++; } - case '*': { - state->cursor += 2; - while (true) { - state->cursor = memchr(state->cursor, '*', state->end - state->cursor); - if (!state->cursor) { - raise_parse_error_at("unexpected end of input, expected closing '*/'", state, state->end); - } else { - state->cursor++; - if (state->cursor < state->end && *state->cursor == '/') { - state->cursor++; - break; - } - } + break; + } + case '*': { + state->cursor++; + + while (true) { + const char *next_match = memchr(state->cursor, '*', state->end - state->cursor); + if (!next_match) { + raise_parse_error_at("unterminated comment, expected closing '*/'", state, start); + } + + state->cursor = next_match + 1; + if (peek(state) == '/') { + state->cursor++; + break; } - break; } - default: - raise_parse_error("unexpected token %s", state); - break; + break; } - } else { - raise_parse_error("unexpected token %s", state); + default: + raise_parse_error_at("unexpected token %s", state, start); + break; } } -static inline void +ALWAYS_INLINE(static) void json_eat_whitespace(JSON_ParserState *state) { - while (state->cursor < state->end && RB_UNLIKELY(whitespace[(unsigned char)*state->cursor])) { - if (RB_LIKELY(*state->cursor != '/')) { - state->cursor++; - } else { - json_eat_comments(state); + while (true) { + switch (peek(state)) { + case ' ': + state->cursor++; + break; + case '\n': + state->cursor++; + + // Heuristic: if we see a newline, there is likely consecutive spaces after it. +#if JSON_CPU_LITTLE_ENDIAN_64BITS + while (rest(state) > 8) { + uint64_t chunk; + memcpy(&chunk, state->cursor, sizeof(uint64_t)); + if (chunk == 0x2020202020202020) { + state->cursor += 8; + continue; + } + + uint32_t consecutive_spaces = trailing_zeros64(chunk ^ 0x2020202020202020) / CHAR_BIT; + state->cursor += consecutive_spaces; + break; + } +#endif + break; + case '\t': + case '\r': + state->cursor++; + break; + case '/': + json_eat_comments(state); + break; + + default: + return; } } } @@ -615,11 +813,22 @@ static inline VALUE build_string(const char *start, const char *end, bool intern return result; } -static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) +static inline bool json_string_cacheable_p(const char *string, size_t length) { + // We mostly want to cache strings that are likely to be repeated. + // Simple heuristics: + // - Common names aren't likely to be very long. So we just don't cache names above an arbitrary threshold. + // - If the first character isn't a letter, we're much less likely to see this string again. + return length <= JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH && rb_isalpha(string[0]); +} + +static inline VALUE json_string_fastpath(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name) +{ + bool intern = is_name || config->freeze; + bool symbolize = is_name && config->symbolize_names; size_t bufferSize = stringEnd - string; - if (is_name && state->in_array) { + if (is_name && state->in_array && RB_LIKELY(json_string_cacheable_p(string, bufferSize))) { VALUE cached_key; if (RB_UNLIKELY(symbolize)) { cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize); @@ -635,104 +844,129 @@ static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *st return build_string(string, stringEnd, intern, symbolize); } -static VALUE json_string_unescape(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) -{ - size_t bufferSize = stringEnd - string; - const char *p = string, *pe = string, *unescape, *bufferStart; - char *buffer; - int unescape_len; - char buf[4]; +#define JSON_MAX_UNESCAPE_POSITIONS 16 +typedef struct _json_unescape_positions { + long size; + const char **positions; + unsigned long additional_backslashes; +} JSON_UnescapePositions; - if (is_name && state->in_array) { - VALUE cached_key; - if (RB_UNLIKELY(symbolize)) { - cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize); - } else { - cached_key = rstring_cache_fetch(&state->name_cache, string, bufferSize); +static inline const char *json_next_backslash(const char *pe, const char *stringEnd, JSON_UnescapePositions *positions) +{ + while (positions->size) { + positions->size--; + const char *next_position = positions->positions[0]; + positions->positions++; + if (next_position >= pe) { + return next_position; } + } - if (RB_LIKELY(cached_key)) { - return cached_key; - } + if (positions->additional_backslashes) { + positions->additional_backslashes--; + return memchr(pe, '\\', stringEnd - pe); } + return NULL; +} + +NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name, JSON_UnescapePositions *positions) +{ + bool intern = is_name || config->freeze; + bool symbolize = is_name && config->symbolize_names; + size_t bufferSize = stringEnd - string; + const char *p = string, *pe = string, *bufferStart; + char *buffer; + VALUE result = rb_str_buf_new(bufferSize); rb_enc_associate_index(result, utf8_encindex); buffer = RSTRING_PTR(result); bufferStart = buffer; - while (pe < stringEnd && (pe = memchr(pe, '\\', stringEnd - pe))) { - unescape = (char *) "?"; - unescape_len = 1; +#define APPEND_CHAR(chr) *buffer++ = chr; p = ++pe; + + while (pe < stringEnd && (pe = json_next_backslash(pe, stringEnd, positions))) { if (pe > p) { MEMCPY(buffer, p, char, pe - p); buffer += pe - p; } switch (*++pe) { + case '"': + case '/': + p = pe; // nothing to unescape just need to skip the backslash + break; + case '\\': + APPEND_CHAR('\\'); + break; case 'n': - unescape = (char *) "\n"; + APPEND_CHAR('\n'); break; case 'r': - unescape = (char *) "\r"; + APPEND_CHAR('\r'); break; case 't': - unescape = (char *) "\t"; - break; - case '"': - unescape = (char *) "\""; - break; - case '\\': - unescape = (char *) "\\"; + APPEND_CHAR('\t'); break; case 'b': - unescape = (char *) "\b"; + APPEND_CHAR('\b'); break; case 'f': - unescape = (char *) "\f"; + APPEND_CHAR('\f'); break; - case 'u': - if (pe > stringEnd - 5) { - raise_parse_error_at("incomplete unicode character escape sequence at %s", state, p); - } else { - uint32_t ch = unescape_unicode(state, (unsigned char *) ++pe); - pe += 3; - /* To handle values above U+FFFF, we take a sequence of - * \uXXXX escapes in the U+D800..U+DBFF then - * U+DC00..U+DFFF ranges, take the low 10 bits from each - * to make a 20-bit number, then add 0x10000 to get the - * final codepoint. - * - * See Unicode 15: 3.8 "Surrogates", 5.3 "Handling - * Surrogate Pairs in UTF-16", and 23.6 "Surrogates - * Area". - */ - if ((ch & 0xFC00) == 0xD800) { - pe++; - if (pe > stringEnd - 6) { - raise_parse_error_at("incomplete surrogate pair at %s", state, p); - } - if (pe[0] == '\\' && pe[1] == 'u') { - uint32_t sur = unescape_unicode(state, (unsigned char *) pe + 2); - ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) - | (sur & 0x3FF)); - pe += 5; - } else { - unescape = (char *) "?"; - break; + case 'u': { + uint32_t ch = unescape_unicode(state, ++pe, stringEnd); + pe += 3; + /* To handle values above U+FFFF, we take a sequence of + * \uXXXX escapes in the U+D800..U+DBFF then + * U+DC00..U+DFFF ranges, take the low 10 bits from each + * to make a 20-bit number, then add 0x10000 to get the + * final codepoint. + * + * See Unicode 15: 3.8 "Surrogates", 5.3 "Handling + * Surrogate Pairs in UTF-16", and 23.6 "Surrogates + * Area". + */ + if ((ch & 0xFC00) == 0xD800) { + pe++; + if (RB_LIKELY((pe <= stringEnd - 6) && memcmp(pe, "\\u", 2) == 0)) { + uint32_t sur = unescape_unicode(state, pe + 2, stringEnd); + + if (RB_UNLIKELY((sur & 0xFC00) != 0xDC00)) { + raise_parse_error_at("invalid surrogate pair at %s", state, p); } + + ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) | (sur & 0x3FF)); + pe += 5; + } else { + raise_parse_error_at("incomplete surrogate pair at %s", state, p); + break; } - unescape_len = convert_UTF32_to_UTF8(buf, ch); - unescape = buf; } + + int unescape_len = convert_UTF32_to_UTF8(buffer, ch); + buffer += unescape_len; + p = ++pe; break; + } default: - p = pe; - continue; + if ((unsigned char)*pe < 0x20) { + if (!config->allow_control_characters) { + if (*pe == '\n') { + raise_parse_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1); + } + raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1); + } + } + + if (config->allow_invalid_escape) { + APPEND_CHAR(*pe); + } else { + raise_parse_error_at("invalid escape character in string: %s", state, pe - 1); + } + break; } - MEMCPY(buffer, unescape, char, unescape_len); - buffer += unescape_len; - p = ++pe; } +#undef APPEND_CHAR if (stringEnd > p) { MEMCPY(buffer, p, char, stringEnd - p); @@ -743,87 +977,99 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c if (symbolize) { result = rb_str_intern(result); } else if (intern) { - result = rb_funcall(rb_str_freeze(result), i_uminus, 0); + result = rb_str_to_interned_str(result); } return result; } #define MAX_FAST_INTEGER_SIZE 18 -static inline VALUE fast_decode_integer(const char *p, const char *pe) -{ - bool negative = false; - if (*p == '-') { - negative = true; - p++; - } +#define MAX_NUMBER_STACK_BUFFER 128 - long long memo = 0; - while (p < pe) { - memo *= 10; - memo += *p - '0'; - p++; - } +typedef VALUE (*json_number_decode_func_t)(const char *ptr); - if (negative) { - memo = -memo; +static inline VALUE json_decode_large_number(const char *start, long len, json_number_decode_func_t func) +{ + if (RB_LIKELY(len < MAX_NUMBER_STACK_BUFFER)) { + char buffer[MAX_NUMBER_STACK_BUFFER]; + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + return func(buffer); + } else { + VALUE buffer_v = rb_str_tmp_new(len); + char *buffer = RSTRING_PTR(buffer_v); + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + VALUE number = func(buffer); + RB_GC_GUARD(buffer_v); + return number; } - return LL2NUM(memo); } -static VALUE json_decode_large_integer(const char *start, long len) +static VALUE json_decode_inum(const char *buffer) +{ + return rb_cstr2inum(buffer, 10); +} + +NOINLINE(static) VALUE json_decode_large_integer(const char *start, long len) { - VALUE buffer_v; - char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - VALUE number = rb_cstr2inum(buffer, 10); - RB_ALLOCV_END(buffer_v); - return number; + return json_decode_large_number(start, len, json_decode_inum); } -static inline VALUE -json_decode_integer(const char *start, const char *end) +static inline VALUE json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const char *start, const char *end) { - long len = end - start; - if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) { - return fast_decode_integer(start, end); + if (RB_LIKELY(mantissa_digits < MAX_FAST_INTEGER_SIZE)) { + if (negative) { + return INT64T2NUM(-((int64_t)mantissa)); } - return json_decode_large_integer(start, len); + return UINT64T2NUM(mantissa); + } + + return json_decode_large_integer(start, end - start); } -static VALUE json_decode_large_float(const char *start, long len) +static VALUE json_decode_dnum(const char *buffer) { - VALUE buffer_v; - char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - VALUE number = DBL2NUM(rb_cstr_to_dbl(buffer, 1)); - RB_ALLOCV_END(buffer_v); - return number; + return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); } -static VALUE json_decode_float(JSON_ParserConfig *config, const char *start, const char *end) +NOINLINE(static) VALUE json_decode_large_float(const char *start, long len) { - long len = end - start; + return json_decode_large_number(start, len, json_decode_dnum); +} +/* Ruby JSON optimized float decoder using vendored Ryu algorithm + * Accepts pre-extracted mantissa and exponent from first-pass validation + */ +static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative, + const char *start, const char *end) +{ if (RB_UNLIKELY(config->decimal_class)) { - VALUE text = rb_str_new(start, len); + VALUE text = rb_str_new(start, end - start); return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text); - } else if (RB_LIKELY(len < 64)) { - char buffer[64]; - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); - } else { - return json_decode_large_float(start, len); } + + if (RB_UNLIKELY(exponent > INT32_MAX)) { + return negative ? CMinusInfinity : CInfinity; + } + + if (RB_UNLIKELY(exponent < INT32_MIN)) { + return rb_float_new(negative ? -0.0 : 0.0); + } + + // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case) + // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308) + if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) { + return json_decode_large_float(start, end - start); + } + + return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, (int32_t)exponent, negative)); } static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count) { - VALUE array = rb_ary_new_from_values(count, rvalue_stack_peek(state->stack, count)); - rvalue_stack_pop(state->stack, count); + VALUE array = rb_ary_new_from_values(count, rvalue_stack_peek(state->value_stack, count)); + rvalue_stack_pop(state->value_stack, count); if (config->freeze) { RB_OBJ_FREEZE(array); @@ -849,7 +1095,7 @@ static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs) return Qfalse; } -static void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key) +NOINLINE(static) void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key) { VALUE message = rb_sprintf( "detected duplicate key %"PRIsVALUE" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", @@ -860,41 +1106,52 @@ static void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_ RB_GC_GUARD(message); } -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key) +NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key) { VALUE message = rb_sprintf( "duplicate key %"PRIsVALUE, rb_inspect(duplicate_key) ); - raise_parse_error(RSTRING_PTR(message), state); - RB_GC_GUARD(message); + long line, column; + cursor_position(state, &line, &column); + rb_str_concat(message, build_parse_error_message("", state, line, column)) ; + rb_exc_raise(parse_error_new(message, line, column)); +} + +NOINLINE(static) void json_on_duplicate_key(JSON_ParserState *state, JSON_ParserConfig *config, size_t count, const VALUE *pairs) +{ + switch (config->on_duplicate_key) { + case JSON_IGNORE: + return; + + case JSON_DEPRECATED: + // Only emit the first few deprecations to avoid spamming. + if (state->emitted_deprecations < 5) { + emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs)); + state->emitted_deprecations++; + } + return; + + case JSON_RAISE: + raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs)); + return; + } + UNREACHABLE; } static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count) { size_t entries_count = count / 2; VALUE object = rb_hash_new_capa(entries_count); - const VALUE *pairs = rvalue_stack_peek(state->stack, count); + const VALUE *pairs = rvalue_stack_peek(state->value_stack, count); rb_hash_bulk_insert(count, pairs, object); if (RB_UNLIKELY(RHASH_SIZE(object) < entries_count)) { - switch (config->on_duplicate_key) { - case JSON_IGNORE: - break; - case JSON_DEPRECATED: - emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs)); - break; - case JSON_RAISE: - raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs)); - break; - } + json_on_duplicate_key(state, config, count, pairs); } - rvalue_stack_pop(state->stack, count); + rvalue_stack_pop(state->value_stack, count); if (config->freeze) { RB_OBJ_FREEZE(object); @@ -903,26 +1160,12 @@ static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfi return object; } -static inline VALUE json_decode_string(JSON_ParserState *state, JSON_ParserConfig *config, const char *start, const char *end, bool escaped, bool is_name) -{ - VALUE string; - bool intern = is_name || config->freeze; - bool symbolize = is_name && config->symbolize_names; - if (escaped) { - string = json_string_unescape(state, start, end, is_name, intern, symbolize); - } else { - string = json_string_fastpath(state, start, end, is_name, intern, symbolize); - } - - return string; -} - static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *config, VALUE value) { if (RB_UNLIKELY(config->on_load_proc)) { value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil); } - rvalue_stack_push(state->stack, value, &state->stack_handle, &state->stack); + rvalue_stack_push(state->value_stack, value, state->value_stack_handle, &state->value_stack); return value; } @@ -939,17 +1182,11 @@ static const bool string_scan_table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -#if (defined(__GNUC__ ) || defined(__clang__)) -#define FORCE_INLINE __attribute__((always_inline)) -#else -#define FORCE_INLINE -#endif - #ifdef HAVE_SIMD static SIMD_Implementation simd_impl = SIMD_NONE; #endif /* HAVE_SIMD */ -static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) +ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state) { #ifdef HAVE_SIMD #if defined(HAVE_SIMD_NEON) @@ -957,7 +1194,7 @@ static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) uint64_t mask = 0; if (string_scan_simd_neon(&state->cursor, state->end, &mask)) { state->cursor += trailing_zeros64(mask) >> 2; - return 1; + return true; } #elif defined(HAVE_SIMD_SSE2) @@ -965,313 +1202,574 @@ static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) int mask = 0; if (string_scan_simd_sse2(&state->cursor, state->end, &mask)) { state->cursor += trailing_zeros(mask); - return 1; + return true; } } #endif /* HAVE_SIMD_NEON or HAVE_SIMD_SSE2 */ #endif /* HAVE_SIMD */ - while (state->cursor < state->end) { + while (!eos(state)) { if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) { - return 1; + return true; } - *state->cursor++; + state->cursor++; } - return 0; + + // 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; } -static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) +static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name, const char *start) { - state->cursor++; - const char *start = state->cursor; - bool escaped = false; + const char *backslashes[JSON_MAX_UNESCAPE_POSITIONS]; + JSON_UnescapePositions positions = { + .size = 0, + .positions = backslashes, + .additional_backslashes = 0, + }; - while (RB_UNLIKELY(string_scan(state))) { + do { switch (*state->cursor) { case '"': { - VALUE string = json_decode_string(state, config, start, state->cursor, escaped, is_name); + VALUE string = json_string_unescape(state, config, start, state->cursor, is_name, &positions); state->cursor++; - return json_push_value(state, config, string); + return string; } case '\\': { - state->cursor++; - escaped = true; - if ((unsigned char)*state->cursor < 0x20) { - raise_parse_error("invalid ASCII control character in string: %s", state); + if (RB_LIKELY(positions.size < JSON_MAX_UNESCAPE_POSITIONS)) { + backslashes[positions.size] = state->cursor; + positions.size++; + } else { + positions.additional_backslashes++; } + state->cursor++; break; } default: - raise_parse_error("invalid ASCII control character in string: %s", state); + if (!config->allow_control_characters) { + raise_parse_error("invalid ASCII control character in string: %s", state); + } break; } state->cursor++; - } + } while (string_scan(state)); raise_parse_error("unexpected end of input, expected closing \"", state); return Qfalse; } -static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) +ALWAYS_INLINE(static) VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) { - json_eat_whitespace(state); - if (state->cursor >= state->end) { - raise_parse_error("unexpected end of input", state); + state->cursor++; + const char *start = state->cursor; + + if (RB_UNLIKELY(!string_scan(state))) { + raise_parse_error("unexpected end of input, expected closing \"", state); } - switch (*state->cursor) { - case 'n': - if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "null", 4) == 0)) { - state->cursor += 4; - return json_push_value(state, config, Qnil); - } + VALUE string; + if (RB_LIKELY(*state->cursor == '"')) { + string = json_string_fastpath(state, config, start, state->cursor, is_name); + state->cursor++; + } + else { + string = json_parse_escaped_string(state, config, is_name, start); + } - raise_parse_error("unexpected token %s", state); - break; - case 't': - if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "true", 4) == 0)) { - state->cursor += 4; - return json_push_value(state, config, Qtrue); - } + return string; +} - raise_parse_error("unexpected token %s", state); - break; - case 'f': - // Note: memcmp with a small power of two compile to an integer comparison - if ((state->end - state->cursor >= 5) && (memcmp(state->cursor + 1, "alse", 4) == 0)) { - state->cursor += 5; - return json_push_value(state, config, Qfalse); - } +#if JSON_CPU_LITTLE_ENDIAN_64BITS +// From: https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/ +// Additional References: +// https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +// http://0x80.pl/notesen/2014-10-12-parsing-decimal-numbers-part-1-swar.html +static inline uint64_t decode_8digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return val; +} - raise_parse_error("unexpected token %s", state); - break; - case 'N': - // Note: memcmp with a small power of two compile to an integer comparison - if (config->allow_nan && (state->end - state->cursor >= 3) && (memcmp(state->cursor + 1, "aN", 2) == 0)) { - state->cursor += 3; - return json_push_value(state, config, CNaN); - } +static inline uint64_t decode_4digits_unrolled(uint32_t val) { + const uint32_t mask = 0x000000FF; + const uint32_t mul1 = 100; + val -= 0x30303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = ((val & mask) * mul1) + (((val >> 16) & mask)); + return val; +} +#endif - raise_parse_error("unexpected token %s", state); - break; - case 'I': - if (config->allow_nan && (state->end - state->cursor >= 8) && (memcmp(state->cursor, "Infinity", 8) == 0)) { - state->cursor += 8; - return json_push_value(state, config, CInfinity); - } +static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulator) +{ + const char *start = state->cursor; - raise_parse_error("unexpected token %s", state); - break; - case '-': - // Note: memcmp with a small power of two compile to an integer comparison - if ((state->end - state->cursor >= 9) && (memcmp(state->cursor + 1, "Infinity", 8) == 0)) { - if (config->allow_nan) { - state->cursor += 9; - return json_push_value(state, config, CMinusInfinity); - } else { - raise_parse_error("unexpected token %s", state); - } - } - // Fallthrough - case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { - bool integer = true; +#if JSON_CPU_LITTLE_ENDIAN_64BITS + while (rest(state) >= sizeof(uint64_t)) { + uint64_t next_8bytes; + memcpy(&next_8bytes, state->cursor, sizeof(uint64_t)); + + // From: https://github.com/simdjson/simdjson/blob/32b301893c13d058095a07d9868edaaa42ee07aa/include/simdjson/generic/numberparsing.h#L333 + // Branchless version of: http://0x80.pl/articles/swar-digits-validate.html + uint64_t match = (next_8bytes & 0xF0F0F0F0F0F0F0F0) | (((next_8bytes + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4); + + if (match == 0x3333333333333333) { // 8 consecutive digits + *accumulator = (*accumulator * 100000000) + decode_8digits_unrolled(next_8bytes); + state->cursor += 8; + continue; + } + + uint32_t consecutive_digits = trailing_zeros64(match ^ 0x3333333333333333) / CHAR_BIT; - // /\A-?(0|[1-9]\d*)(\.\d+)?([Ee][-+]?\d+)?/ - const char *start = state->cursor; + if (consecutive_digits >= 4) { + *accumulator = (*accumulator * 10000) + decode_4digits_unrolled((uint32_t)next_8bytes); + state->cursor += 4; + consecutive_digits -= 4; + } + + while (consecutive_digits) { + *accumulator = *accumulator * 10 + (*state->cursor - '0'); + consecutive_digits--; state->cursor++; + } - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { - state->cursor++; - } + return (int)(state->cursor - start); + } +#endif + + char next_char; + while (rb_isdigit(next_char = peek(state))) { + *accumulator = *accumulator * 10 + (next_char - '0'); + state->cursor++; + } + return (int)(state->cursor - start); +} - long integer_length = state->cursor - start; +static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig *config, bool negative, const char *start) +{ + bool integer = true; + const char first_digit = *state->cursor; - if (RB_UNLIKELY(start[0] == '0' && integer_length > 1)) { - raise_parse_error_at("invalid number: %s", state, start); - } else if (RB_UNLIKELY(integer_length > 2 && start[0] == '-' && start[1] == '0')) { - raise_parse_error_at("invalid number: %s", state, start); - } else if (RB_UNLIKELY(integer_length == 1 && start[0] == '-')) { - raise_parse_error_at("invalid number: %s", state, start); - } + // Variables for Ryu optimization - extract digits during parsing + int64_t exponent = 0; + int decimal_point_pos = -1; + uint64_t mantissa = 0; - if ((state->cursor < state->end) && (*state->cursor == '.')) { - integer = false; - state->cursor++; + // Parse integer part and extract mantissa digits + int mantissa_digits = json_parse_digits(state, &mantissa); + + if (RB_UNLIKELY((first_digit == '0' && mantissa_digits > 1) || (negative && mantissa_digits == 0))) { + raise_parse_error_at("invalid number: %s", state, start); + } + + // Parse fractional part + if (peek(state) == '.') { + integer = false; + decimal_point_pos = mantissa_digits; // Remember position of decimal point + state->cursor++; + + int fractional_digits = json_parse_digits(state, &mantissa); + mantissa_digits += fractional_digits; + + if (RB_UNLIKELY(!fractional_digits)) { + raise_parse_error_at("invalid number: %s", state, start); + } + } + + // Parse exponent + if (rb_tolower(peek(state)) == 'e') { + integer = false; + state->cursor++; + + bool negative_exponent = false; + const char next_char = peek(state); + if (next_char == '-' || next_char == '+') { + negative_exponent = next_char == '-'; + state->cursor++; + } + + uint64_t abs_exponent = 0; + int exponent_digits = json_parse_digits(state, &abs_exponent); + + if (RB_UNLIKELY(!exponent_digits)) { + raise_parse_error_at("invalid number: %s", state, start); + } + + if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) { + exponent = negative_exponent ? INT64_MIN : INT64_MAX; + } else { + exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent; + } + } + + if (integer) { + return json_decode_integer(mantissa, mantissa_digits, negative, start, state->cursor); + } + + // Adjust exponent based on decimal point position + if (decimal_point_pos >= 0) { + exponent -= (mantissa_digits - decimal_point_pos); + } + + return json_decode_float(config, mantissa, mantissa_digits, exponent, negative, start, state->cursor); +} + +static inline VALUE json_parse_positive_number(JSON_ParserState *state, JSON_ParserConfig *config) +{ + return json_parse_number(state, config, false, state->cursor); +} + +static inline VALUE json_parse_negative_number(JSON_ParserState *state, JSON_ParserConfig *config) +{ + return json_parse_number(state, config, true, state->cursor - 1); +} - if (state->cursor == state->end || *state->cursor < '0' || *state->cursor > '9') { - raise_parse_error("invalid number: %s", state); +// How many values (array elements, or interleaved object keys+values) have been +// pushed onto the rvalue stack since this container opened. Used to size the +// bulk decode on close, and to tell the first key/colon from later ones. +static inline long json_frame_entry_count(const json_frame *frame, const rvalue_stack *value_stack) +{ + return value_stack->head - frame->value_stack_head; +} + +// A complete value now sits on top of the rvalue stack. Advance the frame that +// was waiting for it: the root document is done, or the enclosing container +// moves on to expecting a ',' or its closing bracket. The caller passes the +// frame it already has in hand -- the one that was expecting the value -- which +// after a container close is the freshly re-exposed parent. +static inline void json_value_completed(json_frame *frame) +{ + JSON_ASSERT((int)JSON_PHASE_DONE == (int)JSON_FRAME_ROOT); + JSON_ASSERT((int)JSON_PHASE_ARRAY_COMMA == (int)JSON_FRAME_ARRAY); + JSON_ASSERT((int)JSON_PHASE_OBJECT_COMMA == (int)JSON_FRAME_OBJECT); + + frame->phase = (enum json_frame_phase) frame->type; +} + +ALWAYS_INLINE(static) bool json_match_keyword(JSON_ParserState *state, const char *keyword, size_t offset) +{ + // It is assumed that since `keyword` is always a literal, the compiler is able to constantize this + // `strlen` and several other computations in that routine, such as eliminating the `if (resumable)` branch. + + size_t len = strlen(keyword); + + // Note: memcmp with a small power of two and a literal string compile to an integer comparison / + // That's why we sometime compare starting from the first byte and sometimes from the second. + if (rest(state) >= len && (memcmp(state->cursor + offset, keyword + offset, len - offset) == 0)) { + state->cursor += len; + return true; + } + return false; +} + +// Parse an arbitrary JSON value iteratively. This is a state machine driven +// entirely by the top frame's phase so it can stop at any value boundary and +// resume purely from the frame stack. A JSON_FRAME_ROOT frame sits at the +// bottom of the stack, so the stack is never empty mid-parse and the document +// itself is just another frame whose value, once parsed, leaves its phase DONE. +static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) +{ + json_frame *frame = json_frame_stack_peek(state->frames); + + switch (frame->phase) { + case JSON_PHASE_DONE: goto JSON_PHASE_DONE; + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: goto JSON_PHASE_OBJECT_KEY; + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; + } + UNREACHABLE_RETURN(Qundef); + + JSON_PHASE_DONE: { + // The root document value is parsed; it is the lone survivor on + // the rvalue stack. + return *rvalue_stack_peek(state->value_stack, 1); + } + + JSON_PHASE_VALUE: { + json_eat_whitespace(state); + + VALUE value; + switch (peek(state)) { + case 'n': + if (json_match_keyword(state, "null", 0)) { + value = Qnil; + break; } + raise_parse_error("unexpected token %s", state); - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { - state->cursor++; + case 't': + if (json_match_keyword(state, "true", 0)) { + value = Qtrue; + break; } - } + raise_parse_error("unexpected token %s", state); - if ((state->cursor < state->end) && ((*state->cursor == 'e') || (*state->cursor == 'E'))) { - integer = false; - state->cursor++; - if ((state->cursor < state->end) && ((*state->cursor == '+') || (*state->cursor == '-'))) { - state->cursor++; + case 'f': + if (json_match_keyword(state, "false", 1)) { + value = Qfalse; + break; } + raise_parse_error("unexpected token %s", state); - if (state->cursor == state->end || *state->cursor < '0' || *state->cursor > '9') { - raise_parse_error("invalid number: %s", state); + case 'N': + // Note: memcmp with a small power of two compile to an integer comparison + if (config->allow_nan && json_match_keyword(state, "NaN", 1)) { + value = CNaN; + break; } + raise_parse_error("unexpected token %s", state); - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { - state->cursor++; + case 'I': + if (config->allow_nan && json_match_keyword(state, "Infinity", 0)) { + value = CInfinity; + break; } - } + raise_parse_error("unexpected token %s", state); - if (integer) { - return json_push_value(state, config, json_decode_integer(start, state->cursor)); + case '-': { + state->cursor++; + if (config->allow_nan && json_match_keyword(state, "Infinity", 0)) { + value = CMinusInfinity; + } else { + value = json_parse_negative_number(state, config); + } + break; } - return json_push_value(state, config, json_decode_float(config, start, state->cursor)); - } - case '"': { - // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"} - return json_parse_string(state, config, false); - break; - } - case '[': { - state->cursor++; - json_eat_whitespace(state); - long stack_head = state->stack->head; - if ((state->cursor < state->end) && (*state->cursor == ']')) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + value = json_parse_positive_number(state, config); + break; + + case '"': + // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"} + value = json_parse_string(state, config, false); + break; + + case '[': { state->cursor++; - return json_push_value(state, config, json_decode_array(state, config, 0)); - } else { + json_eat_whitespace(state); + + if (peek(state) == ']') { + state->cursor++; + value = json_decode_array(state, config, 0); + break; + } + state->current_nesting++; if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting); } state->in_array++; - json_parse_any(state, config); + + // Phase stays VALUE: the next iteration reads the first element. + frame = json_frame_stack_push(state, (json_frame){ + .type = JSON_FRAME_ARRAY, + .phase = JSON_PHASE_VALUE, + .value_stack_head = state->value_stack->head, + }); + goto JSON_PHASE_VALUE; } + case '{': { + const char *object_start_cursor = state->cursor; - while (true) { + state->cursor++; json_eat_whitespace(state); - if (state->cursor < state->end) { - if (*state->cursor == ']') { - state->cursor++; - long count = state->stack->head - stack_head; - state->current_nesting--; - state->in_array--; - return json_push_value(state, config, json_decode_array(state, config, count)); - } + if (peek(state) == '}') { + state->cursor++; + value = json_decode_object(state, config, 0); + break; + } - if (*state->cursor == ',') { - state->cursor++; - if (config->allow_trailing_comma) { - json_eat_whitespace(state); - if ((state->cursor < state->end) && (*state->cursor == ']')) { - continue; - } - } - json_parse_any(state, config); - continue; - } + state->current_nesting++; + if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { + rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting); } - raise_parse_error("expected ',' or ']' after array value", state); + // Phase KEY: the next iteration reads the first key. + frame = json_frame_stack_push(state, (json_frame){ + .type = JSON_FRAME_OBJECT, + .phase = JSON_PHASE_OBJECT_KEY, + .value_stack_head = state->value_stack->head, + .start_cursor = object_start_cursor, + }); + goto JSON_PHASE_OBJECT_KEY; } - break; + + case 0: + raise_parse_error("unexpected end of input", state); + + default: + raise_parse_error("unexpected character: %s", state); } - case '{': { - const char *object_start_cursor = state->cursor; - state->cursor++; - json_eat_whitespace(state); - long stack_head = state->stack->head; + json_push_value(state, config, value); + json_value_completed(frame); - if ((state->cursor < state->end) && (*state->cursor == '}')) { - state->cursor++; - return json_push_value(state, config, json_decode_object(state, config, 0)); - } else { - state->current_nesting++; - if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { - rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting); - } + switch (frame->phase) { + case JSON_PHASE_DONE: goto JSON_PHASE_DONE; + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: UNREACHABLE_RETURN(Qundef); + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; + } + UNREACHABLE_RETURN(Qundef); + } - if (*state->cursor != '"') { - raise_parse_error("expected object key, got %s", state); - } - json_parse_string(state, config, true); + JSON_PHASE_OBJECT_KEY: { + JSON_ASSERT(frame->type == JSON_FRAME_OBJECT); - json_eat_whitespace(state); - if ((state->cursor >= state->end) || (*state->cursor != ':')) { - raise_parse_error("expected ':' after object key", state); - } - state->cursor++; + json_eat_whitespace(state); - json_parse_any(state, config); + if (RB_LIKELY(peek(state) == '"')) { + json_push_value(state, config, json_parse_string(state, config, true)); + frame->phase = JSON_PHASE_OBJECT_COLON; + goto JSON_PHASE_OBJECT_COLON; + } else { + // The message differs for the first key vs. a key after a + // ',': the first is the only one reached with nothing pushed + // for this object yet. + if (json_frame_entry_count(frame, state->value_stack) == 0) { + raise_parse_error("expected object key, got %s", state); + } else { + raise_parse_error("expected object key, got: %s", state); } + } + UNREACHABLE_RETURN(Qundef); + } - while (true) { - json_eat_whitespace(state); + JSON_PHASE_OBJECT_COLON: { + JSON_ASSERT(frame->type == JSON_FRAME_OBJECT); - if (state->cursor < state->end) { - if (*state->cursor == '}') { - state->cursor++; - state->current_nesting--; - size_t count = state->stack->head - stack_head; + json_eat_whitespace(state); - // Temporary rewind cursor in case an error is raised - const char *final_cursor = state->cursor; - state->cursor = object_start_cursor; - VALUE object = json_decode_object(state, config, count); - state->cursor = final_cursor; + if (RB_LIKELY(peek(state) == ':')) { + state->cursor++; + frame->phase = JSON_PHASE_VALUE; + goto JSON_PHASE_VALUE; + } else { + // First colon (only the first pair's key is pushed, nothing + // else) vs. a later one. + if (json_frame_entry_count(frame, state->value_stack) == 1) { + raise_parse_error("expected ':' after object key", state); + } else { + raise_parse_error("expected ':' after object key, got: %s", state); + } + } + UNREACHABLE_RETURN(Qundef); + } - return json_push_value(state, config, object); - } + JSON_PHASE_ARRAY_COMMA: { + JSON_ASSERT(frame->type == JSON_FRAME_ARRAY); - if (*state->cursor == ',') { - state->cursor++; - json_eat_whitespace(state); + json_eat_whitespace(state); - if (config->allow_trailing_comma) { - if ((state->cursor < state->end) && (*state->cursor == '}')) { - continue; - } - } + const char next_char = peek(state); - if (*state->cursor != '"') { - raise_parse_error("expected object key, got: %s", state); - } - json_parse_string(state, config, true); + if (RB_LIKELY(next_char == ',')) { + state->cursor++; + if (config->allow_trailing_comma) { + json_eat_whitespace(state); + if (peek(state) == ']') { + // Trailing comma: stay in COMMA to close on the next iteration. + goto JSON_PHASE_ARRAY_COMMA; + } + } + frame->phase = JSON_PHASE_VALUE; + goto JSON_PHASE_VALUE; + } else if (next_char == ']') { + state->cursor++; + long count = json_frame_entry_count(frame, state->value_stack); + state->current_nesting--; + state->in_array--; + json_frame_stack_pop(state->frames); + json_push_value(state, config, json_decode_array(state, config, count)); + frame = json_frame_stack_peek(state->frames); + json_value_completed(frame); + + switch (frame->phase) { + case JSON_PHASE_DONE: goto JSON_PHASE_DONE; + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: UNREACHABLE_RETURN(Qundef); + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; + } + } else { + raise_parse_error("expected ',' or ']' after array value", state); + } + UNREACHABLE_RETURN(Qundef); + } - json_eat_whitespace(state); - if ((state->cursor >= state->end) || (*state->cursor != ':')) { - raise_parse_error("expected ':' after object key, got: %s", state); - } - state->cursor++; + JSON_PHASE_OBJECT_COMMA: { + JSON_ASSERT(frame->type == JSON_FRAME_OBJECT); - json_parse_any(state, config); + json_eat_whitespace(state); + const char next_char = peek(state); - continue; - } + if (RB_LIKELY(next_char == ',')) { + state->cursor++; + + if (config->allow_trailing_comma) { + json_eat_whitespace(state); + if (peek(state) == '}') { + // Trailing comma: stay in COMMA to close on the next iteration. + goto JSON_PHASE_OBJECT_COMMA; } + } - raise_parse_error("expected ',' or '}' after object value, got: %s", state); + frame->phase = JSON_PHASE_OBJECT_KEY; + goto JSON_PHASE_OBJECT_KEY; + } else if (next_char == '}') { + state->cursor++; + state->current_nesting--; + size_t count = json_frame_entry_count(frame, state->value_stack); + + // Temporary rewind cursor in case an error is raised + const char *final_cursor = state->cursor; + state->cursor = frame->start_cursor; + VALUE object = json_decode_object(state, config, count); + state->cursor = final_cursor; + + json_push_value(state, config, object); + json_frame_stack_pop(state->frames); + frame = json_frame_stack_peek(state->frames); + json_value_completed(frame); + + switch (frame->phase) { + case JSON_PHASE_DONE: goto JSON_PHASE_DONE; + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: UNREACHABLE_RETURN(Qundef); + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; } - break; + } else { + raise_parse_error("expected ',' or '}' after object value, got: %s", state); } - - default: - raise_parse_error("unexpected character: %s", state); - break; + UNREACHABLE_RETURN(Qundef); } - raise_parse_error("unreachable: %s", state); + UNREACHABLE_RETURN(Qundef); } static void json_ensure_eof(JSON_ParserState *state) { json_eat_whitespace(state); - if (state->cursor != state->end) { + if (!eos(state)) { raise_parse_error("unexpected token at end of stream %s", state); } } @@ -1290,38 +1788,56 @@ static void json_ensure_eof(JSON_ParserState *state) static VALUE convert_encoding(VALUE source) { - int encindex = RB_ENCODING_GET(source); + StringValue(source); + int encindex = RB_ENCODING_GET(source); - if (RB_LIKELY(encindex == utf8_encindex)) { + if (RB_LIKELY(encindex == utf8_encindex)) { + return source; + } + + if (encindex == binary_encindex) { + // For historical reason, we silently reinterpret binary strings as UTF-8 + return rb_enc_associate_index(rb_str_dup(source), utf8_encindex); + } + + source = rb_funcall(source, i_encode, 1, Encoding_UTF_8); + StringValue(source); return source; - } +} - if (encindex == binary_encindex) { - // For historical reason, we silently reinterpret binary strings as UTF-8 - return rb_enc_associate_index(rb_str_dup(source), utf8_encindex); - } +struct parser_config_init_args { + JSON_ParserConfig *config; + VALUE self; +}; - return rb_funcall(source, i_encode, 1, Encoding_UTF_8); +static void parser_config_wb_write(VALUE self, VALUE *dest, VALUE val) +{ + *dest = val; + if (self) RB_OBJ_WRITTEN(self, Qundef, val); } static int parser_config_init_i(VALUE key, VALUE val, VALUE data) { - JSON_ParserConfig *config = (JSON_ParserConfig *)data; - - if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; } - else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); } - else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } - else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } - else if (key == sym_freeze) { config->freeze = RTEST(val); } - else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; } - else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } - else if (key == sym_decimal_class) { + struct parser_config_init_args *args = (struct parser_config_init_args *)data; + JSON_ParserConfig *config = args->config; + VALUE self = args->self; + + if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; } + else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); } + else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } + else if (key == sym_allow_control_characters) { config->allow_control_characters = RTEST(val); } + else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); } + else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } + else if (key == sym_freeze) { config->freeze = RTEST(val); } + else if (key == sym_on_load) { parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); } + else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } + else if (key == sym_decimal_class) { if (RTEST(val)) { if (rb_respond_to(val, i_try_convert)) { - config->decimal_class = val; + parser_config_wb_write(self, &config->decimal_class, val); config->decimal_method_id = i_try_convert; } else if (rb_respond_to(val, i_new)) { - config->decimal_class = val; + parser_config_wb_write(self, &config->decimal_class, val); config->decimal_method_id = i_new; } else if (RB_TYPE_P(val, T_CLASS)) { VALUE name = rb_class_name(val); @@ -1330,7 +1846,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) if (last_colon) { const char *mod_path_end = last_colon - 1; VALUE mod_path = rb_str_substr(name, 0, mod_path_end - name_cstr); - config->decimal_class = rb_path_to_class(mod_path); + parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path)); const char *method_name_beg = last_colon + 1; long before_len = method_name_beg - name_cstr; @@ -1338,7 +1854,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) VALUE method_name = rb_str_substr(name, before_len, len); config->decimal_method_id = SYM2ID(rb_str_intern(method_name)); } else { - config->decimal_class = rb_mKernel; + parser_config_wb_write(self, &config->decimal_class, rb_mKernel); config->decimal_method_id = SYM2ID(rb_str_intern(name)); } } @@ -1348,16 +1864,21 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) return ST_CONTINUE; } -static void parser_config_init(JSON_ParserConfig *config, VALUE opts) +static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self) { config->max_nesting = 100; + struct parser_config_init_args args = { + .config = config, + .self = self, + }; + if (!NIL_P(opts)) { Check_Type(opts, T_HASH); if (RHASH_SIZE(opts) > 0) { // We assume in most cases few keys are set so it's faster to go over // the provided keys than to check all possible keys. - rb_hash_foreach(opts, parser_config_init_i, (VALUE)config); + rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args); } } @@ -1388,36 +1909,62 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts) */ static VALUE cParserConfig_initialize(VALUE self, VALUE opts) { + rb_check_frozen(self); GET_PARSER_CONFIG; - parser_config_init(config, opts); - - RB_OBJ_WRITTEN(self, Qundef, config->decimal_class); + parser_config_init(config, opts, self); return self; } -static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource) +static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src) { - Vsource = convert_encoding(StringValue(Vsource)); - StringValue(Vsource); + VALUE Vsource = convert_encoding(src); + + // Ensure the string isn't mutated under us. + // The classic API to use is `rb_str_locktmp`, but then we'd + // need to use `rb_protect` to make sure we always unlock. + if (Vsource == src) { + Vsource = rb_str_new_frozen(Vsource); + } VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA]; - rvalue_stack stack = { + rvalue_stack value_stack = { .type = RVALUE_STACK_STACK_ALLOCATED, .ptr = rvalue_stack_buffer, .capa = RVALUE_STACK_INITIAL_CAPA, }; + // Seed the frame stack with the root frame, establishing the invariant that + // json_parse_any always has a top frame to dispatch on (so the stack is never + // empty mid-parse). + json_frame frame_stack_buffer[JSON_FRAME_STACK_INITIAL_CAPA]; + frame_stack_buffer[0] = (json_frame){ + .type = JSON_FRAME_ROOT, + .phase = JSON_PHASE_VALUE, + }; + json_frame_stack frames = { + .type = RVALUE_STACK_STACK_ALLOCATED, + .ptr = frame_stack_buffer, + .capa = JSON_FRAME_STACK_INITIAL_CAPA, + .head = 1, + }; + long len; const char *start; + RSTRING_GETMEM(Vsource, start, len); + VALUE value_stack_handle = 0; + VALUE frame_stack_handle = 0; JSON_ParserState _state = { .start = start, .cursor = start, .end = start + len, - .stack = &stack, + .value_stack = &value_stack, + .value_stack_handle = &value_stack_handle, + .frames = &frames, + .frame_stack_handle = &frame_stack_handle, }; JSON_ParserState *state = &_state; @@ -1425,8 +1972,11 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource) // This may be skipped in case of exception, but // it won't cause a leak. - rvalue_stack_eagerly_release(state->stack_handle); - + rvalue_stack_eagerly_release(value_stack_handle); + json_frame_stack_eagerly_release(frame_stack_handle); + RB_GC_GUARD(value_stack_handle); + RB_GC_GUARD(frame_stack_handle); + RB_GC_GUARD(Vsource); json_ensure_eof(state); return result; @@ -1447,12 +1997,9 @@ static VALUE cParserConfig_parse(VALUE self, VALUE Vsource) static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts) { - Vsource = convert_encoding(StringValue(Vsource)); - StringValue(Vsource); - JSON_ParserConfig _config = {0}; JSON_ParserConfig *config = &_config; - parser_config_init(config, opts); + parser_config_init(config, opts, false); return cParser_parse(config, Vsource); } @@ -1460,30 +2007,35 @@ static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts) static void JSON_ParserConfig_mark(void *ptr) { JSON_ParserConfig *config = ptr; - rb_gc_mark(config->on_load_proc); - rb_gc_mark(config->decimal_class); + rb_gc_mark_movable(config->on_load_proc); + rb_gc_mark_movable(config->decimal_class); } -static void JSON_ParserConfig_free(void *ptr) +static size_t JSON_ParserConfig_memsize(const void *ptr) { - JSON_ParserConfig *config = ptr; - ruby_xfree(config); +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + return 0; +#else + return sizeof(JSON_ParserConfig); +#endif } -static size_t JSON_ParserConfig_memsize(const void *ptr) +static void JSON_ParserConfig_compact(void *ptr) { - return sizeof(JSON_ParserConfig); + JSON_ParserConfig *config = ptr; + config->on_load_proc = rb_gc_location(config->on_load_proc); + config->decimal_class = rb_gc_location(config->decimal_class); } static const rb_data_type_t JSON_ParserConfig_type = { - "JSON::Ext::Parser/ParserConfig", - { - JSON_ParserConfig_mark, - JSON_ParserConfig_free, - JSON_ParserConfig_memsize, + .wrap_struct_name = "JSON::Ext::Parser/ParserConfig", + .function = { + .dmark = JSON_ParserConfig_mark, + .dfree = RUBY_DEFAULT_FREE, + .dsize = JSON_ParserConfig_memsize, + .dcompact = JSON_ParserConfig_compact, }, - 0, 0, - RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, }; static VALUE cJSON_parser_s_allocate(VALUE klass) @@ -1527,16 +2079,14 @@ void Init_parser(void) sym_max_nesting = ID2SYM(rb_intern("max_nesting")); sym_allow_nan = ID2SYM(rb_intern("allow_nan")); sym_allow_trailing_comma = ID2SYM(rb_intern("allow_trailing_comma")); + sym_allow_control_characters = ID2SYM(rb_intern("allow_control_characters")); + sym_allow_invalid_escape = ID2SYM(rb_intern("allow_invalid_escape")); sym_symbolize_names = ID2SYM(rb_intern("symbolize_names")); sym_freeze = ID2SYM(rb_intern("freeze")); sym_on_load = ID2SYM(rb_intern("on_load")); sym_decimal_class = ID2SYM(rb_intern("decimal_class")); sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key")); - i_chr = rb_intern("chr"); - i_aset = rb_intern("[]="); - i_aref = rb_intern("[]"); - i_leftshift = rb_intern("<<"); i_new = rb_intern("new"); i_try_convert = rb_intern("try_convert"); i_uminus = rb_intern("-@"); diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index 3abbdb0209..611b41b066 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -1,10 +1,14 @@ +#include "../json.h" + typedef enum { SIMD_NONE, SIMD_NEON, SIMD_SSE2 } SIMD_Implementation; -#ifdef JSON_ENABLE_SIMD +#ifndef __has_builtin // Optional of course. + #define __has_builtin(x) 0 // Compatibility with non-clang compilers. +#endif #ifdef __clang__ # if __has_builtin(__builtin_ctzll) @@ -20,6 +24,8 @@ typedef enum { static inline uint32_t trailing_zeros64(uint64_t input) { + JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior + #if HAVE_BUILTIN_CTZLL return __builtin_ctzll(input); #else @@ -35,6 +41,8 @@ static inline uint32_t trailing_zeros64(uint64_t input) static inline int trailing_zeros(int input) { + JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior + #if HAVE_BUILTIN_CTZLL return __builtin_ctz(input); #else @@ -48,14 +56,36 @@ static inline int trailing_zeros(int input) #endif } -#if (defined(__GNUC__ ) || defined(__clang__)) -#define FORCE_INLINE __attribute__((always_inline)) -#else -#define FORCE_INLINE -#endif +#ifdef JSON_ENABLE_SIMD +#define SIMD_MINIMUM_THRESHOLD 4 -#define SIMD_MINIMUM_THRESHOLD 6 +ALWAYS_INLINE(static) void json_fast_memcpy16(char *dst, const char *src, size_t len) +{ + RBIMPL_ASSERT_OR_ASSUME(len < 16); + RBIMPL_ASSERT_OR_ASSUME(len >= SIMD_MINIMUM_THRESHOLD); // 4 +#if defined(__has_builtin) && __has_builtin(__builtin_memcpy) + // If __builtin_memcpy is available, use it to copy between SIMD_MINIMUM_THRESHOLD (4) and vec_len-1 (15) bytes. + // These copies overlap. The first copy will copy the first 8 (or 4) bytes. The second copy will copy + // the last 8 (or 4) bytes but overlap with the first copy. The overlapping bytes will be in the correct + // position in both copies. + + // Please do not attempt to replace __builtin_memcpy with memcpy without profiling and/or looking at the + // generated assembly. On clang-specifically (tested on Apple clang version 17.0.0 (clang-1700.0.13.3)), + // when using memcpy, the compiler will notice the only difference is a 4 or 8 and generate a conditional + // select instruction instead of direct loads and stores with a branch. This ends up slower than the branch + // plus two loads and stores generated when using __builtin_memcpy. + if (len >= 8) { + __builtin_memcpy(dst, src, 8); + __builtin_memcpy(dst + len - 8, src + len - 8, 8); + } else { + __builtin_memcpy(dst, src, 4); + __builtin_memcpy(dst + len - 4, src + len - 4, 4); + } +#else + MEMCPY(dst, src, char, len); +#endif +} #if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64) #include <arm_neon.h> @@ -70,14 +100,14 @@ static inline SIMD_Implementation find_simd_implementation(void) #define HAVE_SIMD_NEON 1 // See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon -static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches) +ALWAYS_INLINE(static) uint64_t neon_match_mask(uint8x16_t matches) { const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4); const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); return mask & 0x8888888888888888ull; } -static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr) +ALWAYS_INLINE(static) uint64_t compute_chunk_mask_neon(const char *ptr) { uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr); @@ -90,7 +120,7 @@ static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr) return neon_match_mask(needs_escape); } -static inline FORCE_INLINE int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) +ALWAYS_INLINE(static) int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) { while (*ptr + sizeof(uint8x16_t) <= end) { uint64_t chunk_mask = compute_chunk_mask_neon(*ptr); @@ -103,16 +133,6 @@ static inline FORCE_INLINE int string_scan_simd_neon(const char **ptr, const cha return 0; } -static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table) -{ - uint8x16x4_t tab; - tab.val[0] = vld1q_u8(table); - tab.val[1] = vld1q_u8(table+16); - tab.val[2] = vld1q_u8(table+32); - tab.val[3] = vld1q_u8(table+48); - return tab; -} - #endif /* ARM Neon Support.*/ #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) @@ -137,7 +157,7 @@ static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table) #define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1)) #define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a) -static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *ptr) +ALWAYS_INLINE(static) TARGET_SSE2 int compute_chunk_mask_sse2(const char *ptr) { __m128i chunk = _mm_loadu_si128((__m128i const*)ptr); // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 @@ -148,7 +168,7 @@ static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *p return _mm_movemask_epi8(needs_escape); } -static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) +ALWAYS_INLINE(static) TARGET_SSE2 int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) { while (*ptr + sizeof(__m128i) <= end) { int chunk_mask = compute_chunk_mask_sse2(*ptr); diff --git a/ext/json/vendor/fpconv.c b/ext/json/vendor/fpconv.c index 75efd46f11..6c9bc2c103 100644 --- a/ext/json/vendor/fpconv.c +++ b/ext/json/vendor/fpconv.c @@ -29,6 +29,10 @@ #include <string.h> #include <stdint.h> +#if JSON_DEBUG +#include <assert.h> +#endif + #define npowers 87 #define steppowers 8 #define firstpower -348 /* 10 ^ -348 */ @@ -320,15 +324,7 @@ static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg) { int exp = absv(K + ndigits - 1); - int max_trailing_zeros = 7; - - if(neg) { - max_trailing_zeros -= 1; - } - - /* write plain integer */ - if(K >= 0 && (exp < (ndigits + max_trailing_zeros))) { - + if(K >= 0 && exp < 15) { memcpy(dest, digits, ndigits); memset(dest + ndigits, '0', K); @@ -432,10 +428,12 @@ static int filter_special(double fp, char* dest) * * Input: * fp -> the double to convert, dest -> destination buffer. - * The generated string will never be longer than 28 characters. - * Make sure to pass a pointer to at least 28 bytes of memory. + * The generated string will never be longer than 32 characters. + * Make sure to pass a pointer to at least 32 bytes of memory. * The emitted string will not be null terminated. * + * + * * Output: * The number of written characters. * @@ -451,7 +449,7 @@ static int filter_special(double fp, char* dest) * } * */ -static int fpconv_dtoa(double d, char dest[28]) +static int fpconv_dtoa(double d, char dest[32]) { char digits[18]; @@ -474,6 +472,9 @@ static int fpconv_dtoa(double d, char dest[28]) int ndigits = grisu2(d, digits, &K); str_len += emit_digits(digits, ndigits, dest + str_len, K, neg); +#if JSON_DEBUG + assert(str_len <= 32); +#endif return str_len; } diff --git a/ext/json/vendor/ryu.h b/ext/json/vendor/ryu.h new file mode 100644 index 0000000000..f06ec814b4 --- /dev/null +++ b/ext/json/vendor/ryu.h @@ -0,0 +1,819 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +// +// --- +// +// Apache License +// Version 2.0, January 2004 +// http://www.apache.org/licenses/ +// +// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +// +// 1. Definitions. +// +// "License" shall mean the terms and conditions for use, reproduction, +// and distribution as defined by Sections 1 through 9 of this document. +// +// "Licensor" shall mean the copyright owner or entity authorized by +// the copyright owner that is granting the License. +// +// "Legal Entity" shall mean the union of the acting entity and all +// other entities that control, are controlled by, or are under common +// control with that entity. For the purposes of this definition, +// "control" means (i) the power, direct or indirect, to cause the +// direction or management of such entity, whether by contract or +// otherwise, or (ii) ownership of fifty percent (50%) or more of the +// outstanding shares, or (iii) beneficial ownership of such entity. +// +// "You" (or "Your") shall mean an individual or Legal Entity +// exercising permissions granted by this License. +// +// "Source" form shall mean the preferred form for making modifications, +// including but not limited to software source code, documentation +// source, and configuration files. +// +// "Object" form shall mean any form resulting from mechanical +// transformation or translation of a Source form, including but +// not limited to compiled object code, generated documentation, +// and conversions to other media types. +// +// "Work" shall mean the work of authorship, whether in Source or +// Object form, made available under the License, as indicated by a +// copyright notice that is included in or attached to the work +// (an example is provided in the Appendix below). +// +// "Derivative Works" shall mean any work, whether in Source or Object +// form, that is based on (or derived from) the Work and for which the +// editorial revisions, annotations, elaborations, or other modifications +// represent, as a whole, an original work of authorship. For the purposes +// of this License, Derivative Works shall not include works that remain +// separable from, or merely link (or bind by name) to the interfaces of, +// the Work and Derivative Works thereof. +// +// "Contribution" shall mean any work of authorship, including +// the original version of the Work and any modifications or additions +// to that Work or Derivative Works thereof, that is intentionally +// submitted to Licensor for inclusion in the Work by the copyright owner +// or by an individual or Legal Entity authorized to submit on behalf of +// the copyright owner. For the purposes of this definition, "submitted" +// means any form of electronic, verbal, or written communication sent +// to the Licensor or its representatives, including but not limited to +// communication on electronic mailing lists, source code control systems, +// and issue tracking systems that are managed by, or on behalf of, the +// Licensor for the purpose of discussing and improving the Work, but +// excluding communication that is conspicuously marked or otherwise +// designated in writing by the copyright owner as "Not a Contribution." +// +// "Contributor" shall mean Licensor and any individual or Legal Entity +// on behalf of whom a Contribution has been received by Licensor and +// subsequently incorporated within the Work. +// +// 2. Grant of Copyright License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// copyright license to reproduce, prepare Derivative Works of, +// publicly display, publicly perform, sublicense, and distribute the +// Work and such Derivative Works in Source or Object form. +// +// 3. Grant of Patent License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// (except as stated in this section) patent license to make, have made, +// use, offer to sell, sell, import, and otherwise transfer the Work, +// where such license applies only to those patent claims licensable +// by such Contributor that are necessarily infringed by their +// Contribution(s) alone or by combination of their Contribution(s) +// with the Work to which such Contribution(s) was submitted. If You +// institute patent litigation against any entity (including a +// cross-claim or counterclaim in a lawsuit) alleging that the Work +// or a Contribution incorporated within the Work constitutes direct +// or contributory patent infringement, then any patent licenses +// granted to You under this License for that Work shall terminate +// as of the date such litigation is filed. +// +// 4. Redistribution. You may reproduce and distribute copies of the +// Work or Derivative Works thereof in any medium, with or without +// modifications, and in Source or Object form, provided that You +// meet the following conditions: +// +// (a) You must give any other recipients of the Work or +// Derivative Works a copy of this License; and +// +// (b) You must cause any modified files to carry prominent notices +// stating that You changed the files; and +// +// (c) You must retain, in the Source form of any Derivative Works +// that You distribute, all copyright, patent, trademark, and +// attribution notices from the Source form of the Work, +// excluding those notices that do not pertain to any part of +// the Derivative Works; and +// +// (d) If the Work includes a "NOTICE" text file as part of its +// distribution, then any Derivative Works that You distribute must +// include a readable copy of the attribution notices contained +// within such NOTICE file, excluding those notices that do not +// pertain to any part of the Derivative Works, in at least one +// of the following places: within a NOTICE text file distributed +// as part of the Derivative Works; within the Source form or +// documentation, if provided along with the Derivative Works; or, +// within a display generated by the Derivative Works, if and +// wherever such third-party notices normally appear. The contents +// of the NOTICE file are for informational purposes only and +// do not modify the License. You may add Your own attribution +// notices within Derivative Works that You distribute, alongside +// or as an addendum to the NOTICE text from the Work, provided +// that such additional attribution notices cannot be construed +// as modifying the License. +// +// You may add Your own copyright statement to Your modifications and +// may provide additional or different license terms and conditions +// for use, reproduction, or distribution of Your modifications, or +// for any such Derivative Works as a whole, provided Your use, +// reproduction, and distribution of the Work otherwise complies with +// the conditions stated in this License. +// +// 5. Submission of Contributions. Unless You explicitly state otherwise, +// any Contribution intentionally submitted for inclusion in the Work +// by You to the Licensor shall be under the terms and conditions of +// this License, without any additional terms or conditions. +// Notwithstanding the above, nothing herein shall supersede or modify +// the terms of any separate license agreement you may have executed +// with Licensor regarding such Contributions. +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor, +// except as required for reasonable and customary use in describing the +// origin of the Work and reproducing the content of the NOTICE file. +// +// 7. Disclaimer of Warranty. Unless required by applicable law or +// agreed to in writing, Licensor provides the Work (and each +// Contributor provides its Contributions) on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied, including, without limitation, any warranties or conditions +// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +// PARTICULAR PURPOSE. You are solely responsible for determining the +// appropriateness of using or redistributing the Work and assume any +// risks associated with Your exercise of permissions under this License. +// +// 8. Limitation of Liability. In no event and under no legal theory, +// whether in tort (including negligence), contract, or otherwise, +// unless required by applicable law (such as deliberate and grossly +// negligent acts) or agreed to in writing, shall any Contributor be +// liable to You for damages, including any direct, indirect, special, +// incidental, or consequential damages of any character arising as a +// result of this License or out of the use or inability to use the +// Work (including but not limited to damages for loss of goodwill, +// work stoppage, computer failure or malfunction, or any and all +// other commercial damages or losses), even if such Contributor +// has been advised of the possibility of such damages. +// +// 9. Accepting Warranty or Additional Liability. While redistributing +// the Work or Derivative Works thereof, You may choose to offer, +// and charge a fee for, acceptance of support, warranty, indemnity, +// or other liability obligations and/or rights consistent with this +// License. However, in accepting such obligations, You may act only +// on Your own behalf and on Your sole responsibility, not on behalf +// of any other Contributor, and only if You agree to indemnify, +// defend, and hold each Contributor harmless for any liability +// incurred by, or claims asserted against, such Contributor by reason +// of your accepting any such warranty or additional liability. +// +// END OF TERMS AND CONDITIONS +// +// APPENDIX: How to apply the Apache License to your work. +// +// To apply the Apache License to your work, attach the following +// boilerplate notice, with the fields enclosed by brackets "[]" +// replaced with your own identifying information. (Don't include +// the brackets!) The text should be enclosed in the appropriate +// comment syntax for the file format. We also recommend that a +// file or class name and description of purpose be included on the +// same "printed page" as the copyright notice for easier +// identification within third-party archives. +// +// Copyright [yyyy] [name of copyright owner] +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// --- +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// --- +// Minimal Ryu implementation adapted for Ruby JSON gem by Josef Šimánek +// Optimized for pre-extracted mantissa/exponent from JSON parsing +// This is a stripped-down version containing only what's needed for +// converting decimal mantissa+exponent to IEEE 754 double precision. + +#ifndef RYU_H +#define RYU_H + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +// Detect __builtin_clzll availability (for floor_log2) +// Note: MSVC doesn't have __builtin_clzll, so we provide a fallback +#ifdef __clang__ + #if __has_builtin(__builtin_clzll) + #define RYU_HAVE_BUILTIN_CLZLL 1 + #else + #define RYU_HAVE_BUILTIN_CLZLL 0 + #endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define RYU_HAVE_BUILTIN_CLZLL 1 +#else + #define RYU_HAVE_BUILTIN_CLZLL 0 +#endif + +// Count leading zeros (for floor_log2) +static inline uint32_t ryu_leading_zeros64(uint64_t input) +{ +#if RYU_HAVE_BUILTIN_CLZLL + return __builtin_clzll(input); +#else + // Fallback: binary search for the highest set bit + // This works on MSVC and other compilers without __builtin_clzll + if (input == 0) return 64; + uint32_t n = 0; + if (input <= 0x00000000FFFFFFFFULL) { n += 32; input <<= 32; } + if (input <= 0x0000FFFFFFFFFFFFULL) { n += 16; input <<= 16; } + if (input <= 0x00FFFFFFFFFFFFFFULL) { n += 8; input <<= 8; } + if (input <= 0x0FFFFFFFFFFFFFFFULL) { n += 4; input <<= 4; } + if (input <= 0x3FFFFFFFFFFFFFFFULL) { n += 2; input <<= 2; } + if (input <= 0x7FFFFFFFFFFFFFFFULL) { n += 1; } + return n; +#endif +} + +// These tables are generated by PrintDoubleLookupTable. +#define DOUBLE_POW5_INV_BITCOUNT 125 +#define DOUBLE_POW5_BITCOUNT 125 + +#define DOUBLE_POW5_INV_TABLE_SIZE 342 +#define DOUBLE_POW5_TABLE_SIZE 326 + +static const uint64_t DOUBLE_POW5_INV_SPLIT[DOUBLE_POW5_INV_TABLE_SIZE][2] = { + { 1u, 2305843009213693952u }, { 11068046444225730970u, 1844674407370955161u }, + { 5165088340638674453u, 1475739525896764129u }, { 7821419487252849886u, 1180591620717411303u }, + { 8824922364862649494u, 1888946593147858085u }, { 7059937891890119595u, 1511157274518286468u }, + { 13026647942995916322u, 1208925819614629174u }, { 9774590264567735146u, 1934281311383406679u }, + { 11509021026396098440u, 1547425049106725343u }, { 16585914450600699399u, 1237940039285380274u }, + { 15469416676735388068u, 1980704062856608439u }, { 16064882156130220778u, 1584563250285286751u }, + { 9162556910162266299u, 1267650600228229401u }, { 7281393426775805432u, 2028240960365167042u }, + { 16893161185646375315u, 1622592768292133633u }, { 2446482504291369283u, 1298074214633706907u }, + { 7603720821608101175u, 2076918743413931051u }, { 2393627842544570617u, 1661534994731144841u }, + { 16672297533003297786u, 1329227995784915872u }, { 11918280793837635165u, 2126764793255865396u }, + { 5845275820328197809u, 1701411834604692317u }, { 15744267100488289217u, 1361129467683753853u }, + { 3054734472329800808u, 2177807148294006166u }, { 17201182836831481939u, 1742245718635204932u }, + { 6382248639981364905u, 1393796574908163946u }, { 2832900194486363201u, 2230074519853062314u }, + { 5955668970331000884u, 1784059615882449851u }, { 1075186361522890384u, 1427247692705959881u }, + { 12788344622662355584u, 2283596308329535809u }, { 13920024512871794791u, 1826877046663628647u }, + { 3757321980813615186u, 1461501637330902918u }, { 10384555214134712795u, 1169201309864722334u }, + { 5547241898389809503u, 1870722095783555735u }, { 4437793518711847602u, 1496577676626844588u }, + { 10928932444453298728u, 1197262141301475670u }, { 17486291911125277965u, 1915619426082361072u }, + { 6610335899416401726u, 1532495540865888858u }, { 12666966349016942027u, 1225996432692711086u }, + { 12888448528943286597u, 1961594292308337738u }, { 17689456452638449924u, 1569275433846670190u }, + { 14151565162110759939u, 1255420347077336152u }, { 7885109000409574610u, 2008672555323737844u }, + { 9997436015069570011u, 1606938044258990275u }, { 7997948812055656009u, 1285550435407192220u }, + { 12796718099289049614u, 2056880696651507552u }, { 2858676849947419045u, 1645504557321206042u }, + { 13354987924183666206u, 1316403645856964833u }, { 17678631863951955605u, 2106245833371143733u }, + { 3074859046935833515u, 1684996666696914987u }, { 13527933681774397782u, 1347997333357531989u }, + { 10576647446613305481u, 2156795733372051183u }, { 15840015586774465031u, 1725436586697640946u }, + { 8982663654677661702u, 1380349269358112757u }, { 18061610662226169046u, 2208558830972980411u }, + { 10759939715039024913u, 1766847064778384329u }, { 12297300586773130254u, 1413477651822707463u }, + { 15986332124095098083u, 2261564242916331941u }, { 9099716884534168143u, 1809251394333065553u }, + { 14658471137111155161u, 1447401115466452442u }, { 4348079280205103483u, 1157920892373161954u }, + { 14335624477811986218u, 1852673427797059126u }, { 7779150767507678651u, 1482138742237647301u }, + { 2533971799264232598u, 1185710993790117841u }, { 15122401323048503126u, 1897137590064188545u }, + { 12097921058438802501u, 1517710072051350836u }, { 5988988032009131678u, 1214168057641080669u }, + { 16961078480698431330u, 1942668892225729070u }, { 13568862784558745064u, 1554135113780583256u }, + { 7165741412905085728u, 1243308091024466605u }, { 11465186260648137165u, 1989292945639146568u }, + { 16550846638002330379u, 1591434356511317254u }, { 16930026125143774626u, 1273147485209053803u }, + { 4951948911778577463u, 2037035976334486086u }, { 272210314680951647u, 1629628781067588869u }, + { 3907117066486671641u, 1303703024854071095u }, { 6251387306378674625u, 2085924839766513752u }, + { 16069156289328670670u, 1668739871813211001u }, { 9165976216721026213u, 1334991897450568801u }, + { 7286864317269821294u, 2135987035920910082u }, { 16897537898041588005u, 1708789628736728065u }, + { 13518030318433270404u, 1367031702989382452u }, { 6871453250525591353u, 2187250724783011924u }, + { 9186511415162383406u, 1749800579826409539u }, { 11038557946871817048u, 1399840463861127631u }, + { 10282995085511086630u, 2239744742177804210u }, { 8226396068408869304u, 1791795793742243368u }, + { 13959814484210916090u, 1433436634993794694u }, { 11267656730511734774u, 2293498615990071511u }, + { 5324776569667477496u, 1834798892792057209u }, { 7949170070475892320u, 1467839114233645767u }, + { 17427382500606444826u, 1174271291386916613u }, { 5747719112518849781u, 1878834066219066582u }, + { 15666221734240810795u, 1503067252975253265u }, { 12532977387392648636u, 1202453802380202612u }, + { 5295368560860596524u, 1923926083808324180u }, { 4236294848688477220u, 1539140867046659344u }, + { 7078384693692692099u, 1231312693637327475u }, { 11325415509908307358u, 1970100309819723960u }, + { 9060332407926645887u, 1576080247855779168u }, { 14626963555825137356u, 1260864198284623334u }, + { 12335095245094488799u, 2017382717255397335u }, { 9868076196075591040u, 1613906173804317868u }, + { 15273158586344293478u, 1291124939043454294u }, { 13369007293925138595u, 2065799902469526871u }, + { 7005857020398200553u, 1652639921975621497u }, { 16672732060544291412u, 1322111937580497197u }, + { 11918976037903224966u, 2115379100128795516u }, { 5845832015580669650u, 1692303280103036413u }, + { 12055363241948356366u, 1353842624082429130u }, { 841837113407818570u, 2166148198531886609u }, + { 4362818505468165179u, 1732918558825509287u }, { 14558301248600263113u, 1386334847060407429u }, + { 12225235553534690011u, 2218135755296651887u }, { 2401490813343931363u, 1774508604237321510u }, + { 1921192650675145090u, 1419606883389857208u }, { 17831303500047873437u, 2271371013423771532u }, + { 6886345170554478103u, 1817096810739017226u }, { 1819727321701672159u, 1453677448591213781u }, + { 16213177116328979020u, 1162941958872971024u }, { 14873036941900635463u, 1860707134196753639u }, + { 15587778368262418694u, 1488565707357402911u }, { 8780873879868024632u, 1190852565885922329u }, + { 2981351763563108441u, 1905364105417475727u }, { 13453127855076217722u, 1524291284333980581u }, + { 7073153469319063855u, 1219433027467184465u }, { 11317045550910502167u, 1951092843947495144u }, + { 12742985255470312057u, 1560874275157996115u }, { 10194388204376249646u, 1248699420126396892u }, + { 1553625868034358140u, 1997919072202235028u }, { 8621598323911307159u, 1598335257761788022u }, + { 17965325103354776697u, 1278668206209430417u }, { 13987124906400001422u, 2045869129935088668u }, + { 121653480894270168u, 1636695303948070935u }, { 97322784715416134u, 1309356243158456748u }, + { 14913111714512307107u, 2094969989053530796u }, { 8241140556867935363u, 1675975991242824637u }, + { 17660958889720079260u, 1340780792994259709u }, { 17189487779326395846u, 2145249268790815535u }, + { 13751590223461116677u, 1716199415032652428u }, { 18379969808252713988u, 1372959532026121942u }, + { 14650556434236701088u, 2196735251241795108u }, { 652398703163629901u, 1757388200993436087u }, + { 11589965406756634890u, 1405910560794748869u }, { 7475898206584884855u, 2249456897271598191u }, + { 2291369750525997561u, 1799565517817278553u }, { 9211793429904618695u, 1439652414253822842u }, + { 18428218302589300235u, 2303443862806116547u }, { 7363877012587619542u, 1842755090244893238u }, + { 13269799239553916280u, 1474204072195914590u }, { 10615839391643133024u, 1179363257756731672u }, + { 2227947767661371545u, 1886981212410770676u }, { 16539753473096738529u, 1509584969928616540u }, + { 13231802778477390823u, 1207667975942893232u }, { 6413489186596184024u, 1932268761508629172u }, + { 16198837793502678189u, 1545815009206903337u }, { 5580372605318321905u, 1236652007365522670u }, + { 8928596168509315048u, 1978643211784836272u }, { 18210923379033183008u, 1582914569427869017u }, + { 7190041073742725760u, 1266331655542295214u }, { 436019273762630246u, 2026130648867672343u }, + { 7727513048493924843u, 1620904519094137874u }, { 9871359253537050198u, 1296723615275310299u }, + { 4726128361433549347u, 2074757784440496479u }, { 7470251503888749801u, 1659806227552397183u }, + { 13354898832594820487u, 1327844982041917746u }, { 13989140502667892133u, 2124551971267068394u }, + { 14880661216876224029u, 1699641577013654715u }, { 11904528973500979224u, 1359713261610923772u }, + { 4289851098633925465u, 2175541218577478036u }, { 18189276137874781665u, 1740432974861982428u }, + { 3483374466074094362u, 1392346379889585943u }, { 1884050330976640656u, 2227754207823337509u }, + { 5196589079523222848u, 1782203366258670007u }, { 15225317707844309248u, 1425762693006936005u }, + { 5913764258841343181u, 2281220308811097609u }, { 8420360221814984868u, 1824976247048878087u }, + { 17804334621677718864u, 1459980997639102469u }, { 17932816512084085415u, 1167984798111281975u }, + { 10245762345624985047u, 1868775676978051161u }, { 4507261061758077715u, 1495020541582440929u }, + { 7295157664148372495u, 1196016433265952743u }, { 7982903447895485668u, 1913626293225524389u }, + { 10075671573058298858u, 1530901034580419511u }, { 4371188443704728763u, 1224720827664335609u }, + { 14372599139411386667u, 1959553324262936974u }, { 15187428126271019657u, 1567642659410349579u }, + { 15839291315758726049u, 1254114127528279663u }, { 3206773216762499739u, 2006582604045247462u }, + { 13633465017635730761u, 1605266083236197969u }, { 14596120828850494932u, 1284212866588958375u }, + { 4907049252451240275u, 2054740586542333401u }, { 236290587219081897u, 1643792469233866721u }, + { 14946427728742906810u, 1315033975387093376u }, { 16535586736504830250u, 2104054360619349402u }, + { 5849771759720043554u, 1683243488495479522u }, { 15747863852001765813u, 1346594790796383617u }, + { 10439186904235184007u, 2154551665274213788u }, { 15730047152871967852u, 1723641332219371030u }, + { 12584037722297574282u, 1378913065775496824u }, { 9066413911450387881u, 2206260905240794919u }, + { 10942479943902220628u, 1765008724192635935u }, { 8753983955121776503u, 1412006979354108748u }, + { 10317025513452932081u, 2259211166966573997u }, { 874922781278525018u, 1807368933573259198u }, + { 8078635854506640661u, 1445895146858607358u }, { 13841606313089133175u, 1156716117486885886u }, + { 14767872471458792434u, 1850745787979017418u }, { 746251532941302978u, 1480596630383213935u }, + { 597001226353042382u, 1184477304306571148u }, { 15712597221132509104u, 1895163686890513836u }, + { 8880728962164096960u, 1516130949512411069u }, { 10793931984473187891u, 1212904759609928855u }, + { 17270291175157100626u, 1940647615375886168u }, { 2748186495899949531u, 1552518092300708935u }, + { 2198549196719959625u, 1242014473840567148u }, { 18275073973719576693u, 1987223158144907436u }, + { 10930710364233751031u, 1589778526515925949u }, { 12433917106128911148u, 1271822821212740759u }, + { 8826220925580526867u, 2034916513940385215u }, { 7060976740464421494u, 1627933211152308172u }, + { 16716827836597268165u, 1302346568921846537u }, { 11989529279587987770u, 2083754510274954460u }, + { 9591623423670390216u, 1667003608219963568u }, { 15051996368420132820u, 1333602886575970854u }, + { 13015147745246481542u, 2133764618521553367u }, { 3033420566713364587u, 1707011694817242694u }, + { 6116085268112601993u, 1365609355853794155u }, { 9785736428980163188u, 2184974969366070648u }, + { 15207286772667951197u, 1747979975492856518u }, { 1097782973908629988u, 1398383980394285215u }, + { 1756452758253807981u, 2237414368630856344u }, { 5094511021344956708u, 1789931494904685075u }, + { 4075608817075965366u, 1431945195923748060u }, { 6520974107321544586u, 2291112313477996896u }, + { 1527430471115325346u, 1832889850782397517u }, { 12289990821117991246u, 1466311880625918013u }, + { 17210690286378213644u, 1173049504500734410u }, { 9090360384495590213u, 1876879207201175057u }, + { 18340334751822203140u, 1501503365760940045u }, { 14672267801457762512u, 1201202692608752036u }, + { 16096930852848599373u, 1921924308174003258u }, { 1809498238053148529u, 1537539446539202607u }, + { 12515645034668249793u, 1230031557231362085u }, { 1578287981759648052u, 1968050491570179337u }, + { 12330676829633449412u, 1574440393256143469u }, { 13553890278448669853u, 1259552314604914775u }, + { 3239480371808320148u, 2015283703367863641u }, { 17348979556414297411u, 1612226962694290912u }, + { 6500486015647617283u, 1289781570155432730u }, { 10400777625036187652u, 2063650512248692368u }, + { 15699319729512770768u, 1650920409798953894u }, { 16248804598352126938u, 1320736327839163115u }, + { 7551343283653851484u, 2113178124542660985u }, { 6041074626923081187u, 1690542499634128788u }, + { 12211557331022285596u, 1352433999707303030u }, { 1091747655926105338u, 2163894399531684849u }, + { 4562746939482794594u, 1731115519625347879u }, { 7339546366328145998u, 1384892415700278303u }, + { 8053925371383123274u, 2215827865120445285u }, { 6443140297106498619u, 1772662292096356228u }, + { 12533209867169019542u, 1418129833677084982u }, { 5295740528502789974u, 2269007733883335972u }, + { 15304638867027962949u, 1815206187106668777u }, { 4865013464138549713u, 1452164949685335022u }, + { 14960057215536570740u, 1161731959748268017u }, { 9178696285890871890u, 1858771135597228828u }, + { 14721654658196518159u, 1487016908477783062u }, { 4398626097073393881u, 1189613526782226450u }, + { 7037801755317430209u, 1903381642851562320u }, { 5630241404253944167u, 1522705314281249856u }, + { 814844308661245011u, 1218164251424999885u }, { 1303750893857992017u, 1949062802279999816u }, + { 15800395974054034906u, 1559250241823999852u }, { 5261619149759407279u, 1247400193459199882u }, + { 12107939454356961969u, 1995840309534719811u }, { 5997002748743659252u, 1596672247627775849u }, + { 8486951013736837725u, 1277337798102220679u }, { 2511075177753209390u, 2043740476963553087u }, + { 13076906586428298482u, 1634992381570842469u }, { 14150874083884549109u, 1307993905256673975u }, + { 4194654460505726958u, 2092790248410678361u }, { 18113118827372222859u, 1674232198728542688u }, + { 3422448617672047318u, 1339385758982834151u }, { 16543964232501006678u, 2143017214372534641u }, + { 9545822571258895019u, 1714413771498027713u }, { 15015355686490936662u, 1371531017198422170u }, + { 5577825024675947042u, 2194449627517475473u }, { 11840957649224578280u, 1755559702013980378u }, + { 16851463748863483271u, 1404447761611184302u }, { 12204946739213931940u, 2247116418577894884u }, + { 13453306206113055875u, 1797693134862315907u }, { 3383947335406624054u, 1438154507889852726u }, + { 16482362180876329456u, 2301047212623764361u }, { 9496540929959153242u, 1840837770099011489u }, + { 11286581558709232917u, 1472670216079209191u }, { 5339916432225476010u, 1178136172863367353u }, + { 4854517476818851293u, 1885017876581387765u }, { 3883613981455081034u, 1508014301265110212u }, + { 14174937629389795797u, 1206411441012088169u }, { 11611853762797942306u, 1930258305619341071u }, + { 5600134195496443521u, 1544206644495472857u }, { 15548153800622885787u, 1235365315596378285u }, + { 6430302007287065643u, 1976584504954205257u }, { 16212288050055383484u, 1581267603963364205u }, + { 12969830440044306787u, 1265014083170691364u }, { 9683682259845159889u, 2024022533073106183u }, + { 15125643437359948558u, 1619218026458484946u }, { 8411165935146048523u, 1295374421166787957u }, + { 17147214310975587960u, 2072599073866860731u }, { 10028422634038560045u, 1658079259093488585u }, + { 8022738107230848036u, 1326463407274790868u }, { 9147032156827446534u, 2122341451639665389u }, + { 11006974540203867551u, 1697873161311732311u }, { 5116230817421183718u, 1358298529049385849u }, + { 15564666937357714594u, 2173277646479017358u }, { 1383687105660440706u, 1738622117183213887u }, + { 12174996128754083534u, 1390897693746571109u }, { 8411947361780802685u, 2225436309994513775u }, + { 6729557889424642148u, 1780349047995611020u }, { 5383646311539713719u, 1424279238396488816u }, + { 1235136468979721303u, 2278846781434382106u }, { 15745504434151418335u, 1823077425147505684u }, + { 16285752362063044992u, 1458461940118004547u }, { 5649904260166615347u, 1166769552094403638u }, + { 5350498001524674232u, 1866831283351045821u }, { 591049586477829062u, 1493465026680836657u }, + { 11540886113407994219u, 1194772021344669325u }, { 18673707743239135u, 1911635234151470921u }, + { 14772334225162232601u, 1529308187321176736u }, { 8128518565387875758u, 1223446549856941389u }, + { 1937583260394870242u, 1957514479771106223u }, { 8928764237799716840u, 1566011583816884978u }, + { 14521709019723594119u, 1252809267053507982u }, { 8477339172590109297u, 2004494827285612772u }, + { 17849917782297818407u, 1603595861828490217u }, { 6901236596354434079u, 1282876689462792174u }, + { 18420676183650915173u, 2052602703140467478u }, { 3668494502695001169u, 1642082162512373983u }, + { 10313493231639821582u, 1313665730009899186u }, { 9122891541139893884u, 2101865168015838698u }, + { 14677010862395735754u, 1681492134412670958u }, { 673562245690857633u, 1345193707530136767u } +}; + +static const uint64_t DOUBLE_POW5_SPLIT[DOUBLE_POW5_TABLE_SIZE][2] = { + { 0u, 1152921504606846976u }, { 0u, 1441151880758558720u }, + { 0u, 1801439850948198400u }, { 0u, 2251799813685248000u }, + { 0u, 1407374883553280000u }, { 0u, 1759218604441600000u }, + { 0u, 2199023255552000000u }, { 0u, 1374389534720000000u }, + { 0u, 1717986918400000000u }, { 0u, 2147483648000000000u }, + { 0u, 1342177280000000000u }, { 0u, 1677721600000000000u }, + { 0u, 2097152000000000000u }, { 0u, 1310720000000000000u }, + { 0u, 1638400000000000000u }, { 0u, 2048000000000000000u }, + { 0u, 1280000000000000000u }, { 0u, 1600000000000000000u }, + { 0u, 2000000000000000000u }, { 0u, 1250000000000000000u }, + { 0u, 1562500000000000000u }, { 0u, 1953125000000000000u }, + { 0u, 1220703125000000000u }, { 0u, 1525878906250000000u }, + { 0u, 1907348632812500000u }, { 0u, 1192092895507812500u }, + { 0u, 1490116119384765625u }, { 4611686018427387904u, 1862645149230957031u }, + { 9799832789158199296u, 1164153218269348144u }, { 12249790986447749120u, 1455191522836685180u }, + { 15312238733059686400u, 1818989403545856475u }, { 14528612397897220096u, 2273736754432320594u }, + { 13692068767113150464u, 1421085471520200371u }, { 12503399940464050176u, 1776356839400250464u }, + { 15629249925580062720u, 2220446049250313080u }, { 9768281203487539200u, 1387778780781445675u }, + { 7598665485932036096u, 1734723475976807094u }, { 274959820560269312u, 2168404344971008868u }, + { 9395221924704944128u, 1355252715606880542u }, { 2520655369026404352u, 1694065894508600678u }, + { 12374191248137781248u, 2117582368135750847u }, { 14651398557727195136u, 1323488980084844279u }, + { 13702562178731606016u, 1654361225106055349u }, { 3293144668132343808u, 2067951531382569187u }, + { 18199116482078572544u, 1292469707114105741u }, { 8913837547316051968u, 1615587133892632177u }, + { 15753982952572452864u, 2019483917365790221u }, { 12152082354571476992u, 1262177448353618888u }, + { 15190102943214346240u, 1577721810442023610u }, { 9764256642163156992u, 1972152263052529513u }, + { 17631875447420442880u, 1232595164407830945u }, { 8204786253993389888u, 1540743955509788682u }, + { 1032610780636961552u, 1925929944387235853u }, { 2951224747111794922u, 1203706215242022408u }, + { 3689030933889743652u, 1504632769052528010u }, { 13834660704216955373u, 1880790961315660012u }, + { 17870034976990372916u, 1175494350822287507u }, { 17725857702810578241u, 1469367938527859384u }, + { 3710578054803671186u, 1836709923159824231u }, { 26536550077201078u, 2295887403949780289u }, + { 11545800389866720434u, 1434929627468612680u }, { 14432250487333400542u, 1793662034335765850u }, + { 8816941072311974870u, 2242077542919707313u }, { 17039803216263454053u, 1401298464324817070u }, + { 12076381983474541759u, 1751623080406021338u }, { 5872105442488401391u, 2189528850507526673u }, + { 15199280947623720629u, 1368455531567204170u }, { 9775729147674874978u, 1710569414459005213u }, + { 16831347453020981627u, 2138211768073756516u }, { 1296220121283337709u, 1336382355046097823u }, + { 15455333206886335848u, 1670477943807622278u }, { 10095794471753144002u, 2088097429759527848u }, + { 6309871544845715001u, 1305060893599704905u }, { 12499025449484531656u, 1631326116999631131u }, + { 11012095793428276666u, 2039157646249538914u }, { 11494245889320060820u, 1274473528905961821u }, + { 532749306367912313u, 1593091911132452277u }, { 5277622651387278295u, 1991364888915565346u }, + { 7910200175544436838u, 1244603055572228341u }, { 14499436237857933952u, 1555753819465285426u }, + { 8900923260467641632u, 1944692274331606783u }, { 12480606065433357876u, 1215432671457254239u }, + { 10989071563364309441u, 1519290839321567799u }, { 9124653435777998898u, 1899113549151959749u }, + { 8008751406574943263u, 1186945968219974843u }, { 5399253239791291175u, 1483682460274968554u }, + { 15972438586593889776u, 1854603075343710692u }, { 759402079766405302u, 1159126922089819183u }, + { 14784310654990170340u, 1448908652612273978u }, { 9257016281882937117u, 1811135815765342473u }, + { 16182956370781059300u, 2263919769706678091u }, { 7808504722524468110u, 1414949856066673807u }, + { 5148944884728197234u, 1768687320083342259u }, { 1824495087482858639u, 2210859150104177824u }, + { 1140309429676786649u, 1381786968815111140u }, { 1425386787095983311u, 1727233711018888925u }, + { 6393419502297367043u, 2159042138773611156u }, { 13219259225790630210u, 1349401336733506972u }, + { 16524074032238287762u, 1686751670916883715u }, { 16043406521870471799u, 2108439588646104644u }, + { 803757039314269066u, 1317774742903815403u }, { 14839754354425000045u, 1647218428629769253u }, + { 4714634887749086344u, 2059023035787211567u }, { 9864175832484260821u, 1286889397367007229u }, + { 16941905809032713930u, 1608611746708759036u }, { 2730638187581340797u, 2010764683385948796u }, + { 10930020904093113806u, 1256727927116217997u }, { 18274212148543780162u, 1570909908895272496u }, + { 4396021111970173586u, 1963637386119090621u }, { 5053356204195052443u, 1227273366324431638u }, + { 15540067292098591362u, 1534091707905539547u }, { 14813398096695851299u, 1917614634881924434u }, + { 13870059828862294966u, 1198509146801202771u }, { 12725888767650480803u, 1498136433501503464u }, + { 15907360959563101004u, 1872670541876879330u }, { 14553786618154326031u, 1170419088673049581u }, + { 4357175217410743827u, 1463023860841311977u }, { 10058155040190817688u, 1828779826051639971u }, + { 7961007781811134206u, 2285974782564549964u }, { 14199001900486734687u, 1428734239102843727u }, + { 13137066357181030455u, 1785917798878554659u }, { 11809646928048900164u, 2232397248598193324u }, + { 16604401366885338411u, 1395248280373870827u }, { 16143815690179285109u, 1744060350467338534u }, + { 10956397575869330579u, 2180075438084173168u }, { 6847748484918331612u, 1362547148802608230u }, + { 17783057643002690323u, 1703183936003260287u }, { 17617136035325974999u, 2128979920004075359u }, + { 17928239049719816230u, 1330612450002547099u }, { 17798612793722382384u, 1663265562503183874u }, + { 13024893955298202172u, 2079081953128979843u }, { 5834715712847682405u, 1299426220705612402u }, + { 16516766677914378815u, 1624282775882015502u }, { 11422586310538197711u, 2030353469852519378u }, + { 11750802462513761473u, 1268970918657824611u }, { 10076817059714813937u, 1586213648322280764u }, + { 12596021324643517422u, 1982767060402850955u }, { 5566670318688504437u, 1239229412751781847u }, + { 2346651879933242642u, 1549036765939727309u }, { 7545000868343941206u, 1936295957424659136u }, + { 4715625542714963254u, 1210184973390411960u }, { 5894531928393704067u, 1512731216738014950u }, + { 16591536947346905892u, 1890914020922518687u }, { 17287239619732898039u, 1181821263076574179u }, + { 16997363506238734644u, 1477276578845717724u }, { 2799960309088866689u, 1846595723557147156u }, + { 10973347230035317489u, 1154122327223216972u }, { 13716684037544146861u, 1442652909029021215u }, + { 12534169028502795672u, 1803316136286276519u }, { 11056025267201106687u, 2254145170357845649u }, + { 18439230838069161439u, 1408840731473653530u }, { 13825666510731675991u, 1761050914342066913u }, + { 3447025083132431277u, 2201313642927583642u }, { 6766076695385157452u, 1375821026829739776u }, + { 8457595869231446815u, 1719776283537174720u }, { 10571994836539308519u, 2149720354421468400u }, + { 6607496772837067824u, 1343575221513417750u }, { 17482743002901110588u, 1679469026891772187u }, + { 17241742735199000331u, 2099336283614715234u }, { 15387775227926763111u, 1312085177259197021u }, + { 5399660979626290177u, 1640106471573996277u }, { 11361262242960250625u, 2050133089467495346u }, + { 11712474920277544544u, 1281333180917184591u }, { 10028907631919542777u, 1601666476146480739u }, + { 7924448521472040567u, 2002083095183100924u }, { 14176152362774801162u, 1251301934489438077u }, + { 3885132398186337741u, 1564127418111797597u }, { 9468101516160310080u, 1955159272639746996u }, + { 15140935484454969608u, 1221974545399841872u }, { 479425281859160394u, 1527468181749802341u }, + { 5210967620751338397u, 1909335227187252926u }, { 17091912818251750210u, 1193334516992033078u }, + { 12141518985959911954u, 1491668146240041348u }, { 15176898732449889943u, 1864585182800051685u }, + { 11791404716994875166u, 1165365739250032303u }, { 10127569877816206054u, 1456707174062540379u }, + { 8047776328842869663u, 1820883967578175474u }, { 836348374198811271u, 2276104959472719343u }, + { 7440246761515338900u, 1422565599670449589u }, { 13911994470321561530u, 1778206999588061986u }, + { 8166621051047176104u, 2222758749485077483u }, { 2798295147690791113u, 1389224218428173427u }, + { 17332926989895652603u, 1736530273035216783u }, { 17054472718942177850u, 2170662841294020979u }, + { 8353202440125167204u, 1356664275808763112u }, { 10441503050156459005u, 1695830344760953890u }, + { 3828506775840797949u, 2119787930951192363u }, { 86973725686804766u, 1324867456844495227u }, + { 13943775212390669669u, 1656084321055619033u }, { 3594660960206173375u, 2070105401319523792u }, + { 2246663100128858359u, 1293815875824702370u }, { 12031700912015848757u, 1617269844780877962u }, + { 5816254103165035138u, 2021587305976097453u }, { 5941001823691840913u, 1263492066235060908u }, + { 7426252279614801142u, 1579365082793826135u }, { 4671129331091113523u, 1974206353492282669u }, + { 5225298841145639904u, 1233878970932676668u }, { 6531623551432049880u, 1542348713665845835u }, + { 3552843420862674446u, 1927935892082307294u }, { 16055585193321335241u, 1204959932551442058u }, + { 10846109454796893243u, 1506199915689302573u }, { 18169322836923504458u, 1882749894611628216u }, + { 11355826773077190286u, 1176718684132267635u }, { 9583097447919099954u, 1470898355165334544u }, + { 11978871809898874942u, 1838622943956668180u }, { 14973589762373593678u, 2298278679945835225u }, + { 2440964573842414192u, 1436424174966147016u }, { 3051205717303017741u, 1795530218707683770u }, + { 13037379183483547984u, 2244412773384604712u }, { 8148361989677217490u, 1402757983365377945u }, + { 14797138505523909766u, 1753447479206722431u }, { 13884737113477499304u, 2191809349008403039u }, + { 15595489723564518921u, 1369880843130251899u }, { 14882676136028260747u, 1712351053912814874u }, + { 9379973133180550126u, 2140438817391018593u }, { 17391698254306313589u, 1337774260869386620u }, + { 3292878744173340370u, 1672217826086733276u }, { 4116098430216675462u, 2090272282608416595u }, + { 266718509671728212u, 1306420176630260372u }, { 333398137089660265u, 1633025220787825465u }, + { 5028433689789463235u, 2041281525984781831u }, { 10060300083759496378u, 1275800953740488644u }, + { 12575375104699370472u, 1594751192175610805u }, { 1884160825592049379u, 1993438990219513507u }, + { 17318501580490888525u, 1245899368887195941u }, { 7813068920331446945u, 1557374211108994927u }, + { 5154650131986920777u, 1946717763886243659u }, { 915813323278131534u, 1216698602428902287u }, + { 14979824709379828129u, 1520873253036127858u }, { 9501408849870009354u, 1901091566295159823u }, + { 12855909558809837702u, 1188182228934474889u }, { 2234828893230133415u, 1485227786168093612u }, + { 2793536116537666769u, 1856534732710117015u }, { 8663489100477123587u, 1160334207943823134u }, + { 1605989338741628675u, 1450417759929778918u }, { 11230858710281811652u, 1813022199912223647u }, + { 9426887369424876662u, 2266277749890279559u }, { 12809333633531629769u, 1416423593681424724u }, + { 16011667041914537212u, 1770529492101780905u }, { 6179525747111007803u, 2213161865127226132u }, + { 13085575628799155685u, 1383226165704516332u }, { 16356969535998944606u, 1729032707130645415u }, + { 15834525901571292854u, 2161290883913306769u }, { 2979049660840976177u, 1350806802445816731u }, + { 17558870131333383934u, 1688508503057270913u }, { 8113529608884566205u, 2110635628821588642u }, + { 9682642023980241782u, 1319147268013492901u }, { 16714988548402690132u, 1648934085016866126u }, + { 11670363648648586857u, 2061167606271082658u }, { 11905663298832754689u, 1288229753919426661u }, + { 1047021068258779650u, 1610287192399283327u }, { 15143834390605638274u, 2012858990499104158u }, + { 4853210475701136017u, 1258036869061940099u }, { 1454827076199032118u, 1572546086327425124u }, + { 1818533845248790147u, 1965682607909281405u }, { 3442426662494187794u, 1228551629943300878u }, + { 13526405364972510550u, 1535689537429126097u }, { 3072948650933474476u, 1919611921786407622u }, + { 15755650962115585259u, 1199757451116504763u }, { 15082877684217093670u, 1499696813895630954u }, + { 9630225068416591280u, 1874621017369538693u }, { 8324733676974063502u, 1171638135855961683u }, + { 5794231077790191473u, 1464547669819952104u }, { 7242788847237739342u, 1830684587274940130u }, + { 18276858095901949986u, 2288355734093675162u }, { 16034722328366106645u, 1430222333808546976u }, + { 1596658836748081690u, 1787777917260683721u }, { 6607509564362490017u, 2234722396575854651u }, + { 1823850468512862308u, 1396701497859909157u }, { 6891499104068465790u, 1745876872324886446u }, + { 17837745916940358045u, 2182346090406108057u }, { 4231062170446641922u, 1363966306503817536u }, + { 5288827713058302403u, 1704957883129771920u }, { 6611034641322878003u, 2131197353912214900u }, + { 13355268687681574560u, 1331998346195134312u }, { 16694085859601968200u, 1664997932743917890u }, + { 11644235287647684442u, 2081247415929897363u }, { 4971804045566108824u, 1300779634956185852u }, + { 6214755056957636030u, 1625974543695232315u }, { 3156757802769657134u, 2032468179619040394u }, + { 6584659645158423613u, 1270292612261900246u }, { 17454196593302805324u, 1587865765327375307u }, + { 17206059723201118751u, 1984832206659219134u }, { 6142101308573311315u, 1240520129162011959u }, + { 3065940617289251240u, 1550650161452514949u }, { 8444111790038951954u, 1938312701815643686u }, + { 665883850346957067u, 1211445438634777304u }, { 832354812933696334u, 1514306798293471630u }, + { 10263815553021896226u, 1892883497866839537u }, { 17944099766707154901u, 1183052186166774710u }, + { 13206752671529167818u, 1478815232708468388u }, { 16508440839411459773u, 1848519040885585485u }, + { 12623618533845856310u, 1155324400553490928u }, { 15779523167307320387u, 1444155500691863660u }, + { 1277659885424598868u, 1805194375864829576u }, { 1597074856780748586u, 2256492969831036970u }, + { 5609857803915355770u, 1410308106144398106u }, { 16235694291748970521u, 1762885132680497632u }, + { 1847873790976661535u, 2203606415850622041u }, { 12684136165428883219u, 1377254009906638775u }, + { 11243484188358716120u, 1721567512383298469u }, { 219297180166231438u, 2151959390479123087u }, + { 7054589765244976505u, 1344974619049451929u }, { 13429923224983608535u, 1681218273811814911u }, + { 12175718012802122765u, 2101522842264768639u }, { 14527352785642408584u, 1313451776415480399u }, + { 13547504963625622826u, 1641814720519350499u }, { 12322695186104640628u, 2052268400649188124u }, + { 16925056528170176201u, 1282667750405742577u }, { 7321262604930556539u, 1603334688007178222u }, + { 18374950293017971482u, 2004168360008972777u }, { 4566814905495150320u, 1252605225005607986u }, + { 14931890668723713708u, 1565756531257009982u }, { 9441491299049866327u, 1957195664071262478u }, + { 1289246043478778550u, 1223247290044539049u }, { 6223243572775861092u, 1529059112555673811u }, + { 3167368447542438461u, 1911323890694592264u }, { 1979605279714024038u, 1194577431684120165u }, + { 7086192618069917952u, 1493221789605150206u }, { 18081112809442173248u, 1866527237006437757u }, + { 13606538515115052232u, 1166579523129023598u }, { 7784801107039039482u, 1458224403911279498u }, + { 507629346944023544u, 1822780504889099373u }, { 5246222702107417334u, 2278475631111374216u }, + { 3278889188817135834u, 1424047269444608885u }, { 8710297504448807696u, 1780059086805761106u } +}; + +// IEEE 754 double precision constants +#define DOUBLE_MANTISSA_BITS 52 +#define DOUBLE_EXPONENT_BITS 11 +#define DOUBLE_EXPONENT_BIAS 1023 + +// Helper: floor(log2(value)) using ryu_leading_zeros64 +static inline uint32_t floor_log2(const uint64_t value) { + return 63 - ryu_leading_zeros64(value); +} + +// Helper: log2(5^e) approximation +static inline int32_t log2pow5(const int32_t e) { + return (int32_t) ((((uint32_t) e) * 1217359) >> 19); +} + +// Helper: ceil(log2(5^e)) +static inline int32_t ceil_log2pow5(const int32_t e) { + return log2pow5(e) + 1; +} + +// Helper: max of two int32 +static inline int32_t max32(int32_t a, int32_t b) { + return a < b ? b : a; +} + +// Helper: convert uint64 bits to double +static inline double int64Bits2Double(uint64_t bits) { + double f; + memcpy(&f, &bits, sizeof(double)); + return f; +} + +// Check if value is multiple of 2^p +static inline bool multipleOfPowerOf2(const uint64_t value, const uint32_t p) { + return (value & ((1ull << p) - 1)) == 0; +} + +// Count how many times value is divisible by 5 +// Uses modular inverse to avoid expensive division +static inline uint32_t pow5Factor(uint64_t value) { + const uint64_t m_inv_5 = 14757395258967641293u; // 5 * m_inv_5 = 1 (mod 2^64) + const uint64_t n_div_5 = 3689348814741910323u; // 2^64 / 5 + uint32_t count = 0; + for (;;) { + value *= m_inv_5; + if (value > n_div_5) + break; + ++count; + } + return count; +} + +// Check if value is multiple of 5^p +// Optimized: uses modular inverse instead of division +static inline bool multipleOfPowerOf5(const uint64_t value, const uint32_t p) { + return pow5Factor(value) >= p; +} + +// 128-bit multiplication with shift +// This is the core operation for converting decimal to binary +#if defined(__SIZEOF_INT128__) +// Use native 128-bit integers if available (GCC/Clang) +static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) { + const unsigned __int128 b0 = ((unsigned __int128) m) * mul[0]; + const unsigned __int128 b2 = ((unsigned __int128) m) * mul[1]; + return (uint64_t) (((b0 >> 64) + b2) >> (j - 64)); +} +#else +// Fallback for systems without 128-bit integers +static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) { + const uint32_t aLo = (uint32_t)a; + const uint32_t aHi = (uint32_t)(a >> 32); + const uint32_t bLo = (uint32_t)b; + const uint32_t bHi = (uint32_t)(b >> 32); + + const uint64_t b00 = (uint64_t)aLo * bLo; + const uint64_t b01 = (uint64_t)aLo * bHi; + const uint64_t b10 = (uint64_t)aHi * bLo; + const uint64_t b11 = (uint64_t)aHi * bHi; + + const uint32_t b00Lo = (uint32_t)b00; + const uint32_t b00Hi = (uint32_t)(b00 >> 32); + + const uint64_t mid1 = b10 + b00Hi; + const uint32_t mid1Lo = (uint32_t)(mid1); + const uint32_t mid1Hi = (uint32_t)(mid1 >> 32); + + const uint64_t mid2 = b01 + mid1Lo; + const uint32_t mid2Lo = (uint32_t)(mid2); + const uint32_t mid2Hi = (uint32_t)(mid2 >> 32); + + const uint64_t pHi = b11 + mid1Hi + mid2Hi; + const uint64_t pLo = ((uint64_t)mid2Lo << 32) | b00Lo; + + *productHi = pHi; + return pLo; +} + +static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) { + return (hi << (64 - dist)) | (lo >> dist); +} + +static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) { + uint64_t high1; + const uint64_t low1 = umul128(m, mul[1], &high1); + uint64_t high0; + umul128(m, mul[0], &high0); + const uint64_t sum = high0 + low1; + if (sum < high0) { + ++high1; + } + return shiftright128(sum, high1, j - 64); +} +#endif + +// Main conversion function: decimal mantissa+exponent to IEEE 754 double +// Optimized for JSON parsing with fast paths for edge cases +static inline double ryu_s2d_from_parts(uint64_t m10, int m10digits, int32_t e10, bool signedM) { + // Fast path: handle zero explicitly (e.g., "0.0", "0e0") + if (m10 == 0) { + return int64Bits2Double(((uint64_t) signedM) << 63); + } + + // Fast path: handle overflow/underflow early + if (m10digits + e10 <= -324) { + // Underflow to zero + return int64Bits2Double(((uint64_t) signedM) << 63); + } + + if (m10digits + e10 >= 310) { + // Overflow to infinity + return int64Bits2Double((((uint64_t) signedM) << 63) | 0x7ff0000000000000ULL); + } + + // Convert decimal to binary: m10 * 10^e10 = m2 * 2^e2 + int32_t e2; + uint64_t m2; + bool trailingZeros; + + if (e10 >= 0) { + // Positive exponent: multiply by 5^e10 and adjust binary exponent + e2 = floor_log2(m10) + e10 + log2pow5(e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 - ceil_log2pow5(e10) + DOUBLE_POW5_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_SPLIT[e10], j); + trailingZeros = e2 < e10 || (e2 - e10 < 64 && multipleOfPowerOf2(m10, e2 - e10)); + } else { + // Negative exponent: divide by 5^(-e10) + e2 = floor_log2(m10) + e10 - ceil_log2pow5(-e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 + ceil_log2pow5(-e10) - 1 + DOUBLE_POW5_INV_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_INV_SPLIT[-e10], j); + trailingZeros = multipleOfPowerOf5(m10, -e10); + } + + // Compute IEEE 754 exponent + uint32_t ieee_e2 = (uint32_t) max32(0, e2 + DOUBLE_EXPONENT_BIAS + floor_log2(m2)); + + if (ieee_e2 > 0x7fe) { + // Overflow to infinity + return int64Bits2Double((((uint64_t) signedM) << 63) | 0x7ff0000000000000ULL); + } + + // Compute shift amount for rounding + int32_t shift = (ieee_e2 == 0 ? 1 : ieee_e2) - e2 - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; + + // IEEE 754 round-to-even (banker's rounding) + trailingZeros &= (m2 & ((1ull << (shift - 1)) - 1)) == 0; + uint64_t lastRemovedBit = (m2 >> (shift - 1)) & 1; + bool roundUp = (lastRemovedBit != 0) && (!trailingZeros || (((m2 >> shift) & 1) != 0)); + + uint64_t ieee_m2 = (m2 >> shift) + roundUp; + ieee_m2 &= (1ull << DOUBLE_MANTISSA_BITS) - 1; + + if (ieee_m2 == 0 && roundUp) { + ieee_e2++; + } + + // Pack sign, exponent, and mantissa into IEEE 754 format + // Match original Ryu: group sign+exponent, then shift and add mantissa + uint64_t ieee = (((((uint64_t) signedM) << DOUBLE_EXPONENT_BITS) | (uint64_t)ieee_e2) << DOUBLE_MANTISSA_BITS) | ieee_m2; + return int64Bits2Double(ieee); +} + +#endif // RYU_H diff --git a/ext/monitor/depend b/ext/monitor/depend deleted file mode 100644 index 0c7d54afc8..0000000000 --- a/ext/monitor/depend +++ /dev/null @@ -1,162 +0,0 @@ -# AUTOGENERATED DEPENDENCIES START -monitor.o: $(RUBY_EXTCONF_H) -monitor.o: $(arch_hdrdir)/ruby/config.h -monitor.o: $(hdrdir)/ruby/assert.h -monitor.o: $(hdrdir)/ruby/backward.h -monitor.o: $(hdrdir)/ruby/backward/2/assume.h -monitor.o: $(hdrdir)/ruby/backward/2/attributes.h -monitor.o: $(hdrdir)/ruby/backward/2/bool.h -monitor.o: $(hdrdir)/ruby/backward/2/inttypes.h -monitor.o: $(hdrdir)/ruby/backward/2/limits.h -monitor.o: $(hdrdir)/ruby/backward/2/long_long.h -monitor.o: $(hdrdir)/ruby/backward/2/stdalign.h -monitor.o: $(hdrdir)/ruby/backward/2/stdarg.h -monitor.o: $(hdrdir)/ruby/defines.h -monitor.o: $(hdrdir)/ruby/intern.h -monitor.o: $(hdrdir)/ruby/internal/abi.h -monitor.o: $(hdrdir)/ruby/internal/anyargs.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/char.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/double.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/int.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/long.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/short.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h -monitor.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h -monitor.o: $(hdrdir)/ruby/internal/assume.h -monitor.o: $(hdrdir)/ruby/internal/attr/alloc_size.h -monitor.o: $(hdrdir)/ruby/internal/attr/artificial.h -monitor.o: $(hdrdir)/ruby/internal/attr/cold.h -monitor.o: $(hdrdir)/ruby/internal/attr/const.h -monitor.o: $(hdrdir)/ruby/internal/attr/constexpr.h -monitor.o: $(hdrdir)/ruby/internal/attr/deprecated.h -monitor.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h -monitor.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h -monitor.o: $(hdrdir)/ruby/internal/attr/error.h -monitor.o: $(hdrdir)/ruby/internal/attr/flag_enum.h -monitor.o: $(hdrdir)/ruby/internal/attr/forceinline.h -monitor.o: $(hdrdir)/ruby/internal/attr/format.h -monitor.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h -monitor.o: $(hdrdir)/ruby/internal/attr/noalias.h -monitor.o: $(hdrdir)/ruby/internal/attr/nodiscard.h -monitor.o: $(hdrdir)/ruby/internal/attr/noexcept.h -monitor.o: $(hdrdir)/ruby/internal/attr/noinline.h -monitor.o: $(hdrdir)/ruby/internal/attr/nonnull.h -monitor.o: $(hdrdir)/ruby/internal/attr/noreturn.h -monitor.o: $(hdrdir)/ruby/internal/attr/packed_struct.h -monitor.o: $(hdrdir)/ruby/internal/attr/pure.h -monitor.o: $(hdrdir)/ruby/internal/attr/restrict.h -monitor.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h -monitor.o: $(hdrdir)/ruby/internal/attr/warning.h -monitor.o: $(hdrdir)/ruby/internal/attr/weakref.h -monitor.o: $(hdrdir)/ruby/internal/cast.h -monitor.o: $(hdrdir)/ruby/internal/compiler_is.h -monitor.o: $(hdrdir)/ruby/internal/compiler_is/apple.h -monitor.o: $(hdrdir)/ruby/internal/compiler_is/clang.h -monitor.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h -monitor.o: $(hdrdir)/ruby/internal/compiler_is/intel.h -monitor.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h -monitor.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h -monitor.o: $(hdrdir)/ruby/internal/compiler_since.h -monitor.o: $(hdrdir)/ruby/internal/config.h -monitor.o: $(hdrdir)/ruby/internal/constant_p.h -monitor.o: $(hdrdir)/ruby/internal/core.h -monitor.o: $(hdrdir)/ruby/internal/core/rarray.h -monitor.o: $(hdrdir)/ruby/internal/core/rbasic.h -monitor.o: $(hdrdir)/ruby/internal/core/rbignum.h -monitor.o: $(hdrdir)/ruby/internal/core/rclass.h -monitor.o: $(hdrdir)/ruby/internal/core/rdata.h -monitor.o: $(hdrdir)/ruby/internal/core/rfile.h -monitor.o: $(hdrdir)/ruby/internal/core/rhash.h -monitor.o: $(hdrdir)/ruby/internal/core/robject.h -monitor.o: $(hdrdir)/ruby/internal/core/rregexp.h -monitor.o: $(hdrdir)/ruby/internal/core/rstring.h -monitor.o: $(hdrdir)/ruby/internal/core/rstruct.h -monitor.o: $(hdrdir)/ruby/internal/core/rtypeddata.h -monitor.o: $(hdrdir)/ruby/internal/ctype.h -monitor.o: $(hdrdir)/ruby/internal/dllexport.h -monitor.o: $(hdrdir)/ruby/internal/dosish.h -monitor.o: $(hdrdir)/ruby/internal/error.h -monitor.o: $(hdrdir)/ruby/internal/eval.h -monitor.o: $(hdrdir)/ruby/internal/event.h -monitor.o: $(hdrdir)/ruby/internal/fl_type.h -monitor.o: $(hdrdir)/ruby/internal/gc.h -monitor.o: $(hdrdir)/ruby/internal/glob.h -monitor.o: $(hdrdir)/ruby/internal/globals.h -monitor.o: $(hdrdir)/ruby/internal/has/attribute.h -monitor.o: $(hdrdir)/ruby/internal/has/builtin.h -monitor.o: $(hdrdir)/ruby/internal/has/c_attribute.h -monitor.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h -monitor.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h -monitor.o: $(hdrdir)/ruby/internal/has/extension.h -monitor.o: $(hdrdir)/ruby/internal/has/feature.h -monitor.o: $(hdrdir)/ruby/internal/has/warning.h -monitor.o: $(hdrdir)/ruby/internal/intern/array.h -monitor.o: $(hdrdir)/ruby/internal/intern/bignum.h -monitor.o: $(hdrdir)/ruby/internal/intern/class.h -monitor.o: $(hdrdir)/ruby/internal/intern/compar.h -monitor.o: $(hdrdir)/ruby/internal/intern/complex.h -monitor.o: $(hdrdir)/ruby/internal/intern/cont.h -monitor.o: $(hdrdir)/ruby/internal/intern/dir.h -monitor.o: $(hdrdir)/ruby/internal/intern/enum.h -monitor.o: $(hdrdir)/ruby/internal/intern/enumerator.h -monitor.o: $(hdrdir)/ruby/internal/intern/error.h -monitor.o: $(hdrdir)/ruby/internal/intern/eval.h -monitor.o: $(hdrdir)/ruby/internal/intern/file.h -monitor.o: $(hdrdir)/ruby/internal/intern/hash.h -monitor.o: $(hdrdir)/ruby/internal/intern/io.h -monitor.o: $(hdrdir)/ruby/internal/intern/load.h -monitor.o: $(hdrdir)/ruby/internal/intern/marshal.h -monitor.o: $(hdrdir)/ruby/internal/intern/numeric.h -monitor.o: $(hdrdir)/ruby/internal/intern/object.h -monitor.o: $(hdrdir)/ruby/internal/intern/parse.h -monitor.o: $(hdrdir)/ruby/internal/intern/proc.h -monitor.o: $(hdrdir)/ruby/internal/intern/process.h -monitor.o: $(hdrdir)/ruby/internal/intern/random.h -monitor.o: $(hdrdir)/ruby/internal/intern/range.h -monitor.o: $(hdrdir)/ruby/internal/intern/rational.h -monitor.o: $(hdrdir)/ruby/internal/intern/re.h -monitor.o: $(hdrdir)/ruby/internal/intern/ruby.h -monitor.o: $(hdrdir)/ruby/internal/intern/select.h -monitor.o: $(hdrdir)/ruby/internal/intern/select/largesize.h -monitor.o: $(hdrdir)/ruby/internal/intern/set.h -monitor.o: $(hdrdir)/ruby/internal/intern/signal.h -monitor.o: $(hdrdir)/ruby/internal/intern/sprintf.h -monitor.o: $(hdrdir)/ruby/internal/intern/string.h -monitor.o: $(hdrdir)/ruby/internal/intern/struct.h -monitor.o: $(hdrdir)/ruby/internal/intern/thread.h -monitor.o: $(hdrdir)/ruby/internal/intern/time.h -monitor.o: $(hdrdir)/ruby/internal/intern/variable.h -monitor.o: $(hdrdir)/ruby/internal/intern/vm.h -monitor.o: $(hdrdir)/ruby/internal/interpreter.h -monitor.o: $(hdrdir)/ruby/internal/iterator.h -monitor.o: $(hdrdir)/ruby/internal/memory.h -monitor.o: $(hdrdir)/ruby/internal/method.h -monitor.o: $(hdrdir)/ruby/internal/module.h -monitor.o: $(hdrdir)/ruby/internal/newobj.h -monitor.o: $(hdrdir)/ruby/internal/scan_args.h -monitor.o: $(hdrdir)/ruby/internal/special_consts.h -monitor.o: $(hdrdir)/ruby/internal/static_assert.h -monitor.o: $(hdrdir)/ruby/internal/stdalign.h -monitor.o: $(hdrdir)/ruby/internal/stdbool.h -monitor.o: $(hdrdir)/ruby/internal/stdckdint.h -monitor.o: $(hdrdir)/ruby/internal/symbol.h -monitor.o: $(hdrdir)/ruby/internal/value.h -monitor.o: $(hdrdir)/ruby/internal/value_type.h -monitor.o: $(hdrdir)/ruby/internal/variable.h -monitor.o: $(hdrdir)/ruby/internal/warning_push.h -monitor.o: $(hdrdir)/ruby/internal/xmalloc.h -monitor.o: $(hdrdir)/ruby/missing.h -monitor.o: $(hdrdir)/ruby/ruby.h -monitor.o: $(hdrdir)/ruby/st.h -monitor.o: $(hdrdir)/ruby/subst.h -monitor.o: monitor.c -# AUTOGENERATED DEPENDENCIES END diff --git a/ext/monitor/extconf.rb b/ext/monitor/extconf.rb deleted file mode 100644 index 78c53fa0c5..0000000000 --- a/ext/monitor/extconf.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'mkmf' -create_makefile('monitor') diff --git a/ext/monitor/lib/monitor.rb b/ext/monitor/lib/monitor.rb deleted file mode 100644 index 82d0a75c56..0000000000 --- a/ext/monitor/lib/monitor.rb +++ /dev/null @@ -1,289 +0,0 @@ -# frozen_string_literal: false -# = monitor.rb -# -# Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org> -# -# This library is distributed under the terms of the Ruby license. -# You can freely distribute/modify this library. -# - -require 'monitor.so' - -# -# In concurrent programming, a monitor is an object or module intended to be -# used safely by more than one thread. The defining characteristic of a -# monitor is that its methods are executed with mutual exclusion. That is, at -# each point in time, at most one thread may be executing any of its methods. -# This mutual exclusion greatly simplifies reasoning about the implementation -# of monitors compared to reasoning about parallel code that updates a data -# structure. -# -# You can read more about the general principles on the Wikipedia page for -# Monitors[https://en.wikipedia.org/wiki/Monitor_%28synchronization%29]. -# -# == Examples -# -# === Simple object.extend -# -# require 'monitor.rb' -# -# buf = [] -# buf.extend(MonitorMixin) -# empty_cond = buf.new_cond -# -# # consumer -# Thread.start do -# loop do -# buf.synchronize do -# empty_cond.wait_while { buf.empty? } -# print buf.shift -# end -# end -# end -# -# # producer -# while line = ARGF.gets -# buf.synchronize do -# buf.push(line) -# empty_cond.signal -# end -# end -# -# The consumer thread waits for the producer thread to push a line to buf -# while <tt>buf.empty?</tt>. The producer thread (main thread) reads a -# line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt> -# to notify the consumer thread of new data. -# -# === Simple Class include -# -# require 'monitor' -# -# class SynchronizedArray < Array -# -# include MonitorMixin -# -# def initialize(*args) -# super(*args) -# end -# -# alias :old_shift :shift -# alias :old_unshift :unshift -# -# def shift(n=1) -# self.synchronize do -# self.old_shift(n) -# end -# end -# -# def unshift(item) -# self.synchronize do -# self.old_unshift(item) -# end -# end -# -# # other methods ... -# end -# -# +SynchronizedArray+ implements an Array with synchronized access to items. -# This Class is implemented as subclass of Array which includes the -# MonitorMixin module. -# -module MonitorMixin - # - # FIXME: This isn't documented in Nutshell. - # - # Since MonitorMixin.new_cond returns a ConditionVariable, and the example - # above calls while_wait and signal, this class should be documented. - # - class ConditionVariable - # - # Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup. - # - # If +timeout+ is given, this method returns after +timeout+ seconds passed, - # even if no other thread doesn't signal. - # - def wait(timeout = nil) - @monitor.mon_check_owner - @monitor.wait_for_cond(@cond, timeout) - end - - # - # Calls wait repeatedly while the given block yields a truthy value. - # - def wait_while - while yield - wait - end - end - - # - # Calls wait repeatedly until the given block yields a truthy value. - # - def wait_until - until yield - wait - end - end - - # - # Wakes up the first thread in line waiting for this lock. - # - def signal - @monitor.mon_check_owner - @cond.signal - end - - # - # Wakes up all threads waiting for this lock. - # - def broadcast - @monitor.mon_check_owner - @cond.broadcast - end - - private - - def initialize(monitor) # :nodoc: - @monitor = monitor - @cond = Thread::ConditionVariable.new - end - end - - def self.extend_object(obj) # :nodoc: - super(obj) - obj.__send__(:mon_initialize) - end - - # - # Attempts to enter exclusive section. Returns +false+ if lock fails. - # - def mon_try_enter - @mon_data.try_enter - end - # For backward compatibility - alias try_mon_enter mon_try_enter - - # - # Enters exclusive section. - # - def mon_enter - @mon_data.enter - end - - # - # Leaves exclusive section. - # - def mon_exit - mon_check_owner - @mon_data.exit - end - - # - # Returns true if this monitor is locked by any thread - # - def mon_locked? - @mon_data.mon_locked? - end - - # - # Returns true if this monitor is locked by current thread. - # - def mon_owned? - @mon_data.mon_owned? - end - - # - # Enters exclusive section and executes the block. Leaves the exclusive - # section automatically when the block exits. See example under - # +MonitorMixin+. - # - def mon_synchronize(&b) - @mon_data.synchronize(&b) - end - alias synchronize mon_synchronize - - # - # Creates a new MonitorMixin::ConditionVariable associated with the - # Monitor object. - # - def new_cond - unless defined?(@mon_data) - mon_initialize - @mon_initialized_by_new_cond = true - end - return ConditionVariable.new(@mon_data) - end - - private - - # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead - # of this constructor. Have look at the examples above to understand how to - # use this module. - def initialize(...) - super - mon_initialize - end - - # Initializes the MonitorMixin after being included in a class or when an - # object has been extended with the MonitorMixin - def mon_initialize - if defined?(@mon_data) - if defined?(@mon_initialized_by_new_cond) - return # already initialized. - elsif @mon_data_owner_object_id == self.object_id - raise ThreadError, "already initialized" - end - end - @mon_data = ::Monitor.new - @mon_data_owner_object_id = self.object_id - end - - # Ensures that the MonitorMixin is owned by the current thread, - # otherwise raises an exception. - def mon_check_owner - @mon_data.mon_check_owner - end -end - -# Use the Monitor class when you want to have a lock object for blocks with -# mutual exclusion. -# -# require 'monitor' -# -# lock = Monitor.new -# lock.synchronize do -# # exclusive access -# end -# -class Monitor - # - # Creates a new MonitorMixin::ConditionVariable associated with the - # Monitor object. - # - def new_cond - ::MonitorMixin::ConditionVariable.new(self) - end - - # for compatibility - alias try_mon_enter try_enter - alias mon_try_enter try_enter - alias mon_enter enter - alias mon_exit exit - alias mon_synchronize synchronize -end - -# Documentation comments: -# - All documentation comes from Nutshell. -# - MonitorMixin.new_cond appears in the example, but is not documented in -# Nutshell. -# - All the internals (internal modules Accessible and Initializable, class -# ConditionVariable) appear in RDoc. It might be good to hide them, by -# making them private, or marking them :nodoc:, etc. -# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but -# not synchronize. -# - mon_owner is in Nutshell, but appears as an accessor in a separate module -# here, so is hard/impossible to RDoc. Some other useful accessors -# (mon_count and some queue stuff) are also in this module, and don't appear -# directly in the RDoc output. -# - in short, it may be worth changing the code layout in this file to make the -# documentation easier diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c deleted file mode 100644 index 819c6e7699..0000000000 --- a/ext/monitor/monitor.c +++ /dev/null @@ -1,255 +0,0 @@ -#include "ruby/ruby.h" - -/* Thread::Monitor */ - -struct rb_monitor { - long count; - const VALUE owner; - const VALUE mutex; -}; - -static void -monitor_mark(void *ptr) -{ - struct rb_monitor *mc = ptr; - rb_gc_mark(mc->owner); - rb_gc_mark(mc->mutex); -} - -static size_t -monitor_memsize(const void *ptr) -{ - return sizeof(struct rb_monitor); -} - -static const rb_data_type_t monitor_data_type = { - "monitor", - {monitor_mark, RUBY_TYPED_DEFAULT_FREE, monitor_memsize,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED -}; - -static VALUE -monitor_alloc(VALUE klass) -{ - struct rb_monitor *mc; - VALUE obj; - - obj = TypedData_Make_Struct(klass, struct rb_monitor, &monitor_data_type, mc); - RB_OBJ_WRITE(obj, &mc->mutex, rb_mutex_new()); - RB_OBJ_WRITE(obj, &mc->owner, Qnil); - mc->count = 0; - - return obj; -} - -static struct rb_monitor * -monitor_ptr(VALUE monitor) -{ - struct rb_monitor *mc; - TypedData_Get_Struct(monitor, struct rb_monitor, &monitor_data_type, mc); - return mc; -} - -static int -mc_owner_p(struct rb_monitor *mc) -{ - return mc->owner == rb_fiber_current(); -} - -/* - * call-seq: - * try_enter -> true or false - * - * Attempts to enter exclusive section. Returns +false+ if lock fails. - */ -static VALUE -monitor_try_enter(VALUE monitor) -{ - struct rb_monitor *mc = monitor_ptr(monitor); - - if (!mc_owner_p(mc)) { - if (!rb_mutex_trylock(mc->mutex)) { - return Qfalse; - } - RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); - mc->count = 0; - } - mc->count += 1; - return Qtrue; -} - -/* - * call-seq: - * enter -> nil - * - * Enters exclusive section. - */ -static VALUE -monitor_enter(VALUE monitor) -{ - struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { - rb_mutex_lock(mc->mutex); - RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); - mc->count = 0; - } - mc->count++; - return Qnil; -} - -/* :nodoc: */ -static VALUE -monitor_check_owner(VALUE monitor) -{ - struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { - rb_raise(rb_eThreadError, "current fiber not owner"); - } - return Qnil; -} - -/* - * call-seq: - * exit -> nil - * - * Leaves exclusive section. - */ -static VALUE -monitor_exit(VALUE monitor) -{ - monitor_check_owner(monitor); - - struct rb_monitor *mc = monitor_ptr(monitor); - - if (mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)mc->count); - mc->count--; - - if (mc->count == 0) { - RB_OBJ_WRITE(monitor, &mc->owner, Qnil); - rb_mutex_unlock(mc->mutex); - } - return Qnil; -} - -/* :nodoc: */ -static VALUE -monitor_locked_p(VALUE monitor) -{ - struct rb_monitor *mc = monitor_ptr(monitor); - return rb_mutex_locked_p(mc->mutex); -} - -/* :nodoc: */ -static VALUE -monitor_owned_p(VALUE monitor) -{ - struct rb_monitor *mc = monitor_ptr(monitor); - return (rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc)) ? Qtrue : Qfalse; -} - -static VALUE -monitor_exit_for_cond(VALUE monitor) -{ - struct rb_monitor *mc = monitor_ptr(monitor); - long cnt = mc->count; - RB_OBJ_WRITE(monitor, &mc->owner, Qnil); - mc->count = 0; - return LONG2NUM(cnt); -} - -struct wait_for_cond_data { - VALUE monitor; - VALUE cond; - VALUE timeout; - VALUE count; -}; - -static VALUE -monitor_wait_for_cond_body(VALUE v) -{ - struct wait_for_cond_data *data = (struct wait_for_cond_data *)v; - struct rb_monitor *mc = monitor_ptr(data->monitor); - // cond.wait(monitor.mutex, timeout) - VALUE signaled = rb_funcall(data->cond, rb_intern("wait"), 2, mc->mutex, data->timeout); - return RTEST(signaled) ? Qtrue : Qfalse; -} - -static VALUE -monitor_enter_for_cond(VALUE v) -{ - // assert(rb_mutex_owned_p(mc->mutex) == Qtrue) - // but rb_mutex_owned_p is not exported... - - struct wait_for_cond_data *data = (struct wait_for_cond_data *)v; - struct rb_monitor *mc = monitor_ptr(data->monitor); - RB_OBJ_WRITE(data->monitor, &mc->owner, rb_fiber_current()); - mc->count = NUM2LONG(data->count); - return Qnil; -} - -/* :nodoc: */ -static VALUE -monitor_wait_for_cond(VALUE monitor, VALUE cond, VALUE timeout) -{ - VALUE count = monitor_exit_for_cond(monitor); - struct wait_for_cond_data data = { - monitor, - cond, - timeout, - count, - }; - - return rb_ensure(monitor_wait_for_cond_body, (VALUE)&data, - monitor_enter_for_cond, (VALUE)&data); -} - -static VALUE -monitor_sync_body(VALUE monitor) -{ - return rb_yield_values(0); -} - -static VALUE -monitor_sync_ensure(VALUE monitor) -{ - return monitor_exit(monitor); -} - -/* - * call-seq: - * synchronize { } -> result of the block - * - * Enters exclusive section and executes the block. Leaves the exclusive - * section automatically when the block exits. See example under - * +MonitorMixin+. - */ -static VALUE -monitor_synchronize(VALUE monitor) -{ - monitor_enter(monitor); - return rb_ensure(monitor_sync_body, monitor, monitor_sync_ensure, monitor); -} - -void -Init_monitor(void) -{ -#ifdef HAVE_RB_EXT_RACTOR_SAFE - rb_ext_ractor_safe(true); -#endif - - VALUE rb_cMonitor = rb_define_class("Monitor", rb_cObject); - rb_define_alloc_func(rb_cMonitor, monitor_alloc); - - rb_define_method(rb_cMonitor, "try_enter", monitor_try_enter, 0); - rb_define_method(rb_cMonitor, "enter", monitor_enter, 0); - rb_define_method(rb_cMonitor, "exit", monitor_exit, 0); - rb_define_method(rb_cMonitor, "synchronize", monitor_synchronize, 0); - - /* internal methods for MonitorMixin */ - rb_define_method(rb_cMonitor, "mon_locked?", monitor_locked_p, 0); - rb_define_method(rb_cMonitor, "mon_check_owner", monitor_check_owner, 0); - rb_define_method(rb_cMonitor, "mon_owned?", monitor_owned_p, 0); - - /* internal methods for MonitorMixin::ConditionVariable */ - rb_define_method(rb_cMonitor, "wait_for_cond", monitor_wait_for_cond, 2); -} diff --git a/ext/objspace/depend b/ext/objspace/depend index 3c11511575..d9dfc0c42b 100644 --- a/ext/objspace/depend +++ b/ext/objspace/depend @@ -182,10 +182,10 @@ object_tracing.o: $(top_srcdir)/id_table.h object_tracing.o: $(top_srcdir)/internal.h object_tracing.o: $(top_srcdir)/internal/array.h object_tracing.o: $(top_srcdir)/internal/basic_operators.h +object_tracing.o: $(top_srcdir)/internal/box.h object_tracing.o: $(top_srcdir)/internal/compilers.h object_tracing.o: $(top_srcdir)/internal/gc.h object_tracing.o: $(top_srcdir)/internal/imemo.h -object_tracing.o: $(top_srcdir)/internal/namespace.h object_tracing.o: $(top_srcdir)/internal/sanitizers.h object_tracing.o: $(top_srcdir)/internal/serial.h object_tracing.o: $(top_srcdir)/internal/set_table.h @@ -391,16 +391,17 @@ objspace.o: $(top_srcdir)/id_table.h objspace.o: $(top_srcdir)/internal.h objspace.o: $(top_srcdir)/internal/array.h objspace.o: $(top_srcdir)/internal/basic_operators.h +objspace.o: $(top_srcdir)/internal/box.h objspace.o: $(top_srcdir)/internal/class.h objspace.o: $(top_srcdir)/internal/compilers.h objspace.o: $(top_srcdir)/internal/gc.h objspace.o: $(top_srcdir)/internal/hash.h objspace.o: $(top_srcdir)/internal/imemo.h -objspace.o: $(top_srcdir)/internal/namespace.h objspace.o: $(top_srcdir)/internal/sanitizers.h objspace.o: $(top_srcdir)/internal/serial.h objspace.o: $(top_srcdir)/internal/set_table.h objspace.o: $(top_srcdir)/internal/static_assert.h +objspace.o: $(top_srcdir)/internal/struct.h objspace.o: $(top_srcdir)/internal/variable.h objspace.o: $(top_srcdir)/internal/vm.h objspace.o: $(top_srcdir)/internal/warnings.h @@ -601,22 +602,24 @@ objspace_dump.o: $(top_srcdir)/ccan/list/list.h objspace_dump.o: $(top_srcdir)/ccan/str/str.h objspace_dump.o: $(top_srcdir)/constant.h objspace_dump.o: $(top_srcdir)/debug_counter.h +objspace_dump.o: $(top_srcdir)/encindex.h objspace_dump.o: $(top_srcdir)/id_table.h objspace_dump.o: $(top_srcdir)/internal.h objspace_dump.o: $(top_srcdir)/internal/array.h objspace_dump.o: $(top_srcdir)/internal/basic_operators.h +objspace_dump.o: $(top_srcdir)/internal/box.h objspace_dump.o: $(top_srcdir)/internal/class.h objspace_dump.o: $(top_srcdir)/internal/compilers.h objspace_dump.o: $(top_srcdir)/internal/gc.h objspace_dump.o: $(top_srcdir)/internal/hash.h objspace_dump.o: $(top_srcdir)/internal/imemo.h objspace_dump.o: $(top_srcdir)/internal/io.h -objspace_dump.o: $(top_srcdir)/internal/namespace.h objspace_dump.o: $(top_srcdir)/internal/sanitizers.h objspace_dump.o: $(top_srcdir)/internal/serial.h objspace_dump.o: $(top_srcdir)/internal/set_table.h objspace_dump.o: $(top_srcdir)/internal/static_assert.h objspace_dump.o: $(top_srcdir)/internal/string.h +objspace_dump.o: $(top_srcdir)/internal/struct.h objspace_dump.o: $(top_srcdir)/internal/variable.h objspace_dump.o: $(top_srcdir)/internal/vm.h objspace_dump.o: $(top_srcdir)/internal/warnings.h diff --git a/ext/objspace/object_tracing.c b/ext/objspace/object_tracing.c index 0156642ef2..74d793a6e2 100644 --- a/ext/objspace/object_tracing.c +++ b/ext/objspace/object_tracing.c @@ -22,7 +22,6 @@ struct traceobj_arg { int running; int keep_remains; VALUE newobj_trace; - VALUE freeobj_trace; st_table *object_table; /* obj (VALUE) -> allocation_info */ st_table *str_table; /* cstr -> refcount */ struct traceobj_arg *prev_traceobj_arg; @@ -91,18 +90,16 @@ newobj_i(VALUE tpval, void *data) VALUE klass = rb_tracearg_defined_class(tparg); struct allocation_info *info; const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : 0; - VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil; + VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_mod_name(klass) : Qnil; const char *class_path_cstr = RTEST(class_path) ? make_unique_str(arg->str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0; st_data_t v; if (st_lookup(arg->object_table, (st_data_t)obj, &v)) { + /* keep_remains kept this slot's entry after its object was freed. The + * allocator has now reused that address, so recycle the dead entry's + * info. A living entry here would mean two live objects at one address. */ info = (struct allocation_info *)v; - if (arg->keep_remains) { - if (info->living) { - /* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */ - } - } - /* reuse info */ + assert(!info->living); delete_unique_str(arg->str_table, info->path); delete_unique_str(arg->str_table, info->class_path); } @@ -121,37 +118,6 @@ newobj_i(VALUE tpval, void *data) st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info); } -static void -freeobj_i(VALUE tpval, void *data) -{ - struct traceobj_arg *arg = (struct traceobj_arg *)data; - rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval); - st_data_t obj = (st_data_t)rb_tracearg_object(tparg); - st_data_t v; - struct allocation_info *info; - - /* Modifying the st table can cause allocations, which can trigger GC. - * Since freeobj_i is called during GC, it must not trigger another GC. */ - VALUE gc_disabled = rb_gc_disable_no_rest(); - - if (arg->keep_remains) { - if (st_lookup(arg->object_table, obj, &v)) { - info = (struct allocation_info *)v; - info->living = 0; - } - } - else { - if (st_delete(arg->object_table, &obj, &v)) { - info = (struct allocation_info *)v; - delete_unique_str(arg->str_table, info->path); - delete_unique_str(arg->str_table, info->class_path); - ruby_xfree(info); - } - } - - if (gc_disabled == Qfalse) rb_gc_enable(); -} - static int free_keys_i(st_data_t key, st_data_t value, st_data_t data) { @@ -171,7 +137,6 @@ allocation_info_tracer_mark(void *ptr) { struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; rb_gc_mark(trace_arg->newobj_trace); - rb_gc_mark(trace_arg->freeobj_trace); } static void @@ -198,12 +163,46 @@ allocation_info_tracer_memsize(const void *ptr) } static int +allocation_info_tracer_weak_reference_i(st_data_t key, st_data_t value, st_data_t data) +{ + struct traceobj_arg *arg = (struct traceobj_arg *)data; + struct allocation_info *info = (struct allocation_info *)value; + + if (rb_gc_handle_weak_references_alive_p((VALUE)key)) { + return ST_CONTINUE; + } + + /* Object was collected. keep_remains keeps the dead entry for reporting. */ + if (arg->keep_remains) { + info->living = 0; + return ST_CONTINUE; + } + else { + delete_unique_str(arg->str_table, info->path); + delete_unique_str(arg->str_table, info->class_path); + ruby_xfree(info); + return ST_DELETE; + } +} + +static void +allocation_info_tracer_weak_reference(void *ptr) +{ + struct traceobj_arg *arg = (struct traceobj_arg *)ptr; + + st_foreach(arg->object_table, allocation_info_tracer_weak_reference_i, (st_data_t)arg); +} + +static int allocation_info_tracer_compact_update_object_table_i(st_data_t key, st_data_t value, st_data_t data) { st_table *table = (st_table *)data; + struct allocation_info *info = (struct allocation_info *)value; - if (!rb_gc_pointer_to_heap_p(key)) { - return ST_DELETE; + /* In keep_remains mode the table keeps entries for freed objects. Their keys + * are dangling, so skip them instead of passing them to rb_gc_location. */ + if (!info->living) { + return ST_CONTINUE; } if (key != rb_gc_location(key)) { @@ -240,6 +239,7 @@ static const rb_data_type_t allocation_info_tracer_type = { allocation_info_tracer_free, /* Never called because global */ allocation_info_tracer_memsize, allocation_info_tracer_compact, + allocation_info_tracer_weak_reference, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; @@ -258,9 +258,10 @@ get_traceobj_arg(void) tmp_trace_arg->running = 0; tmp_trace_arg->keep_remains = tmp_keep_remains; tmp_trace_arg->newobj_trace = 0; - tmp_trace_arg->freeobj_trace = 0; tmp_trace_arg->object_table = st_init_numtable(); tmp_trace_arg->str_table = st_init_strtable(); + + rb_gc_declare_weak_references(obj); } return tmp_trace_arg; } @@ -282,10 +283,8 @@ trace_object_allocations_start(VALUE self) else { if (arg->newobj_trace == 0) { arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg); - arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg); } rb_tracepoint_enable(arg->newobj_trace); - rb_tracepoint_enable(arg->freeobj_trace); } return Qnil; @@ -313,9 +312,6 @@ trace_object_allocations_stop(VALUE self) if (arg->newobj_trace != 0) { rb_tracepoint_disable(arg->newobj_trace); } - if (arg->freeobj_trace != 0) { - rb_tracepoint_disable(arg->freeobj_trace); - } } return Qnil; @@ -411,6 +407,13 @@ object_allocations_reporter(FILE *out, void *ptr) fprintf(out, "== object_allocations_reporter: END\n"); } +/* + * call-seq: trace_object_allocations_debug_start + * + * Starts tracing object allocations for GC debugging. + * If you encounter the BUG "... is T_NONE" (and so on) on your + * application, please try this method at the beginning of your app. + */ static VALUE trace_object_allocations_debug_start(VALUE self) { diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 8b3045fda3..20eab820a1 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -30,19 +30,33 @@ /* * call-seq: - * ObjectSpace.memsize_of(obj) -> Integer + * ObjectSpace.memsize_of(obj) -> integer * - * Return consuming memory size of obj in bytes. + * Returns the amount of memory in bytes consumed by +obj+. * - * Note that the return size is incomplete. You need to deal with this - * information as only a *HINT*. Especially, the size of +T_DATA+ may not be - * correct. + * The returned size includes the slot that +obj+ occupies plus any memory + * that +obj+ allocates outside of that slot, such as the storage backing a + * large String, Array, or Hash: * - * This method is only expected to work with CRuby. + * require 'objspace' + * + * ObjectSpace.memsize_of("small") # => 40 + * ObjectSpace.memsize_of("a" * 1000) # => 1041 + * ObjectSpace.memsize_of([1, 2, 3]) # => 40 + * ObjectSpace.memsize_of(Array.new(100)) # => 840 + * + * Special constants such as +true+, +false+, +nil+, small integers, and some + * symbols do not occupy a slot, so their size is reported as +0+: + * + * ObjectSpace.memsize_of(true) # => 0 + * ObjectSpace.memsize_of(42) # => 0 * - * From Ruby 3.2 with Variable Width Allocation, it returns the actual slot - * size used plus any additional memory allocated outside the slot (such - * as external strings, arrays, or hash tables). + * The returned size is only a hint and may be an underestimate, since it does + * not account for all of the memory that +obj+ references. In particular, the + * size of a +T_DATA+ object (an object implemented in C, such as one defined + * by a C extension) may not be reported correctly. + * + * This method is only expected to work with CRuby. */ static VALUE @@ -108,28 +122,24 @@ each_object_with_flags(each_obj_with_flags cb, void *ctx) /* * call-seq: - * ObjectSpace.memsize_of_all([klass]) -> Integer - * - * Return consuming memory size of all living objects in bytes. + * ObjectSpace.memsize_of_all(klass = nil) -> integer * - * If +klass+ (should be Class object) is given, return the total memory size - * of instances of the given class. + * Returns the total memory size of all living objects in bytes. * - * Note that the returned size is incomplete. You need to deal with this - * information as only a *HINT*. Especially, the size of +T_DATA+ may not be - * correct. + * ObjectSpace.memsize_of_all # => 12502001 * - * Note that this method does *NOT* return total malloc'ed memory size. + * If +klass+ is given (which must be a Class or Module), returns the total + * memory size of objects whose class is, or is a subclass, of +klass+. * - * This method can be defined by the following Ruby code: + * class MyClass; end + * ObjectSpace.memsize_of_all(MyClass) # => 0 + * o = MyClass.new + * ObjectSpace.memsize_of_all(MyClass) # => 40 * - * def memsize_of_all klass = false - * total = 0 - * ObjectSpace.each_object{|e| - * total += ObjectSpace.memsize_of(e) if klass == false || e.kind_of?(klass) - * } - * total - * end + * Note that the value returned may be an underestimate of the actual amount + * of memory used. Therefore, the value returned should only be used as a hint, + * rather than a source of truth. In particular, the size of +T_DATA+ objects may + * not be correct. * * This method is only expected to work with C Ruby. */ @@ -141,6 +151,7 @@ memsize_of_all_m(int argc, VALUE *argv, VALUE self) if (argc > 0) { rb_scan_args(argc, argv, "01", &data.klass); + if (!NIL_P(data.klass)) rb_obj_is_kind_of(Qnil, data.klass); } each_object_with_flags(total_i, &data); @@ -224,23 +235,26 @@ type2sym(enum ruby_value_type i) /* * call-seq: - * ObjectSpace.count_objects_size([result_hash]) -> hash + * ObjectSpace.count_objects_size(result_hash = {}) -> result_hash * * Counts objects size (in bytes) for each type. * - * Note that this information is incomplete. You need to deal with - * this information as only a *HINT*. Especially, total size of - * T_DATA may be wrong. + * Note that the returned size may not be accurate, so it should only + * be used as a hint. Specifically, the size for +T_DATA+ may be + * inaccurate because these are custom objects defined in Ruby and + * native extensions and so they may not accurately report their + * memory size. * - * It returns a hash as: - * {:TOTAL=>1461154, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249, ...} + * It returns a hash that looks like: * - * If the optional argument, result_hash, is given, - * it is overwritten and returned. - * This is intended to avoid probe effect. + * {TOTAL: 1461154, T_CLASS: 158280, T_MODULE: 20672, T_STRING: 527249, ...} + * + * The contents of the returned hash are implementation specific and + * may be changed in future versions without notice. * - * The contents of the returned hash is implementation defined. - * It may be changed in future. + * If the optional argument, +result_hash+, is given, + * it is overwritten and returned. + * This is intended to avoid the probe effect. * * This method is only expected to work with C Ruby. */ @@ -295,28 +309,27 @@ size_t rb_sym_immortal_count(void); /* * call-seq: - * ObjectSpace.count_symbols([result_hash]) -> hash + * ObjectSpace.count_symbols(result_hash = nil) -> hash * - * Counts symbols for each Symbol type. + * Returns a hash containing the number of objects for each Symbol type. * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. + * The types of Symbols are the following: * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. + * - +mortal_dynamic_symbol+: Symbols that are garbage collectable. + * - +immortal_dynamic_symbol+: Symbols that are objects allocated from the + * garbage collector, but are not garbage collectable. + * - +immortal_static_symbol+: Symbols that are not allocated from the + * garbage collector, and are thus not garbage collectable. + * - +immortal_symbol+: the sum of +immortal_dynamic_symbol+ and +immortal_static_symbol+. * - * Note: - * The contents of the returned hash is implementation defined. - * It may be changed in future. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * This method is only expected to work with C Ruby. + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * - * On this version of MRI, they have 3 types of Symbols (and 1 total counts). - * - * * mortal_dynamic_symbol: GC target symbols (collected by GC) - * * immortal_dynamic_symbol: Immortal symbols promoted from dynamic symbols (do not collected by GC) - * * immortal_static_symbol: Immortal symbols (do not collected by GC) - * * immortal_symbol: total immortal symbols (immortal_dynamic_symbol+immortal_static_symbol) + * This method is only expected to work with C Ruby. */ static VALUE @@ -336,35 +349,6 @@ count_symbols(int argc, VALUE *argv, VALUE os) return hash; } -/* - * call-seq: - * ObjectSpace.count_nodes([result_hash]) -> hash - * - * Counts nodes for each node type. - * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: - * - * {:NODE_METHOD=>2027, :NODE_FBODY=>1927, :NODE_CFUNC=>1798, ...} - * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. - * - * Note: - * The contents of the returned hash is implementation defined. - * It may be changed in future. - * - * This method is only expected to work with C Ruby. - */ - -static VALUE -count_nodes(int argc, VALUE *argv, VALUE os) -{ - return setup_hash(argc, argv); -} - static void cto_i(VALUE v, void *data) { @@ -394,32 +378,22 @@ cto_i(VALUE v, void *data) /* * call-seq: - * ObjectSpace.count_tdata_objects([result_hash]) -> hash - * - * Counts objects for each +T_DATA+ type. - * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: + * ObjectSpace.count_tdata_objects(result_hash = nil) -> hash * - * {RubyVM::InstructionSequence=>504, :parser=>5, :barrier=>6, - * :mutex=>6, Proc=>60, RubyVM::Env=>57, Mutex=>1, Encoding=>99, - * ThreadGroup=>1, Binding=>1, Thread=>1, RubyVM=>1, :iseq=>1, - * Random=>1, ARGF.class=>1, Data=>1, :autoload=>3, Time=>2} - * # T_DATA objects existing at startup on r32276. + * Returns a hash containing the number of objects for each +T_DATA+ type. + * The keys are Class objects when the +T_DATA+ object has an associated class, + * or Symbol objects of the name defined in the +rb_data_type_struct+ for internal + * +T_DATA+ objects. * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. + * ObjectSpace.count_tdata_objects + * # => {RBS::Location => 39255, marshal_compat_table: 1, Encoding => 103, mutex: 1, ... } * - * The contents of the returned hash is implementation specific and may change - * in the future. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * In this version, keys are Class object or Symbol object. - * - * If object is kind of normal (accessible) object, the key is Class object. - * If object is not a kind of normal (internal) object, the key is symbol - * name, registered by rb_data_type_struct. + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * * This method is only expected to work with C Ruby. */ @@ -458,28 +432,22 @@ count_imemo_objects_i(VALUE v, void *data) /* * call-seq: - * ObjectSpace.count_imemo_objects([result_hash]) -> hash - * - * Counts objects for each +T_IMEMO+ type. - * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. + * ObjectSpace.count_imemo_objects(result_hash = nil) -> hash * - * It returns a hash as: + * Returns a hash containing the number of objects for each +T_IMEMO+ type. + * The keys are Symbol objects of the +T_IMEMO+ type name. + * +T_IMEMO+ objects are Ruby internal objects that are not visible to Ruby + * programs. * - * {:imemo_ifunc=>8, - * :imemo_svar=>7, - * :imemo_cref=>509, - * :imemo_memo=>1, - * :imemo_throw_data=>1} + * ObjectSpace.count_imemo_objects + * # => {imemo_callcache: 5482, imemo_constcache: 1258, imemo_ment: 13906, ... } * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * The contents of the returned hash is implementation specific and may change - * in the future. - * - * In this version, keys are symbol objects. + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * * This method is only expected to work with C Ruby. */ @@ -500,10 +468,13 @@ count_imemo_objects(int argc, VALUE *argv, VALUE self) INIT_IMEMO_TYPE_ID(imemo_ment); INIT_IMEMO_TYPE_ID(imemo_iseq); INIT_IMEMO_TYPE_ID(imemo_tmpbuf); + INIT_IMEMO_TYPE_ID(imemo_cvar_entry); INIT_IMEMO_TYPE_ID(imemo_callinfo); INIT_IMEMO_TYPE_ID(imemo_callcache); INIT_IMEMO_TYPE_ID(imemo_constcache); INIT_IMEMO_TYPE_ID(imemo_fields); + INIT_IMEMO_TYPE_ID(imemo_subclasses); + INIT_IMEMO_TYPE_ID(imemo_cdhash); #undef INIT_IMEMO_TYPE_ID } @@ -602,42 +573,37 @@ collect_values(st_data_t key, st_data_t value, st_data_t data) * call-seq: * ObjectSpace.reachable_objects_from(obj) -> array or nil * - * [MRI specific feature] Return all reachable objects from `obj'. - * - * This method returns all reachable objects from `obj'. + * Returns all reachable objects from +obj+ as an array: * - * If `obj' has two or more references to the same object `x', then returned - * array only includes one `x' object. + * ObjectSpace.reachable_objects_from(['a', 'b', 'c']) + * #=> [Array, 'a', 'b', 'c'] * - * If `obj' is a non-markable (non-heap management) object such as true, - * false, nil, symbols and Fixnums (and Flonum) then it simply returns nil. + * The returned array is deduplicated, meaning that if +obj+ refers + * to another object more than once, it will only be added to the array + * once: * - * If `obj' has references to an internal object, then it returns instances of - * ObjectSpace::InternalObjectWrapper class. This object contains a reference - * to an internal object and you can check the type of internal object with - * `type' method. + * ObjectSpace.reachable_objects_from([v = 'a', v, v]) + * #=> [Array, 'a'] * - * If `obj' is instance of ObjectSpace::InternalObjectWrapper class, then this - * method returns all reachable object from an internal object, which is - * pointed by `obj'. + * Returns +nil+ if +obj+ is not a markable object (i.e. non-heap + * managed) object. Non-markable objects include +true+, +false+, + * +nil+, certain symbols, small integers, and floats: * - * With this method, you can find memory leaks. - * - * This method is only expected to work with C Ruby. + * ObjectSpace.reachable_objects_from(1) + * #=> nil * - * Example: - * ObjectSpace.reachable_objects_from(['a', 'b', 'c']) - * #=> [Array, 'a', 'b', 'c'] + * All references to internal objects in the returned array are wrapped + * using ObjectSpace::InternalObjectWrapper objects. This object contains + * a reference to the internal object and the type of the object can + * be accessed using the ObjectSpace::InternalObjectWrapper#type method. * - * ObjectSpace.reachable_objects_from(['a', 'a', 'a']) - * #=> [Array, 'a', 'a', 'a'] # all 'a' strings have different object id + * If +obj+ is instance of ObjectSpace::InternalObjectWrapper, then this + * method returns all reachable object from the internal object. * - * ObjectSpace.reachable_objects_from([v = 'a', v, v]) - * #=> [Array, 'a'] - * - * ObjectSpace.reachable_objects_from(1) - * #=> nil # 1 is not markable (heap managed) object + * This method is useful for debugging purposes, such as finding + * memory leaks. * + * This method is only expected to work with C Ruby. */ static VALUE @@ -713,7 +679,30 @@ collect_values_of_values(VALUE category, VALUE category_objects, VALUE categorie * call-seq: * ObjectSpace.reachable_objects_from_root -> hash * - * [MRI specific feature] Return all reachable objects from root. + * Returns a hash of objects directly reachable from the VM roots, + * grouped by the root that reaches them. + * + * The roots are the entry points the garbage collector starts from when it + * marks live objects, such as the virtual machine and the global variable + * table. The keys of the returned hash are strings naming each root, and each + * value is an array of the objects reachable from that root: + * + * require 'objspace' + * + * reachable = ObjectSpace.reachable_objects_from_root + * reachable.keys # => ["vm", "global_tbl", "machine_context", "global_symbols"] + * reachable.values.first # => [#<Ractor:#1 running>, ...] + * + * The returned hash compares its keys by identity, so it cannot be indexed + * with a string literal; iterate over it (or over its #values) instead. + * + * Any reference to an internal object is wrapped in an + * ObjectSpace::InternalObjectWrapper object. + * + * This method is useful for debugging the object graph, for example when + * tracking down the cause of a memory leak. + * + * This method is only expected to work with C Ruby. */ static VALUE reachable_objects_from_root(VALUE self) @@ -745,12 +734,28 @@ wrap_klass_iow(VALUE klass) /* * call-seq: - * ObjectSpace.internal_class_of(obj) -> Class or Module + * ObjectSpace.internal_class_of(obj) -> class or module * - * [MRI specific feature] Return internal class of obj. - * obj can be an instance of InternalObjectWrapper. + * Returns the real class of +obj+, which may differ from the class returned + * by Object#class. + * + * Ruby inserts hidden classes into an object's ancestry, such as a singleton + * class or an included module's iclass. Object#class skips over these, but + * this method returns the first one, including any hidden class: + * + * require 'objspace' + * + * s = "x" + * def s.foo; end # gives +s+ a singleton class + * s.class # => String + * ObjectSpace.internal_class_of(s) # => #<Class:#<String:0x000000012574c1f8>> + * + * +obj+ may be an ObjectSpace::InternalObjectWrapper, in which case the class + * of the wrapped internal object is returned. * * Note that you should not use this method in your application. + * + * This method is only expected to work with C Ruby. */ static VALUE objspace_internal_class_of(VALUE self, VALUE obj) @@ -834,7 +839,6 @@ Init_objspace(void) rb_define_module_function(rb_mObjSpace, "count_objects_size", count_objects_size, -1); rb_define_module_function(rb_mObjSpace, "count_symbols", count_symbols, -1); - rb_define_module_function(rb_mObjSpace, "count_nodes", count_nodes, -1); rb_define_module_function(rb_mObjSpace, "count_tdata_objects", count_tdata_objects, -1); rb_define_module_function(rb_mObjSpace, "count_imemo_objects", count_imemo_objects, -1); diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 94a9d43f98..68ada01af3 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -29,7 +29,7 @@ #include "ruby/util.h" #include "ruby/io.h" #include "vm_callinfo.h" -#include "vm_core.h" +#include "vm_sync.h" RUBY_EXTERN const char ruby_hexdigits[]; @@ -573,10 +573,15 @@ 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, RTYPEDDATA_TYPE(obj)->wrap_struct_name); + dump_append(dc, type->wrap_struct_name); dump_append(dc, "\""); + if (!(type->flags & RUBY_TYPED_FREE_IMMEDIATELY) && + type->function.dfree != RUBY_DEFAULT_FREE) { + dump_append(dc, ", \"free_immediately\":false"); + } } break; @@ -587,14 +592,14 @@ dump_object(VALUE obj, struct dump_config *dc) break; case T_OBJECT: - if (FL_TEST(obj, ROBJECT_EMBED)) { + if (!FL_TEST_RAW(obj, ROBJECT_HEAP)) { dump_append(dc, ", \"embedded\":true"); } dump_append(dc, ", \"ivars\":"); dump_append_lu(dc, ROBJECT_FIELDS_COUNT(obj)); - if (rb_shape_obj_too_complex_p(obj)) { - dump_append(dc, ", \"too_complex_shape\":true"); + if (rb_obj_shape_complex_p(obj)) { + dump_append(dc, ", \"complex_shape\":true"); } break; @@ -771,15 +776,16 @@ dump_result(struct dump_config *dc) return dc->given_output; } -/* :nodoc: */ static VALUE -objspace_dump(VALUE os, VALUE obj, VALUE output) +dump_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE obj = ((VALUE*)args_p)[0]; + VALUE output = ((VALUE*)args_p)[1]; + if (!RB_SPECIAL_CONST_P(obj)) { dc.cur_page_slot_size = rb_gc_obj_slot_size(obj); } - dump_output(&dc, output, Qnil, Qnil, Qnil); dump_object(obj, &dc); @@ -787,6 +793,16 @@ objspace_dump(VALUE os, VALUE obj, VALUE output) return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump(VALUE os, VALUE obj, VALUE output) +{ + VALUE args[2]; + args[0] = obj; + args[1] = output; + return rb_vm_lock_with_barrier(dump_locked, (void*)args); +} + static void shape_id_i(shape_id_t shape_id, void *data) { @@ -804,7 +820,7 @@ shape_id_i(shape_id_t shape_id, void *data) if (RSHAPE_TYPE(shape_id) != SHAPE_ROOT) { dump_append(dc, ", \"parent_id\":"); - dump_append_lu(dc, RSHAPE_PARENT_RAW_ID(shape_id)); + dump_append_lu(dc, RSHAPE_PARENT_OFFSET(shape_id)); } dump_append(dc, ", \"depth\":"); @@ -835,11 +851,15 @@ shape_id_i(shape_id_t shape_id, void *data) dump_append(dc, "}\n"); } -/* :nodoc: */ static VALUE -objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) +dump_all_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE output = ((VALUE*)args_p)[0]; + VALUE full = ((VALUE*)args_p)[1]; + VALUE since = ((VALUE*)args_p)[2]; + VALUE shapes = ((VALUE*)args_p)[3]; + dump_output(&dc, output, full, since, shapes); if (!dc.partial_dump || dc.since == 0) { @@ -860,9 +880,23 @@ objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) /* :nodoc: */ static VALUE -objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) +{ + VALUE args[4]; + args[0] = output; + args[1] = full; + args[2] = since; + args[3] = shapes; + return rb_vm_lock_with_barrier(dump_all_locked, (void*)args); +} + +static VALUE +dump_shapes_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE output = ((VALUE*)args_p)[0]; + VALUE shapes = ((VALUE*)args_p)[1]; + dump_output(&dc, output, Qfalse, Qnil, shapes); if (RTEST(shapes)) { @@ -871,6 +905,16 @@ objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +{ + VALUE args[2]; + args[0] = output; + args[1] = shapes; + return rb_vm_lock_with_barrier(dump_shapes_locked, (void*)args); +} + void Init_objspace_dump(VALUE rb_mObjSpace) { @@ -878,6 +922,9 @@ Init_objspace_dump(VALUE rb_mObjSpace) #if 0 rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */ #endif +#ifdef HAVE_RB_EXT_RACTOR_SAFE + RB_EXT_RACTOR_SAFE(true); +#endif rb_define_module_function(rb_mObjSpace, "_dump", objspace_dump, 2); rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 4); diff --git a/ext/openssl/History.md b/ext/openssl/History.md index ad3417f9d0..ce01b3e0f2 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,132 @@ +Version 4.0.2 +============= + +Merged changes in 3.2.4 and 3.3.3. + + +Version 4.0.1 +============= + +Notable changes +--------------- + +* Add `sync_close` keyword argument to `OpenSSL::SSL::SSLSocket.new` as a + short-hand for setting `sync_close` attribute on the created `SSLSocket` + instance. + [[GitHub #955]](https://github.com/ruby/openssl/issues/955) + [[GitHub #996]](https://github.com/ruby/openssl/pull/996) + + +Bug fixes +--------- + +* Fix uninitialized variables in `OpenSSL::OCSP::BasicResponse#status`. + [[GitHub #1004]](https://github.com/ruby/openssl/pull/1004) + + +Version 4.0.0 +============= + +Compatibility +------------- + +* Ruby >= 2.7 +* OpenSSL >= 1.1.1, LibreSSL >= 3.9, and AWS-LC 1.66.0 + - Removed support for OpenSSL 1.0.2-1.1.0 and LibreSSL 3.1-3.8. + [[GitHub #835]](https://github.com/ruby/openssl/issues/835) + - Added support for AWS-LC. + [[GitHub #833]](https://github.com/ruby/openssl/issues/833) + + +Notable changes +--------------- + +* `OpenSSL::SSL` + - Reduce overhead when writing to `OpenSSL::SSL::SSLSocket`. `#syswrite` no + longer creates a temporary String object. + [[GitHub #831]](https://github.com/ruby/openssl/pull/831) + - Make `OpenSSL::SSL::SSLContext#min_version=` and `#max_version=` wrap the + corresponding OpenSSL APIs directly, and remove the fallback to SSL options. + [[GitHub #849]](https://github.com/ruby/openssl/pull/849) + - Add `OpenSSL::SSL::SSLContext#sigalgs=` and `#client_sigalgs=` for + specifying signature algorithms to use for connections. + [[GitHub #895]](https://github.com/ruby/openssl/pull/895) + - Rename `OpenSSL::SSL::SSLContext#ecdh_curves=` to `#groups=` following + the underlying OpenSSL API rename. This method is no longer specific to + ECDHE. The old method remains as an alias. + [[GitHub #900]](https://github.com/ruby/openssl/pull/900) + - Add `OpenSSL::SSL::SSLSocket#sigalg`, `#peer_sigalg`, and `#group` for + getting the signature algorithm and the key agreement group used in the + current connection. + [[GitHub #908]](https://github.com/ruby/openssl/pull/908) + - Enable `SSL_CTX_set_dh_auto()` for servers by default. + [[GitHub #924]](https://github.com/ruby/openssl/pull/924) + - Improve Ractor compatibility. Note that the internal-use constant + `OpenSSL::SSL::SSLContext::DEFAULT_PARAMS` is now frozen. + [[GitHub #925]](https://github.com/ruby/openssl/pull/925) +* `OpenSSL::PKey` + - Remove `OpenSSL::PKey::EC::Point#mul` support with array arguments. The + underlying OpenSSL API has been removed, and the method has been deprecated + since ruby/openssl v3.0.0. + [[GitHub #843]](https://github.com/ruby/openssl/pull/843) + - `OpenSSL::PKey::{RSA,DSA,DH}#params` uses `nil` to indicate missing fields + instead of the number `0`. + [[GitHub #774]](https://github.com/ruby/openssl/pull/774) + - Unify `OpenSSL::PKey::PKeyError` classes. The former subclasses + `OpenSSL::PKey::DHError`, `OpenSSL::PKey::DSAError`, + `OpenSSL::PKey::ECError`, and `OpenSSL::PKey::RSAError` have been merged + into a single class. + [[GitHub #929]](https://github.com/ruby/openssl/pull/929) +* `OpenSSL::Cipher` + - `OpenSSL::Cipher#encrypt` and `#decrypt` no longer accept arguments. + Passing passwords has been deprecated since Ruby 1.8.2 (released in 2004). + [[GitHub #887]](https://github.com/ruby/openssl/pull/887) + - `OpenSSL::Cipher#final` raises `OpenSSL::Cipher::AuthTagError` when the + integrity check fails for AEAD ciphers. `OpenSSL::Cipher::AuthTagError` is a + new subclass of `OpenSSL::Cipher::CipherError`, which was previously raised. + [[GitHub #939]](https://github.com/ruby/openssl/pull/939) + - `OpenSSL::Cipher.new` now raises `OpenSSL::Cipher::CipherError` instead of + `RuntimeError` when OpenSSL does not recognize the algorithm. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) + - Add support for "fetched" cipher algorithms with OpenSSL 3.0 or later. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) +* `OpenSSL::Digest` + - `OpenSSL::Digest.new` now raises `OpenSSL::Digest::DigestError` instead of + `RuntimeError` when OpenSSL does not recognize the algorithm. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) + - Add support for "fetched" digest algorithms with OpenSSL 3.0 or later. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) +* `OpenSSL::ASN1.decode` now assumes a 1950-2049 year range for `UTCTime` + according to RFC 5280. It previously used a 1969-2068 range. The encoder + has always used the 1950-2049 range. + [[GitHub #909]](https://github.com/ruby/openssl/pull/909) +* `OpenSSL::OpenSSLError`, the base class for all ruby/openssl errors, carry + an additional attribute `#errors` to keep the content of OpenSSL's error + queue. Also, add `#detailed_message` for Ruby 3.2 or later. + [[GitHub #976]](https://github.com/ruby/openssl/pull/976) +* `OpenSSL::PKCS7.new` raises `OpenSSL::PKCS7::PKCS7Error` instead of + `ArgumentError` on error to be consistent with other constructors. + [[GitHub #983]](https://github.com/ruby/openssl/pull/983) + + +Version 3.3.3 +============= + +Merged changes in 3.2.4. + + +Version 3.3.2 +============= + +Merged changes in 3.1.3 and 3.2.3. + + +Version 3.3.1 +============= + +Merged changes in 3.1.2 and 3.2.2. + + Version 3.3.0 ============= @@ -74,6 +203,28 @@ And various non-user-visible changes and bug fixes. Please see the commit history for more details. +Version 3.2.4 +============= + +Notable changes +--------------- + +* Add support for OpenSSL 4.0. + [[GitHub #1051]](https://github.com/ruby/openssl/pull/1051) + + +Version 3.2.3 +============= + +Merged changes in 3.1.3. + + +Version 3.2.2 +============= + +Merged changes in 3.1.2. + + Version 3.2.1 ============= @@ -120,6 +271,33 @@ Notable changes [[GitHub #141]](https://github.com/ruby/openssl/pull/141) +Version 3.1.3 +============= + +Bug fixes +--------- + +* Fix missing NULL check for `EVP_PKEY_get0()` functions with OpenSSL 3.x. + [[GitHub #957]](https://github.com/ruby/openssl/pull/957) + + +Version 3.1.2 +============= + +Bug fixes +--------- + +* Fix crash when attempting to export an incomplete `OpenSSL::PKey::DSA` key. + [[GitHub #845]](https://github.com/ruby/openssl/issues/845) + [[GitHub #847]](https://github.com/ruby/openssl/pull/847) +* Remove the `OpenSSL::X509::V_FLAG_CRL_CHECK_ALL` flag from the default store + used by `OpenSSL::SSL::SSLContext#set_params`. It causes certificate + verification to fail with OpenSSL 3.6.0. It has no effect with any other + OpenSSL versions. + [[GitHub #949]](https://github.com/ruby/openssl/issues/949) + [[GitHub #950]](https://github.com/ruby/openssl/pull/950) + + Version 3.1.1 ============= diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 8aac52ef47..1f32980940 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -38,8 +38,12 @@ Logging::message "=== OpenSSL for Ruby configurator ===\n" $defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") +# Missing in TruffleRuby +have_func("rb_call_super_kw(0, NULL, 0)", "ruby.h") +# Ruby 3.1 have_func("rb_io_descriptor", "ruby/io.h") -have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.1 +have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") +# Ruby 3.2 have_func("rb_io_timeout", "ruby/io.h") Logging::message "=== Checking for system dependent stuff... ===\n" @@ -131,6 +135,7 @@ Logging::message "=== Checking for OpenSSL features... ===\n" evp_h = "openssl/evp.h".freeze ts_h = "openssl/ts.h".freeze ssl_h = "openssl/ssl.h".freeze +stack_h = "openssl/stack.h".freeze # compile options have_func("RAND_egd()", "openssl/rand.h") @@ -146,8 +151,11 @@ have_func("EVP_PBE_scrypt(\"\", 0, (unsigned char *)\"\", 0, 0, 0, 0, 0, NULL, 0 # added in OpenSSL 1.1.1 and LibreSSL 3.5.0, then removed in LibreSSL 4.0.0 have_func("EVP_PKEY_check(NULL)", evp_h) +# added in OpenSSL 1.1.1, currently not in LibreSSL +have_func("OPENSSL_sk_new_reserve(NULL, 0)", stack_h) + # added in 3.0.0 -have_func("SSL_set0_tmp_dh_pkey(NULL, NULL)", ssl_h) +have_func("SSL_CTX_set0_tmp_dh_pkey(NULL, NULL)", ssl_h) have_func("ERR_get_error_all(NULL, NULL, NULL, NULL, NULL)", "openssl/err.h") have_func("SSL_CTX_load_verify_file(NULL, \"\")", ssl_h) have_func("BN_check_prime(NULL, NULL, NULL)", "openssl/bn.h") @@ -165,6 +173,9 @@ have_func("TS_VERIFY_CTX_set0_certs(NULL, NULL)", ts_h) # added in 3.5.0 have_func("SSL_get0_peer_signature_name(NULL, NULL)", ssl_h) +# added in 4.0.0 +have_func("ASN1_BIT_STRING_set1(NULL, NULL, 0, 0)", "openssl/asn1.h") + Logging::message "=== Checking done. ===\n" # Append flags from environment variables. diff --git a/ext/openssl/lib/openssl.rb b/ext/openssl/lib/openssl.rb index 48f88dcbba..98fa8d39f2 100644 --- a/ext/openssl/lib/openssl.rb +++ b/ext/openssl/lib/openssl.rb @@ -12,7 +12,6 @@ require 'openssl.so' -require_relative 'openssl/asn1' require_relative 'openssl/bn' require_relative 'openssl/cipher' require_relative 'openssl/digest' @@ -24,12 +23,16 @@ require_relative 'openssl/version' require_relative 'openssl/x509' module OpenSSL - # call-seq: - # OpenSSL.secure_compare(string, string) -> boolean + # :call-seq: + # OpenSSL.secure_compare(string, string) -> true or false # # Constant time memory comparison. Inputs are hashed using SHA-256 to mask # the length of the secret. Returns +true+ if the strings are identical, # +false+ otherwise. + # + # This method is expensive due to the SHA-256 hashing. In most cases, where + # the input lengths are known to be equal or are not sensitive, + # OpenSSL.fixed_length_secure_compare should be used instead. def self.secure_compare(a, b) hashed_a = OpenSSL::Digest.digest('SHA256', a) hashed_b = OpenSSL::Digest.digest('SHA256', b) diff --git a/ext/openssl/lib/openssl/asn1.rb b/ext/openssl/lib/openssl/asn1.rb deleted file mode 100644 index 89fa28e1fb..0000000000 --- a/ext/openssl/lib/openssl/asn1.rb +++ /dev/null @@ -1,188 +0,0 @@ -# frozen_string_literal: true -#-- -# -# = Ruby-space definitions that completes C-space funcs for ASN.1 -# -# = Licence -# This program is licensed under the same licence as Ruby. -# (See the file 'COPYING'.) -#++ - -module OpenSSL - module ASN1 - class ASN1Data - # - # Carries the value of a ASN.1 type. - # Please confer Constructive and Primitive for the mappings between - # ASN.1 data types and Ruby classes. - # - attr_accessor :value - - # An Integer representing the tag number of this ASN1Data. Never +nil+. - attr_accessor :tag - - # A Symbol representing the tag class of this ASN1Data. Never +nil+. - # See ASN1Data for possible values. - attr_accessor :tag_class - - # - # Never +nil+. A boolean value indicating whether the encoding uses - # indefinite length (in the case of parsing) or whether an indefinite - # length form shall be used (in the encoding case). - # In DER, every value uses definite length form. But in scenarios where - # large amounts of data need to be transferred it might be desirable to - # have some kind of streaming support available. - # For example, huge OCTET STRINGs are preferably sent in smaller-sized - # chunks, each at a time. - # This is possible in BER by setting the length bytes of an encoding - # to zero and by this indicating that the following value will be - # sent in chunks. Indefinite length encodings are always constructed. - # The end of such a stream of chunks is indicated by sending a EOC - # (End of Content) tag. SETs and SEQUENCEs may use an indefinite length - # encoding, but also primitive types such as e.g. OCTET STRINGS or - # BIT STRINGS may leverage this functionality (cf. ITU-T X.690). - # - attr_accessor :indefinite_length - - alias infinite_length indefinite_length - alias infinite_length= indefinite_length= - - # - # :call-seq: - # OpenSSL::ASN1::ASN1Data.new(value, tag, tag_class) => ASN1Data - # - # _value_: Please have a look at Constructive and Primitive to see how Ruby - # types are mapped to ASN.1 types and vice versa. - # - # _tag_: An Integer indicating the tag number. - # - # _tag_class_: A Symbol indicating the tag class. Please cf. ASN1 for - # possible values. - # - # == Example - # asn1_int = OpenSSL::ASN1Data.new(42, 2, :UNIVERSAL) # => Same as OpenSSL::ASN1::Integer.new(42) - # tagged_int = OpenSSL::ASN1Data.new(42, 0, :CONTEXT_SPECIFIC) # implicitly 0-tagged INTEGER - # - def initialize(value, tag, tag_class) - raise ASN1Error, "invalid tag class" unless tag_class.is_a?(Symbol) - - @tag = tag - @value = value - @tag_class = tag_class - @indefinite_length = false - end - end - - module TaggedASN1Data - # - # May be used as a hint for encoding a value either implicitly or - # explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. - # _tagging_ is not set when a ASN.1 structure is parsed using - # OpenSSL::ASN1.decode. - # - attr_accessor :tagging - - # :call-seq: - # OpenSSL::ASN1::Primitive.new(value [, tag, tagging, tag_class ]) => Primitive - # - # _value_: is mandatory. - # - # _tag_: optional, may be specified for tagged values. If no _tag_ is - # specified, the UNIVERSAL tag corresponding to the Primitive sub-class - # is used by default. - # - # _tagging_: may be used as an encoding hint to encode a value either - # explicitly or implicitly, see ASN1 for possible values. - # - # _tag_class_: if _tag_ and _tagging_ are +nil+ then this is set to - # +:UNIVERSAL+ by default. If either _tag_ or _tagging_ are set then - # +:CONTEXT_SPECIFIC+ is used as the default. For possible values please - # cf. ASN1. - # - # == Example - # int = OpenSSL::ASN1::Integer.new(42) - # zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :IMPLICIT) - # private_explicit_zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :EXPLICIT, :PRIVATE) - # - def initialize(value, tag = nil, tagging = nil, tag_class = nil) - tag ||= ASN1.take_default_tag(self.class) - - raise ASN1Error, "must specify tag number" unless tag - - if tagging - raise ASN1Error, "invalid tagging method" unless tagging.is_a?(Symbol) - end - - tag_class ||= tagging ? :CONTEXT_SPECIFIC : :UNIVERSAL - - raise ASN1Error, "invalid tag class" unless tag_class.is_a?(Symbol) - - @tagging = tagging - super(value ,tag, tag_class) - end - end - - class Primitive < ASN1Data - include TaggedASN1Data - - undef_method :indefinite_length= - undef_method :infinite_length= - end - - class Constructive < ASN1Data - include TaggedASN1Data - include Enumerable - - # :call-seq: - # asn1_ary.each { |asn1| block } => asn1_ary - # - # Calls the given block once for each element in self, passing that element - # as parameter _asn1_. If no block is given, an enumerator is returned - # instead. - # - # == Example - # asn1_ary.each do |asn1| - # puts asn1 - # end - # - def each(&blk) - @value.each(&blk) - - self - end - end - - class Boolean < Primitive ; end - class Integer < Primitive ; end - class Enumerated < Primitive ; end - - class BitString < Primitive - attr_accessor :unused_bits - - def initialize(*) - super - - @unused_bits = 0 - end - end - - class EndOfContent < ASN1Data - def initialize - super("", 0, :UNIVERSAL) - end - end - - # :nodoc: - def self.take_default_tag(klass) - tag = CLASS_TAG_MAP[klass] - - return tag if tag - - sklass = klass.superclass - - return unless sklass - - take_default_tag(sklass) - end - end -end diff --git a/ext/openssl/lib/openssl/digest.rb b/ext/openssl/lib/openssl/digest.rb index 5cda1e931c..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 @@ -57,7 +61,7 @@ module OpenSSL # OpenSSL::Digest("MD5") # # => OpenSSL::Digest::MD5 # - # Digest("Foo") + # OpenSSL::Digest("Foo") # # => NameError: wrong constant name Foo def Digest(name) diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb index 3d1e8885ca..39871e15dd 100644 --- a/ext/openssl/lib/openssl/pkey.rb +++ b/ext/openssl/lib/openssl/pkey.rb @@ -7,6 +7,9 @@ require_relative 'marshal' module OpenSSL::PKey + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + DHError = PKeyError + class DH include OpenSSL::Marshal @@ -102,7 +105,7 @@ module OpenSSL::PKey # puts dh0.pub_key == dh.pub_key #=> false def generate_key! if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 - raise DHError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \ + raise PKeyError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \ "use OpenSSL::PKey.generate_key instead" end @@ -147,6 +150,9 @@ module OpenSSL::PKey end end + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + DSAError = PKeyError + class DSA include OpenSSL::Marshal @@ -242,13 +248,9 @@ module OpenSSL::PKey # sig = dsa.sign_raw(nil, digest) # p dsa.verify_raw(nil, sig, digest) #=> true def syssign(string) - q or raise OpenSSL::PKey::DSAError, "incomplete DSA" - private? or raise OpenSSL::PKey::DSAError, "Private DSA key needed!" - begin - sign_raw(nil, string) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::DSAError, $!.message - end + q or raise PKeyError, "incomplete DSA" + private? or raise PKeyError, "Private DSA key needed!" + sign_raw(nil, string) end # :call-seq: @@ -266,12 +268,13 @@ module OpenSSL::PKey # A \DSA signature value. def sysverify(digest, sig) verify_raw(nil, sig, digest) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::DSAError, $!.message end end if defined?(EC) + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + ECError = PKeyError + class EC include OpenSSL::Marshal @@ -282,8 +285,6 @@ module OpenSSL::PKey # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. def dsa_sign_asn1(data) sign_raw(nil, data) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::ECError, $!.message end # :call-seq: @@ -293,8 +294,6 @@ module OpenSSL::PKey # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. def dsa_verify_asn1(data, sig) verify_raw(nil, sig, data) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::ECError, $!.message end # :call-seq: @@ -334,6 +333,9 @@ module OpenSSL::PKey end end + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + RSAError = PKeyError + class RSA include OpenSSL::Marshal @@ -407,15 +409,11 @@ module OpenSSL::PKey # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and # PKey::PKey#verify_recover instead. def private_encrypt(string, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - private? or raise OpenSSL::PKey::RSAError, "private key needed." - begin - sign_raw(nil, string, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + private? or raise PKeyError, "private key needed." + sign_raw(nil, string, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end # :call-seq: @@ -430,14 +428,10 @@ module OpenSSL::PKey # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and # PKey::PKey#verify_recover instead. def public_decrypt(string, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - begin - verify_recover(nil, string, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + verify_recover(nil, string, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end # :call-seq: @@ -452,14 +446,10 @@ module OpenSSL::PKey # <b>Deprecated in version 3.0</b>. # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead. def public_encrypt(data, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - begin - encrypt(data, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + encrypt(data, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end # :call-seq: @@ -473,15 +463,11 @@ module OpenSSL::PKey # <b>Deprecated in version 3.0</b>. # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead. def private_decrypt(data, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - private? or raise OpenSSL::PKey::RSAError, "private key needed." - begin - decrypt(data, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + private? or raise PKeyError, "private key needed." + decrypt(data, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end PKCS1_PADDING = 1 @@ -500,7 +486,7 @@ module OpenSSL::PKey when PKCS1_OAEP_PADDING "oaep" else - raise OpenSSL::PKey::PKeyError, "unsupported padding mode" + raise PKeyError, "unsupported padding mode" end end end diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index a0ad5dc3a6..3268c126b9 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -32,25 +32,6 @@ module OpenSSL }.call } - if defined?(OpenSSL::PKey::DH) - DH_ffdhe2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz -+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a -87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 -YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi -7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD -ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== ------END DH PARAMETERS----- - _end_of_pem_ - private_constant :DH_ffdhe2048 - - DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc: - warn "using default DH parameters." if $VERBOSE - DH_ffdhe2048 - } - end - if !OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL") DEFAULT_PARAMS.merge!( min_version: OpenSSL::SSL::TLS1_VERSION, @@ -85,26 +66,13 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== AES256-SHA256 AES128-SHA AES256-SHA - }.join(":"), + }.join(":").freeze, ) end + DEFAULT_PARAMS.freeze DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: DEFAULT_CERT_STORE.set_default_paths - DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - - # A callback invoked when DH parameters are required for ephemeral DH key - # exchange. - # - # The callback is invoked with the SSLSocket, a - # flag indicating the use of an export cipher and the keylength - # required. - # - # The callback must return an OpenSSL::PKey::DH instance of the correct - # key length. - # - # <b>Deprecated in version 3.0.</b> Use #tmp_dh= instead. - attr_accessor :tmp_dh_callback # A callback invoked at connect time to distinguish between multiple # server names. @@ -147,7 +115,14 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== params.each{|name, value| self.__send__("#{name}=", value) } if self.verify_mode != OpenSSL::SSL::VERIFY_NONE unless self.ca_file or self.ca_path or self.cert_store - self.cert_store = DEFAULT_CERT_STORE + if not defined?(Ractor) or Ractor.current == Ractor.main + self.cert_store = DEFAULT_CERT_STORE + else + self.cert_store = Ractor.current[:__openssl_default_store__] ||= + OpenSSL::X509::Store.new.tap { |store| + store.set_default_paths + } + end end end return params @@ -457,10 +432,6 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== @context.client_cert_cb end - def tmp_dh_callback - @context.tmp_dh_callback || OpenSSL::SSL::SSLContext::DEFAULT_TMP_DH_CALLBACK - end - def session_new_cb @context.session_new_cb end diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb index 3398fe39cc..395a720a31 100644 --- a/ext/openssl/lib/openssl/version.rb +++ b/ext/openssl/lib/openssl/version.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true module OpenSSL - VERSION = "3.3.0" + # The version string of Ruby/OpenSSL. + VERSION = "4.0.2" end diff --git a/ext/openssl/lib/openssl/x509.rb b/ext/openssl/lib/openssl/x509.rb index 6459d37b12..66765ffeab 100644 --- a/ext/openssl/lib/openssl/x509.rb +++ b/ext/openssl/lib/openssl/x509.rb @@ -346,6 +346,15 @@ module OpenSSL include Extension::CRLDistributionPoints include Extension::AuthorityInfoAccess + def inspect + "#<#{self.class}: " \ + "subject=#{subject.inspect}, " \ + "issuer=#{issuer.inspect}, " \ + "serial=#{serial.inspect}, " \ + "not_before=#{not_before.inspect rescue "(error)"}, " \ + "not_after=#{not_after.inspect rescue "(error)"}>" + end + def pretty_print(q) q.object_group(self) { q.breakable diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 2ec1551885..af1775e3b0 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "3.3.0" + spec.version = "4.0.2" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] spec.summary = %q{SSL/TLS and general-purpose cryptography for Ruby} diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h index 6592f9ccea..ed3b5b7c0f 100644 --- a/ext/openssl/openssl_missing.h +++ b/ext/openssl/openssl_missing.h @@ -29,4 +29,27 @@ # define EVP_PKEY_eq(a, b) EVP_PKEY_cmp(a, b) #endif +/* added in 4.0.0 */ +#ifndef HAVE_ASN1_BIT_STRING_SET1 +static inline int +ASN1_BIT_STRING_set1(ASN1_BIT_STRING *bitstr, const uint8_t *data, + size_t length, int unused_bits) +{ + if (length > INT_MAX || !ASN1_STRING_set(bitstr, data, (int)length)) + return 0; + bitstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07); + bitstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits; + return 1; +} + +static inline int +ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *bitstr, size_t *length, + int *unused_bits) +{ + *length = bitstr->length; + *unused_bits = bitstr->flags & 0x07; + return 1; +} +#endif + #endif /* _OSSL_OPENSSL_MISSING_H_ */ diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 60780790b0..5716e6f100 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -13,71 +13,75 @@ /* * Data Conversion */ -#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ -VALUE \ -ossl_##name##_ary2sk0(VALUE ary) \ -{ \ - STACK_OF(type) *sk; \ - VALUE val; \ - type *x; \ - int i; \ - \ - Check_Type(ary, T_ARRAY); \ - sk = sk_##type##_new_null(); \ - if (!sk) ossl_raise(eOSSLError, NULL); \ - \ - for (i = 0; i < RARRAY_LEN(ary); i++) { \ - val = rb_ary_entry(ary, i); \ - if (!rb_obj_is_kind_of(val, expected_class)) { \ - sk_##type##_pop_free(sk, type##_free); \ - ossl_raise(eOSSLError, "object in array not" \ - " of class ##type##"); \ - } \ - x = dup(val); /* NEED TO DUP */ \ - sk_##type##_push(sk, x); \ - } \ - return (VALUE)sk; \ -} \ - \ -STACK_OF(type) * \ -ossl_protect_##name##_ary2sk(VALUE ary, int *status) \ -{ \ - return (STACK_OF(type)*)rb_protect( \ - (VALUE (*)(VALUE))ossl_##name##_ary2sk0, \ - ary, \ - status); \ -} \ - \ -STACK_OF(type) * \ -ossl_##name##_ary2sk(VALUE ary) \ -{ \ - STACK_OF(type) *sk; \ - int status = 0; \ - \ - sk = ossl_protect_##name##_ary2sk(ary, &status); \ - if (status) rb_jump_tag(status); \ - \ - return sk; \ +#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ +VALUE \ +ossl_##name##_ary2sk0(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + VALUE val; \ + type *x; \ + int i; \ + \ + Check_Type(ary, T_ARRAY); \ + sk = sk_##type##_new_null(); \ + if (!sk) ossl_raise(eOSSLError, NULL); \ + \ + for (i = 0; i < RARRAY_LEN(ary); i++) { \ + val = rb_ary_entry(ary, i); \ + if (!rb_obj_is_kind_of(val, expected_class)) { \ + sk_##type##_pop_free(sk, type##_free); \ + ossl_raise(eOSSLError, "object in array not" \ + " of class ##type##"); \ + } \ + x = dup(val); /* NEED TO DUP */ \ + if (!sk_##type##_push(sk, x)) { \ + type##_free(x); \ + sk_##type##_pop_free(sk, type##_free); \ + ossl_raise(eOSSLError, NULL); \ + } \ + } \ + return (VALUE)sk; \ +} \ + \ +STACK_OF(type) * \ +ossl_protect_##name##_ary2sk(VALUE ary, int *status) \ +{ \ + return (STACK_OF(type)*)rb_protect( \ + (VALUE (*)(VALUE))ossl_##name##_ary2sk0, \ + ary, \ + status); \ +} \ + \ +STACK_OF(type) * \ +ossl_##name##_ary2sk(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + int status = 0; \ + \ + sk = ossl_protect_##name##_ary2sk(ary, &status); \ + if (status) rb_jump_tag(status); \ + \ + return sk; \ } OSSL_IMPL_ARY2SK(x509, X509, cX509Cert, DupX509CertPtr) -#define OSSL_IMPL_SK2ARY(name, type) \ -VALUE \ -ossl_##name##_sk2ary(const STACK_OF(type) *sk) \ -{ \ - type *t; \ - int i, num; \ - VALUE ary; \ - \ - RUBY_ASSERT(sk != NULL); \ - num = sk_##type##_num(sk); \ - ary = rb_ary_new_capa(num); \ - \ - for (i=0; i<num; i++) { \ - t = sk_##type##_value(sk, i); \ - rb_ary_push(ary, ossl_##name##_new(t)); \ - } \ - return ary; \ +#define OSSL_IMPL_SK2ARY(name, type) \ +VALUE \ +ossl_##name##_sk2ary(const STACK_OF(type) *sk) \ +{ \ + type *t; \ + int i, num; \ + VALUE ary; \ + \ + RUBY_ASSERT(sk != NULL); \ + num = sk_##type##_num(sk); \ + ary = rb_ary_new_capa(num); \ + \ + for (i=0; i<num; i++) { \ + t = sk_##type##_value(sk, i); \ + rb_ary_push(ary, ossl_##name##_new(t)); \ + } \ + return ary; \ } OSSL_IMPL_SK2ARY(x509, X509) OSSL_IMPL_SK2ARY(x509crl, X509_CRL) @@ -97,14 +101,14 @@ ossl_str_new(const char *ptr, long len, int *pstate) str = rb_protect(ossl_str_new_i, len, &state); if (pstate) - *pstate = state; + *pstate = state; if (state) { - if (!pstate) - rb_set_errinfo(Qnil); - return Qnil; + if (!pstate) + rb_set_errinfo(Qnil); + return Qnil; } if (ptr) - memcpy(RSTRING_PTR(str), ptr, len); + memcpy(RSTRING_PTR(str), ptr, len); return str; } @@ -117,22 +121,22 @@ ossl_buf2str(char *buf, int len) str = ossl_str_new(buf, len, &state); OPENSSL_free(buf); if (state) - rb_jump_tag(state); + rb_jump_tag(state); return str; } void -ossl_bin2hex(unsigned char *in, char *out, size_t inlen) +ossl_bin2hex(const unsigned char *in, char *out, size_t inlen) { const char *hex = "0123456789abcdef"; size_t i; assert(inlen <= LONG_MAX / 2); for (i = 0; i < inlen; i++) { - unsigned char p = in[i]; + unsigned char p = in[i]; - out[i * 2 + 0] = hex[p >> 4]; - out[i * 2 + 1] = hex[p & 0x0f]; + out[i * 2 + 0] = hex[p >> 4]; + out[i * 2 + 1] = hex[p & 0x0f]; } } @@ -143,14 +147,14 @@ VALUE ossl_pem_passwd_value(VALUE pass) { if (NIL_P(pass)) - return Qnil; + return Qnil; StringValue(pass); /* PEM_BUFSIZE is currently used as the second argument of pem_password_cb, * that is +max_len+ of ossl_pem_passwd_cb() */ if (RSTRING_LEN(pass) > PEM_BUFSIZE) - ossl_raise(eOSSLError, "password must not be longer than %d bytes", PEM_BUFSIZE); + ossl_raise(eOSSLError, "password must not be longer than %d bytes", PEM_BUFSIZE); return pass; } @@ -160,7 +164,7 @@ ossl_pem_passwd_cb0(VALUE flag) { VALUE pass = rb_yield(flag); if (NIL_P(pass)) - return Qnil; + return Qnil; StringValue(pass); return pass; } @@ -173,46 +177,46 @@ ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd_) VALUE rflag, pass = (VALUE)pwd_; if (RTEST(pass)) { - /* PEM_def_callback(buf, max_len, flag, StringValueCStr(pass)) does not - * work because it does not allow NUL characters and truncates to 1024 - * bytes silently if the input is over 1024 bytes */ - if (RB_TYPE_P(pass, T_STRING)) { - len = RSTRING_LEN(pass); - if (len <= max_len) { - memcpy(buf, RSTRING_PTR(pass), len); - return (int)len; - } - } - OSSL_Debug("passed data is not valid String???"); - return -1; + /* PEM_def_callback(buf, max_len, flag, StringValueCStr(pass)) does not + * work because it does not allow NUL characters and truncates to 1024 + * bytes silently if the input is over 1024 bytes */ + if (RB_TYPE_P(pass, T_STRING)) { + len = RSTRING_LEN(pass); + if (len <= max_len) { + memcpy(buf, RSTRING_PTR(pass), len); + return (int)len; + } + } + OSSL_Debug("passed data is not valid String???"); + return -1; } if (!rb_block_given_p()) { - return PEM_def_callback(buf, max_len, flag, NULL); + return PEM_def_callback(buf, max_len, flag, NULL); } while (1) { - /* - * when the flag is nonzero, this password - * will be used to perform encryption; otherwise it will - * be used to perform decryption. - */ - rflag = flag ? Qtrue : Qfalse; - pass = rb_protect(ossl_pem_passwd_cb0, rflag, &status); - if (status) { - /* ignore an exception raised. */ - rb_set_errinfo(Qnil); - return -1; - } - if (NIL_P(pass)) - return -1; - len = RSTRING_LEN(pass); - if (len > max_len) { - rb_warning("password must not be longer than %d bytes", max_len); - continue; - } - memcpy(buf, RSTRING_PTR(pass), len); - break; + /* + * when the flag is nonzero, this password + * will be used to perform encryption; otherwise it will + * be used to perform decryption. + */ + rflag = flag ? Qtrue : Qfalse; + pass = rb_protect(ossl_pem_passwd_cb0, rflag, &status); + if (status) { + /* ignore an exception raised. */ + rb_set_errinfo(Qnil); + return -1; + } + if (NIL_P(pass)) + return -1; + len = RSTRING_LEN(pass); + if (len > max_len) { + rb_warning("password must not be longer than %d bytes", max_len); + continue; + } + memcpy(buf, RSTRING_PTR(pass), len); + break; } return (int)len; } @@ -247,19 +251,24 @@ VALUE ossl_to_der_if_possible(VALUE obj) { if(rb_respond_to(obj, ossl_s_to_der)) - return ossl_to_der(obj); + return ossl_to_der(obj); return obj; } /* * Errors */ +static ID id_i_errors; + +static void collect_errors_into(VALUE ary); + VALUE ossl_make_error(VALUE exc, VALUE str) { unsigned long e; const char *data; int flags; + VALUE errors = rb_ary_new(); if (NIL_P(str)) str = rb_str_new(NULL, 0); @@ -276,10 +285,12 @@ ossl_make_error(VALUE exc, VALUE str) rb_str_cat_cstr(str, msg ? msg : "(null)"); if (flags & ERR_TXT_STRING && data) rb_str_catf(str, " (%s)", data); - ossl_clear_error(); + collect_errors_into(errors); } - return rb_exc_new_str(exc, str); + VALUE obj = rb_exc_new_str(exc, str); + rb_ivar_set(obj, id_i_errors, errors); + return obj; } void @@ -289,24 +300,23 @@ ossl_raise(VALUE exc, const char *fmt, ...) VALUE err; if (fmt) { - va_start(args, fmt); - err = rb_vsprintf(fmt, args); - va_end(args); + va_start(args, fmt); + err = rb_vsprintf(fmt, args); + va_end(args); } else { - err = Qnil; + err = Qnil; } rb_exc_raise(ossl_make_error(exc, err)); } -void -ossl_clear_error(void) +static void +collect_errors_into(VALUE ary) { - if (dOSSL == Qtrue) { + if (dOSSL == Qtrue || !NIL_P(ary)) { unsigned long e; const char *file, *data, *func, *lib, *reason; - char append[256] = ""; int line, flags; #ifdef HAVE_ERR_GET_ERROR_ALL @@ -318,13 +328,18 @@ ossl_clear_error(void) lib = ERR_lib_error_string(e); reason = ERR_reason_error_string(e); + VALUE str = rb_sprintf("error:%08lX:%s:%s:%s", e, lib ? lib : "", + func ? func : "", reason ? reason : ""); if (flags & ERR_TXT_STRING) { if (!data) data = "(null)"; - snprintf(append, sizeof(append), " (%s)", data); + rb_str_catf(str, " (%s)", data); } - rb_warn("error on stack: error:%08lX:%s:%s:%s%s", e, lib ? lib : "", - func ? func : "", reason ? reason : "", append); + + if (dOSSL == Qtrue) + rb_warn("error on stack: %"PRIsVALUE, str); + if (!NIL_P(ary)) + rb_ary_push(ary, str); } } else { @@ -332,14 +347,59 @@ ossl_clear_error(void) } } +void +ossl_clear_error(void) +{ + collect_errors_into(Qnil); +} + +/* + * call-seq: + * ossl_error.detailed_message(**) -> string + * + * Returns the exception message decorated with the captured \OpenSSL error + * queue entries. + */ +static VALUE +osslerror_detailed_message(int argc, VALUE *argv, VALUE self) +{ + VALUE str; +#ifdef HAVE_RB_CALL_SUPER_KW + // Ruby >= 3.2 + if (RTEST(rb_funcall(rb_eException, rb_intern("method_defined?"), 1, + ID2SYM(rb_intern("detailed_message"))))) + str = rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS); + else +#endif + str = rb_funcall(self, rb_intern("message"), 0); + VALUE errors = rb_attr_get(self, id_i_errors); + + // OpenSSLError was not created by ossl_make_error() + if (!RB_TYPE_P(errors, T_ARRAY)) + return str; + + str = rb_str_resurrect(str); + rb_str_catf(str, "\nOpenSSL error queue reported %ld errors:", + RARRAY_LEN(errors)); + for (long i = 0; i < RARRAY_LEN(errors); i++) { + VALUE err = RARRAY_AREF(errors, i); + rb_str_catf(str, "\n%"PRIsVALUE, err); + } + return str; +} + /* * call-seq: * OpenSSL.errors -> [String...] * - * See any remaining errors held in queue. + * Returns any remaining errors held in the \OpenSSL thread-local error queue + * and clears the queue. This should normally return an empty array. + * + * This is intended for debugging Ruby/OpenSSL. If you see any errors here, + * it likely indicates a bug in the extension. Please file an issue at + * https://github.com/ruby/openssl. * - * Any errors you see here are probably due to a bug in Ruby's OpenSSL - * implementation. + * For debugging your program, OpenSSL.debug= may be useful. */ static VALUE ossl_get_errors(VALUE _) @@ -363,6 +423,8 @@ VALUE dOSSL; /* * call-seq: * OpenSSL.debug -> true | false + * + * Returns whether Ruby/OpenSSL's debug mode is currently enabled. */ static VALUE ossl_debug_get(VALUE self) @@ -372,9 +434,9 @@ ossl_debug_get(VALUE self) /* * call-seq: - * OpenSSL.debug = boolean -> boolean + * OpenSSL.debug = boolean * - * Turns on or off debug mode. With debug mode, all errors added to the OpenSSL + * Turns on or off debug mode. With debug mode, all errors added to the \OpenSSL * error queue will be printed to stderr. */ static VALUE @@ -388,6 +450,8 @@ ossl_debug_set(VALUE self, VALUE val) /* * call-seq: * OpenSSL.fips_mode -> true | false + * + * Returns whether the FIPS mode is currently enabled. */ static VALUE ossl_fips_mode_get(VALUE self) @@ -408,10 +472,10 @@ ossl_fips_mode_get(VALUE self) /* * call-seq: - * OpenSSL.fips_mode = boolean -> boolean + * OpenSSL.fips_mode = boolean * * Turns FIPS mode on or off. Turning on FIPS mode will obviously only have an - * effect for FIPS-capable installations of the OpenSSL library. Trying to do + * effect for FIPS-capable installations of the \OpenSSL library. Trying to do * so otherwise will result in an error. * * === Examples @@ -434,52 +498,60 @@ ossl_fips_mode_set(VALUE self, VALUE enabled) return enabled; #elif defined(OPENSSL_FIPS) || defined(OPENSSL_IS_AWSLC) if (RTEST(enabled)) { - int mode = FIPS_mode(); - if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ - ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + int mode = FIPS_mode(); + if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); } else { - if(!FIPS_mode_set(0)) /* turning off twice is OK */ - ossl_raise(eOSSLError, "Turning off FIPS mode failed"); + if(!FIPS_mode_set(0)) /* turning off twice is OK */ + ossl_raise(eOSSLError, "Turning off FIPS mode failed"); } return enabled; #else if (RTEST(enabled)) - ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode"); + ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode"); return enabled; #endif } /* * call-seq: - * OpenSSL.fixed_length_secure_compare(string, string) -> boolean + * OpenSSL.fixed_length_secure_compare(string, string) -> true or false * * Constant time memory comparison for fixed length strings, such as results - * of HMAC calculations. + * of \HMAC calculations. * * Returns +true+ if the strings are identical, +false+ if they are of the same - * length but not identical. If the length is different, +ArgumentError+ is + * length but not identical. If the length is different, ArgumentError is * raised. */ static VALUE ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) { - const unsigned char *p1 = (const unsigned char *)StringValuePtr(str1); - const unsigned char *p2 = (const unsigned char *)StringValuePtr(str2); - long len1 = RSTRING_LEN(str1); - long len2 = RSTRING_LEN(str2); + const unsigned char *p1; + const unsigned char *p2; + long len1; + long len2; + + StringValue(str1); + StringValue(str2); + + p1 = (const unsigned char *)RSTRING_PTR(str1); + p2 = (const unsigned char *)RSTRING_PTR(str2); + len1 = RSTRING_LEN(str1); + len2 = RSTRING_LEN(str2); if (len1 != len2) { ossl_raise(rb_eArgError, "inputs must be of equal length"); } switch (CRYPTO_memcmp(p1, p2, len1)) { - case 0: return Qtrue; - default: return Qfalse; + case 0: return Qtrue; + default: return Qfalse; } } /* - * OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the + * OpenSSL provides \SSL, TLS and general purpose cryptography. It wraps the * OpenSSL[https://www.openssl.org/] library. * * = Examples @@ -534,7 +606,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * === Loading an Encrypted Key * - * OpenSSL will prompt you for your password when loading an encrypted key. + * \OpenSSL will prompt you for your password when loading an encrypted key. * If you will not be able to type in the password you may provide it when * loading the key: * @@ -597,7 +669,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * == PBKDF2 Password-based Encryption * - * If supported by the underlying OpenSSL version used, Password-based + * If supported by the underlying \OpenSSL version used, Password-based * Encryption should use the features of PKCS5. If not supported or if * required by legacy applications, the older, less secure methods specified * in RFC 2898 are also supported (see below). @@ -656,7 +728,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * decrypted = cipher.update encrypted * decrypted << cipher.final * - * == X509 Certificates + * == \X509 Certificates * * === Creating a Certificate * @@ -693,7 +765,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * extension_factory.create_extension('subjectKeyIdentifier', 'hash') * * The list of supported extensions (and in some cases their possible values) - * can be derived from the "objects.h" file in the OpenSSL source code. + * can be derived from the "objects.h" file in the \OpenSSL source code. * * === Signing a Certificate * @@ -847,23 +919,23 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * io.write csr_cert.to_pem * end * - * == SSL and TLS Connections + * == \SSL and TLS Connections * - * Using our created key and certificate we can create an SSL or TLS connection. - * An SSLContext is used to set up an SSL session. + * Using our created key and certificate we can create an \SSL or TLS + * connection. An OpenSSL::SSL::SSLContext is used to set up an \SSL session. * * context = OpenSSL::SSL::SSLContext.new * - * === SSL Server + * === \SSL Server * - * An SSL server requires the certificate and private key to communicate + * An \SSL server requires the certificate and private key to communicate * securely with its clients: * * context.cert = cert * context.key = key * - * Then create an SSLServer with a TCP server socket and the context. Use the - * SSLServer like an ordinary TCP server. + * Then create an OpenSSL::SSL::SSLServer with a TCP server socket and the + * context. Use the SSLServer like an ordinary TCP server. * * require 'socket' * @@ -882,14 +954,15 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * ssl_connection.close * end * - * === SSL client + * === \SSL client * - * An SSL client is created with a TCP socket and the context. - * SSLSocket#connect must be called to initiate the SSL handshake and start - * encryption. A key and certificate are not required for the client socket. + * An \SSL client is created with a TCP socket and the context. + * OpenSSL::SSL::SSLSocket#connect must be called to initiate the \SSL handshake + * and start encryption. A key and certificate are not required for the client + * socket. * - * Note that SSLSocket#close doesn't close the underlying socket by default. Set - * SSLSocket#sync_close to true if you want. + * Note that OpenSSL::SSL::SSLSocket#close doesn't close the underlying socket + * by default. Set OpenSSL::SSL::SSLSocket#sync_close to true if you want. * * require 'socket' * @@ -905,7 +978,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * === Peer Verification * - * An unverified SSL connection does not provide much security. For enhanced + * An unverified \SSL connection does not provide much security. For enhanced * security the client or server can verify the certificate of its peer. * * The client can be modified to verify the server's certificate against the @@ -956,22 +1029,34 @@ Init_openssl(void) rb_define_singleton_method(mOSSL, "fixed_length_secure_compare", ossl_crypto_fixed_length_secure_compare, 2); /* - * Version of OpenSSL the ruby OpenSSL extension was built with + * \OpenSSL library version string used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. */ - rb_define_const(mOSSL, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT)); + rb_define_const(mOSSL, "OPENSSL_VERSION", + rb_obj_freeze(rb_str_new_cstr(OPENSSL_VERSION_TEXT))); /* - * Version of OpenSSL the ruby OpenSSL extension is running with + * \OpenSSL library version string currently used at runtime. */ - rb_define_const(mOSSL, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION))); + rb_define_const( + mOSSL, + "OPENSSL_LIBRARY_VERSION", + rb_obj_freeze(rb_str_new_cstr(OpenSSL_version(OPENSSL_VERSION))) + ); /* - * Version number of OpenSSL the ruby OpenSSL extension was built with - * (base 16). The formats are below. + * \OpenSSL library version number used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. + * + * The version number is encoded into a single integer value. The number + * follows the format: * - * [OpenSSL 3] <tt>0xMNN00PP0 (major minor 00 patch 0)</tt> - * [OpenSSL before 3] <tt>0xMNNFFPPS (major minor fix patch status)</tt> - * [LibreSSL] <tt>0x20000000 (fixed value)</tt> + * [\OpenSSL 3.0.0 or later] + * <tt>0xMNN00PP0</tt> (major minor 00 patch 0) + * [\OpenSSL 1.1.1 or earlier] + * <tt>0xMNNFFPPS</tt> (major minor fix patch status) + * [LibreSSL] + * <tt>0x20000000</tt> (a fixed value) * * See also the man page OPENSSL_VERSION_NUMBER(3). */ @@ -979,9 +1064,12 @@ Init_openssl(void) #if defined(LIBRESSL_VERSION_NUMBER) /* - * Version number of LibreSSL the ruby OpenSSL extension was built with - * (base 16). The format is <tt>0xMNNFF00f (major minor fix 00 - * status)</tt>. This constant is only defined in LibreSSL cases. + * LibreSSL library version number used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. + * + * This constant is only defined if the extension was compiled against + * LibreSSL. The number follows the format: + * <tt>0xMNNFF00f</tt> (major minor fix 00 status). * * See also the man page LIBRESSL_VERSION_NUMBER(3). */ @@ -989,30 +1077,50 @@ Init_openssl(void) #endif /* - * Boolean indicating whether OpenSSL is FIPS-capable or not + * Boolean indicating whether the \OpenSSL library is FIPS-capable or not. + * Always <tt>true</tt> for \OpenSSL 3.0 and later. + * + * This is obsolete and will be removed in the future. + * See also OpenSSL.fips_mode. */ rb_define_const(mOSSL, "OPENSSL_FIPS", /* OpenSSL 3 is FIPS-capable even when it is installed without fips option */ #if OSSL_OPENSSL_PREREQ(3, 0, 0) Qtrue #elif defined(OPENSSL_FIPS) - Qtrue + Qtrue #elif defined(OPENSSL_IS_AWSLC) // AWS-LC FIPS can only be enabled during compile time. - FIPS_mode() ? Qtrue : Qfalse + FIPS_mode() ? Qtrue : Qfalse #else - Qfalse + Qfalse #endif - ); + ); rb_define_module_function(mOSSL, "fips_mode", ossl_fips_mode_get, 0); rb_define_module_function(mOSSL, "fips_mode=", ossl_fips_mode_set, 1); rb_global_variable(&eOSSLError); /* - * Generic error, - * common for all classes under OpenSSL module + * Generic error class for OpenSSL. All error classes in this library + * inherit from this class. + * + * This class indicates that an error was reported by the underlying + * \OpenSSL library. + */ + eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); + /* + * \OpenSSL error queue entries captured at the time the exception was + * raised. The same information is printed to stderr if OpenSSL.debug is + * set to +true+. + * + * This is an array of zero or more strings, ordered from the oldest to the + * newest. The format of the strings is not stable and may vary across + * versions of \OpenSSL or versions of this Ruby extension. + * + * See also the man page ERR_get_error(3). */ - eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError); + rb_attr(eOSSLError, rb_intern_const("errors"), 1, 0, 0); + rb_define_method(eOSSLError, "detailed_message", osslerror_detailed_message, -1); /* * Init debug core @@ -1028,6 +1136,7 @@ Init_openssl(void) * Get ID of to_der */ ossl_s_to_der = rb_intern("to_der"); + id_i_errors = rb_intern("@errors"); /* * Init components diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 22471d2085..0b479a7200 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -74,6 +74,10 @@ # include <openssl/provider.h> #endif +#if OSSL_OPENSSL_PREREQ(3, 0, 0) +# define OSSL_HAVE_IMMUTABLE_PKEY +#endif + /* * Common Module */ @@ -88,10 +92,10 @@ extern VALUE eOSSLError; * CheckTypes */ #define OSSL_Check_Kind(obj, klass) do {\ - if (!rb_obj_is_kind_of((obj), (klass))) {\ - ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected kind of %"PRIsVALUE")",\ - rb_obj_class(obj), (klass));\ - }\ + if (!rb_obj_is_kind_of((obj), (klass))) {\ + ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected kind of %"PRIsVALUE")",\ + rb_obj_class(obj), (klass));\ + }\ } while (0) /* @@ -127,7 +131,7 @@ do{\ * Convert binary string to hex string. The caller is responsible for * ensuring out has (2 * len) bytes of capacity. */ -void ossl_bin2hex(unsigned char *in, char *out, size_t len); +void ossl_bin2hex(const unsigned char *in, char *out, size_t len); /* * Our default PEM callback @@ -170,11 +174,11 @@ VALUE ossl_to_der_if_possible(VALUE); extern VALUE dOSSL; #define OSSL_Debug(...) do { \ - if (dOSSL == Qtrue) { \ - fprintf(stderr, "OSSL_DEBUG: "); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ - } \ + if (dOSSL == Qtrue) { \ + fprintf(stderr, "OSSL_DEBUG: "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ + } \ } while (0) /* diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 186679da4c..517212b4d5 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -9,63 +9,81 @@ */ #include "ossl.h" -static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, - int depth, int yield, long *num_read); +/********/ +/* + * ASN1 module + */ +#define ossl_asn1_get_value(o) rb_attr_get((o),sivVALUE) +#define ossl_asn1_get_tag(o) rb_attr_get((o),sivTAG) +#define ossl_asn1_get_tagging(o) rb_attr_get((o),sivTAGGING) +#define ossl_asn1_get_tag_class(o) rb_attr_get((o),sivTAG_CLASS) +#define ossl_asn1_get_indefinite_length(o) rb_attr_get((o),sivINDEFINITE_LENGTH) + +#define ossl_asn1_set_value(o,v) rb_ivar_set((o),sivVALUE,(v)) +#define ossl_asn1_set_tag(o,v) rb_ivar_set((o),sivTAG,(v)) +#define ossl_asn1_set_tagging(o,v) rb_ivar_set((o),sivTAGGING,(v)) +#define ossl_asn1_set_tag_class(o,v) rb_ivar_set((o),sivTAG_CLASS,(v)) +#define ossl_asn1_set_indefinite_length(o,v) rb_ivar_set((o),sivINDEFINITE_LENGTH,(v)) + +VALUE mASN1; +static VALUE eASN1Error; + +VALUE cASN1Data; +static VALUE cASN1Primitive; +static VALUE cASN1Constructive; + +static VALUE cASN1EndOfContent; +static VALUE cASN1Boolean; /* BOOLEAN */ +static VALUE cASN1Integer, cASN1Enumerated; /* INTEGER */ +static VALUE cASN1BitString; /* BIT STRING */ +static VALUE cASN1OctetString, cASN1UTF8String; /* STRINGs */ +static VALUE cASN1NumericString, cASN1PrintableString; +static VALUE cASN1T61String, cASN1VideotexString; +static VALUE cASN1IA5String, cASN1GraphicString; +static VALUE cASN1ISO64String, cASN1GeneralString; +static VALUE cASN1UniversalString, cASN1BMPString; +static VALUE cASN1Null; /* NULL */ +static VALUE cASN1ObjectId; /* OBJECT IDENTIFIER */ +static VALUE cASN1UTCTime, cASN1GeneralizedTime; /* TIME */ +static VALUE cASN1Sequence, cASN1Set; /* CONSTRUCTIVE */ + +static VALUE sym_IMPLICIT, sym_EXPLICIT; +static VALUE sym_UNIVERSAL, sym_APPLICATION, sym_CONTEXT_SPECIFIC, sym_PRIVATE; +static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINDEFINITE_LENGTH, sivUNUSED_BITS; +static ID id_each; /* * DATE conversion */ +static VALUE +time_utc_new(VALUE args) +{ + return rb_funcallv(rb_cTime, rb_intern("utc"), 6, (VALUE *)args); +} + +static VALUE +time_utc_new_rescue(VALUE args, VALUE exc) +{ + rb_raise(eASN1Error, "invalid time"); +} + VALUE asn1time_to_time(const ASN1_TIME *time) { struct tm tm; - VALUE argv[6]; - int count; - - memset(&tm, 0, sizeof(struct tm)); - - switch (time->type) { - case V_ASN1_UTCTIME: - count = sscanf((const char *)time->data, "%2d%2d%2d%2d%2d%2dZ", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, - &tm.tm_sec); - - if (count == 5) { - tm.tm_sec = 0; - } else if (count != 6) { - ossl_raise(rb_eTypeError, "bad UTCTIME format: \"%s\"", - time->data); - } - if (tm.tm_year < 50) { - tm.tm_year += 2000; - } else { - tm.tm_year += 1900; - } - break; - case V_ASN1_GENERALIZEDTIME: - count = sscanf((const char *)time->data, "%4d%2d%2d%2d%2d%2dZ", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, - &tm.tm_sec); - if (count == 5) { - tm.tm_sec = 0; - } - else if (count != 6) { - ossl_raise(rb_eTypeError, "bad GENERALIZEDTIME format: \"%s\"", - time->data); - } - break; - default: - rb_warning("unknown time format"); - return Qnil; - } - argv[0] = INT2NUM(tm.tm_year); - argv[1] = INT2NUM(tm.tm_mon); - argv[2] = INT2NUM(tm.tm_mday); - argv[3] = INT2NUM(tm.tm_hour); - argv[4] = INT2NUM(tm.tm_min); - argv[5] = INT2NUM(tm.tm_sec); - - return rb_funcall2(rb_cTime, rb_intern("utc"), 6, argv); + if (!ASN1_TIME_to_tm(time, &tm)) + ossl_raise(eASN1Error, "ASN1_TIME_to_tm"); + + VALUE args[] = { + INT2NUM(tm.tm_year + 1900), + INT2NUM(tm.tm_mon + 1), + INT2NUM(tm.tm_mday), + INT2NUM(tm.tm_hour), + INT2NUM(tm.tm_min), + INT2NUM(tm.tm_sec), + }; + return rb_rescue2(time_utc_new, (VALUE)args, time_utc_new_rescue, Qnil, + rb_eArgError, 0); } static VALUE @@ -80,13 +98,13 @@ ossl_time_split(VALUE time, time_t *sec, int *days) VALUE num = rb_Integer(time); if (FIXNUM_P(num)) { - time_t t = FIX2LONG(num); - *sec = t % 86400; - *days = rb_long2int(t / 86400); + time_t t = FIX2LONG(num); + *sec = t % 86400; + *days = rb_long2int(t / 86400); } else { - *days = NUM2INT(rb_funcall(num, rb_intern("/"), 1, INT2FIX(86400))); - *sec = NUM2TIMET(rb_funcall(num, rb_intern("%"), 1, INT2FIX(86400))); + *days = NUM2INT(rb_funcall(num, rb_intern("/"), 1, INT2FIX(86400))); + *sec = NUM2TIMET(rb_funcall(num, rb_intern("%"), 1, INT2FIX(86400))); } } @@ -96,7 +114,8 @@ ossl_time_split(VALUE time, time_t *sec, int *days) VALUE asn1str_to_str(const ASN1_STRING *str) { - return rb_str_new((const char *)str->data, str->length); + return rb_str_new((const char *)ASN1_STRING_get0_data(str), + ASN1_STRING_length(str)); } /* @@ -109,18 +128,19 @@ asn1integer_to_num(const ASN1_INTEGER *ai) VALUE num; if (!ai) { - ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); + ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); } - if (ai->type == V_ASN1_ENUMERATED) - /* const_cast: workaround for old OpenSSL */ - bn = ASN1_ENUMERATED_to_BN((ASN1_ENUMERATED *)ai, NULL); + + num = ossl_bn_new(BN_value_one()); + bn = GetBNPtr(num); + + if (ASN1_STRING_type(ai) == V_ASN1_ENUMERATED) + bn = ASN1_ENUMERATED_to_BN(ai, bn); else - bn = ASN1_INTEGER_to_BN(ai, NULL); + bn = ASN1_INTEGER_to_BN(ai, bn); if (!bn) - ossl_raise(eOSSLError, NULL); - num = ossl_bn_new(bn); - BN_free(bn); + ossl_raise(eOSSLError, NULL); return num; } @@ -131,12 +151,12 @@ num_to_asn1integer(VALUE obj, ASN1_INTEGER *ai) BIGNUM *bn; if (NIL_P(obj)) - ossl_raise(rb_eTypeError, "Can't convert nil into Integer"); + ossl_raise(rb_eTypeError, "Can't convert nil into Integer"); bn = GetBNPtr(obj); if (!(ai = BN_to_ASN1_INTEGER(bn, ai))) - ossl_raise(eOSSLError, NULL); + ossl_raise(eOSSLError, NULL); return ai; } @@ -147,43 +167,47 @@ asn1integer_to_num_i(VALUE arg) return asn1integer_to_num((ASN1_INTEGER *)arg); } -/********/ /* - * ASN1 module + * ASN1_OBJECT conversions */ -#define ossl_asn1_get_value(o) rb_attr_get((o),sivVALUE) -#define ossl_asn1_get_tag(o) rb_attr_get((o),sivTAG) -#define ossl_asn1_get_tagging(o) rb_attr_get((o),sivTAGGING) -#define ossl_asn1_get_tag_class(o) rb_attr_get((o),sivTAG_CLASS) -#define ossl_asn1_get_indefinite_length(o) rb_attr_get((o),sivINDEFINITE_LENGTH) - -#define ossl_asn1_set_indefinite_length(o,v) rb_ivar_set((o),sivINDEFINITE_LENGTH,(v)) - -VALUE mASN1; -VALUE eASN1Error; +VALUE +ossl_asn1obj_to_string_oid(const ASN1_OBJECT *a1obj) +{ + VALUE str; + int len; -VALUE cASN1Data; -static VALUE cASN1Primitive; -static VALUE cASN1Constructive; + str = rb_usascii_str_new(NULL, 127); + len = OBJ_obj2txt(RSTRING_PTR(str), RSTRING_LENINT(str), a1obj, 1); + if (len <= 0 || len == INT_MAX) + ossl_raise(eOSSLError, "OBJ_obj2txt"); + if (len > RSTRING_LEN(str)) { + /* +1 is for the \0 terminator added by OBJ_obj2txt() */ + rb_str_resize(str, len + 1); + len = OBJ_obj2txt(RSTRING_PTR(str), len + 1, a1obj, 1); + if (len <= 0) + ossl_raise(eOSSLError, "OBJ_obj2txt"); + } + rb_str_set_len(str, len); + return str; +} -static VALUE cASN1EndOfContent; -static VALUE cASN1Boolean; /* BOOLEAN */ -static VALUE cASN1Integer, cASN1Enumerated; /* INTEGER */ -static VALUE cASN1BitString; /* BIT STRING */ -static VALUE cASN1OctetString, cASN1UTF8String; /* STRINGs */ -static VALUE cASN1NumericString, cASN1PrintableString; -static VALUE cASN1T61String, cASN1VideotexString; -static VALUE cASN1IA5String, cASN1GraphicString; -static VALUE cASN1ISO64String, cASN1GeneralString; -static VALUE cASN1UniversalString, cASN1BMPString; -static VALUE cASN1Null; /* NULL */ -static VALUE cASN1ObjectId; /* OBJECT IDENTIFIER */ -static VALUE cASN1UTCTime, cASN1GeneralizedTime; /* TIME */ -static VALUE cASN1Sequence, cASN1Set; /* CONSTRUCTIVE */ +VALUE +ossl_asn1obj_to_string(const ASN1_OBJECT *obj) +{ + int nid = OBJ_obj2nid(obj); + if (nid != NID_undef) + return rb_str_new_cstr(OBJ_nid2sn(nid)); + return ossl_asn1obj_to_string_oid(obj); +} -static VALUE sym_IMPLICIT, sym_EXPLICIT; -static VALUE sym_UNIVERSAL, sym_APPLICATION, sym_CONTEXT_SPECIFIC, sym_PRIVATE; -static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINDEFINITE_LENGTH, sivUNUSED_BITS; +VALUE +ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *obj) +{ + int nid = OBJ_obj2nid(obj); + if (nid != NID_undef) + return rb_str_new_cstr(OBJ_nid2ln(nid)); + return ossl_asn1obj_to_string_oid(obj); +} /* * Ruby to ASN1 converters @@ -192,9 +216,9 @@ static ASN1_BOOLEAN obj_to_asn1bool(VALUE obj) { if (NIL_P(obj)) - ossl_raise(rb_eTypeError, "Can't convert nil into Boolean"); + ossl_raise(rb_eTypeError, "Can't convert nil into Boolean"); - return RTEST(obj) ? 0xff : 0x0; + return RTEST(obj) ? 0xff : 0x0; } static ASN1_INTEGER* @@ -204,19 +228,19 @@ obj_to_asn1int(VALUE obj) } static ASN1_BIT_STRING* -obj_to_asn1bstr(VALUE obj, long unused_bits) +obj_to_asn1bstr(VALUE obj, int unused_bits) { ASN1_BIT_STRING *bstr; if (unused_bits < 0 || unused_bits > 7) - ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\ - "the range 0 to 7"); + ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\ + "the range 0 to 7"); StringValue(obj); - if(!(bstr = ASN1_BIT_STRING_new())) - ossl_raise(eASN1Error, NULL); - ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LENINT(obj)); - bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07); /* clear */ - bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits; + if (!(bstr = ASN1_BIT_STRING_new())) + ossl_raise(eASN1Error, "ASN1_BIT_STRING_new"); + if (!ASN1_BIT_STRING_set1(bstr, (uint8_t *)RSTRING_PTR(obj), + RSTRING_LEN(obj), unused_bits)) + ossl_raise(eASN1Error, "ASN1_BIT_STRING_set1"); return bstr; } @@ -228,8 +252,11 @@ obj_to_asn1str(VALUE obj) StringValue(obj); if(!(str = ASN1_STRING_new())) - ossl_raise(eASN1Error, NULL); - ASN1_STRING_set(str, RSTRING_PTR(obj), RSTRING_LENINT(obj)); + ossl_raise(eASN1Error, NULL); + if(!ASN1_STRING_set(str, RSTRING_PTR(obj), RSTRING_LENINT(obj))) { + ASN1_STRING_free(str); + ossl_raise(eASN1Error, NULL); + } return str; } @@ -240,15 +267,15 @@ obj_to_asn1null(VALUE obj) ASN1_NULL *null; if(!NIL_P(obj)) - ossl_raise(eASN1Error, "nil expected"); + ossl_raise(eASN1Error, "nil expected"); if(!(null = ASN1_NULL_new())) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return null; } -static ASN1_OBJECT* -obj_to_asn1obj(VALUE obj) +ASN1_OBJECT * +ossl_to_asn1obj(VALUE obj) { ASN1_OBJECT *a1obj; @@ -270,7 +297,7 @@ obj_to_asn1utime(VALUE time) ossl_time_split(time, &sec, &off_days); if (!(t = ASN1_UTCTIME_adj(NULL, sec, off_days, 0))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return t; } @@ -285,7 +312,7 @@ obj_to_asn1gtime(VALUE time) ossl_time_split(time, &sec, &off_days); if (!(t = ASN1_GENERALIZEDTIME_adj(NULL, sec, off_days, 0))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return t; } @@ -298,8 +325,11 @@ obj_to_asn1derstr(VALUE obj) str = ossl_to_der(obj); if(!(a1str = ASN1_STRING_new())) - ossl_raise(eASN1Error, NULL); - ASN1_STRING_set(a1str, RSTRING_PTR(str), RSTRING_LENINT(str)); + ossl_raise(eASN1Error, NULL); + if(!ASN1_STRING_set(a1str, RSTRING_PTR(str), RSTRING_LENINT(str))) { + ASN1_STRING_free(a1str); + ossl_raise(eASN1Error, NULL); + } return a1str; } @@ -313,9 +343,9 @@ decode_bool(unsigned char* der, long length) const unsigned char *p = der; if (length != 3) - ossl_raise(eASN1Error, "invalid length for BOOLEAN"); + ossl_raise(eASN1Error, "invalid length for BOOLEAN"); if (p[0] != 1 || p[1] != 1) - ossl_raise(eASN1Error, "invalid BOOLEAN"); + ossl_raise(eASN1Error, "invalid BOOLEAN"); return p[2] ? Qtrue : Qfalse; } @@ -330,9 +360,9 @@ decode_int(unsigned char* der, long length) p = der; if(!(ai = d2i_ASN1_INTEGER(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ret = rb_protect(asn1integer_to_num_i, - (VALUE)ai, &status); + (VALUE)ai, &status); ASN1_INTEGER_free(ai); if(status) rb_jump_tag(status); @@ -340,22 +370,25 @@ decode_int(unsigned char* der, long length) } static VALUE -decode_bstr(unsigned char* der, long length, long *unused_bits) +decode_bstr(unsigned char* der, long length, int *unused_bits) { ASN1_BIT_STRING *bstr; const unsigned char *p; - long len; + size_t len; VALUE ret; + int state; p = der; - if(!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); - len = bstr->length; - *unused_bits = 0; - if(bstr->flags & ASN1_STRING_FLAG_BITS_LEFT) - *unused_bits = bstr->flags & 0x07; - ret = rb_str_new((const char *)bstr->data, len); + if (!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length))) + ossl_raise(eASN1Error, "d2i_ASN1_BIT_STRING"); + if (!ASN1_BIT_STRING_get_length(bstr, &len, unused_bits)) { + ASN1_BIT_STRING_free(bstr); + ossl_raise(eASN1Error, "ASN1_BIT_STRING_get_length"); + } + ret = ossl_str_new((const char *)ASN1_STRING_get0_data(bstr), len, &state); ASN1_BIT_STRING_free(bstr); + if (state) + rb_jump_tag(state); return ret; } @@ -370,9 +403,9 @@ decode_enum(unsigned char* der, long length) p = der; if(!(ai = d2i_ASN1_ENUMERATED(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ret = rb_protect(asn1integer_to_num_i, - (VALUE)ai, &status); + (VALUE)ai, &status); ASN1_ENUMERATED_free(ai); if(status) rb_jump_tag(status); @@ -387,38 +420,33 @@ decode_null(unsigned char* der, long length) p = der; if(!(null = d2i_ASN1_NULL(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ASN1_NULL_free(null); return Qnil; } +VALUE +asn1obj_to_string_i(VALUE arg) +{ + return ossl_asn1obj_to_string((const ASN1_OBJECT *)arg); +} + static VALUE decode_obj(unsigned char* der, long length) { ASN1_OBJECT *obj; const unsigned char *p; VALUE ret; - int nid; - BIO *bio; + int state; p = der; - if(!(obj = d2i_ASN1_OBJECT(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); - if((nid = OBJ_obj2nid(obj)) != NID_undef){ - ASN1_OBJECT_free(obj); - ret = rb_str_new2(OBJ_nid2sn(nid)); - } - else{ - if(!(bio = BIO_new(BIO_s_mem()))){ - ASN1_OBJECT_free(obj); - ossl_raise(eASN1Error, NULL); - } - i2a_ASN1_OBJECT(bio, obj); - ASN1_OBJECT_free(obj); - ret = ossl_membio2str(bio); - } - + if (!(obj = d2i_ASN1_OBJECT(NULL, &p, length))) + ossl_raise(eASN1Error, "d2i_ASN1_OBJECT"); + ret = rb_protect(asn1obj_to_string_i, (VALUE)obj, &state); + ASN1_OBJECT_free(obj); + if (state) + rb_jump_tag(state); return ret; } @@ -432,9 +460,9 @@ decode_time(unsigned char* der, long length) p = der; if(!(time = d2i_ASN1_TIME(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ret = rb_protect(asn1time_to_time_i, - (VALUE)time, &status); + (VALUE)time, &status); ASN1_TIME_free(time); if(status) rb_jump_tag(status); @@ -445,7 +473,7 @@ static VALUE decode_eoc(unsigned char *der, long length) { if (length != 2 || !(der[0] == 0x00 && der[1] == 0x00)) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return rb_str_new("", 0); } @@ -510,62 +538,62 @@ ossl_asn1_get_asn1type(VALUE obj) tag = ossl_asn1_default_tag(obj); value = ossl_asn1_get_value(obj); switch(tag){ - case V_ASN1_BOOLEAN: - ptr = (void*)(VALUE)obj_to_asn1bool(value); - free_func = NULL; - break; - case V_ASN1_INTEGER: /* FALLTHROUGH */ - case V_ASN1_ENUMERATED: - ptr = obj_to_asn1int(value); - free_func = (free_func_type *)ASN1_INTEGER_free; - break; - case V_ASN1_BIT_STRING: + case V_ASN1_BOOLEAN: + ptr = (void*)(VALUE)obj_to_asn1bool(value); + free_func = NULL; + break; + case V_ASN1_INTEGER: /* FALLTHROUGH */ + case V_ASN1_ENUMERATED: + ptr = obj_to_asn1int(value); + free_func = (free_func_type *)ASN1_INTEGER_free; + break; + case V_ASN1_BIT_STRING: rflag = rb_attr_get(obj, sivUNUSED_BITS); - ptr = obj_to_asn1bstr(value, NUM2INT(rflag)); - free_func = (free_func_type *)ASN1_BIT_STRING_free; - break; - case V_ASN1_NULL: - ptr = obj_to_asn1null(value); - free_func = (free_func_type *)ASN1_NULL_free; - break; - case V_ASN1_OCTET_STRING: /* FALLTHROUGH */ - case V_ASN1_UTF8STRING: /* FALLTHROUGH */ - case V_ASN1_NUMERICSTRING: /* FALLTHROUGH */ - case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */ - case V_ASN1_T61STRING: /* FALLTHROUGH */ - case V_ASN1_VIDEOTEXSTRING: /* FALLTHROUGH */ - case V_ASN1_IA5STRING: /* FALLTHROUGH */ - case V_ASN1_GRAPHICSTRING: /* FALLTHROUGH */ - case V_ASN1_ISO64STRING: /* FALLTHROUGH */ - case V_ASN1_GENERALSTRING: /* FALLTHROUGH */ - case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */ - case V_ASN1_BMPSTRING: - ptr = obj_to_asn1str(value); - free_func = (free_func_type *)ASN1_STRING_free; - break; - case V_ASN1_OBJECT: - ptr = obj_to_asn1obj(value); - free_func = (free_func_type *)ASN1_OBJECT_free; - break; - case V_ASN1_UTCTIME: - ptr = obj_to_asn1utime(value); - free_func = (free_func_type *)ASN1_TIME_free; - break; - case V_ASN1_GENERALIZEDTIME: - ptr = obj_to_asn1gtime(value); - free_func = (free_func_type *)ASN1_TIME_free; - break; - case V_ASN1_SET: /* FALLTHROUGH */ - case V_ASN1_SEQUENCE: - ptr = obj_to_asn1derstr(obj); - free_func = (free_func_type *)ASN1_STRING_free; - break; - default: - ossl_raise(eASN1Error, "unsupported ASN.1 type"); + ptr = obj_to_asn1bstr(value, NUM2INT(rflag)); + free_func = (free_func_type *)ASN1_BIT_STRING_free; + break; + case V_ASN1_NULL: + ptr = obj_to_asn1null(value); + free_func = (free_func_type *)ASN1_NULL_free; + break; + case V_ASN1_OCTET_STRING: /* FALLTHROUGH */ + case V_ASN1_UTF8STRING: /* FALLTHROUGH */ + case V_ASN1_NUMERICSTRING: /* FALLTHROUGH */ + case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */ + case V_ASN1_T61STRING: /* FALLTHROUGH */ + case V_ASN1_VIDEOTEXSTRING: /* FALLTHROUGH */ + case V_ASN1_IA5STRING: /* FALLTHROUGH */ + case V_ASN1_GRAPHICSTRING: /* FALLTHROUGH */ + case V_ASN1_ISO64STRING: /* FALLTHROUGH */ + case V_ASN1_GENERALSTRING: /* FALLTHROUGH */ + case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */ + case V_ASN1_BMPSTRING: + ptr = obj_to_asn1str(value); + free_func = (free_func_type *)ASN1_STRING_free; + break; + case V_ASN1_OBJECT: + ptr = ossl_to_asn1obj(value); + free_func = (free_func_type *)ASN1_OBJECT_free; + break; + case V_ASN1_UTCTIME: + ptr = obj_to_asn1utime(value); + free_func = (free_func_type *)ASN1_TIME_free; + break; + case V_ASN1_GENERALIZEDTIME: + ptr = obj_to_asn1gtime(value); + free_func = (free_func_type *)ASN1_TIME_free; + break; + case V_ASN1_SET: /* FALLTHROUGH */ + case V_ASN1_SEQUENCE: + ptr = obj_to_asn1derstr(obj); + free_func = (free_func_type *)ASN1_STRING_free; + break; + default: + ossl_raise(eASN1Error, "unsupported ASN.1 type"); } if(!(ret = OPENSSL_malloc(sizeof(ASN1_TYPE)))){ - if(free_func) free_func(ptr); - ossl_raise(eASN1Error, "ASN1_TYPE alloc failure"); + if(free_func) free_func(ptr); + ossl_raise(eASN1Error, "ASN1_TYPE alloc failure"); } memset(ret, 0, sizeof(ASN1_TYPE)); ASN1_TYPE_set(ret, tag, ptr); @@ -580,10 +608,10 @@ ossl_asn1_default_tag(VALUE obj) tmp_class = CLASS_OF(obj); while (!NIL_P(tmp_class)) { - tag = rb_hash_lookup(class_tag_map, tmp_class); - if (tag != Qnil) - return NUM2INT(tag); - tmp_class = rb_class_superclass(tmp_class); + tag = rb_hash_lookup(class_tag_map, tmp_class); + if (tag != Qnil) + return NUM2INT(tag); + tmp_class = rb_class_superclass(tmp_class); } return -1; @@ -596,7 +624,7 @@ ossl_asn1_tag(VALUE obj) tag = ossl_asn1_get_tag(obj); if(NIL_P(tag)) - ossl_raise(eASN1Error, "tag number not specified"); + ossl_raise(eASN1Error, "tag number not specified"); return NUM2INT(tag); } @@ -608,28 +636,57 @@ ossl_asn1_tag_class(VALUE obj) s = ossl_asn1_get_tag_class(obj); if (NIL_P(s) || s == sym_UNIVERSAL) - return V_ASN1_UNIVERSAL; + return V_ASN1_UNIVERSAL; else if (s == sym_APPLICATION) - return V_ASN1_APPLICATION; + return V_ASN1_APPLICATION; else if (s == sym_CONTEXT_SPECIFIC) - return V_ASN1_CONTEXT_SPECIFIC; + return V_ASN1_CONTEXT_SPECIFIC; else if (s == sym_PRIVATE) - return V_ASN1_PRIVATE; + return V_ASN1_PRIVATE; else - ossl_raise(eASN1Error, "invalid tag class"); + ossl_raise(eASN1Error, "invalid tag class"); } static VALUE ossl_asn1_class2sym(int tc) { if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE) - return sym_PRIVATE; + return sym_PRIVATE; else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC) - return sym_CONTEXT_SPECIFIC; + return sym_CONTEXT_SPECIFIC; else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION) - return sym_APPLICATION; + return sym_APPLICATION; else - return sym_UNIVERSAL; + return sym_UNIVERSAL; +} + +/* + * call-seq: + * OpenSSL::ASN1::ASN1Data.new(value, tag, tag_class) => ASN1Data + * + * _value_: Please have a look at Constructive and Primitive to see how Ruby + * types are mapped to ASN.1 types and vice versa. + * + * _tag_: An Integer indicating the tag number. + * + * _tag_class_: A Symbol indicating the tag class. Please cf. ASN1 for + * possible values. + * + * == Example + * asn1_int = OpenSSL::ASN1Data.new(42, 2, :UNIVERSAL) # => Same as OpenSSL::ASN1::Integer.new(42) + * tagged_int = OpenSSL::ASN1Data.new(42, 0, :CONTEXT_SPECIFIC) # implicitly 0-tagged INTEGER + */ +static VALUE +ossl_asn1data_initialize(VALUE self, VALUE value, VALUE tag, VALUE tag_class) +{ + if(!SYMBOL_P(tag_class)) + ossl_raise(eASN1Error, "invalid tag class"); + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_indefinite_length(self, Qfalse); + + return self; } static VALUE @@ -645,35 +702,35 @@ to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) body_length = RSTRING_LENINT(body); if (ossl_asn1_get_tagging(self) == sym_EXPLICIT) { - int inner_length, e_encoding = indef_len ? 2 : 1; - - if (default_tag_number == -1) - ossl_raise(eASN1Error, "explicit tagging of unknown tag"); - - inner_length = ASN1_object_size(encoding, body_length, default_tag_number); - total_length = ASN1_object_size(e_encoding, inner_length, tag_number); - str = rb_str_new(NULL, total_length); - p = (unsigned char *)RSTRING_PTR(str); - /* Put explicit tag */ - ASN1_put_object(&p, e_encoding, inner_length, tag_number, tag_class); - /* Append inner object */ - ASN1_put_object(&p, encoding, body_length, default_tag_number, V_ASN1_UNIVERSAL); - memcpy(p, RSTRING_PTR(body), body_length); - p += body_length; - if (indef_len) { - ASN1_put_eoc(&p); /* For inner object */ - ASN1_put_eoc(&p); /* For wrapper object */ - } + int inner_length, e_encoding = indef_len ? 2 : 1; + + if (default_tag_number == -1) + ossl_raise(eASN1Error, "explicit tagging of unknown tag"); + + inner_length = ASN1_object_size(encoding, body_length, default_tag_number); + total_length = ASN1_object_size(e_encoding, inner_length, tag_number); + str = rb_str_new(NULL, total_length); + p = (unsigned char *)RSTRING_PTR(str); + /* Put explicit tag */ + ASN1_put_object(&p, e_encoding, inner_length, tag_number, tag_class); + /* Append inner object */ + ASN1_put_object(&p, encoding, body_length, default_tag_number, V_ASN1_UNIVERSAL); + memcpy(p, RSTRING_PTR(body), body_length); + p += body_length; + if (indef_len) { + ASN1_put_eoc(&p); /* For inner object */ + ASN1_put_eoc(&p); /* For wrapper object */ + } } else { - total_length = ASN1_object_size(encoding, body_length, tag_number); - str = rb_str_new(NULL, total_length); - p = (unsigned char *)RSTRING_PTR(str); - ASN1_put_object(&p, encoding, body_length, tag_number, tag_class); - memcpy(p, RSTRING_PTR(body), body_length); - p += body_length; - if (indef_len) - ASN1_put_eoc(&p); + total_length = ASN1_object_size(encoding, body_length, tag_number); + str = rb_str_new(NULL, total_length); + p = (unsigned char *)RSTRING_PTR(str); + ASN1_put_object(&p, encoding, body_length, tag_number, tag_class); + memcpy(p, RSTRING_PTR(body), body_length); + p += body_length; + if (indef_len) + ASN1_put_eoc(&p); } assert(p - (unsigned char *)RSTRING_PTR(str) == total_length); return str; @@ -696,83 +753,88 @@ ossl_asn1data_to_der(VALUE self) VALUE value = ossl_asn1_get_value(self); if (rb_obj_is_kind_of(value, rb_cArray)) - return ossl_asn1cons_to_der(self); + return ossl_asn1cons_to_der(self); else { - if (RTEST(ossl_asn1_get_indefinite_length(self))) - ossl_raise(eASN1Error, "indefinite length form cannot be used " \ - "with primitive encoding"); - return ossl_asn1prim_to_der(self); + if (RTEST(ossl_asn1_get_indefinite_length(self))) + ossl_raise(eASN1Error, "indefinite length form cannot be used " \ + "with primitive encoding"); + return ossl_asn1prim_to_der(self); } } +static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self); +static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, + int depth, int yield, long *num_read); + static VALUE int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, - VALUE tc, long *num_read) + VALUE tc, long *num_read) { VALUE value, asn1data; unsigned char *p; - long flag = 0; + int flag = 0; p = *pp; if(tc == sym_UNIVERSAL && tag < ossl_asn1_info_size) { - switch(tag){ - case V_ASN1_EOC: - value = decode_eoc(p, hlen+length); - break; - case V_ASN1_BOOLEAN: - value = decode_bool(p, hlen+length); - break; - case V_ASN1_INTEGER: - value = decode_int(p, hlen+length); - break; - case V_ASN1_BIT_STRING: - value = decode_bstr(p, hlen+length, &flag); - break; - case V_ASN1_NULL: - value = decode_null(p, hlen+length); - break; - case V_ASN1_ENUMERATED: - value = decode_enum(p, hlen+length); - break; - case V_ASN1_OBJECT: - value = decode_obj(p, hlen+length); - break; - case V_ASN1_UTCTIME: /* FALLTHROUGH */ - case V_ASN1_GENERALIZEDTIME: - value = decode_time(p, hlen+length); - break; - default: - /* use original value */ - p += hlen; - value = rb_str_new((const char *)p, length); - break; - } + switch(tag){ + case V_ASN1_EOC: + value = decode_eoc(p, hlen+length); + break; + case V_ASN1_BOOLEAN: + value = decode_bool(p, hlen+length); + break; + case V_ASN1_INTEGER: + value = decode_int(p, hlen+length); + break; + case V_ASN1_BIT_STRING: + value = decode_bstr(p, hlen+length, &flag); + break; + case V_ASN1_NULL: + value = decode_null(p, hlen+length); + break; + case V_ASN1_ENUMERATED: + value = decode_enum(p, hlen+length); + break; + case V_ASN1_OBJECT: + value = decode_obj(p, hlen+length); + break; + case V_ASN1_UTCTIME: /* FALLTHROUGH */ + case V_ASN1_GENERALIZEDTIME: + value = decode_time(p, hlen+length); + break; + default: + /* use original value */ + p += hlen; + value = rb_str_new((const char *)p, length); + break; + } } else { - p += hlen; - value = rb_str_new((const char *)p, length); + p += hlen; + value = rb_str_new((const char *)p, length); } *pp += hlen + length; *num_read = hlen + length; if (tc == sym_UNIVERSAL && - tag < ossl_asn1_info_size && ossl_asn1_info[tag].klass) { - VALUE klass = *ossl_asn1_info[tag].klass; - if (tag == V_ASN1_EOC) - asn1data = rb_funcall(cASN1EndOfContent, rb_intern("new"), 0); - else { - VALUE args[4] = { value, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(klass, rb_intern("new"), 4, args); - } - if(tag == V_ASN1_BIT_STRING){ - rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag)); - } + tag < ossl_asn1_info_size && ossl_asn1_info[tag].klass) { + VALUE klass = *ossl_asn1_info[tag].klass; + VALUE args[4]; + args[0] = value; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = tc; + asn1data = rb_obj_alloc(klass); + ossl_asn1_initialize(4, args, asn1data); + if(tag == V_ASN1_BIT_STRING){ + rb_ivar_set(asn1data, sivUNUSED_BITS, INT2NUM(flag)); + } } else { - VALUE args[3] = { value, INT2NUM(tag), tc }; - asn1data = rb_funcallv_public(cASN1Data, rb_intern("new"), 3, args); + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, value, INT2NUM(tag), tc); } return asn1data; @@ -780,8 +842,8 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, static VALUE int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length, - long *offset, int depth, int yield, int j, - int tag, VALUE tc, long *num_read) + long *offset, int depth, int yield, int j, + int tag, VALUE tc, long *num_read) { VALUE value, asn1data, ary; int indefinite; @@ -792,50 +854,52 @@ int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length, available_len = indefinite ? max_len : length; while (available_len > 0) { - long inner_read = 0; - value = ossl_asn1_decode0(pp, available_len, &off, depth + 1, yield, &inner_read); - *num_read += inner_read; - available_len -= inner_read; + long inner_read = 0; + value = ossl_asn1_decode0(pp, available_len, &off, depth + 1, yield, &inner_read); + *num_read += inner_read; + available_len -= inner_read; - if (indefinite) { + if (indefinite) { if (ossl_asn1_tag(value) == V_ASN1_EOC && ossl_asn1_get_tag_class(value) == sym_UNIVERSAL) break; if (available_len == 0) ossl_raise(eASN1Error, "EOC missing in indefinite length encoding"); - } - rb_ary_push(ary, value); + } + rb_ary_push(ary, value); } if (tc == sym_UNIVERSAL) { - if (tag == V_ASN1_SEQUENCE) { - VALUE args[4] = { ary, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(cASN1Sequence, rb_intern("new"), 4, args); - } else if (tag == V_ASN1_SET) { - VALUE args[4] = { ary, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(cASN1Set, rb_intern("new"), 4, args); - } else { - VALUE args[4] = { ary, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(cASN1Constructive, rb_intern("new"), 4, args); - } + VALUE args[4]; + if (tag == V_ASN1_SEQUENCE || tag == V_ASN1_SET) + asn1data = rb_obj_alloc(*ossl_asn1_info[tag].klass); + else + asn1data = rb_obj_alloc(cASN1Constructive); + args[0] = ary; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = tc; + ossl_asn1_initialize(4, args, asn1data); } else { - VALUE args[3] = {ary, INT2NUM(tag), tc}; - asn1data = rb_funcallv_public(cASN1Data, rb_intern("new"), 3, args); + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, ary, INT2NUM(tag), tc); } if (indefinite) - ossl_asn1_set_indefinite_length(asn1data, Qtrue); + ossl_asn1_set_indefinite_length(asn1data, Qtrue); else - ossl_asn1_set_indefinite_length(asn1data, Qfalse); + ossl_asn1_set_indefinite_length(asn1data, Qfalse); *offset = off; return asn1data; } +#define MAX_NESTING_DEPTH 200 + static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, - int yield, long *num_read) + int yield, long *num_read) { unsigned char *start, *p; const unsigned char *p0; @@ -843,6 +907,10 @@ ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, int tag, tc, j; VALUE asn1data, tag_class; + if (depth > MAX_NESTING_DEPTH) { + ossl_raise(eASN1Error, "nesting depth %d exceeds limit", depth); + } + p = *pp; start = p; p0 = p; @@ -851,46 +919,46 @@ ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, if(j & 0x80) ossl_raise(eASN1Error, NULL); if(len > length) ossl_raise(eASN1Error, "value is too short"); if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE) - tag_class = sym_PRIVATE; + tag_class = sym_PRIVATE; else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC) - tag_class = sym_CONTEXT_SPECIFIC; + tag_class = sym_CONTEXT_SPECIFIC; else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION) - tag_class = sym_APPLICATION; + tag_class = sym_APPLICATION; else - tag_class = sym_UNIVERSAL; + tag_class = sym_UNIVERSAL; hlen = p - start; if(yield) { - VALUE arg = rb_ary_new(); - rb_ary_push(arg, LONG2NUM(depth)); - rb_ary_push(arg, LONG2NUM(*offset)); - rb_ary_push(arg, LONG2NUM(hlen)); - rb_ary_push(arg, LONG2NUM(len)); - rb_ary_push(arg, (j & V_ASN1_CONSTRUCTED) ? Qtrue : Qfalse); - rb_ary_push(arg, ossl_asn1_class2sym(tc)); - rb_ary_push(arg, INT2NUM(tag)); - rb_yield(arg); + VALUE arg = rb_ary_new(); + rb_ary_push(arg, LONG2NUM(depth)); + rb_ary_push(arg, LONG2NUM(*offset)); + rb_ary_push(arg, LONG2NUM(hlen)); + rb_ary_push(arg, LONG2NUM(len)); + rb_ary_push(arg, (j & V_ASN1_CONSTRUCTED) ? Qtrue : Qfalse); + rb_ary_push(arg, ossl_asn1_class2sym(tc)); + rb_ary_push(arg, INT2NUM(tag)); + rb_yield(arg); } if(j & V_ASN1_CONSTRUCTED) { - *pp += hlen; - off += hlen; - asn1data = int_ossl_asn1_decode0_cons(pp, length - hlen, len, &off, depth, yield, j, tag, tag_class, &inner_read); - inner_read += hlen; + *pp += hlen; + off += hlen; + asn1data = int_ossl_asn1_decode0_cons(pp, length - hlen, len, &off, depth, yield, j, tag, tag_class, &inner_read); + inner_read += hlen; } else { - if ((j & 0x01) && (len == 0)) - ossl_raise(eASN1Error, "indefinite length for primitive value"); - asn1data = int_ossl_asn1_decode0_prim(pp, len, hlen, tag, tag_class, &inner_read); - off += hlen + len; + if ((j & 0x01) && (len == 0)) + ossl_raise(eASN1Error, "indefinite length for primitive value"); + asn1data = int_ossl_asn1_decode0_prim(pp, len, hlen, tag, tag_class, &inner_read); + off += hlen + len; } if (num_read) - *num_read = inner_read; + *num_read = inner_read; if (len != 0 && inner_read != hlen + len) { - ossl_raise(eASN1Error, - "Type mismatch. Bytes read: %ld Bytes available: %ld", - inner_read, hlen + len); + ossl_raise(eASN1Error, + "Type mismatch. Bytes read: %ld Bytes available: %ld", + inner_read, hlen + len); } *offset = off; @@ -901,9 +969,9 @@ static void int_ossl_decode_sanity_check(long len, long read, long offset) { if (len != 0 && (read != len || offset != len)) { - ossl_raise(eASN1Error, - "Type mismatch. Total bytes read: %ld Bytes available: %ld Offset: %ld", - read, len, offset); + ossl_raise(eASN1Error, + "Type mismatch. Total bytes read: %ld Bytes available: %ld Offset: %ld", + read, len, offset); } } @@ -1003,17 +1071,94 @@ ossl_asn1_decode_all(VALUE self, VALUE obj) tmp_len = len; ary = rb_ary_new(); while (tmp_len > 0) { - long tmp_read = 0; - val = ossl_asn1_decode0(&p, tmp_len, &offset, 0, 0, &tmp_read); - rb_ary_push(ary, val); - read += tmp_read; - tmp_len -= tmp_read; + long tmp_read = 0; + val = ossl_asn1_decode0(&p, tmp_len, &offset, 0, 0, &tmp_read); + rb_ary_push(ary, val); + read += tmp_read; + tmp_len -= tmp_read; } RB_GC_GUARD(tmp); int_ossl_decode_sanity_check(len, read, offset); return ary; } +/* + * call-seq: + * OpenSSL::ASN1::Primitive.new(value [, tag, tagging, tag_class ]) => Primitive + * + * _value_: is mandatory. + * + * _tag_: optional, may be specified for tagged values. If no _tag_ is + * specified, the UNIVERSAL tag corresponding to the Primitive sub-class + * is used by default. + * + * _tagging_: may be used as an encoding hint to encode a value either + * explicitly or implicitly, see ASN1 for possible values. + * + * _tag_class_: if _tag_ and _tagging_ are +nil+ then this is set to + * +:UNIVERSAL+ by default. If either _tag_ or _tagging_ are set then + * +:CONTEXT_SPECIFIC+ is used as the default. For possible values please + * cf. ASN1. + * + * == Example + * int = OpenSSL::ASN1::Integer.new(42) + * zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :IMPLICIT) + * private_explicit_zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :EXPLICIT, :PRIVATE) + */ +static VALUE +ossl_asn1_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE value, tag, tagging, tag_class; + int default_tag; + + rb_scan_args(argc, argv, "13", &value, &tag, &tagging, &tag_class); + default_tag = ossl_asn1_default_tag(self); + + if (default_tag == -1 || argc > 1) { + if(NIL_P(tag)) + ossl_raise(eASN1Error, "must specify tag number"); + if(!NIL_P(tagging) && !SYMBOL_P(tagging)) + ossl_raise(eASN1Error, "invalid tagging method"); + if(NIL_P(tag_class)) { + if (NIL_P(tagging)) + tag_class = sym_UNIVERSAL; + else + tag_class = sym_CONTEXT_SPECIFIC; + } + if(!SYMBOL_P(tag_class)) + ossl_raise(eASN1Error, "invalid tag class"); + } + else{ + tag = INT2NUM(default_tag); + tagging = Qnil; + tag_class = sym_UNIVERSAL; + } + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tagging(self, tagging); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_indefinite_length(self, Qfalse); + if (default_tag == V_ASN1_BIT_STRING) + rb_ivar_set(self, sivUNUSED_BITS, INT2FIX(0)); + + return self; +} + +static VALUE +ossl_asn1eoc_initialize(VALUE self) { + VALUE tag, tagging, tag_class, value; + tag = INT2FIX(0); + tagging = Qnil; + tag_class = sym_UNIVERSAL; + value = rb_str_new("", 0); + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tagging(self, tagging); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_indefinite_length(self, Qfalse); + return self; +} + static VALUE ossl_asn1eoc_to_der(VALUE self) { @@ -1036,20 +1181,20 @@ ossl_asn1prim_to_der(VALUE self) VALUE str; if (ossl_asn1_default_tag(self) == -1) { - str = ossl_asn1_get_value(self); - return to_der_internal(self, 0, 0, StringValue(str)); + str = ossl_asn1_get_value(self); + return to_der_internal(self, 0, 0, StringValue(str)); } asn1 = ossl_asn1_get_asn1type(self); alllen = i2d_ASN1_TYPE(asn1, NULL); if (alllen < 0) { - ASN1_TYPE_free(asn1); - ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); + ASN1_TYPE_free(asn1); + ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); } str = ossl_str_new(NULL, alllen, &state); if (state) { - ASN1_TYPE_free(asn1); - rb_jump_tag(state); + ASN1_TYPE_free(asn1); + rb_jump_tag(state); } p0 = p1 = (unsigned char *)RSTRING_PTR(str); if (i2d_ASN1_TYPE(asn1, &p0) < 0) { @@ -1062,7 +1207,7 @@ ossl_asn1prim_to_der(VALUE self) /* Strip header since to_der_internal() wants only the payload */ j = ASN1_get_object((const unsigned char **)&p1, &bodylen, &tag, &tc, alllen); if (j & 0x80) - ossl_raise(eASN1Error, "ASN1_get_object"); /* should not happen */ + ossl_raise(eASN1Error, "ASN1_get_object"); /* should not happen */ return to_der_internal(self, 0, 0, rb_str_drop_bytes(str, alllen - bodylen)); } @@ -1084,22 +1229,22 @@ ossl_asn1cons_to_der(VALUE self) ary = rb_convert_type(ossl_asn1_get_value(self), T_ARRAY, "Array", "to_a"); str = rb_str_new(NULL, 0); for (i = 0; i < RARRAY_LEN(ary); i++) { - VALUE item = RARRAY_AREF(ary, i); - - if (indef_len && rb_obj_is_kind_of(item, cASN1EndOfContent)) { - if (i != RARRAY_LEN(ary) - 1) - ossl_raise(eASN1Error, "illegal EOC octets in value"); - - /* - * EOC is not really part of the content, but we required to add one - * at the end in the past. - */ - break; - } - - item = ossl_to_der_if_possible(item); - StringValue(item); - rb_str_append(str, item); + VALUE item = RARRAY_AREF(ary, i); + + if (indef_len && rb_obj_is_kind_of(item, cASN1EndOfContent)) { + if (i != RARRAY_LEN(ary) - 1) + ossl_raise(eASN1Error, "illegal EOC octets in value"); + + /* + * EOC is not really part of the content, but we required to add one + * at the end in the past. + */ + break; + } + + item = ossl_to_der_if_possible(item); + StringValue(item); + rb_str_append(str, item); } return to_der_internal(self, 1, indef_len, str); @@ -1107,6 +1252,27 @@ ossl_asn1cons_to_der(VALUE self) /* * call-seq: + * asn1_ary.each { |asn1| block } => asn1_ary + * + * Calls the given block once for each element in self, passing that element + * as parameter _asn1_. If no block is given, an enumerator is returned + * instead. + * + * == Example + * asn1_ary.each do |asn1| + * puts asn1 + * end + */ +static VALUE +ossl_asn1cons_each(VALUE self) +{ + rb_block_call(ossl_asn1_get_value(self), id_each, 0, 0, 0, 0); + + return self; +} + +/* + * call-seq: * OpenSSL::ASN1::ObjectId.register(object_id, short_name, long_name) * * This adds a new ObjectId to the internal tables. Where _object_id_ is the @@ -1124,7 +1290,7 @@ ossl_asn1obj_s_register(VALUE self, VALUE oid, VALUE sn, VALUE ln) StringValueCStr(ln); if(!OBJ_create(RSTRING_PTR(oid), RSTRING_PTR(sn), RSTRING_PTR(ln))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return Qtrue; } @@ -1144,7 +1310,7 @@ ossl_asn1obj_get_sn(VALUE self) val = ossl_asn1_get_value(self); if ((nid = OBJ_txt2nid(StringValueCStr(val))) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); + ret = rb_str_new2(OBJ_nid2sn(nid)); return ret; } @@ -1164,7 +1330,7 @@ ossl_asn1obj_get_ln(VALUE self) val = ossl_asn1_get_value(self); if ((nid = OBJ_txt2nid(StringValueCStr(val))) != NID_undef) - ret = rb_str_new2(OBJ_nid2ln(nid)); + ret = rb_str_new2(OBJ_nid2ln(nid)); return ret; } @@ -1172,23 +1338,7 @@ ossl_asn1obj_get_ln(VALUE self) static VALUE asn1obj_get_oid_i(VALUE vobj) { - ASN1_OBJECT *a1obj = (void *)vobj; - VALUE str; - int len; - - str = rb_usascii_str_new(NULL, 127); - len = OBJ_obj2txt(RSTRING_PTR(str), RSTRING_LENINT(str), a1obj, 1); - if (len <= 0 || len == INT_MAX) - ossl_raise(eASN1Error, "OBJ_obj2txt"); - if (len > RSTRING_LEN(str)) { - /* +1 is for the \0 terminator added by OBJ_obj2txt() */ - rb_str_resize(str, len + 1); - len = OBJ_obj2txt(RSTRING_PTR(str), len + 1, a1obj, 1); - if (len <= 0) - ossl_raise(eASN1Error, "OBJ_obj2txt"); - } - rb_str_set_len(str, len); - return str; + return ossl_asn1obj_to_string_oid((const ASN1_OBJECT *)vobj); } /* @@ -1205,11 +1355,11 @@ ossl_asn1obj_get_oid(VALUE self) ASN1_OBJECT *a1obj; int state; - a1obj = obj_to_asn1obj(ossl_asn1_get_value(self)); + a1obj = ossl_to_asn1obj(ossl_asn1_get_value(self)); str = rb_protect(asn1obj_get_oid_i, (VALUE)a1obj, &state); ASN1_OBJECT_free(a1obj); if (state) - rb_jump_tag(state); + rb_jump_tag(state); return str; } @@ -1234,7 +1384,7 @@ ossl_asn1obj_eq(VALUE self, VALUE other) #define OSSL_ASN1_IMPL_FACTORY_METHOD(klass) \ static VALUE ossl_asn1_##klass(int argc, VALUE *argv, VALUE self)\ -{ return rb_funcallv_public(cASN1##klass, rb_intern("new"), argc, argv); } +{ return rb_funcall3(cASN1##klass, rb_intern("new"), argc, argv); } OSSL_ASN1_IMPL_FACTORY_METHOD(Boolean) OSSL_ASN1_IMPL_FACTORY_METHOD(Integer) @@ -1264,13 +1414,6 @@ void Init_ossl_asn1(void) { #undef rb_intern - VALUE ary; - int i; - -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif sym_UNIVERSAL = ID2SYM(rb_intern_const("UNIVERSAL")); sym_CONTEXT_SPECIFIC = ID2SYM(rb_intern_const("CONTEXT_SPECIFIC")); @@ -1420,17 +1563,20 @@ Init_ossl_asn1(void) rb_define_module_function(mASN1, "traverse", ossl_asn1_traverse, 1); rb_define_module_function(mASN1, "decode", ossl_asn1_decode, 1); rb_define_module_function(mASN1, "decode_all", ossl_asn1_decode_all, 1); - ary = rb_ary_new(); + VALUE ary = rb_ary_new_capa(ossl_asn1_info_size); + for (int i = 0; i < ossl_asn1_info_size; i++) { + const char *name = ossl_asn1_info[i].name; + if (name[0] == '[') + continue; + rb_define_const(mASN1, name, INT2NUM(i)); + rb_ary_store(ary, i, rb_obj_freeze(rb_str_new_cstr(name))); + } + rb_obj_freeze(ary); /* * Array storing tag names at the tag's index. */ rb_define_const(mASN1, "UNIVERSAL_TAG_NAME", ary); - for(i = 0; i < ossl_asn1_info_size; i++){ - if(ossl_asn1_info[i].name[0] == '[') continue; - rb_define_const(mASN1, ossl_asn1_info[i].name, INT2NUM(i)); - rb_ary_store(ary, i, rb_str_new2(ossl_asn1_info[i].name)); - } /* Document-class: OpenSSL::ASN1::ASN1Data * @@ -1520,6 +1666,42 @@ Init_ossl_asn1(void) * puts int2.value # => 1 */ cASN1Data = rb_define_class_under(mASN1, "ASN1Data", rb_cObject); + /* + * Carries the value of a ASN.1 type. + * Please confer Constructive and Primitive for the mappings between + * ASN.1 data types and Ruby classes. + */ + rb_attr(cASN1Data, rb_intern("value"), 1, 1, 0); + /* + * An Integer representing the tag number of this ASN1Data. Never +nil+. + */ + rb_attr(cASN1Data, rb_intern("tag"), 1, 1, 0); + /* + * A Symbol representing the tag class of this ASN1Data. Never +nil+. + * See ASN1Data for possible values. + */ + rb_attr(cASN1Data, rb_intern("tag_class"), 1, 1, 0); + /* + * Never +nil+. A boolean value indicating whether the encoding uses + * indefinite length (in the case of parsing) or whether an indefinite + * length form shall be used (in the encoding case). + * In DER, every value uses definite length form. But in scenarios where + * large amounts of data need to be transferred it might be desirable to + * have some kind of streaming support available. + * For example, huge OCTET STRINGs are preferably sent in smaller-sized + * chunks, each at a time. + * This is possible in BER by setting the length bytes of an encoding + * to zero and by this indicating that the following value will be + * sent in chunks. Indefinite length encodings are always constructed. + * The end of such a stream of chunks is indicated by sending a EOC + * (End of Content) tag. SETs and SEQUENCEs may use an indefinite length + * encoding, but also primitive types such as e.g. OCTET STRINGS or + * BIT STRINGS may leverage this functionality (cf. ITU-T X.690). + */ + rb_attr(cASN1Data, rb_intern("indefinite_length"), 1, 1, 0); + rb_define_alias(cASN1Data, "infinite_length", "indefinite_length"); + rb_define_alias(cASN1Data, "infinite_length=", "indefinite_length="); + rb_define_method(cASN1Data, "initialize", ossl_asn1data_initialize, 3); rb_define_method(cASN1Data, "to_der", ossl_asn1data_to_der, 0); /* Document-class: OpenSSL::ASN1::Primitive @@ -1587,6 +1769,16 @@ Init_ossl_asn1(void) * prim_zero_tagged_explicit = <class>.new(value, 0, :EXPLICIT) */ cASN1Primitive = rb_define_class_under(mASN1, "Primitive", cASN1Data); + /* + * May be used as a hint for encoding a value either implicitly or + * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. + * _tagging_ is not set when a ASN.1 structure is parsed using + * OpenSSL::ASN1.decode. + */ + rb_attr(cASN1Primitive, rb_intern("tagging"), 1, 1, Qtrue); + rb_undef_method(cASN1Primitive, "indefinite_length="); + rb_undef_method(cASN1Primitive, "infinite_length="); + rb_define_method(cASN1Primitive, "initialize", ossl_asn1_initialize, -1); rb_define_method(cASN1Primitive, "to_der", ossl_asn1prim_to_der, 0); /* Document-class: OpenSSL::ASN1::Constructive @@ -1617,7 +1809,17 @@ Init_ossl_asn1(void) * set = OpenSSL::ASN1::Set.new( [ int, str ] ) */ cASN1Constructive = rb_define_class_under(mASN1,"Constructive", cASN1Data); + rb_include_module(cASN1Constructive, rb_mEnumerable); + /* + * May be used as a hint for encoding a value either implicitly or + * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. + * _tagging_ is not set when a ASN.1 structure is parsed using + * OpenSSL::ASN1.decode. + */ + rb_attr(cASN1Constructive, rb_intern("tagging"), 1, 1, Qtrue); + rb_define_method(cASN1Constructive, "initialize", ossl_asn1_initialize, -1); rb_define_method(cASN1Constructive, "to_der", ossl_asn1cons_to_der, 0); + rb_define_method(cASN1Constructive, "each", ossl_asn1cons_each, 0); #define OSSL_ASN1_DEFINE_CLASS(name, super) \ do{\ @@ -1666,10 +1868,13 @@ do{\ rb_define_alias(cASN1ObjectId, "short_name", "sn"); rb_define_alias(cASN1ObjectId, "long_name", "ln"); rb_define_method(cASN1ObjectId, "==", ossl_asn1obj_eq, 1); + rb_attr(cASN1BitString, rb_intern("unused_bits"), 1, 1, 0); + rb_define_method(cASN1EndOfContent, "initialize", ossl_asn1eoc_initialize, 0); rb_define_method(cASN1EndOfContent, "to_der", ossl_asn1eoc_to_der, 0); class_tag_map = rb_hash_new(); + rb_gc_register_mark_object(class_tag_map); rb_hash_aset(class_tag_map, cASN1EndOfContent, INT2NUM(V_ASN1_EOC)); rb_hash_aset(class_tag_map, cASN1Boolean, INT2NUM(V_ASN1_BOOLEAN)); rb_hash_aset(class_tag_map, cASN1Integer, INT2NUM(V_ASN1_INTEGER)); @@ -1693,5 +1898,7 @@ do{\ rb_hash_aset(class_tag_map, cASN1GeneralString, INT2NUM(V_ASN1_GENERALSTRING)); rb_hash_aset(class_tag_map, cASN1UniversalString, INT2NUM(V_ASN1_UNIVERSALSTRING)); rb_hash_aset(class_tag_map, cASN1BMPString, INT2NUM(V_ASN1_BMPSTRING)); - rb_define_const(mASN1, "CLASS_TAG_MAP", class_tag_map); + rb_obj_freeze(class_tag_map); + + id_each = rb_intern_const("each"); } diff --git a/ext/openssl/ossl_asn1.h b/ext/openssl/ossl_asn1.h index 3b689c0ee7..b605df8f3f 100644 --- a/ext/openssl/ossl_asn1.h +++ b/ext/openssl/ossl_asn1.h @@ -32,10 +32,24 @@ VALUE asn1integer_to_num(const ASN1_INTEGER *); ASN1_INTEGER *num_to_asn1integer(VALUE, ASN1_INTEGER *); /* + * ASN1_OBJECT conversions + */ +ASN1_OBJECT *ossl_to_asn1obj(VALUE obj); +/* + * Returns the short name if available, the dotted decimal notation otherwise. + * This is the most common way to return ASN1_OBJECT to Ruby. + */ +VALUE ossl_asn1obj_to_string(const ASN1_OBJECT *a1obj); +/* + * However, some places use long names instead. This is likely unintentional, + * but we keep the current behavior in existing methods. + */ +VALUE ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *a1obj); + +/* * ASN1 module */ extern VALUE mASN1; -extern VALUE eASN1Error; extern VALUE cASN1Data; diff --git a/ext/openssl/ossl_bio.c b/ext/openssl/ossl_bio.c index 2ef2080507..cc03c5d5f7 100644 --- a/ext/openssl/ossl_bio.c +++ b/ext/openssl/ossl_bio.c @@ -16,11 +16,11 @@ ossl_obj2bio(volatile VALUE *pobj) BIO *bio; if (RB_TYPE_P(obj, T_FILE)) - obj = rb_funcallv(obj, rb_intern("read"), 0, NULL); + obj = rb_funcallv(obj, rb_intern("read"), 0, NULL); StringValue(obj); bio = BIO_new_mem_buf(RSTRING_PTR(obj), RSTRING_LENINT(obj)); if (!bio) - ossl_raise(eOSSLError, "BIO_new_mem_buf"); + ossl_raise(eOSSLError, "BIO_new_mem_buf"); *pobj = obj; return bio; } @@ -32,11 +32,15 @@ ossl_membio2str(BIO *bio) int state; BUF_MEM *buf; - BIO_get_mem_ptr(bio, &buf); + if (BIO_get_mem_ptr(bio, &buf) <= 0) { + BIO_free(bio); + ossl_raise(eOSSLError, "BIO_get_mem_ptr"); + } + ret = ossl_str_new(buf->data, buf->length, &state); BIO_free(bio); if (state) - rb_jump_tag(state); + rb_jump_tag(state); return ret; } diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c index a6fa2c1dab..9014f2df2b 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -11,19 +11,19 @@ #include "ossl.h" #define NewBN(klass) \ - TypedData_Wrap_Struct((klass), &ossl_bn_type, 0) + TypedData_Wrap_Struct((klass), &ossl_bn_type, 0) #define SetBN(obj, bn) do { \ - if (!(bn)) { \ - ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ - } \ - RTYPEDDATA_DATA(obj) = (bn); \ + if (!(bn)) { \ + ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ + } \ + RTYPEDDATA_DATA(obj) = (bn); \ } while (0) #define GetBN(obj, bn) do { \ - TypedData_Get_Struct((obj), BIGNUM, &ossl_bn_type, (bn)); \ - if (!(bn)) { \ - ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ - } \ + TypedData_Get_Struct((obj), BIGNUM, &ossl_bn_type, (bn)); \ + if (!(bn)) { \ + ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ + } \ } while (0) static void @@ -35,7 +35,7 @@ ossl_bn_free(void *ptr) static const rb_data_type_t ossl_bn_type = { "OpenSSL/BN", { - 0, ossl_bn_free, + 0, ossl_bn_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE, }; @@ -75,40 +75,40 @@ integer_to_bnptr(VALUE obj, BIGNUM *orig) BIGNUM *bn; if (FIXNUM_P(obj)) { - long i; - unsigned char bin[sizeof(long)]; - long n = FIX2LONG(obj); - unsigned long un = labs(n); - - for (i = sizeof(long) - 1; 0 <= i; i--) { - bin[i] = un & 0xff; - un >>= 8; - } - - bn = BN_bin2bn(bin, sizeof(bin), orig); - if (!bn) - ossl_raise(eBNError, "BN_bin2bn"); - if (n < 0) - BN_set_negative(bn, 1); + long i; + unsigned char bin[sizeof(long)]; + long n = FIX2LONG(obj); + unsigned long un = labs(n); + + for (i = sizeof(long) - 1; 0 <= i; i--) { + bin[i] = un & 0xff; + un >>= 8; + } + + bn = BN_bin2bn(bin, sizeof(bin), orig); + if (!bn) + ossl_raise(eBNError, "BN_bin2bn"); + if (n < 0) + BN_set_negative(bn, 1); } else { /* assuming Bignum */ - size_t len = rb_absint_size(obj, NULL); - unsigned char *bin; - VALUE buf; - int sign; - - if (INT_MAX < len) { - rb_raise(eBNError, "bignum too long"); - } - bin = (unsigned char*)ALLOCV_N(unsigned char, buf, len); - sign = rb_integer_pack(obj, bin, len, 1, 0, INTEGER_PACK_BIG_ENDIAN); - - bn = BN_bin2bn(bin, (int)len, orig); - ALLOCV_END(buf); - if (!bn) - ossl_raise(eBNError, "BN_bin2bn"); - if (sign < 0) - BN_set_negative(bn, 1); + size_t len = rb_absint_size(obj, NULL); + unsigned char *bin; + VALUE buf; + int sign; + + if (INT_MAX < len) { + rb_raise(eBNError, "bignum too long"); + } + bin = (unsigned char*)ALLOCV_N(unsigned char, buf, len); + sign = rb_integer_pack(obj, bin, len, 1, 0, INTEGER_PACK_BIG_ENDIAN); + + bn = BN_bin2bn(bin, (int)len, orig); + ALLOCV_END(buf); + if (!bn) + ossl_raise(eBNError, "BN_bin2bn"); + if (sign < 0) + BN_set_negative(bn, 1); } return bn; @@ -121,11 +121,11 @@ try_convert_to_bn(VALUE obj) VALUE newobj = Qnil; if (rb_obj_is_kind_of(obj, cBN)) - return obj; + return obj; if (RB_INTEGER_TYPE_P(obj)) { - newobj = NewBN(cBN); /* Handle potential mem leaks */ - bn = integer_to_bnptr(obj, NULL); - SetBN(newobj, bn); + newobj = NewBN(cBN); /* Handle potential mem leaks */ + bn = integer_to_bnptr(obj, NULL); + SetBN(newobj, bn); } return newobj; @@ -139,7 +139,7 @@ ossl_bn_value_ptr(volatile VALUE *ptr) tmp = try_convert_to_bn(*ptr); if (NIL_P(tmp)) - ossl_raise(rb_eTypeError, "Cannot convert into OpenSSL::BN"); + ossl_raise(rb_eTypeError, "Cannot convert into OpenSSL::BN"); GetBN(tmp, bn); *ptr = tmp; @@ -209,7 +209,7 @@ ossl_bn_alloc(VALUE klass) VALUE obj = NewBN(klass); if (!(bn = BN_new())) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } SetBN(obj, bn); @@ -251,7 +251,7 @@ ossl_bn_initialize(int argc, VALUE *argv, VALUE self) char *ptr; if (rb_scan_args(argc, argv, "11", &str, &bs) == 2) { - base = NUM2INT(bs); + base = NUM2INT(bs); } if (NIL_P(str)) { @@ -260,49 +260,49 @@ ossl_bn_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (RB_INTEGER_TYPE_P(str)) { - GetBN(self, bn); - integer_to_bnptr(str, bn); + GetBN(self, bn); + integer_to_bnptr(str, bn); - return self; + return self; } if (RTEST(rb_obj_is_kind_of(str, cBN))) { - BIGNUM *other; - - GetBN(self, bn); - GetBN(str, other); /* Safe - we checked kind_of? above */ - if (!BN_copy(bn, other)) { - ossl_raise(eBNError, NULL); - } - return self; + BIGNUM *other; + + GetBN(self, bn); + GetBN(str, other); /* Safe - we checked kind_of? above */ + if (!BN_copy(bn, other)) { + ossl_raise(eBNError, NULL); + } + return self; } GetBN(self, bn); switch (base) { - case 0: + case 0: ptr = StringValuePtr(str); if (!BN_mpi2bn((unsigned char *)ptr, RSTRING_LENINT(str), bn)) { - ossl_raise(eBNError, NULL); - } - break; - case 2: + ossl_raise(eBNError, NULL); + } + break; + case 2: ptr = StringValuePtr(str); if (!BN_bin2bn((unsigned char *)ptr, RSTRING_LENINT(str), bn)) { - ossl_raise(eBNError, NULL); - } - break; - case 10: - if (!BN_dec2bn(&bn, StringValueCStr(str))) { - ossl_raise(eBNError, NULL); - } - break; - case 16: - if (!BN_hex2bn(&bn, StringValueCStr(str))) { - ossl_raise(eBNError, NULL); - } - break; - default: - ossl_raise(rb_eArgError, "invalid radix %d", base); + ossl_raise(eBNError, NULL); + } + break; + case 10: + if (!BN_dec2bn(&bn, StringValueCStr(str))) { + ossl_raise(eBNError, NULL); + } + break; + case 16: + if (!BN_hex2bn(&bn, StringValueCStr(str))) { + ossl_raise(eBNError, NULL); + } + break; + default: + ossl_raise(rb_eArgError, "invalid radix %d", base); } return self; } @@ -334,32 +334,32 @@ ossl_bn_to_s(int argc, VALUE *argv, VALUE self) char *buf; if (rb_scan_args(argc, argv, "01", &bs) == 1) { - base = NUM2INT(bs); + base = NUM2INT(bs); } GetBN(self, bn); switch (base) { - case 0: - len = BN_bn2mpi(bn, NULL); + case 0: + len = BN_bn2mpi(bn, NULL); str = rb_str_new(0, len); - if (BN_bn2mpi(bn, (unsigned char *)RSTRING_PTR(str)) != len) - ossl_raise(eBNError, NULL); - break; - case 2: - len = BN_num_bytes(bn); + if (BN_bn2mpi(bn, (unsigned char *)RSTRING_PTR(str)) != len) + ossl_raise(eBNError, NULL); + break; + case 2: + len = BN_num_bytes(bn); str = rb_str_new(0, len); - if (BN_bn2bin(bn, (unsigned char *)RSTRING_PTR(str)) != len) - ossl_raise(eBNError, NULL); - break; - case 10: - if (!(buf = BN_bn2dec(bn))) ossl_raise(eBNError, NULL); - str = ossl_buf2str(buf, rb_long2int(strlen(buf))); - break; - case 16: - if (!(buf = BN_bn2hex(bn))) ossl_raise(eBNError, NULL); - str = ossl_buf2str(buf, rb_long2int(strlen(buf))); - break; - default: - ossl_raise(rb_eArgError, "invalid radix %d", base); + if (BN_bn2bin(bn, (unsigned char *)RSTRING_PTR(str)) != len) + ossl_raise(eBNError, NULL); + break; + case 10: + if (!(buf = BN_bn2dec(bn))) ossl_raise(eBNError, NULL); + str = ossl_buf2str(buf, rb_long2int(strlen(buf))); + break; + case 16: + if (!(buf = BN_bn2hex(bn))) ossl_raise(eBNError, NULL); + str = ossl_buf2str(buf, rb_long2int(strlen(buf))); + break; + default: + ossl_raise(rb_eArgError, "invalid radix %d", base); } return str; @@ -379,7 +379,7 @@ ossl_bn_to_i(VALUE self) GetBN(self, bn); if (!(txt = BN_bn2hex(bn))) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } num = rb_cstr_to_inum(txt, 16, Qtrue); OPENSSL_free(txt); @@ -397,31 +397,31 @@ static VALUE ossl_bn_coerce(VALUE self, VALUE other) { switch(TYPE(other)) { - case T_STRING: - self = ossl_bn_to_s(0, NULL, self); - break; - case T_FIXNUM: - case T_BIGNUM: - self = ossl_bn_to_i(self); - break; - default: - if (!RTEST(rb_obj_is_kind_of(other, cBN))) { - ossl_raise(rb_eTypeError, "Don't know how to coerce"); - } + case T_STRING: + self = ossl_bn_to_s(0, NULL, self); + break; + case T_FIXNUM: + case T_BIGNUM: + self = ossl_bn_to_i(self); + break; + default: + if (!RTEST(rb_obj_is_kind_of(other, cBN))) { + ossl_raise(rb_eTypeError, "Don't know how to coerce"); + } } return rb_assoc_new(other, self); } -#define BIGNUM_BOOL1(func) \ - static VALUE \ - ossl_bn_##func(VALUE self) \ - { \ - BIGNUM *bn; \ - GetBN(self, bn); \ - if (BN_##func(bn)) { \ - return Qtrue; \ - } \ - return Qfalse; \ +#define BIGNUM_BOOL1(func) \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn; \ + GetBN(self, bn); \ + if (BN_##func(bn)) { \ + return Qtrue; \ + } \ + return Qfalse; \ } /* @@ -456,27 +456,27 @@ ossl_bn_is_negative(VALUE self) GetBN(self, bn); if (BN_is_zero(bn)) - return Qfalse; + return Qfalse; return BN_is_negative(bn) ? Qtrue : Qfalse; } -#define BIGNUM_1c(func) \ - static VALUE \ - ossl_bn_##func(VALUE self) \ - { \ - BIGNUM *bn, *result; \ - VALUE obj; \ - GetBN(self, bn); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn, ossl_bn_ctx) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_1c(func) \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn, *result; \ + VALUE obj; \ + GetBN(self, bn); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn, ossl_bn_ctx) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -486,23 +486,23 @@ ossl_bn_is_negative(VALUE self) */ BIGNUM_1c(sqr) -#define BIGNUM_2(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn1, bn2) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_2(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn1, bn2) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -519,23 +519,23 @@ BIGNUM_2(add) */ BIGNUM_2(sub) -#define BIGNUM_2c(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn1, bn2, ossl_bn_ctx) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_2c(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn1, bn2, ossl_bn_ctx) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -573,18 +573,18 @@ BIGNUM_2c(gcd) */ BIGNUM_2c(mod_sqr) -#define BIGNUM_2cr(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_##func(NULL, bn1, bn2, ossl_bn_ctx))) \ - ossl_raise(eBNError, NULL); \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_2cr(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_##func(NULL, bn1, bn2, ossl_bn_ctx))) \ + ossl_raise(eBNError, NULL); \ + SetBN(obj, result); \ + return obj; \ } /* @@ -619,16 +619,16 @@ ossl_bn_div(VALUE self, VALUE other) obj1 = NewBN(klass); obj2 = NewBN(klass); if (!(r1 = BN_new())) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } if (!(r2 = BN_new())) { - BN_free(r1); - ossl_raise(eBNError, NULL); + BN_free(r1); + ossl_raise(eBNError, NULL); } if (!BN_div(r1, r2, bn1, bn2, ossl_bn_ctx)) { - BN_free(r1); - BN_free(r2); - ossl_raise(eBNError, NULL); + BN_free(r1); + BN_free(r2); + ossl_raise(eBNError, NULL); } SetBN(obj1, r1); SetBN(obj2, r2); @@ -636,24 +636,24 @@ ossl_bn_div(VALUE self, VALUE other) return rb_ary_new3(2, obj1, obj2); } -#define BIGNUM_3c(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other1, VALUE other2) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other1); \ - BIGNUM *bn3 = GetBNPtr(other2), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_3c(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other1, VALUE other2) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other1); \ + BIGNUM *bn3 = GetBNPtr(other2), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -684,17 +684,17 @@ BIGNUM_3c(mod_mul) */ BIGNUM_3c(mod_exp) -#define BIGNUM_BIT(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE bit) \ - { \ - BIGNUM *bn; \ - rb_check_frozen(self); \ - GetBN(self, bn); \ - if (BN_##func(bn, NUM2INT(bit)) <= 0) { \ - ossl_raise(eBNError, NULL); \ - } \ - return self; \ +#define BIGNUM_BIT(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE bit) \ + { \ + BIGNUM *bn; \ + rb_check_frozen(self); \ + GetBN(self, bn); \ + if (BN_##func(bn, NUM2INT(bit)) <= 0) { \ + ossl_raise(eBNError, NULL); \ + } \ + return self; \ } /* @@ -733,30 +733,30 @@ ossl_bn_is_bit_set(VALUE self, VALUE bit) b = NUM2INT(bit); GetBN(self, bn); if (BN_is_bit_set(bn, b)) { - return Qtrue; + return Qtrue; } return Qfalse; } -#define BIGNUM_SHIFT(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE bits) \ - { \ - BIGNUM *bn, *result; \ - int b; \ - VALUE obj; \ - b = NUM2INT(bits); \ - GetBN(self, bn); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn, b) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_SHIFT(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE bits) \ + { \ + BIGNUM *bn, *result; \ + int b; \ + VALUE obj; \ + b = NUM2INT(bits); \ + GetBN(self, bn); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn, b) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -773,18 +773,18 @@ BIGNUM_SHIFT(lshift) */ BIGNUM_SHIFT(rshift) -#define BIGNUM_SELF_SHIFT(func) \ - static VALUE \ - ossl_bn_self_##func(VALUE self, VALUE bits) \ - { \ - BIGNUM *bn; \ - int b; \ - rb_check_frozen(self); \ - b = NUM2INT(bits); \ - GetBN(self, bn); \ - if (BN_##func(bn, bn, b) <= 0) \ - ossl_raise(eBNError, NULL); \ - return self; \ +#define BIGNUM_SELF_SHIFT(func) \ + static VALUE \ + ossl_bn_self_##func(VALUE self, VALUE bits) \ + { \ + BIGNUM *bn; \ + int b; \ + rb_check_frozen(self); \ + b = NUM2INT(bits); \ + GetBN(self, bn); \ + if (BN_##func(bn, bn, b) <= 0) \ + ossl_raise(eBNError, NULL); \ + return self; \ } /* @@ -886,32 +886,32 @@ ossl_bn_s_generate_prime(int argc, VALUE *argv, VALUE klass) num = NUM2INT(vnum); if (vsafe == Qfalse) { - safe = 0; + safe = 0; } if (!NIL_P(vadd)) { - add = GetBNPtr(vadd); - rem = NIL_P(vrem) ? NULL : GetBNPtr(vrem); + add = GetBNPtr(vadd); + rem = NIL_P(vrem) ? NULL : GetBNPtr(vrem); } obj = NewBN(klass); if (!(result = BN_new())) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } if (!BN_generate_prime_ex(result, num, safe, add, rem, NULL)) { - BN_free(result); - ossl_raise(eBNError, NULL); + BN_free(result); + ossl_raise(eBNError, NULL); } SetBN(obj, result); return obj; } -#define BIGNUM_NUM(func) \ - static VALUE \ - ossl_bn_##func(VALUE self) \ - { \ - BIGNUM *bn; \ - GetBN(self, bn); \ - return INT2NUM(BN_##func(bn)); \ +#define BIGNUM_NUM(func) \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn; \ + GetBN(self, bn); \ + return INT2NUM(BN_##func(bn)); \ } /* @@ -942,7 +942,7 @@ ossl_bn_copy(VALUE self, VALUE other) bn2 = GetBNPtr(other); if (!BN_copy(bn1, bn2)) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } return self; } @@ -961,7 +961,7 @@ ossl_bn_uplus(VALUE self) obj = NewBN(cBN); bn2 = BN_dup(bn1); if (!bn2) - ossl_raise(eBNError, "BN_dup"); + ossl_raise(eBNError, "BN_dup"); SetBN(obj, bn2); return obj; @@ -981,7 +981,7 @@ ossl_bn_uminus(VALUE self) obj = NewBN(cBN); bn2 = BN_dup(bn1); if (!bn2) - ossl_raise(eBNError, "BN_dup"); + ossl_raise(eBNError, "BN_dup"); SetBN(obj, bn2); BN_set_negative(bn2, !BN_is_negative(bn2)); @@ -1006,13 +1006,13 @@ ossl_bn_abs(VALUE self) } } -#define BIGNUM_CMP(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other); \ - GetBN(self, bn1); \ - return INT2NUM(BN_##func(bn1, bn2)); \ +#define BIGNUM_CMP(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other); \ + GetBN(self, bn1); \ + return INT2NUM(BN_##func(bn1, bn2)); \ } /* @@ -1049,11 +1049,11 @@ ossl_bn_eq(VALUE self, VALUE other) GetBN(self, bn1); other = try_convert_to_bn(other); if (NIL_P(other)) - return Qfalse; + return Qfalse; GetBN(other, bn2); if (!BN_cmp(bn1, bn2)) { - return Qtrue; + return Qtrue; } return Qfalse; } @@ -1072,7 +1072,7 @@ ossl_bn_eql(VALUE self, VALUE other) BIGNUM *bn1, *bn2; if (!rb_obj_is_kind_of(other, cBN)) - return Qfalse; + return Qfalse; GetBN(self, bn1); GetBN(other, bn2); @@ -1099,8 +1099,8 @@ ossl_bn_hash(VALUE self) len = BN_num_bytes(bn); buf = ALLOCV(tmp, len); if (BN_bn2bin(bn, buf) != len) { - ALLOCV_END(tmp); - ossl_raise(eBNError, "BN_bn2bin"); + ALLOCV_END(tmp); + ossl_raise(eBNError, "BN_bn2bin"); } hash = ST2FIX(rb_memhash(buf, len)); @@ -1202,11 +1202,6 @@ ossl_bn_set_flags(VALUE self, VALUE arg) void Init_ossl_bn(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - #ifdef HAVE_RB_EXT_RACTOR_SAFE ossl_bn_ctx_key = rb_ractor_local_storage_ptr_newkey(&ossl_bn_ctx_key_type); #else diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 07e651335d..e9dcd943e3 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -14,7 +14,7 @@ #define AllocCipher(obj, ctx) do { \ (ctx) = EVP_CIPHER_CTX_new(); \ if (!(ctx)) \ - ossl_raise(rb_eRuntimeError, NULL); \ + ossl_raise(rb_eRuntimeError, NULL); \ RTYPEDDATA_DATA(obj) = (ctx); \ } while (0) #define GetCipherInit(obj, ctx) do { \ @@ -23,7 +23,7 @@ #define GetCipher(obj, ctx) do { \ GetCipherInit((obj), (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "Cipher not initialized!"); \ + ossl_raise(rb_eRuntimeError, "Cipher not initialized!"); \ } \ } while (0) @@ -32,7 +32,8 @@ */ static VALUE cCipher; static VALUE eCipherError; -static ID id_auth_tag_len, id_key_set; +static VALUE eAuthTagError; +static ID id_auth_tag_len, id_key_set, id_cipher_holder; static VALUE ossl_cipher_alloc(VALUE klass); static void ossl_cipher_free(void *ptr); @@ -40,35 +41,63 @@ static void ossl_cipher_free(void *ptr); static const rb_data_type_t ossl_cipher_type = { "OpenSSL/Cipher", { - 0, ossl_cipher_free, + 0, ossl_cipher_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; +#ifdef OSSL_USE_PROVIDER +static void +ossl_evp_cipher_free(void *ptr) +{ + // This is safe to call against const EVP_CIPHER * returned by + // EVP_get_cipherbyname() + EVP_CIPHER_free(ptr); +} + +static const rb_data_type_t ossl_evp_cipher_holder_type = { + "OpenSSL/EVP_CIPHER", + { + .dfree = ossl_evp_cipher_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; +#endif + /* * PUBLIC */ const EVP_CIPHER * -ossl_evp_get_cipherbyname(VALUE obj) +ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder) { + *holder = Qnil; if (rb_obj_is_kind_of(obj, cCipher)) { - EVP_CIPHER_CTX *ctx; - - GetCipher(obj, ctx); - - return EVP_CIPHER_CTX_cipher(ctx); + EVP_CIPHER_CTX *ctx; + GetCipher(obj, ctx); + EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_CIPHER_CTX_cipher(ctx); +#ifdef OSSL_USE_PROVIDER + *holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL); + if (!EVP_CIPHER_up_ref(cipher)) + ossl_raise(eCipherError, "EVP_CIPHER_up_ref"); + RTYPEDDATA_DATA(*holder) = cipher; +#endif + return cipher; } - else { - const EVP_CIPHER *cipher; - - StringValueCStr(obj); - cipher = EVP_get_cipherbyname(RSTRING_PTR(obj)); - if (!cipher) - ossl_raise(rb_eArgError, - "unsupported cipher algorithm: %"PRIsVALUE, obj); - return cipher; + const char *name = StringValueCStr(obj); + EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_get_cipherbyname(name); +#ifdef OSSL_USE_PROVIDER + if (!cipher) { + ossl_clear_error(); + *holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL); + cipher = EVP_CIPHER_fetch(NULL, name, NULL); + RTYPEDDATA_DATA(*holder) = cipher; } +#endif + if (!cipher) + ossl_raise(eCipherError, "unsupported cipher algorithm: %"PRIsVALUE, + obj); + return cipher; } VALUE @@ -77,10 +106,13 @@ ossl_cipher_new(const EVP_CIPHER *cipher) VALUE ret; EVP_CIPHER_CTX *ctx; + // NOTE: This does not set id_cipher_holder because this function should + // only be called from ossl_engine.c, which will not use any + // reference-counted ciphers. ret = ossl_cipher_alloc(cCipher); AllocCipher(ret, ctx); if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return ret; } @@ -113,19 +145,17 @@ ossl_cipher_initialize(VALUE self, VALUE str) { EVP_CIPHER_CTX *ctx; const EVP_CIPHER *cipher; - char *name; + VALUE cipher_holder; - name = StringValueCStr(str); GetCipherInit(self, ctx); if (ctx) { - ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); + ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); } + cipher = ossl_evp_cipher_fetch(str, &cipher_holder); AllocCipher(self, ctx); - if (!(cipher = EVP_get_cipherbyname(name))) { - ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%"PRIsVALUE")", str); - } if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, "EVP_CipherInit_ex"); + rb_ivar_set(self, id_cipher_holder, cipher_holder); return self; } @@ -141,11 +171,11 @@ ossl_cipher_copy(VALUE self, VALUE other) GetCipherInit(self, ctx1); if (!ctx1) { - AllocCipher(self, ctx1); + AllocCipher(self, ctx1); } GetCipher(other, ctx2); if (EVP_CIPHER_CTX_copy(ctx1, ctx2) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return self; } @@ -170,8 +200,8 @@ ossl_s_ciphers(VALUE self) ary = rb_ary_new(); OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH, - add_cipher_name_to_ary, - (void*)ary); + add_cipher_name_to_ary, + (void*)ary); return ary; } @@ -192,7 +222,7 @@ ossl_cipher_reset(VALUE self) GetCipher(self, ctx); if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return self; } @@ -218,9 +248,8 @@ ossl_cipher_init(VALUE self, int enc) * * Initializes the Cipher for encryption. * - * Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the - * following methods: - * * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen] + * Make sure to call either #encrypt or #decrypt before using the Cipher for + * any operation or setting any parameters. * * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1). */ @@ -236,9 +265,8 @@ ossl_cipher_encrypt(VALUE self) * * Initializes the Cipher for decryption. * - * Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the - * following methods: - * * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen] + * Make sure to call either #encrypt or #decrypt before using the Cipher for + * any operation or setting any parameters. * * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0). */ @@ -254,46 +282,42 @@ ossl_cipher_decrypt(VALUE self) * * Generates and sets the key/IV based on a password. * - * *WARNING*: This method is only PKCS5 v1.5 compliant when using RC2, RC4-40, - * or DES with MD5 or SHA1. Using anything else (like AES) will generate the - * key/iv using an OpenSSL specific method. This method is deprecated and - * should no longer be used. Use a PKCS5 v2 key generation method from - * OpenSSL::PKCS5 instead. + * *WARNING*: This method is deprecated and should not be used. This method + * corresponds to EVP_BytesToKey(), a non-standard OpenSSL extension of the + * legacy PKCS #5 v1.5 key derivation function. See OpenSSL::KDF for other + * options to derive keys from passwords. * * === Parameters * * _salt_ must be an 8 byte string if provided. * * _iterations_ is an integer with a default of 2048. * * _digest_ is a Digest object that defaults to 'MD5' - * - * A minimum of 1000 iterations is recommended. - * */ static VALUE ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) { EVP_CIPHER_CTX *ctx; const EVP_MD *digest; - VALUE vpass, vsalt, viter, vdigest; + VALUE vpass, vsalt, viter, vdigest, md_holder; unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH], *salt = NULL; int iter; rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest); StringValue(vpass); if(!NIL_P(vsalt)){ - StringValue(vsalt); - if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN) - ossl_raise(eCipherError, "salt must be an 8-octet string"); - salt = (unsigned char *)RSTRING_PTR(vsalt); + StringValue(vsalt); + if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN) + ossl_raise(eCipherError, "salt must be an 8-octet string"); + salt = (unsigned char *)RSTRING_PTR(vsalt); } iter = NIL_P(viter) ? 2048 : NUM2INT(viter); if (iter <= 0) - rb_raise(rb_eArgError, "iterations must be a positive integer"); - digest = NIL_P(vdigest) ? EVP_md5() : ossl_evp_get_digestbyname(vdigest); + rb_raise(rb_eArgError, "iterations must be a positive integer"); + digest = NIL_P(vdigest) ? EVP_md5() : ossl_evp_md_fetch(vdigest, &md_holder); GetCipher(self, ctx); EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt, - (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv); + (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv); if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); OPENSSL_cleanse(key, sizeof key); OPENSSL_cleanse(iv, sizeof iv); @@ -304,25 +328,25 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) static int ossl_cipher_update_long(EVP_CIPHER_CTX *ctx, unsigned char *out, long *out_len_ptr, - const unsigned char *in, long in_len) + const unsigned char *in, long in_len) { int out_part_len; int limit = INT_MAX / 2 + 1; long out_len = 0; do { - int in_part_len = in_len > limit ? limit : (int)in_len; + int in_part_len = in_len > limit ? limit : (int)in_len; - if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0, - &out_part_len, in, in_part_len)) - return 0; + if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0, + &out_part_len, in, in_part_len)) + return 0; - out_len += out_part_len; - in += in_part_len; + out_len += out_part_len; + in += in_part_len; } while ((in_len -= limit) > 0); if (out_len_ptr) - *out_len_ptr = out_len; + *out_len_ptr = out_len; return 1; } @@ -338,6 +362,9 @@ ossl_cipher_update_long(EVP_CIPHER_CTX *ctx, unsigned char *out, long *out_len_p * * If _buffer_ is given, the encryption/decryption result will be written to * it. _buffer_ will be resized automatically. + * + * *NOTE*: When decrypting using an AEAD cipher, the integrity of the output + * is not verified until #final has been called. */ static VALUE ossl_cipher_update(int argc, VALUE *argv, VALUE self) @@ -350,7 +377,7 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "11", &data, &str); if (!RTEST(rb_attr_get(self, id_key_set))) - ossl_raise(eCipherError, "key not set"); + ossl_raise(eCipherError, "key not set"); StringValue(data); in = (unsigned char *)RSTRING_PTR(data); @@ -369,14 +396,14 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) * currently implemented in OpenSSL, but this can change in the future. */ if (in_len > LONG_MAX - EVP_MAX_BLOCK_LENGTH) { - ossl_raise(rb_eRangeError, - "data too big to make output buffer: %ld bytes", in_len); + ossl_raise(rb_eRangeError, + "data too big to make output buffer: %ld bytes", in_len); } out_len = in_len + EVP_MAX_BLOCK_LENGTH; - if (NIL_P(str)) { - str = rb_str_new(0, out_len); - } else { + if (NIL_P(str)) + str = rb_str_buf_new(out_len); + else { StringValue(str); if ((long)rb_str_capacity(str) >= out_len) rb_str_modify(str); @@ -384,9 +411,9 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) rb_str_modify_expand(str, out_len - RSTRING_LEN(str)); } - if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len)) - ossl_raise(eCipherError, NULL); - assert(out_len <= RSTRING_LEN(str)); + if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), + &out_len, in, in_len)) + ossl_raise(eCipherError, "EVP_CipherUpdate"); rb_str_set_len(str, out_len); return str; @@ -397,14 +424,17 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) * cipher.final -> string * * Returns the remaining data held in the cipher object. Further calls to - * Cipher#update or Cipher#final will return garbage. This call should always + * Cipher#update or Cipher#final are invalid. This call should always * be made as the last call of an encryption or decryption operation, after * having fed the entire plaintext or ciphertext to the Cipher instance. * - * If an authenticated cipher was used, a CipherError is raised if the tag - * could not be authenticated successfully. Only call this method after - * setting the authentication tag and passing the entire contents of the - * ciphertext into the cipher. + * When encrypting using an AEAD cipher, the authentication tag can be + * retrieved by #auth_tag after #final has been called. + * + * When decrypting using an AEAD cipher, this method will verify the integrity + * of the ciphertext and the associated data with the authentication tag, + * which must be set by #auth_tag= prior to calling this method. + * If the verification fails, CipherError will be raised. */ static VALUE ossl_cipher_final(VALUE self) @@ -415,9 +445,17 @@ ossl_cipher_final(VALUE self) GetCipher(self, ctx); str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx)); - if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) - ossl_raise(eCipherError, NULL); - assert(out_len <= RSTRING_LEN(str)); + if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) { + /* For AEAD ciphers, this is likely an authentication failure */ + if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) { + /* For AEAD ciphers, EVP_CipherFinal_ex failures are authentication tag verification failures */ + ossl_raise(eAuthTagError, "AEAD authentication tag verification failed"); + } + else { + /* For non-AEAD ciphers */ + ossl_raise(eCipherError, "cipher final failed"); + } + } rb_str_set_len(str, out_len); return str; @@ -442,7 +480,7 @@ ossl_cipher_name(VALUE self) /* * call-seq: - * cipher.key = string -> string + * cipher.key = string * * Sets the cipher key. To generate a key, you should either use a secure * random byte string or, if the key is to be derived from a password, you @@ -450,6 +488,8 @@ ossl_cipher_name(VALUE self) * generate a secure random-based key, Cipher#random_key may be used. * * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + * + * See also the man page EVP_CipherInit_ex(3). */ static VALUE ossl_cipher_set_key(VALUE self, VALUE key) @@ -462,10 +502,10 @@ ossl_cipher_set_key(VALUE self, VALUE key) key_len = EVP_CIPHER_CTX_key_length(ctx); if (RSTRING_LEN(key) != key_len) - ossl_raise(rb_eArgError, "key must be %d bytes", key_len); + ossl_raise(rb_eArgError, "key must be %d bytes", key_len); if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); rb_ivar_set(self, id_key_set, Qtrue); @@ -474,15 +514,21 @@ ossl_cipher_set_key(VALUE self, VALUE key) /* * call-seq: - * cipher.iv = string -> string + * cipher.iv = string * * Sets the cipher IV. Please note that since you should never be using ECB * mode, an IV is always explicitly required and should be set prior to - * encryption. The IV itself can be safely transmitted in public, but it - * should be unpredictable to prevent certain kinds of attacks. You may use - * Cipher#random_iv to create a secure random IV. + * encryption. The IV itself can be safely transmitted in public. * - * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + * This method expects the String to have the length equal to #iv_len. To use + * a different IV length with an AEAD cipher, #iv_len= must be set prior to + * calling this method. + * + * *NOTE*: In OpenSSL API conventions, the IV value may correspond to the + * "nonce" instead in some cipher modes. Refer to the OpenSSL man pages for + * details. + * + * See also the man page EVP_CipherInit_ex(3). */ static VALUE ossl_cipher_set_iv(VALUE self, VALUE iv) @@ -494,14 +540,14 @@ ossl_cipher_set_iv(VALUE self, VALUE iv) GetCipher(self, ctx); if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) - iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); + iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); if (!iv_len) - iv_len = EVP_CIPHER_CTX_iv_length(ctx); + iv_len = EVP_CIPHER_CTX_iv_length(ctx); if (RSTRING_LEN(iv) != iv_len) - ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len); + ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len); if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, (unsigned char *)RSTRING_PTR(iv), -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return iv; } @@ -510,8 +556,7 @@ ossl_cipher_set_iv(VALUE self, VALUE iv) * call-seq: * cipher.authenticated? -> true | false * - * Indicated whether this Cipher instance uses an Authenticated Encryption - * mode. + * Indicates whether this Cipher instance uses an AEAD mode. */ static VALUE ossl_cipher_is_authenticated(VALUE self) @@ -525,21 +570,23 @@ ossl_cipher_is_authenticated(VALUE self) /* * call-seq: - * cipher.auth_data = string -> string + * cipher.auth_data = string + * + * Sets additional authenticated data (AAD), also called associated data, for + * this Cipher. This method is available for AEAD ciphers. * - * Sets the cipher's additional authenticated data. This field must be - * set when using AEAD cipher modes such as GCM or CCM. If no associated - * data shall be used, this method must *still* be called with a value of "". * The contents of this field should be non-sensitive data which will be * added to the ciphertext to generate the authentication tag which validates * the contents of the ciphertext. * - * The AAD must be set prior to encryption or decryption. In encryption mode, - * it must be set after calling Cipher#encrypt and setting Cipher#key= and - * Cipher#iv=. When decrypting, the authenticated data must be set after key, - * iv and especially *after* the authentication tag has been set. I.e. set it - * only after calling Cipher#decrypt, Cipher#key=, Cipher#iv= and - * Cipher#auth_tag= first. + * This method must be called after #key= and #iv= have been set, but before + * starting actual encryption or decryption with #update. In some cipher modes, + * #auth_tag_len= and #ccm_data_len= may also need to be called before this + * method. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CipherUpdate() with the output buffer + * set to NULL. */ static VALUE ossl_cipher_set_auth_data(VALUE self, VALUE data) @@ -555,7 +602,7 @@ ossl_cipher_set_auth_data(VALUE self, VALUE data) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "AEAD not supported by this cipher"); + ossl_raise(eCipherError, "AEAD not supported by this cipher"); if (!ossl_cipher_update_long(ctx, NULL, &out_len, in, in_len)) ossl_raise(eCipherError, "couldn't set additional authenticated data"); @@ -567,16 +614,17 @@ ossl_cipher_set_auth_data(VALUE self, VALUE data) * call-seq: * cipher.auth_tag(tag_len = 16) -> String * - * Gets the authentication tag generated by Authenticated Encryption Cipher - * modes (GCM for example). This tag may be stored along with the ciphertext, - * then set on the decryption cipher to authenticate the contents of the - * ciphertext against changes. If the optional integer parameter _tag_len_ is - * given, the returned tag will be _tag_len_ bytes long. If the parameter is - * omitted, the default length of 16 bytes or the length previously set by - * #auth_tag_len= will be used. For maximum security, the longest possible - * should be chosen. + * Gets the generated authentication tag. This method is available for AEAD + * ciphers, and should be called after encryption has been finalized by calling + * #final. + * + * The returned tag will be _tag_len_ bytes long. Some cipher modes require + * the desired length in advance using a separate call to #auth_tag_len=, + * before starting encryption. * - * The tag may only be retrieved after calling Cipher#final. + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_GET_TAG. */ static VALUE ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) @@ -587,34 +635,42 @@ ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &vtag_len); if (NIL_P(vtag_len)) - vtag_len = rb_attr_get(self, id_auth_tag_len); + vtag_len = rb_attr_get(self, id_auth_tag_len); if (!NIL_P(vtag_len)) - tag_len = NUM2INT(vtag_len); + tag_len = NUM2INT(vtag_len); GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "authentication tag not supported by this cipher"); + ossl_raise(eCipherError, "authentication tag not supported by this cipher"); ret = rb_str_new(NULL, tag_len); - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, RSTRING_PTR(ret))) - ossl_raise(eCipherError, "retrieving the authentication tag failed"); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, RSTRING_PTR(ret)) <= 0) + ossl_raise(eCipherError, "retrieving the authentication tag failed"); return ret; } /* * call-seq: - * cipher.auth_tag = string -> string + * cipher.auth_tag = string * * Sets the authentication tag to verify the integrity of the ciphertext. - * This can be called only when the cipher supports AE. The tag must be set - * after calling Cipher#decrypt, Cipher#key= and Cipher#iv=, but before - * calling Cipher#final. After all decryption is performed, the tag is - * verified automatically in the call to Cipher#final. * - * For OCB mode, the tag length must be supplied with #auth_tag_len= - * beforehand. + * The authentication tag must be set before #final is called. The tag is + * verified during the #final call. + * + * Note that, for CCM mode and OCB mode, the expected length of the tag must + * be set before starting decryption by a separate call to #auth_tag_len=. + * The content of the tag can be provided at any time before #final is called. + * + * *NOTE*: The caller must ensure that the String passed to this method has + * the desired length. Some cipher modes support variable tag lengths, and + * this method may accept a truncated tag without raising an exception. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_SET_TAG. */ static VALUE ossl_cipher_set_auth_tag(VALUE self, VALUE vtag) @@ -629,24 +685,27 @@ ossl_cipher_set_auth_tag(VALUE self, VALUE vtag) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "authentication tag not supported by this cipher"); + ossl_raise(eCipherError, "authentication tag not supported by this cipher"); - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag)) - ossl_raise(eCipherError, "unable to set AEAD tag"); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag) <= 0) + ossl_raise(eCipherError, "unable to set AEAD tag"); return vtag; } /* * call-seq: - * cipher.auth_tag_len = Integer -> Integer + * cipher.auth_tag_len = integer + * + * Sets the length of the expected authentication tag for this Cipher. This + * method is available for some of AEAD ciphers that require the length to be + * set before starting encryption or decryption, such as CCM mode or OCB mode. * - * Sets the length of the authentication tag to be generated or to be given for - * AEAD ciphers that requires it as in input parameter. Note that not all AEAD - * ciphers support this method. + * For CCM mode and OCB mode, the tag length must be set before #iv= is set. * - * In OCB mode, the length must be supplied both when encrypting and when - * decrypting, and must be before specifying an IV. + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_SET_TAG and a NULL buffer. */ static VALUE ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen) @@ -656,10 +715,10 @@ ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "AEAD not supported by this cipher"); + ossl_raise(eCipherError, "AEAD not supported by this cipher"); - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, NULL)) - ossl_raise(eCipherError, "unable to set authentication tag length"); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, NULL) <= 0) + ossl_raise(eCipherError, "unable to set authentication tag length"); /* for #auth_tag */ rb_ivar_set(self, id_auth_tag_len, INT2NUM(tag_len)); @@ -669,11 +728,16 @@ ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen) /* * call-seq: - * cipher.iv_len = integer -> integer + * cipher.iv_len = integer * - * Sets the IV/nonce length of the Cipher. Normally block ciphers don't allow - * changing the IV length, but some make use of IV for 'nonce'. You may need - * this for interoperability with other applications. + * Sets the IV/nonce length for this Cipher. This method is available for AEAD + * ciphers that support variable IV lengths. This method can be called if a + * different IV length than OpenSSL's default is desired, prior to calling + * #iv=. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_SET_IVLEN. */ static VALUE ossl_cipher_set_iv_length(VALUE self, VALUE iv_length) @@ -683,10 +747,10 @@ ossl_cipher_set_iv_length(VALUE self, VALUE iv_length) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "cipher does not support AEAD"); + ossl_raise(eCipherError, "cipher does not support AEAD"); - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL)) - ossl_raise(eCipherError, "unable to set IV length"); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL) <= 0) + ossl_raise(eCipherError, "unable to set IV length"); /* * EVP_CIPHER_CTX_iv_length() returns the default length. So we need to save @@ -699,13 +763,14 @@ ossl_cipher_set_iv_length(VALUE self, VALUE iv_length) /* * call-seq: - * cipher.key_len = integer -> integer + * cipher.key_len = integer * * Sets the key length of the cipher. If the cipher is a fixed length cipher * then attempting to set the key length to any value other than the fixed * value is an error. * - * Under normal circumstances you do not need to call this method (and probably shouldn't). + * Under normal circumstances you do not need to call this method (and + * probably shouldn't). * * See EVP_CIPHER_CTX_set_key_length for further information. */ @@ -722,13 +787,16 @@ ossl_cipher_set_key_length(VALUE self, VALUE key_length) return key_length; } +// TODO: Should #padding= take a boolean value instead? /* * call-seq: - * cipher.padding = integer -> integer + * cipher.padding = 1 or 0 * - * Enables or disables padding. By default encryption operations are padded using standard block padding and the - * padding is checked and removed when decrypting. If the pad parameter is zero then no padding is performed, the - * total amount of data encrypted or decrypted must then be a multiple of the block size or an error will occur. + * Enables or disables padding. By default encryption operations are padded + * using standard block padding and the padding is checked and removed when + * decrypting. If the pad parameter is zero then no padding is performed, the + * total amount of data encrypted or decrypted must then be a multiple of the + * block size or an error will occur. * * See EVP_CIPHER_CTX_set_padding for further information. */ @@ -740,7 +808,7 @@ ossl_cipher_set_padding(VALUE self, VALUE padding) GetCipher(self, ctx); if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return padding; } @@ -774,9 +842,9 @@ ossl_cipher_iv_length(VALUE self) GetCipher(self, ctx); if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) - len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); + len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); if (!len) - len = EVP_CIPHER_CTX_iv_length(ctx); + len = EVP_CIPHER_CTX_iv_length(ctx); return INT2NUM(len); } @@ -799,13 +867,17 @@ ossl_cipher_block_size(VALUE self) /* * call-seq: - * cipher.ccm_data_len = integer -> integer + * cipher.ccm_data_len = integer * - * Sets the length of the plaintext / ciphertext message that will be - * processed in CCM mode. Make sure to call this method after #key= and - * #iv= have been set, and before #auth_data=. + * Sets the total length of the plaintext / ciphertext message that will be + * processed by #update in CCM mode. * - * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + * Make sure to call this method after #key= and #iv= have been set, and + * before #auth_data= or #update are called. + * + * This method is only available for CCM mode ciphers. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). */ static VALUE ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len) @@ -828,11 +900,6 @@ ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len) void Init_ossl_cipher(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-class: OpenSSL::Cipher * * Provides symmetric algorithms for encryption and decryption. The @@ -988,24 +1055,28 @@ Init_ossl_cipher(void) * could otherwise be exploited to modify ciphertexts in ways beneficial to * potential attackers. * - * An associated data is used where there is additional information, such as + * Associated data, also called additional authenticated data (AAD), is + * optionally used where there is additional information, such as * headers or some metadata, that must be also authenticated but not - * necessarily need to be encrypted. If no associated data is needed for - * encryption and later decryption, the OpenSSL library still requires a - * value to be set - "" may be used in case none is available. + * necessarily need to be encrypted. * * An example using the GCM (Galois/Counter Mode). You have 16 bytes _key_, * 12 bytes (96 bits) _nonce_ and the associated data _auth_data_. Be sure * not to reuse the _key_ and _nonce_ pair. Reusing an nonce ruins the * security guarantees of GCM mode. * + * key = OpenSSL::Random.random_bytes(16) + * nonce = OpenSSL::Random.random_bytes(12) + * auth_data = "authenticated but unencrypted data" + * data = "encrypted data" + * * cipher = OpenSSL::Cipher.new('aes-128-gcm').encrypt * cipher.key = key * cipher.iv = nonce * cipher.auth_data = auth_data * * encrypted = cipher.update(data) + cipher.final - * tag = cipher.auth_tag # produces 16 bytes tag by default + * tag = cipher.auth_tag(16) * * Now you are the receiver. You know the _key_ and have received _nonce_, * _auth_data_, _encrypted_ and _tag_ through an untrusted network. Note @@ -1018,15 +1089,21 @@ Init_ossl_cipher(void) * decipher = OpenSSL::Cipher.new('aes-128-gcm').decrypt * decipher.key = key * decipher.iv = nonce - * decipher.auth_tag = tag + * decipher.auth_tag = tag # could be called at any time before #final * decipher.auth_data = auth_data * * decrypted = decipher.update(encrypted) + decipher.final * * puts data == decrypted #=> true + * + * Note that other AEAD ciphers may require additional steps, such as + * setting the expected tag length (#auth_tag_len=) or the total data + * length (#ccm_data_len=) in advance. Make sure to read the relevant man + * page for details. */ cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject); eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError); + eAuthTagError = rb_define_class_under(cCipher, "AuthTagError", eCipherError); rb_define_alloc_func(cCipher, ossl_cipher_alloc); rb_define_method(cCipher, "initialize_copy", ossl_cipher_copy, 1); @@ -1056,4 +1133,5 @@ Init_ossl_cipher(void) id_auth_tag_len = rb_intern_const("auth_tag_len"); id_key_set = rb_intern_const("key_set"); + id_cipher_holder = rb_intern_const("EVP_CIPHER_holder"); } diff --git a/ext/openssl/ossl_cipher.h b/ext/openssl/ossl_cipher.h index 12da68ca3e..fba63a140f 100644 --- a/ext/openssl/ossl_cipher.h +++ b/ext/openssl/ossl_cipher.h @@ -10,7 +10,16 @@ #if !defined(_OSSL_CIPHER_H_) #define _OSSL_CIPHER_H_ -const EVP_CIPHER *ossl_evp_get_cipherbyname(VALUE); +/* + * Gets EVP_CIPHER from a String or an OpenSSL::Digest instance (discouraged, + * but still supported for compatibility). A holder object is created if the + * EVP_CIPHER is a "fetched" algorithm. + */ +const EVP_CIPHER *ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder); +/* + * This is meant for OpenSSL::Engine#cipher. EVP_CIPHER must not be a fetched + * one. + */ VALUE ossl_cipher_new(const EVP_CIPHER *); void Init_ossl_cipher(void); diff --git a/ext/openssl/ossl_config.c b/ext/openssl/ossl_config.c index ee2ff6786f..274875a978 100644 --- a/ext/openssl/ossl_config.c +++ b/ext/openssl/ossl_config.c @@ -413,11 +413,6 @@ Init_ossl_config(void) char *path; VALUE path_str; -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-class: OpenSSL::Config * * Configuration for the openssl library. @@ -426,7 +421,7 @@ Init_ossl_config(void) * configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for * the location of the file for your host. * - * See also http://www.openssl.org/docs/apps/config.html + * See also https://docs.openssl.org/master/man5/config/ */ cConfig = rb_define_class_under(mOSSL, "Config", rb_cObject); diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index 329de6c1ba..e23968b1e3 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -12,7 +12,7 @@ #define GetDigest(obj, ctx) do { \ TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_digest_type, (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \ } \ } while (0) @@ -21,6 +21,7 @@ */ static VALUE cDigest; static VALUE eDigestError; +static ID id_md_holder; static VALUE ossl_digest_alloc(VALUE klass); @@ -33,39 +34,67 @@ ossl_digest_free(void *ctx) static const rb_data_type_t ossl_digest_type = { "OpenSSL/Digest", { - 0, ossl_digest_free, + 0, ossl_digest_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; +#ifdef OSSL_USE_PROVIDER +static void +ossl_evp_md_free(void *ptr) +{ + // This is safe to call against const EVP_MD * returned by + // EVP_get_digestbyname() + EVP_MD_free(ptr); +} + +static const rb_data_type_t ossl_evp_md_holder_type = { + "OpenSSL/EVP_MD", + { + .dfree = ossl_evp_md_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; +#endif + /* * Public */ const EVP_MD * -ossl_evp_get_digestbyname(VALUE obj) +ossl_evp_md_fetch(VALUE obj, volatile VALUE *holder) { - const EVP_MD *md; - ASN1_OBJECT *oid = NULL; - - if (RB_TYPE_P(obj, T_STRING)) { - const char *name = StringValueCStr(obj); - - md = EVP_get_digestbyname(name); - if (!md) { - oid = OBJ_txt2obj(name, 0); - md = EVP_get_digestbyobj(oid); - ASN1_OBJECT_free(oid); - } - if(!md) - ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm (%"PRIsVALUE").", obj); - } else { + *holder = Qnil; + if (rb_obj_is_kind_of(obj, cDigest)) { EVP_MD_CTX *ctx; - GetDigest(obj, ctx); - - md = EVP_MD_CTX_get0_md(ctx); + EVP_MD *md = (EVP_MD *)EVP_MD_CTX_get0_md(ctx); +#ifdef OSSL_USE_PROVIDER + *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); + if (!EVP_MD_up_ref(md)) + ossl_raise(eDigestError, "EVP_MD_up_ref"); + RTYPEDDATA_DATA(*holder) = md; +#endif + return md; } + const char *name = StringValueCStr(obj); + EVP_MD *md = (EVP_MD *)EVP_get_digestbyname(name); + if (!md) { + ASN1_OBJECT *oid = OBJ_txt2obj(name, 0); + md = (EVP_MD *)EVP_get_digestbyobj(oid); + ASN1_OBJECT_free(oid); + } +#ifdef OSSL_USE_PROVIDER + if (!md) { + ossl_clear_error(); + *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); + md = EVP_MD_fetch(NULL, name, NULL); + RTYPEDDATA_DATA(*holder) = md; + } +#endif + if (!md) + ossl_raise(eDigestError, "unsupported digest algorithm: %"PRIsVALUE, + obj); return md; } @@ -75,14 +104,17 @@ ossl_digest_new(const EVP_MD *md) VALUE ret; EVP_MD_CTX *ctx; + // NOTE: This does not set id_md_holder because this function should + // only be called from ossl_engine.c, which will not use any + // reference-counted digests. ret = ossl_digest_alloc(cDigest); ctx = EVP_MD_CTX_new(); if (!ctx) - ossl_raise(eDigestError, "EVP_MD_CTX_new"); + ossl_raise(eDigestError, "EVP_MD_CTX_new"); RTYPEDDATA_DATA(ret) = ctx; if (!EVP_DigestInit_ex(ctx, md, NULL)) - ossl_raise(eDigestError, "Digest initialization failed"); + ossl_raise(eDigestError, "Digest initialization failed"); return ret; } @@ -121,21 +153,22 @@ ossl_digest_initialize(int argc, VALUE *argv, VALUE self) { EVP_MD_CTX *ctx; const EVP_MD *md; - VALUE type, data; + VALUE type, data, md_holder; rb_scan_args(argc, argv, "11", &type, &data); - md = ossl_evp_get_digestbyname(type); + md = ossl_evp_md_fetch(type, &md_holder); if (!NIL_P(data)) StringValue(data); TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx); if (!ctx) { - RTYPEDDATA_DATA(self) = ctx = EVP_MD_CTX_new(); - if (!ctx) - ossl_raise(eDigestError, "EVP_MD_CTX_new"); + RTYPEDDATA_DATA(self) = ctx = EVP_MD_CTX_new(); + if (!ctx) + ossl_raise(eDigestError, "EVP_MD_CTX_new"); } if (!EVP_DigestInit_ex(ctx, md, NULL)) - ossl_raise(eDigestError, "Digest initialization failed"); + ossl_raise(eDigestError, "Digest initialization failed"); + rb_ivar_set(self, id_md_holder, md_holder); if (!NIL_P(data)) return ossl_digest_update(self, data); return self; @@ -152,14 +185,14 @@ ossl_digest_copy(VALUE self, VALUE other) TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx1); if (!ctx1) { - RTYPEDDATA_DATA(self) = ctx1 = EVP_MD_CTX_new(); - if (!ctx1) - ossl_raise(eDigestError, "EVP_MD_CTX_new"); + RTYPEDDATA_DATA(self) = ctx1 = EVP_MD_CTX_new(); + if (!ctx1) + ossl_raise(eDigestError, "EVP_MD_CTX_new"); } GetDigest(other, ctx2); if (!EVP_MD_CTX_copy(ctx1, ctx2)) { - ossl_raise(eDigestError, NULL); + ossl_raise(eDigestError, NULL); } return self; } @@ -184,8 +217,8 @@ ossl_s_digests(VALUE self) ary = rb_ary_new(); OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH, - add_digest_name_to_ary, - (void*)ary); + add_digest_name_to_ary, + (void*)ary); return ary; } @@ -205,7 +238,7 @@ ossl_digest_reset(VALUE self) GetDigest(self, ctx); if (EVP_DigestInit_ex(ctx, EVP_MD_CTX_get0_md(ctx), NULL) != 1) { - ossl_raise(eDigestError, "Digest initialization failed."); + ossl_raise(eDigestError, "Digest initialization failed."); } return self; @@ -235,7 +268,7 @@ ossl_digest_update(VALUE self, VALUE data) GetDigest(self, ctx); if (!EVP_DigestUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) - ossl_raise(eDigestError, "EVP_DigestUpdate"); + ossl_raise(eDigestError, "EVP_DigestUpdate"); return self; } @@ -254,7 +287,7 @@ ossl_digest_finish(VALUE self) GetDigest(self, ctx); str = rb_str_new(NULL, EVP_MD_CTX_size(ctx)); if (!EVP_DigestFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), NULL)) - ossl_raise(eDigestError, "EVP_DigestFinal_ex"); + ossl_raise(eDigestError, "EVP_DigestFinal_ex"); return str; } @@ -332,11 +365,6 @@ ossl_digest_block_length(VALUE self) void Init_ossl_digest(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-class: OpenSSL::Digest * * OpenSSL::Digest allows you to compute message digests (sometimes @@ -442,4 +470,6 @@ Init_ossl_digest(void) rb_define_method(cDigest, "block_length", ossl_digest_block_length, 0); rb_define_method(cDigest, "name", ossl_digest_name, 0); + + id_md_holder = rb_intern_const("EVP_MD_holder"); } diff --git a/ext/openssl/ossl_digest.h b/ext/openssl/ossl_digest.h index 588a0c6f57..9c3bb2b149 100644 --- a/ext/openssl/ossl_digest.h +++ b/ext/openssl/ossl_digest.h @@ -10,7 +10,15 @@ #if !defined(_OSSL_DIGEST_H_) #define _OSSL_DIGEST_H_ -const EVP_MD *ossl_evp_get_digestbyname(VALUE); +/* + * Gets EVP_MD from a String or an OpenSSL::Digest instance (discouraged, but + * still supported for compatibility). A holder object is created if the EVP_MD + * is a "fetched" algorithm. + */ +const EVP_MD *ossl_evp_md_fetch(VALUE obj, volatile VALUE *holder); +/* + * This is meant for OpenSSL::Engine#digest. EVP_MD must not be a fetched one. + */ VALUE ossl_digest_new(const EVP_MD *); void Init_ossl_digest(void); diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c index 1565068743..a2bcb07ea4 100644 --- a/ext/openssl/ossl_engine.c +++ b/ext/openssl/ossl_engine.c @@ -16,7 +16,7 @@ TypedData_Wrap_Struct((klass), &ossl_engine_type, 0) #define SetEngine(obj, engine) do { \ if (!(engine)) { \ - ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (engine); \ } while(0) @@ -49,12 +49,12 @@ static VALUE eEngineError; */ #define OSSL_ENGINE_LOAD_IF_MATCH(engine_name, x) \ do{\ - if(!strcmp(#engine_name, RSTRING_PTR(name))){\ - if (OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_##x, NULL))\ - return Qtrue;\ - else\ - ossl_raise(eEngineError, "OPENSSL_init_crypto"); \ - }\ + if(!strcmp(#engine_name, RSTRING_PTR(name))){\ + if (OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_##x, NULL))\ + return Qtrue;\ + else\ + ossl_raise(eEngineError, "OPENSSL_init_crypto"); \ + }\ }while(0) static void @@ -66,7 +66,7 @@ ossl_engine_free(void *engine) static const rb_data_type_t ossl_engine_type = { "OpenSSL/Engine", { - 0, ossl_engine_free, + 0, ossl_engine_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -130,12 +130,12 @@ ossl_engine_s_engines(VALUE klass) ary = rb_ary_new(); for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)){ - obj = NewEngine(klass); - /* Need a ref count of two here because of ENGINE_free being - * called internally by OpenSSL when moving to the next ENGINE - * and by us when releasing the ENGINE reference */ - ENGINE_up_ref(e); - SetEngine(obj, e); + obj = NewEngine(klass); + /* Need a ref count of two here because of ENGINE_free being + * called internally by OpenSSL when moving to the next ENGINE + * and by us when releasing the ENGINE reference */ + ENGINE_up_ref(e); + SetEngine(obj, e); rb_ary_push(ary, obj); } @@ -163,13 +163,13 @@ ossl_engine_s_by_id(VALUE klass, VALUE id) ossl_engine_s_load(1, &id, klass); obj = NewEngine(klass); if(!(e = ENGINE_by_id(RSTRING_PTR(id)))) - ossl_raise(eEngineError, NULL); + ossl_raise(eEngineError, NULL); SetEngine(obj, e); if(rb_block_given_p()) rb_yield(obj); if(!ENGINE_init(e)) - ossl_raise(eEngineError, NULL); + ossl_raise(eEngineError, NULL); ENGINE_ctrl(e, ENGINE_CTRL_SET_PASSWORD_CALLBACK, - 0, NULL, (void(*)(void))ossl_pem_passwd_cb); + 0, NULL, (void(*)(void))ossl_pem_passwd_cb); ossl_clear_error(); return obj; @@ -184,7 +184,7 @@ ossl_engine_s_by_id(VALUE klass, VALUE id) * OpenSSL::Engine.load * OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...] * OpenSSL::Engine.engines.first.id - * #=> "rsax" + * #=> "rsax" */ static VALUE ossl_engine_get_id(VALUE self) @@ -203,7 +203,7 @@ ossl_engine_get_id(VALUE self) * OpenSSL::Engine.load * OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...] * OpenSSL::Engine.engines.first.name - * #=> "RSAX engine support" + * #=> "RSAX engine support" * */ static VALUE @@ -274,11 +274,11 @@ ossl_engine_get_cipher(VALUE self, VALUE name) * Will raise an EngineError if the digest is unavailable. * * e = OpenSSL::Engine.by_id("openssl") - * #=> #<OpenSSL::Engine id="openssl" name="Software engine support"> + * #=> #<OpenSSL::Engine id="openssl" name="Software engine support"> * e.digest("SHA1") - * #=> #<OpenSSL::Digest: da39a3ee5e6b4b0d3255bfef95601890afd80709> + * #=> #<OpenSSL::Digest: da39a3ee5e6b4b0d3255bfef95601890afd80709> * e.digest("zomg") - * #=> OpenSSL::Engine::EngineError: no such digest `zomg' + * #=> OpenSSL::Engine::EngineError: no such digest `zomg' */ static VALUE ossl_engine_get_digest(VALUE self, VALUE name) @@ -365,7 +365,7 @@ ossl_engine_load_pubkey(int argc, VALUE *argv, VALUE self) * your OS. * * [All flags] 0xFFFF - * [No flags] 0x0000 + * [No flags] 0x0000 * * See also <openssl/engine.h> */ @@ -399,7 +399,7 @@ ossl_engine_ctrl_cmd(int argc, VALUE *argv, VALUE self) GetEngine(self, e); rb_scan_args(argc, argv, "11", &cmd, &val); ret = ENGINE_ctrl_cmd_string(e, StringValueCStr(cmd), - NIL_P(val) ? NULL : StringValueCStr(val), 0); + NIL_P(val) ? NULL : StringValueCStr(val), 0); if (!ret) ossl_raise(eEngineError, NULL); return self; @@ -409,11 +409,11 @@ static VALUE ossl_engine_cmd_flag_to_name(int flag) { switch(flag){ - case ENGINE_CMD_FLAG_NUMERIC: return rb_str_new2("NUMERIC"); - case ENGINE_CMD_FLAG_STRING: return rb_str_new2("STRING"); - case ENGINE_CMD_FLAG_NO_INPUT: return rb_str_new2("NO_INPUT"); - case ENGINE_CMD_FLAG_INTERNAL: return rb_str_new2("INTERNAL"); - default: return rb_str_new2("UNKNOWN"); + case ENGINE_CMD_FLAG_NUMERIC: return rb_str_new2("NUMERIC"); + case ENGINE_CMD_FLAG_STRING: return rb_str_new2("STRING"); + case ENGINE_CMD_FLAG_NO_INPUT: return rb_str_new2("NO_INPUT"); + case ENGINE_CMD_FLAG_INTERNAL: return rb_str_new2("INTERNAL"); + default: return rb_str_new2("UNKNOWN"); } } @@ -433,13 +433,13 @@ ossl_engine_get_cmds(VALUE self) GetEngine(self, e); ary = rb_ary_new(); if ((defn = ENGINE_get_cmd_defns(e)) != NULL){ - for (p = defn; p->cmd_num > 0; p++){ - tmp = rb_ary_new(); - rb_ary_push(tmp, rb_str_new2(p->cmd_name)); - rb_ary_push(tmp, rb_str_new2(p->cmd_desc)); - rb_ary_push(tmp, ossl_engine_cmd_flag_to_name(p->cmd_flags)); - rb_ary_push(ary, tmp); - } + for (p = defn; p->cmd_num > 0; p++){ + tmp = rb_ary_new(); + rb_ary_push(tmp, rb_str_new2(p->cmd_name)); + rb_ary_push(tmp, rb_str_new2(p->cmd_desc)); + rb_ary_push(tmp, ossl_engine_cmd_flag_to_name(p->cmd_flags)); + rb_ary_push(ary, tmp); + } } return ary; @@ -458,7 +458,7 @@ ossl_engine_inspect(VALUE self) GetEngine(self, e); return rb_sprintf("#<%"PRIsVALUE" id=\"%s\" name=\"%s\">", - rb_obj_class(self), ENGINE_get_id(e), ENGINE_get_name(e)); + rb_obj_class(self), ENGINE_get_id(e), ENGINE_get_name(e)); } #define DefEngineConst(x) rb_define_const(cEngine, #x, INT2NUM(ENGINE_##x)) @@ -466,11 +466,6 @@ ossl_engine_inspect(VALUE self) void Init_ossl_engine(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - cEngine = rb_define_class_under(mOSSL, "Engine", rb_cObject); eEngineError = rb_define_class_under(cEngine, "EngineError", eOSSLError); diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c index b304827579..ddcc6a5f8d 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -14,7 +14,7 @@ #define GetHMAC(obj, ctx) do { \ TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_hmac_type, (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \ + ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \ } \ } while (0) @@ -23,6 +23,7 @@ */ static VALUE cHMAC; static VALUE eHMACError; +static ID id_md_holder; /* * Public @@ -40,7 +41,7 @@ ossl_hmac_free(void *ctx) static const rb_data_type_t ossl_hmac_type = { "OpenSSL/HMAC", { - 0, ossl_hmac_free, + 0, ossl_hmac_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -73,17 +74,17 @@ ossl_hmac_alloc(VALUE klass) * * === Example * - * key = 'key' - * instance = OpenSSL::HMAC.new(key, 'SHA1') - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f - * instance.class - * #=> OpenSSL::HMAC + * key = 'key' + * instance = OpenSSL::HMAC.new(key, 'SHA1') + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance.class + * #=> OpenSSL::HMAC * * === A note about comparisons * * Two instances can be securely compared with #== in constant time: * - * other_instance = OpenSSL::HMAC.new('key', 'SHA1') + * other_instance = OpenSSL::HMAC.new('key', 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * instance == other_instance * #=> true @@ -94,19 +95,22 @@ ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest) { EVP_MD_CTX *ctx; EVP_PKEY *pkey; + const EVP_MD *md; + VALUE md_holder; GetHMAC(self, ctx); StringValue(key); + md = ossl_evp_md_fetch(digest, &md_holder); pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, (unsigned char *)RSTRING_PTR(key), RSTRING_LENINT(key)); if (!pkey) ossl_raise(eHMACError, "EVP_PKEY_new_raw_private_key"); - if (EVP_DigestSignInit(ctx, NULL, ossl_evp_get_digestbyname(digest), - NULL, pkey) != 1) { + if (EVP_DigestSignInit(ctx, NULL, md, NULL, pkey) != 1) { EVP_PKEY_free(pkey); ossl_raise(eHMACError, "EVP_DigestSignInit"); } + rb_ivar_set(self, id_md_holder, md_holder); /* Decrement reference counter; EVP_MD_CTX still keeps it */ EVP_PKEY_free(pkey); @@ -138,13 +142,13 @@ ossl_hmac_copy(VALUE self, VALUE other) * * === Example * - * first_chunk = 'The quick brown fox jumps ' - * second_chunk = 'over the lazy dog' + * first_chunk = 'The quick brown fox jumps ' + * second_chunk = 'over the lazy dog' * - * instance.update(first_chunk) - * #=> 5b9a8038a65d571076d97fe783989e52278a492a - * instance.update(second_chunk) - * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 + * instance.update(first_chunk) + * #=> 5b9a8038a65d571076d97fe783989e52278a492a + * instance.update(second_chunk) + * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 * */ static VALUE @@ -222,14 +226,14 @@ ossl_hmac_hexdigest(VALUE self) * * === Example * - * data = "The quick brown fox jumps over the lazy dog" - * instance = OpenSSL::HMAC.new('key', 'SHA1') - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * data = "The quick brown fox jumps over the lazy dog" + * instance = OpenSSL::HMAC.new('key', 'SHA1') + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * - * instance.update(data) - * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 - * instance.reset - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance.update(data) + * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 + * instance.reset + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * */ static VALUE @@ -252,11 +256,6 @@ ossl_hmac_reset(VALUE self) void Init_ossl_hmac(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * Document-class: OpenSSL::HMAC * @@ -300,4 +299,6 @@ Init_ossl_hmac(void) rb_define_method(cHMAC, "hexdigest", ossl_hmac_hexdigest, 0); rb_define_alias(cHMAC, "inspect", "hexdigest"); rb_define_alias(cHMAC, "to_s", "hexdigest"); + + id_md_holder = rb_intern_const("EVP_MD_holder"); } diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index f349939a80..99a3589b39 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -7,6 +7,27 @@ static VALUE mKDF, eKDF; +struct pbkdf2_hmac_args { + char *pass; + int passlen; + unsigned char *salt; + int saltlen; + int iters; + const EVP_MD *md; + int len; + unsigned char *out; +}; + +static void * +pbkdf2_hmac_nogvl(void *args_) +{ + struct pbkdf2_hmac_args *args = (struct pbkdf2_hmac_args *)args_; + int ret = PKCS5_PBKDF2_HMAC(args->pass, args->passlen, args->salt, + args->saltlen, args->iters, args->md, + args->len, args->out); + return (void *)(uintptr_t)ret; +} + /* * call-seq: * KDF.pbkdf2_hmac(pass, salt:, iterations:, length:, hash:) -> aString @@ -18,6 +39,10 @@ static VALUE mKDF, eKDF; * 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 @@ -35,16 +60,16 @@ static VALUE mKDF, eKDF; static VALUE kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) { - VALUE pass, salt, opts, kwargs[4], str; + VALUE pass, salt, opts, kwargs[4], str, md_holder, pass_tmp, salt_tmp; static ID kwargs_ids[4]; - int iters, len; + int passlen, saltlen, iters, len; const EVP_MD *md; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt"); - kwargs_ids[1] = rb_intern_const("iterations"); - kwargs_ids[2] = rb_intern_const("length"); - kwargs_ids[3] = rb_intern_const("hash"); + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("iterations"); + kwargs_ids[2] = rb_intern_const("length"); + kwargs_ids[3] = rb_intern_const("hash"); } rb_scan_args(argc, argv, "1:", &pass, &opts); rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); @@ -53,19 +78,57 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) salt = StringValue(kwargs[0]); iters = NUM2INT(kwargs[1]); len = NUM2INT(kwargs[2]); - md = ossl_evp_get_digestbyname(kwargs[3]); - - str = rb_str_new(0, len); - if (!PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass), - (unsigned char *)RSTRING_PTR(salt), - RSTRING_LENINT(salt), iters, md, len, - (unsigned char *)RSTRING_PTR(str))) - ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC"); - + md = ossl_evp_md_fetch(kwargs[3], &md_holder); + passlen = RSTRING_LENINT(pass); + saltlen = RSTRING_LENINT(salt); + str = rb_str_new(NULL, len); + struct pbkdf2_hmac_args args = { + .pass = ALLOCV(pass_tmp, passlen), + .passlen = passlen, + .salt = ALLOCV(salt_tmp, saltlen), + .saltlen = saltlen, + .iters = iters, + .md = md, + .len = len, + .out = (unsigned char *)RSTRING_PTR(str), + }; + memcpy(args.pass, RSTRING_PTR(pass), passlen); + memcpy(args.salt, RSTRING_PTR(salt), saltlen); + if (!rb_thread_call_without_gvl(pbkdf2_hmac_nogvl, &args, NULL, NULL)) + ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC"); + OPENSSL_cleanse(args.pass, passlen); + ALLOCV_END(pass_tmp); + ALLOCV_END(salt_tmp); return str; } #if defined(HAVE_EVP_PBE_SCRYPT) +struct scrypt_args { + char *pass; + size_t passlen; + unsigned char *salt; + size_t saltlen; + uint64_t N, r, p; + size_t len; + unsigned char *out; +}; + +static void * +scrypt_nogvl(void *args_) +{ + struct scrypt_args *args = (struct scrypt_args *)args_; + /* + * OpenSSL uses 32MB by default (if zero is specified), which is too + * small. Let's not limit memory consumption but just let malloc() fail + * inside OpenSSL. The amount is controllable by other parameters. + */ + uint64_t maxmem = UINT64_MAX; + int ret = EVP_PBE_scrypt(args->pass, args->passlen, + args->salt, args->saltlen, args->N, args->r, + args->p, maxmem, args->out, args->len); + return (void *)(uintptr_t)ret; +} + /* * call-seq: * KDF.scrypt(pass, salt:, N:, r:, p:, length:) -> aString @@ -84,6 +147,10 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) * * 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. @@ -101,17 +168,18 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) static VALUE kdf_scrypt(int argc, VALUE *argv, VALUE self) { - VALUE pass, salt, opts, kwargs[5], str; + VALUE pass, salt, opts, kwargs[5], str, pass_tmp, salt_tmp; static ID kwargs_ids[5]; - size_t len; - uint64_t N, r, p, maxmem; + size_t passlen, saltlen; + long len; + uint64_t N, r, p; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt"); - kwargs_ids[1] = rb_intern_const("N"); - kwargs_ids[2] = rb_intern_const("r"); - kwargs_ids[3] = rb_intern_const("p"); - kwargs_ids[4] = rb_intern_const("length"); + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("N"); + kwargs_ids[2] = rb_intern_const("r"); + kwargs_ids[3] = rb_intern_const("p"); + kwargs_ids[4] = rb_intern_const("length"); } rb_scan_args(argc, argv, "1:", &pass, &opts); rb_get_kwargs(opts, kwargs_ids, 5, 0, kwargs); @@ -122,19 +190,27 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) r = NUM2UINT64T(kwargs[2]); p = NUM2UINT64T(kwargs[3]); len = NUM2LONG(kwargs[4]); - /* - * OpenSSL uses 32MB by default (if zero is specified), which is too small. - * Let's not limit memory consumption but just let malloc() fail inside - * OpenSSL. The amount is controllable by other parameters. - */ - maxmem = SIZE_MAX; - - str = rb_str_new(0, len); - if (!EVP_PBE_scrypt(RSTRING_PTR(pass), RSTRING_LEN(pass), - (unsigned char *)RSTRING_PTR(salt), RSTRING_LEN(salt), - N, r, p, maxmem, (unsigned char *)RSTRING_PTR(str), len)) - ossl_raise(eKDF, "EVP_PBE_scrypt"); - + passlen = RSTRING_LEN(pass); + saltlen = RSTRING_LEN(salt); + str = rb_str_new(NULL, len); + struct scrypt_args args = { + .pass = ALLOCV(pass_tmp, passlen), + .passlen = passlen, + .salt = ALLOCV(salt_tmp, saltlen), + .saltlen = saltlen, + .N = N, + .r = r, + .p = p, + .len = len, + .out = (unsigned char *)RSTRING_PTR(str), + }; + memcpy(args.pass, RSTRING_PTR(pass), passlen); + memcpy(args.salt, RSTRING_PTR(salt), saltlen); + if (!rb_thread_call_without_gvl(scrypt_nogvl, &args, NULL, NULL)) + ossl_raise(eKDF, "EVP_PBE_scrypt"); + OPENSSL_cleanse(args.pass, passlen); + ALLOCV_END(pass_tmp); + ALLOCV_END(salt_tmp); return str; } #endif @@ -172,7 +248,7 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) static VALUE kdf_hkdf(int argc, VALUE *argv, VALUE self) { - VALUE ikm, salt, info, opts, kwargs[4], str; + VALUE ikm, salt, info, opts, kwargs[4], str, md_holder; static ID kwargs_ids[4]; int saltlen, ikmlen, infolen; size_t len; @@ -180,10 +256,10 @@ kdf_hkdf(int argc, VALUE *argv, VALUE self) EVP_PKEY_CTX *pctx; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt"); - kwargs_ids[1] = rb_intern_const("info"); - kwargs_ids[2] = rb_intern_const("length"); - kwargs_ids[3] = rb_intern_const("hash"); + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("info"); + kwargs_ids[2] = rb_intern_const("length"); + kwargs_ids[3] = rb_intern_const("hash"); } rb_scan_args(argc, argv, "1:", &ikm, &opts); rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); @@ -196,39 +272,39 @@ kdf_hkdf(int argc, VALUE *argv, VALUE self) infolen = RSTRING_LENINT(info); len = (size_t)NUM2LONG(kwargs[2]); if (len > LONG_MAX) - rb_raise(rb_eArgError, "length must be non-negative"); - md = ossl_evp_get_digestbyname(kwargs[3]); + rb_raise(rb_eArgError, "length must be non-negative"); + md = ossl_evp_md_fetch(kwargs[3], &md_holder); str = rb_str_new(NULL, (long)len); pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); if (!pctx) - ossl_raise(eKDF, "EVP_PKEY_CTX_new_id"); + ossl_raise(eKDF, "EVP_PKEY_CTX_new_id"); if (EVP_PKEY_derive_init(pctx) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_derive_init"); + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_derive_init"); } if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md"); + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md"); } if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt), - saltlen) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt"); + saltlen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt"); } if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm), - ikmlen) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key"); + ikmlen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key"); } if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info), - infolen) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info"); + infolen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info"); } if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_derive"); + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_derive"); } rb_str_set_len(str, (long)len); EVP_PKEY_CTX_free(pctx); @@ -239,11 +315,6 @@ kdf_hkdf(int argc, VALUE *argv, VALUE self) void Init_ossl_kdf(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * Document-module: OpenSSL::KDF * diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index ffed3a64a6..8440c2ee82 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_netscape_spki_type, 0) #define SetSPKI(obj, spki) do { \ if (!(spki)) { \ - ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (spki); \ } while (0) #define GetSPKI(obj, spki) do { \ TypedData_Get_Struct((obj), NETSCAPE_SPKI, &ossl_netscape_spki_type, (spki)); \ if (!(spki)) { \ - ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ } \ } while (0) @@ -48,7 +48,7 @@ ossl_netscape_spki_free(void *spki) static const rb_data_type_t ossl_netscape_spki_type = { "OpenSSL/NETSCAPE_SPKI", { - 0, ossl_netscape_spki_free, + 0, ossl_netscape_spki_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -61,7 +61,7 @@ ossl_spki_alloc(VALUE klass) obj = NewSPKI(klass); if (!(spki = NETSCAPE_SPKI_new())) { - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } SetSPKI(obj, spki); @@ -83,15 +83,15 @@ ossl_spki_initialize(int argc, VALUE *argv, VALUE self) const unsigned char *p; if (rb_scan_args(argc, argv, "01", &buffer) == 0) { - return self; + return self; } StringValue(buffer); if (!(spki = NETSCAPE_SPKI_b64_decode(RSTRING_PTR(buffer), RSTRING_LENINT(buffer)))) { - ossl_clear_error(); - p = (unsigned char *)RSTRING_PTR(buffer); - if (!(spki = d2i_NETSCAPE_SPKI(NULL, &p, RSTRING_LEN(buffer)))) { - ossl_raise(eSPKIError, NULL); - } + ossl_clear_error(); + p = (unsigned char *)RSTRING_PTR(buffer); + if (!(spki = d2i_NETSCAPE_SPKI(NULL, &p, RSTRING_LEN(buffer)))) { + ossl_raise(eSPKIError, NULL); + } } NETSCAPE_SPKI_free(DATA_PTR(self)); SetSPKI(self, spki); @@ -140,7 +140,7 @@ ossl_spki_to_pem(VALUE self) GetSPKI(self, spki); if (!(data = NETSCAPE_SPKI_b64_encode(spki))) { - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } str = ossl_buf2str(data, rb_long2int(strlen(data))); @@ -162,11 +162,11 @@ ossl_spki_print(VALUE self) GetSPKI(self, spki); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } if (!NETSCAPE_SPKI_print(out, spki)) { - BIO_free(out); - ossl_raise(eSPKIError, NULL); + BIO_free(out); + ossl_raise(eSPKIError, NULL); } return ossl_membio2str(out); @@ -187,7 +187,7 @@ ossl_spki_get_public_key(VALUE self) GetSPKI(self, spki); if (!(pkey = NETSCAPE_SPKI_get_pubkey(spki))) { /* adds an reference */ - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } return ossl_pkey_wrap(pkey); @@ -214,7 +214,7 @@ ossl_spki_set_public_key(VALUE self, VALUE key) pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) - ossl_raise(eSPKIError, "NETSCAPE_SPKI_set_pubkey"); + ossl_raise(eSPKIError, "NETSCAPE_SPKI_set_pubkey"); return key; } @@ -230,13 +230,12 @@ ossl_spki_get_challenge(VALUE self) NETSCAPE_SPKI *spki; GetSPKI(self, spki); - if (spki->spkac->challenge->length <= 0) { - OSSL_Debug("Challenge.length <= 0?"); - return rb_str_new(0, 0); + if (ASN1_STRING_length(spki->spkac->challenge) <= 0) { + OSSL_Debug("Challenge.length <= 0?"); + return rb_str_new(0, 0); } - return rb_str_new((const char *)spki->spkac->challenge->data, - spki->spkac->challenge->length); + return asn1str_to_str(spki->spkac->challenge); } /* @@ -257,8 +256,8 @@ ossl_spki_set_challenge(VALUE self, VALUE str) StringValue(str); GetSPKI(self, spki); if (!ASN1_STRING_set(spki->spkac->challenge, RSTRING_PTR(str), - RSTRING_LENINT(str))) { - ossl_raise(eSPKIError, NULL); + RSTRING_LENINT(str))) { + ossl_raise(eSPKIError, NULL); } return str; @@ -283,13 +282,13 @@ ossl_spki_sign(VALUE self, VALUE key, VALUE digest) NETSCAPE_SPKI *spki; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); GetSPKI(self, spki); - if (!NETSCAPE_SPKI_sign(spki, pkey, md)) { - ossl_raise(eSPKIError, NULL); - } + if (!NETSCAPE_SPKI_sign(spki, pkey, md)) + ossl_raise(eSPKIError, "NETSCAPE_SPKI_sign"); return self; } @@ -315,12 +314,12 @@ ossl_spki_verify(VALUE self, VALUE key) ossl_pkey_check_public_key(pkey); switch (NETSCAPE_SPKI_verify(spki, pkey)) { case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; case 1: - return Qtrue; + return Qtrue; default: - ossl_raise(eSPKIError, "NETSCAPE_SPKI_verify"); + ossl_raise(eSPKIError, "NETSCAPE_SPKI_verify"); } } @@ -378,11 +377,6 @@ ossl_spki_verify(VALUE self, VALUE key) void Init_ossl_ns_spki(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - mNetscape = rb_define_module_under(mOSSL, "Netscape"); eSPKIError = rb_define_class_under(mNetscape, "SPKIError", eOSSLError); diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 5a3a71cae0..9dd4b466d2 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -84,7 +84,7 @@ ossl_ocsp_request_free(void *ptr) static const rb_data_type_t ossl_ocsp_request_type = { "OpenSSL/OCSP/REQUEST", { - 0, ossl_ocsp_request_free, + 0, ossl_ocsp_request_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -98,7 +98,7 @@ ossl_ocsp_response_free(void *ptr) static const rb_data_type_t ossl_ocsp_response_type = { "OpenSSL/OCSP/RESPONSE", { - 0, ossl_ocsp_response_free, + 0, ossl_ocsp_response_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -112,7 +112,7 @@ ossl_ocsp_basicresp_free(void *ptr) static const rb_data_type_t ossl_ocsp_basicresp_type = { "OpenSSL/OCSP/BASICRESP", { - 0, ossl_ocsp_basicresp_free, + 0, ossl_ocsp_basicresp_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -126,7 +126,7 @@ ossl_ocsp_singleresp_free(void *ptr) static const rb_data_type_t ossl_ocsp_singleresp_type = { "OpenSSL/OCSP/SINGLERESP", { - 0, ossl_ocsp_singleresp_free, + 0, ossl_ocsp_singleresp_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -140,7 +140,7 @@ ossl_ocsp_certid_free(void *ptr) static const rb_data_type_t ossl_ocsp_certid_type = { "OpenSSL/OCSP/CERTID", { - 0, ossl_ocsp_certid_free, + 0, ossl_ocsp_certid_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -171,7 +171,7 @@ ossl_ocspreq_alloc(VALUE klass) obj = NewOCSPReq(klass); if (!(req = OCSP_REQUEST_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPReq(obj, req); return obj; @@ -189,7 +189,7 @@ ossl_ocspreq_initialize_copy(VALUE self, VALUE other) req_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_REQUEST), req); if (!req_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPReq(self, req_new); OCSP_REQUEST_free(req_old); @@ -215,15 +215,15 @@ ossl_ocspreq_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &arg); if(!NIL_P(arg)){ - GetOCSPReq(self, req); - arg = ossl_to_der_if_possible(arg); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - req_new = d2i_OCSP_REQUEST(NULL, &p, RSTRING_LEN(arg)); - if (!req_new) - ossl_raise(eOCSPError, "d2i_OCSP_REQUEST"); - SetOCSPReq(self, req_new); - OCSP_REQUEST_free(req); + GetOCSPReq(self, req); + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + req_new = d2i_OCSP_REQUEST(NULL, &p, RSTRING_LEN(arg)); + if (!req_new) + ossl_raise(eOCSPError, "d2i_OCSP_REQUEST"); + SetOCSPReq(self, req_new); + OCSP_REQUEST_free(req); } return self; @@ -249,13 +249,13 @@ ossl_ocspreq_add_nonce(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &val); if(NIL_P(val)) { - GetOCSPReq(self, req); - ret = OCSP_request_add1_nonce(req, NULL, -1); + GetOCSPReq(self, req); + ret = OCSP_request_add1_nonce(req, NULL, -1); } else{ - StringValue(val); - GetOCSPReq(self, req); - ret = OCSP_request_add1_nonce(req, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); + StringValue(val); + GetOCSPReq(self, req); + ret = OCSP_request_add1_nonce(req, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); } if(!ret) ossl_raise(eOCSPError, NULL); @@ -312,10 +312,10 @@ ossl_ocspreq_add_certid(VALUE self, VALUE certid) GetOCSPCertId(certid, id); if (!(id_new = OCSP_CERTID_dup(id))) - ossl_raise(eOCSPError, "OCSP_CERTID_dup"); + ossl_raise(eOCSPError, "OCSP_CERTID_dup"); if (!OCSP_request_add0_id(req, id_new)) { - OCSP_CERTID_free(id_new); - ossl_raise(eOCSPError, "OCSP_request_add0_id"); + OCSP_CERTID_free(id_new); + ossl_raise(eOCSPError, "OCSP_request_add0_id"); } return self; @@ -369,7 +369,7 @@ ossl_ocspreq_get_certid(VALUE self) static VALUE ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self) { - VALUE signer_cert, signer_key, certs, flags, digest; + VALUE signer_cert, signer_key, certs, flags, digest, md_holder; OCSP_REQUEST *req; X509 *signer; EVP_PKEY *key; @@ -383,19 +383,17 @@ ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self) signer = GetX509CertPtr(signer_cert); key = GetPrivPKeyPtr(signer_key); if (!NIL_P(flags)) - flg = NUM2INT(flags); - if (NIL_P(digest)) - md = NULL; - else - md = ossl_evp_get_digestbyname(digest); + flg = NUM2INT(flags); + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); if (NIL_P(certs)) - flg |= OCSP_NOCERTS; + flg |= OCSP_NOCERTS; else - x509s = ossl_x509_ary2sk(certs); + x509s = ossl_x509_ary2sk(certs); ret = OCSP_request_sign(req, signer, key, md, x509s, flg); sk_X509_pop_free(x509s, X509_free); - if (!ret) ossl_raise(eOCSPError, NULL); + if (!ret) + ossl_raise(eOCSPError, "OCSP_request_sign"); return self; } @@ -429,7 +427,7 @@ ossl_ocspreq_verify(int argc, VALUE *argv, VALUE self) result = OCSP_request_verify(req, x509s, x509st, flg); sk_X509_pop_free(x509s, X509_free); if (result <= 0) - ossl_clear_error(); + ossl_clear_error(); return result > 0 ? Qtrue : Qfalse; } @@ -448,11 +446,11 @@ ossl_ocspreq_to_der(VALUE self) GetOCSPReq(self, req); if((len = i2d_OCSP_REQUEST(req, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_OCSP_REQUEST(req, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -496,7 +494,7 @@ ossl_ocspres_s_create(VALUE klass, VALUE status, VALUE basic_resp) else GetOCSPBasicRes(basic_resp, bs); /* NO NEED TO DUP */ obj = NewOCSPRes(klass); if(!(res = OCSP_response_create(st, bs))) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPRes(obj, res); return obj; @@ -510,7 +508,7 @@ ossl_ocspres_alloc(VALUE klass) obj = NewOCSPRes(klass); if(!(res = OCSP_RESPONSE_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPRes(obj, res); return obj; @@ -528,7 +526,7 @@ ossl_ocspres_initialize_copy(VALUE self, VALUE other) res_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_RESPONSE), res); if (!res_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPRes(self, res_new); OCSP_RESPONSE_free(res_old); @@ -554,15 +552,15 @@ ossl_ocspres_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &arg); if(!NIL_P(arg)){ - GetOCSPRes(self, res); - arg = ossl_to_der_if_possible(arg); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - res_new = d2i_OCSP_RESPONSE(NULL, &p, RSTRING_LEN(arg)); - if (!res_new) - ossl_raise(eOCSPError, "d2i_OCSP_RESPONSE"); - SetOCSPRes(self, res_new); - OCSP_RESPONSE_free(res); + GetOCSPRes(self, res); + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + res_new = d2i_OCSP_RESPONSE(NULL, &p, RSTRING_LEN(arg)); + if (!res_new) + ossl_raise(eOCSPError, "d2i_OCSP_RESPONSE"); + SetOCSPRes(self, res_new); + OCSP_RESPONSE_free(res); } return self; @@ -623,7 +621,7 @@ ossl_ocspres_get_basic(VALUE self) GetOCSPRes(self, res); ret = NewOCSPBasicRes(cOCSPBasicRes); if(!(bs = OCSP_response_get1_basic(res))) - return Qnil; + return Qnil; SetOCSPBasicRes(ret, bs); return ret; @@ -646,11 +644,11 @@ ossl_ocspres_to_der(VALUE self) GetOCSPRes(self, res); if((len = i2d_OCSP_RESPONSE(res, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_OCSP_RESPONSE(res, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -667,7 +665,7 @@ ossl_ocspbres_alloc(VALUE klass) obj = NewOCSPBasicRes(klass); if(!(bs = OCSP_BASICRESP_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPBasicRes(obj, bs); return obj; @@ -685,7 +683,7 @@ ossl_ocspbres_initialize_copy(VALUE self, VALUE other) bs_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_BASICRESP), bs); if (!bs_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPBasicRes(self, bs_new); OCSP_BASICRESP_free(bs_old); @@ -710,15 +708,15 @@ ossl_ocspbres_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &arg); if (!NIL_P(arg)) { - GetOCSPBasicRes(self, res); - arg = ossl_to_der_if_possible(arg); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - res_new = d2i_OCSP_BASICRESP(NULL, &p, RSTRING_LEN(arg)); - if (!res_new) - ossl_raise(eOCSPError, "d2i_OCSP_BASICRESP"); - SetOCSPBasicRes(self, res_new); - OCSP_BASICRESP_free(res); + GetOCSPBasicRes(self, res); + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + res_new = d2i_OCSP_BASICRESP(NULL, &p, RSTRING_LEN(arg)); + if (!res_new) + ossl_raise(eOCSPError, "d2i_OCSP_BASICRESP"); + SetOCSPBasicRes(self, res_new); + OCSP_BASICRESP_free(res); } return self; @@ -763,13 +761,13 @@ ossl_ocspbres_add_nonce(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &val); if(NIL_P(val)) { - GetOCSPBasicRes(self, bs); - ret = OCSP_basic_add1_nonce(bs, NULL, -1); + GetOCSPBasicRes(self, bs); + ret = OCSP_basic_add1_nonce(bs, NULL, -1); } else{ - StringValue(val); - GetOCSPBasicRes(self, bs); - ret = OCSP_basic_add1_nonce(bs, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); + StringValue(val); + GetOCSPBasicRes(self, bs); + ret = OCSP_basic_add1_nonce(bs, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); } if(!ret) ossl_raise(eOCSPError, NULL); @@ -782,12 +780,12 @@ add_status_convert_time(VALUE obj) ASN1_TIME *time; if (RB_INTEGER_TYPE_P(obj)) - time = X509_gmtime_adj(NULL, NUM2INT(obj)); + time = X509_gmtime_adj(NULL, NUM2INT(obj)); else - time = ossl_x509_time_adjust(NULL, obj); + time = ossl_x509_time_adjust(NULL, obj); if (!time) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); return (VALUE)time; } @@ -821,8 +819,8 @@ add_status_convert_time(VALUE obj) */ static VALUE ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status, - VALUE reason, VALUE revtime, - VALUE thisupd, VALUE nextupd, VALUE ext) + VALUE reason, VALUE revtime, + VALUE thisupd, VALUE nextupd, VALUE ext) { OCSP_BASICRESP *bs; OCSP_SINGLERESP *single; @@ -836,16 +834,16 @@ ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status, GetOCSPCertId(cid, id); st = NUM2INT(status); if (!NIL_P(ext)) { /* All ext's members must be X509::Extension */ - ext = rb_check_array_type(ext); - for (i = 0; i < RARRAY_LEN(ext); i++) - OSSL_Check_Kind(RARRAY_AREF(ext, i), cX509Ext); + ext = rb_check_array_type(ext); + for (i = 0; i < RARRAY_LEN(ext); i++) + OSSL_Check_Kind(RARRAY_AREF(ext, i), cX509Ext); } if (st == V_OCSP_CERTSTATUS_REVOKED) { - rsn = NUM2INT(reason); - tmp = rb_protect(add_status_convert_time, revtime, &rstatus); - if (rstatus) goto err; - rev = (ASN1_TIME *)tmp; + rsn = NUM2INT(reason); + tmp = rb_protect(add_status_convert_time, revtime, &rstatus); + if (rstatus) goto err; + rev = (ASN1_TIME *)tmp; } tmp = rb_protect(add_status_convert_time, thisupd, &rstatus); @@ -853,29 +851,29 @@ ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status, ths = (ASN1_TIME *)tmp; if (!NIL_P(nextupd)) { - tmp = rb_protect(add_status_convert_time, nextupd, &rstatus); - if (rstatus) goto err; - nxt = (ASN1_TIME *)tmp; + tmp = rb_protect(add_status_convert_time, nextupd, &rstatus); + if (rstatus) goto err; + nxt = (ASN1_TIME *)tmp; } if(!(single = OCSP_basic_add1_status(bs, id, st, rsn, rev, ths, nxt))){ - error = 1; - goto err; + error = 1; + goto err; } if(!NIL_P(ext)){ - X509_EXTENSION *x509ext; - - for(i = 0; i < RARRAY_LEN(ext); i++){ - x509ext = GetX509ExtPtr(RARRAY_AREF(ext, i)); - if(!OCSP_SINGLERESP_add_ext(single, x509ext, -1)){ - error = 1; - goto err; - } - } + X509_EXTENSION *x509ext; + + for(i = 0; i < RARRAY_LEN(ext); i++){ + x509ext = GetX509ExtPtr(RARRAY_AREF(ext, i)); + if(!OCSP_SINGLERESP_add_ext(single, x509ext, -1)){ + error = 1; + goto err; + } + } } - err: + err: ASN1_TIME_free(ths); ASN1_TIME_free(nxt); ASN1_TIME_free(rev); @@ -907,8 +905,8 @@ ossl_ocspbres_get_status(VALUE self) int count = OCSP_resp_count(bs); for (int i = 0; i < count; i++) { OCSP_SINGLERESP *single = OCSP_resp_get0(bs, i); - ASN1_TIME *revtime, *thisupd, *nextupd; - int reason; + ASN1_TIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL; + int reason = -1; int status = OCSP_single_get0_status(single, &reason, &revtime, &thisupd, &nextupd); if (status < 0) @@ -924,7 +922,7 @@ ossl_ocspbres_get_status(VALUE self) VALUE ext = rb_ary_new(); int ext_count = OCSP_SINGLERESP_get_ext_count(single); for (int j = 0; j < ext_count; j++) { - X509_EXTENSION *x509ext = OCSP_SINGLERESP_get_ext(single, j); + const X509_EXTENSION *x509ext = OCSP_SINGLERESP_get_ext(single, j); rb_ary_push(ext, ossl_x509ext_new(x509ext)); } rb_ary_push(ary, ext); @@ -980,7 +978,7 @@ ossl_ocspbres_find_response(VALUE self, VALUE target) GetOCSPBasicRes(self, bs); if ((n = OCSP_resp_find(bs, id, -1)) == -1) - return Qnil; + return Qnil; return ossl_ocspsres_new(OCSP_resp_get0(bs, n)); } @@ -1000,7 +998,7 @@ ossl_ocspbres_find_response(VALUE self, VALUE target) static VALUE ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self) { - VALUE signer_cert, signer_key, certs, flags, digest; + VALUE signer_cert, signer_key, certs, flags, digest, md_holder; OCSP_BASICRESP *bs; X509 *signer; EVP_PKEY *key; @@ -1014,19 +1012,17 @@ ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self) signer = GetX509CertPtr(signer_cert); key = GetPrivPKeyPtr(signer_key); if (!NIL_P(flags)) - flg = NUM2INT(flags); - if (NIL_P(digest)) - md = NULL; - else - md = ossl_evp_get_digestbyname(digest); + flg = NUM2INT(flags); + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); if (NIL_P(certs)) - flg |= OCSP_NOCERTS; + flg |= OCSP_NOCERTS; else - x509s = ossl_x509_ary2sk(certs); + x509s = ossl_x509_ary2sk(certs); ret = OCSP_basic_sign(bs, signer, key, md, x509s, flg); sk_X509_pop_free(x509s, X509_free); - if (!ret) ossl_raise(eOCSPError, NULL); + if (!ret) + ossl_raise(eOCSPError, "OCSP_basic_sign"); return self; } @@ -1055,7 +1051,7 @@ ossl_ocspbres_verify(int argc, VALUE *argv, VALUE self) result = OCSP_basic_verify(bs, x509s, x509st, flg); sk_X509_pop_free(x509s, X509_free); if (result <= 0) - ossl_clear_error(); + ossl_clear_error(); return result > 0 ? Qtrue : Qfalse; } @@ -1076,11 +1072,11 @@ ossl_ocspbres_to_der(VALUE self) GetOCSPBasicRes(self, res); if ((len = i2d_OCSP_BASICRESP(res, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_OCSP_BASICRESP(res, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -1114,7 +1110,7 @@ ossl_ocspsres_alloc(VALUE klass) obj = NewOCSPSingleRes(klass); if (!(sres = OCSP_SINGLERESP_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPSingleRes(obj, sres); return obj; @@ -1139,7 +1135,7 @@ ossl_ocspsres_initialize(VALUE self, VALUE arg) p = (unsigned char*)RSTRING_PTR(arg); res_new = d2i_OCSP_SINGLERESP(NULL, &p, RSTRING_LEN(arg)); if (!res_new) - ossl_raise(eOCSPError, "d2i_OCSP_SINGLERESP"); + ossl_raise(eOCSPError, "d2i_OCSP_SINGLERESP"); SetOCSPSingleRes(self, res_new); OCSP_SINGLERESP_free(res); @@ -1158,7 +1154,7 @@ ossl_ocspsres_initialize_copy(VALUE self, VALUE other) sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres); if (!sres_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPSingleRes(self, sres_new); OCSP_SINGLERESP_free(sres_old); @@ -1197,15 +1193,15 @@ ossl_ocspsres_check_validity(int argc, VALUE *argv, VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, &this_update, &next_update); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); ret = OCSP_check_validity(this_update, next_update, nsec, maxsec); if (ret) - return Qtrue; + return Qtrue; else { - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; } } @@ -1247,7 +1243,7 @@ ossl_ocspsres_get_cert_status(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, NULL, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); return INT2NUM(status); } @@ -1266,9 +1262,9 @@ ossl_ocspsres_get_this_update(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, &time, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -1287,9 +1283,9 @@ ossl_ocspsres_get_next_update(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, NULL, &time); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -1308,11 +1304,11 @@ ossl_ocspsres_get_revocation_time(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, &time, NULL, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (status != V_OCSP_CERTSTATUS_REVOKED) - ossl_raise(eOCSPError, "certificate is not revoked"); + ossl_raise(eOCSPError, "certificate is not revoked"); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -1330,9 +1326,9 @@ ossl_ocspsres_get_revocation_reason(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, &reason, NULL, NULL, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (status != V_OCSP_CERTSTATUS_REVOKED) - ossl_raise(eOCSPError, "certificate is not revoked"); + ossl_raise(eOCSPError, "certificate is not revoked"); return INT2NUM(reason); } @@ -1345,7 +1341,6 @@ static VALUE ossl_ocspsres_get_extensions(VALUE self) { OCSP_SINGLERESP *sres; - X509_EXTENSION *ext; int count, i; VALUE ary; @@ -1354,8 +1349,8 @@ ossl_ocspsres_get_extensions(VALUE self) count = OCSP_SINGLERESP_get_ext_count(sres); ary = rb_ary_new2(count); for (i = 0; i < count; i++) { - ext = OCSP_SINGLERESP_get_ext(sres, i); - rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */ + const X509_EXTENSION *ext = OCSP_SINGLERESP_get_ext(sres, i); + rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */ } return ary; @@ -1377,11 +1372,11 @@ ossl_ocspsres_to_der(VALUE self) GetOCSPSingleRes(self, sres); if ((len = i2d_OCSP_SINGLERESP(sres, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_OCSP_SINGLERESP(sres, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -1399,7 +1394,7 @@ ossl_ocspcid_alloc(VALUE klass) obj = NewOCSPCertId(klass); if(!(id = OCSP_CERTID_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPCertId(obj, id); return obj; @@ -1417,7 +1412,7 @@ ossl_ocspcid_initialize_copy(VALUE self, VALUE other) cid_new = OCSP_CERTID_dup(cid); if (!cid_new) - ossl_raise(eOCSPError, "OCSP_CERTID_dup"); + ossl_raise(eOCSPError, "OCSP_CERTID_dup"); SetOCSPCertId(self, cid_new); OCSP_CERTID_free(cid_old); @@ -1447,27 +1442,28 @@ ossl_ocspcid_initialize(int argc, VALUE *argv, VALUE self) GetOCSPCertId(self, id); if (rb_scan_args(argc, argv, "12", &subject, &issuer, &digest) == 1) { - VALUE arg; - const unsigned char *p; - - arg = ossl_to_der_if_possible(subject); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - newid = d2i_OCSP_CERTID(NULL, &p, RSTRING_LEN(arg)); - if (!newid) - ossl_raise(eOCSPError, "d2i_OCSP_CERTID"); + VALUE arg; + const unsigned char *p; + + arg = ossl_to_der_if_possible(subject); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + newid = d2i_OCSP_CERTID(NULL, &p, RSTRING_LEN(arg)); + if (!newid) + ossl_raise(eOCSPError, "d2i_OCSP_CERTID"); } else { - X509 *x509s, *x509i; - const EVP_MD *md; + X509 *x509s, *x509i; + const EVP_MD *md; + VALUE md_holder; - x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */ - x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */ - md = !NIL_P(digest) ? ossl_evp_get_digestbyname(digest) : NULL; + x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */ + x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */ + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); - newid = OCSP_cert_to_id(md, x509s, x509i); - if (!newid) - ossl_raise(eOCSPError, "OCSP_cert_to_id"); + newid = OCSP_cert_to_id(md, x509s, x509i); + if (!newid) + ossl_raise(eOCSPError, "OCSP_cert_to_id"); } SetOCSPCertId(self, newid); @@ -1553,8 +1549,9 @@ ossl_ocspcid_get_issuer_name_hash(VALUE self) GetOCSPCertId(self, id); OCSP_id_get0_info(&name_hash, NULL, NULL, NULL, id); - ret = rb_str_new(NULL, name_hash->length * 2); - ossl_bin2hex(name_hash->data, RSTRING_PTR(ret), name_hash->length); + ret = rb_str_new(NULL, ASN1_STRING_length(name_hash) * 2); + ossl_bin2hex(ASN1_STRING_get0_data(name_hash), RSTRING_PTR(ret), + ASN1_STRING_length(name_hash)); return ret; } @@ -1576,8 +1573,9 @@ ossl_ocspcid_get_issuer_key_hash(VALUE self) GetOCSPCertId(self, id); OCSP_id_get0_info(NULL, NULL, &key_hash, NULL, id); - ret = rb_str_new(NULL, key_hash->length * 2); - ossl_bin2hex(key_hash->data, RSTRING_PTR(ret), key_hash->length); + ret = rb_str_new(NULL, ASN1_STRING_length(key_hash) * 2); + ossl_bin2hex(ASN1_STRING_get0_data(key_hash), RSTRING_PTR(ret), + ASN1_STRING_length(key_hash)); return ret; } @@ -1594,19 +1592,10 @@ ossl_ocspcid_get_hash_algorithm(VALUE self) { OCSP_CERTID *id; ASN1_OBJECT *oid; - BIO *out; GetOCSPCertId(self, id); OCSP_id_get0_info(NULL, &oid, NULL, NULL, id); - - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eOCSPError, "BIO_new"); - - if (!i2a_ASN1_OBJECT(out, oid)) { - BIO_free(out); - ossl_raise(eOCSPError, "i2a_ASN1_OBJECT"); - } - return ossl_membio2str(out); + return ossl_asn1obj_to_string_long_name(oid); } /* @@ -1625,11 +1614,11 @@ ossl_ocspcid_to_der(VALUE self) GetOCSPCertId(self, id); if ((len = i2d_OCSP_CERTID(id, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_OCSP_CERTID(id, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -1638,11 +1627,6 @@ ossl_ocspcid_to_der(VALUE self) void Init_ossl_ocsp(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * OpenSSL::OCSP implements Online Certificate Status Protocol requests * and responses. diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c index 0b7469e673..a47c81354c 100644 --- a/ext/openssl/ossl_pkcs12.c +++ b/ext/openssl/ossl_pkcs12.c @@ -42,7 +42,7 @@ ossl_pkcs12_free(void *ptr) static const rb_data_type_t ossl_pkcs12_type = { "OpenSSL/PKCS12", { - 0, ossl_pkcs12_free, + 0, ossl_pkcs12_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -72,7 +72,7 @@ ossl_pkcs12_initialize_copy(VALUE self, VALUE other) p12_new = ASN1_dup((i2d_of_void *)i2d_PKCS12, (d2i_of_void *)d2i_PKCS12, (char *)p12); if (!p12_new) - ossl_raise(ePKCS12Error, "ASN1_dup"); + ossl_raise(ePKCS12Error, "ASN1_dup"); SetPKCS12(self, p12_new); PKCS12_free(p12_old); @@ -122,11 +122,11 @@ ossl_pkcs12_s_create(int argc, VALUE *argv, VALUE self) /* TODO: make a VALUE to nid function */ if (!NIL_P(key_nid)) { if ((nkey = OBJ_txt2nid(StringValueCStr(key_nid))) == NID_undef) - ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, key_nid); + ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, key_nid); } if (!NIL_P(cert_nid)) { if ((ncert = OBJ_txt2nid(StringValueCStr(cert_nid))) == NID_undef) - ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, cert_nid); + ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, cert_nid); } if (!NIL_P(key_iter)) kiter = NUM2INT(key_iter); @@ -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,29 +199,31 @@ 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)) - ossl_raise(ePKCS12Error, "PKCS12_parse"); + 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); - if (st) goto err; + pkey = rb_protect(ossl_pkey_wrap_i, (VALUE)key, &st); + if (st) goto err; } if (x509) { - cert = rb_protect(ossl_x509_new_i, (VALUE)x509, &st); - if (st) goto err; + cert = rb_protect(ossl_x509_new_i, (VALUE)x509, &st); + if (st) goto err; } if (x509s) { - ca = rb_protect(ossl_x509_sk2ary_i, (VALUE)x509s, &st); - if (st) goto err; + ca = rb_protect(ossl_x509_sk2ary_i, (VALUE)x509s, &st); + if (st) goto err; } err: @@ -244,11 +247,11 @@ ossl_pkcs12_to_der(VALUE self) GetPKCS12(self, p12); if((len = i2d_PKCS12(p12, NULL)) <= 0) - ossl_raise(ePKCS12Error, NULL); + ossl_raise(ePKCS12Error, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_PKCS12(p12, &p) <= 0) - ossl_raise(ePKCS12Error, NULL); + ossl_raise(ePKCS12Error, NULL); ossl_str_adjust(str, p); return str; @@ -271,7 +274,7 @@ static VALUE pkcs12_set_mac(int argc, VALUE *argv, VALUE self) { PKCS12 *p12; - VALUE pass, salt, iter, md_name; + VALUE pass, salt, iter, md_name, md_holder = Qnil; int iter_i = 0; const EVP_MD *md_type = NULL; @@ -285,7 +288,7 @@ pkcs12_set_mac(int argc, VALUE *argv, VALUE self) if (!NIL_P(iter)) iter_i = NUM2INT(iter); if (!NIL_P(md_name)) - md_type = ossl_evp_get_digestbyname(md_name); + md_type = ossl_evp_md_fetch(md_name, &md_holder); if (!PKCS12_set_mac(p12, RSTRING_PTR(pass), RSTRING_LENINT(pass), !NIL_P(salt) ? (unsigned char *)RSTRING_PTR(salt) : NULL, @@ -300,11 +303,6 @@ void Init_ossl_pkcs12(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * Defines a file format commonly used to store private keys with * accompanying public key certificates, protected with a password-based diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 910ef9665c..44e8cb305b 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -28,14 +28,14 @@ TypedData_Wrap_Struct((klass), &ossl_pkcs7_signer_info_type, 0) #define SetPKCS7si(obj, p7si) do { \ if (!(p7si)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (p7si); \ } while (0) #define GetPKCS7si(obj, p7si) do { \ TypedData_Get_Struct((obj), PKCS7_SIGNER_INFO, &ossl_pkcs7_signer_info_type, (p7si)); \ if (!(p7si)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ } \ } while (0) @@ -43,14 +43,14 @@ TypedData_Wrap_Struct((klass), &ossl_pkcs7_recip_info_type, 0) #define SetPKCS7ri(obj, p7ri) do { \ if (!(p7ri)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (p7ri); \ } while (0) #define GetPKCS7ri(obj, p7ri) do { \ TypedData_Get_Struct((obj), PKCS7_RECIP_INFO, &ossl_pkcs7_recip_info_type, (p7ri)); \ if (!(p7ri)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ } \ } while (0) @@ -68,6 +68,7 @@ static VALUE cPKCS7; static VALUE cPKCS7Signer; static VALUE cPKCS7Recipient; static VALUE ePKCS7Error; +static ID id_md_holder, id_cipher_holder; static void ossl_pkcs7_free(void *ptr) @@ -78,7 +79,7 @@ ossl_pkcs7_free(void *ptr) static const rb_data_type_t ossl_pkcs7_type = { "OpenSSL/PKCS7", { - 0, ossl_pkcs7_free, + 0, ossl_pkcs7_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -106,7 +107,7 @@ ossl_pkcs7_signer_info_free(void *ptr) static const rb_data_type_t ossl_pkcs7_signer_info_type = { "OpenSSL/PKCS7/SIGNER_INFO", { - 0, ossl_pkcs7_signer_info_free, + 0, ossl_pkcs7_signer_info_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -120,7 +121,7 @@ ossl_pkcs7_recip_info_free(void *ptr) static const rb_data_type_t ossl_pkcs7_recip_info_type = { "OpenSSL/PKCS7/RECIP_INFO", { - 0, ossl_pkcs7_recip_info_free, + 0, ossl_pkcs7_recip_info_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -237,7 +238,7 @@ ossl_pkcs7_s_write_smime(int argc, VALUE *argv, VALUE klass) if(NIL_P(data)) data = ossl_pkcs7_get_data(pkcs7); GetPKCS7(pkcs7, p7); if(!NIL_P(data) && PKCS7_is_detached(p7)) - flg |= PKCS7_DETACHED; + flg |= PKCS7_DETACHED; in = NIL_P(data) ? NULL : ossl_obj2bio(&data); if(!(out = BIO_new(BIO_s_mem()))){ BIO_free(in); @@ -278,16 +279,16 @@ ossl_pkcs7_s_sign(int argc, VALUE *argv, VALUE klass) in = ossl_obj2bio(&data); if(NIL_P(certs)) x509s = NULL; else{ - x509s = ossl_protect_x509_ary2sk(certs, &status); - if(status){ - BIO_free(in); - rb_jump_tag(status); - } + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } } if(!(pkcs7 = PKCS7_sign(x509, pkey, x509s, in, flg))){ - BIO_free(in); - sk_X509_pop_free(x509s, X509_free); - ossl_raise(ePKCS7Error, NULL); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7(ret, pkcs7); ossl_pkcs7_set_data(ret, data); @@ -312,7 +313,7 @@ ossl_pkcs7_s_sign(int argc, VALUE *argv, VALUE klass) static VALUE ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) { - VALUE certs, data, cipher, flags; + VALUE certs, data, cipher, flags, cipher_holder; STACK_OF(X509) *x509s; BIO *in; const EVP_CIPHER *ciph; @@ -326,23 +327,24 @@ ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) "cipher must be specified. Before version 3.3, " \ "the default cipher was RC2-40-CBC."); } - ciph = ossl_evp_get_cipherbyname(cipher); + ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder); flg = NIL_P(flags) ? 0 : NUM2INT(flags); ret = NewPKCS7(cPKCS7); in = ossl_obj2bio(&data); x509s = ossl_protect_x509_ary2sk(certs, &status); if(status){ - BIO_free(in); - rb_jump_tag(status); + BIO_free(in); + rb_jump_tag(status); } if (!(p7 = PKCS7_encrypt(x509s, in, ciph, flg))) { - BIO_free(in); - sk_X509_pop_free(x509s, X509_free); - ossl_raise(ePKCS7Error, NULL); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); } BIO_free(in); SetPKCS7(ret, p7); ossl_pkcs7_set_data(ret, data); + rb_ivar_set(ret, id_cipher_holder, cipher_holder); sk_X509_pop_free(x509s, X509_free); return ret; @@ -356,7 +358,7 @@ ossl_pkcs7_alloc(VALUE klass) obj = NewPKCS7(klass); if (!(pkcs7 = PKCS7_new())) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7(obj, pkcs7); @@ -378,7 +380,7 @@ ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self) VALUE arg; if(rb_scan_args(argc, argv, "01", &arg) == 0) - return self; + return self; arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); p7 = d2i_PKCS7_bio(in, NULL); @@ -388,10 +390,10 @@ ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self) } BIO_free(in); if (!p7) - ossl_raise(rb_eArgError, "Could not parse the PKCS7"); + ossl_raise(ePKCS7Error, "Could not parse the PKCS7"); if (!p7->d.ptr) { PKCS7_free(p7); - ossl_raise(rb_eArgError, "No content in PKCS7"); + ossl_raise(ePKCS7Error, "No content in PKCS7"); } RTYPEDDATA_DATA(self) = p7; @@ -416,7 +418,7 @@ ossl_pkcs7_copy(VALUE self, VALUE other) pkcs7 = PKCS7_dup(b); if (!pkcs7) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } DATA_PTR(self) = pkcs7; PKCS7_free(a); @@ -448,13 +450,13 @@ ossl_pkcs7_sym2typeid(VALUE sym) RSTRING_GETMEM(sym, s, l); for(i = 0; ; i++){ - 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){ - ret = p7_type_tab[i].nid; - break; - } + if(i == numberof(p7_type_tab)) + ossl_raise(ePKCS7Error, "unknown type \"%"PRIsVALUE"\"", sym); + if(strlen(p7_type_tab[i].name) != l) continue; + if(memcmp(p7_type_tab[i].name, s, l) == 0){ + ret = p7_type_tab[i].nid; + break; + } } return ret; @@ -471,7 +473,7 @@ ossl_pkcs7_set_type(VALUE self, VALUE type) GetPKCS7(self, p7); if(!PKCS7_set_type(p7, ossl_pkcs7_sym2typeid(type))) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); return type; } @@ -487,15 +489,15 @@ ossl_pkcs7_get_type(VALUE self) GetPKCS7(self, p7); if(PKCS7_type_is_signed(p7)) - return ID2SYM(rb_intern("signed")); + return ID2SYM(rb_intern("signed")); if(PKCS7_type_is_encrypted(p7)) - return ID2SYM(rb_intern("encrypted")); + return ID2SYM(rb_intern("encrypted")); if(PKCS7_type_is_enveloped(p7)) - return ID2SYM(rb_intern("enveloped")); + return ID2SYM(rb_intern("enveloped")); if(PKCS7_type_is_signedAndEnveloped(p7)) - return ID2SYM(rb_intern("signedAndEnveloped")); + return ID2SYM(rb_intern("signedAndEnveloped")); if(PKCS7_type_is_data(p7)) - return ID2SYM(rb_intern("data")); + return ID2SYM(rb_intern("data")); return Qnil; } @@ -506,9 +508,9 @@ ossl_pkcs7_set_detached(VALUE self, VALUE flag) GetPKCS7(self, p7); if(flag != Qtrue && flag != Qfalse) - ossl_raise(ePKCS7Error, "must specify a boolean"); + ossl_raise(ePKCS7Error, "must specify a boolean"); if(!PKCS7_set_detached(p7, flag == Qtrue ? 1 : 0)) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); return flag; } @@ -535,11 +537,14 @@ static VALUE ossl_pkcs7_set_cipher(VALUE self, VALUE cipher) { PKCS7 *pkcs7; + const EVP_CIPHER *ciph; + VALUE cipher_holder; GetPKCS7(self, pkcs7); - if (!PKCS7_set_cipher(pkcs7, ossl_evp_get_cipherbyname(cipher))) { - ossl_raise(ePKCS7Error, NULL); - } + ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder); + if (!PKCS7_set_cipher(pkcs7, ciph)) + ossl_raise(ePKCS7Error, "PKCS7_set_cipher"); + rb_ivar_set(self, id_cipher_holder, cipher_holder); return cipher; } @@ -579,8 +584,8 @@ ossl_pkcs7_get_signer(VALUE self) num = sk_PKCS7_SIGNER_INFO_num(sk); ary = rb_ary_new_capa(num); for (i=0; i<num; i++) { - PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(sk, i); - rb_ary_push(ary, ossl_pkcs7si_new(si)); + PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(sk, i); + rb_ary_push(ary, ossl_pkcs7si_new(si)); } return ary; @@ -617,9 +622,9 @@ ossl_pkcs7_get_recipient(VALUE self) GetPKCS7(self, pkcs7); if (PKCS7_type_is_enveloped(pkcs7)) - sk = pkcs7->d.enveloped->recipientinfo; + sk = pkcs7->d.enveloped->recipientinfo; else if (PKCS7_type_is_signedAndEnveloped(pkcs7)) - sk = pkcs7->d.signed_and_enveloped->recipientinfo; + sk = pkcs7->d.signed_and_enveloped->recipientinfo; else sk = NULL; if (!sk) return rb_ary_new(); num = sk_PKCS7_RECIP_INFO_num(sk); @@ -641,7 +646,7 @@ ossl_pkcs7_add_certificate(VALUE self, VALUE cert) GetPKCS7(self, pkcs7); x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ if (!PKCS7_add_certificate(pkcs7, x509)){ - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } return self; @@ -657,13 +662,13 @@ pkcs7_get_certs(VALUE self) GetPKCS7(self, pkcs7); i = OBJ_obj2nid(pkcs7->type); switch(i){ - case NID_pkcs7_signed: + case NID_pkcs7_signed: certs = pkcs7->d.sign->cert; break; - case NID_pkcs7_signedAndEnveloped: + case NID_pkcs7_signedAndEnveloped: certs = pkcs7->d.signed_and_enveloped->cert; break; - default: + default: certs = NULL; } @@ -680,13 +685,13 @@ pkcs7_get_crls(VALUE self) GetPKCS7(self, pkcs7); i = OBJ_obj2nid(pkcs7->type); switch(i){ - case NID_pkcs7_signed: + case NID_pkcs7_signed: crls = pkcs7->d.sign->crl; break; - case NID_pkcs7_signedAndEnveloped: + case NID_pkcs7_signedAndEnveloped: crls = pkcs7->d.signed_and_enveloped->crl; break; - default: + default: crls = NULL; } @@ -733,7 +738,7 @@ ossl_pkcs7_add_crl(VALUE self, VALUE crl) GetPKCS7(self, pkcs7); /* NO DUP needed! */ x509crl = GetX509CRLPtr(crl); if (!PKCS7_add_crl(pkcs7, x509crl)) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } return self; @@ -789,16 +794,16 @@ ossl_pkcs7_verify(int argc, VALUE *argv, VALUE self) in = NIL_P(indata) ? NULL : ossl_obj2bio(&indata); if(NIL_P(certs)) x509s = NULL; else{ - x509s = ossl_protect_x509_ary2sk(certs, &status); - if(status){ - BIO_free(in); - rb_jump_tag(status); - } + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } } if(!(out = BIO_new(BIO_s_mem()))){ - BIO_free(in); - sk_X509_pop_free(x509s, X509_free); - ossl_raise(ePKCS7Error, NULL); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); } ok = PKCS7_verify(p7, x509s, x509st, in, out, flg); BIO_free(in); @@ -832,10 +837,10 @@ ossl_pkcs7_decrypt(int argc, VALUE *argv, VALUE self) flg = NIL_P(flags) ? 0 : NUM2INT(flags); GetPKCS7(self, p7); if(!(out = BIO_new(BIO_s_mem()))) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); if(!PKCS7_decrypt(p7, key, x509, out, flg)){ - BIO_free(out); - ossl_raise(ePKCS7Error, NULL); + BIO_free(out); + ossl_raise(ePKCS7Error, NULL); } str = ossl_membio2str(out); /* out will be free */ @@ -894,11 +899,11 @@ ossl_pkcs7_to_der(VALUE self) GetPKCS7(self, pkcs7); if((len = i2d_PKCS7(pkcs7, NULL)) <= 0) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_PKCS7(pkcs7, &p) <= 0) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); ossl_str_adjust(str, p); return str; @@ -932,11 +937,11 @@ ossl_pkcs7_to_pem(VALUE self) GetPKCS7(self, pkcs7); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } if (!PEM_write_bio_PKCS7(out, pkcs7)) { - BIO_free(out); - ossl_raise(ePKCS7Error, NULL); + BIO_free(out); + ossl_raise(ePKCS7Error, NULL); } str = ossl_membio2str(out); @@ -954,7 +959,7 @@ ossl_pkcs7si_alloc(VALUE klass) obj = NewPKCS7si(klass); if (!(p7si = PKCS7_SIGNER_INFO_new())) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7si(obj, p7si); @@ -968,14 +973,15 @@ ossl_pkcs7si_initialize(VALUE self, VALUE cert, VALUE key, VALUE digest) EVP_PKEY *pkey; X509 *x509; const EVP_MD *md; + VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); GetPKCS7si(self, p7si); - if (!(PKCS7_SIGNER_INFO_set(p7si, x509, pkey, md))) { - ossl_raise(ePKCS7Error, NULL); - } + if (PKCS7_SIGNER_INFO_set(p7si, x509, pkey, md) <= 0) + ossl_raise(ePKCS7Error, "PKCS7_SIGNER_INFO_set"); + rb_ivar_set(self, id_md_holder, md_holder); return self; } @@ -1004,15 +1010,15 @@ static VALUE ossl_pkcs7si_get_signed_time(VALUE self) { PKCS7_SIGNER_INFO *p7si; - ASN1_TYPE *asn1obj; + const ASN1_TYPE *asn1obj; GetPKCS7si(self, p7si); if (!(asn1obj = PKCS7_get_signed_attribute(p7si, NID_pkcs9_signingTime))) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } if (asn1obj->type == V_ASN1_UTCTIME) { - return asn1time_to_time(asn1obj->value.utctime); + return asn1time_to_time(asn1obj->value.utctime); } /* * OR @@ -1034,7 +1040,7 @@ ossl_pkcs7ri_alloc(VALUE klass) obj = NewPKCS7ri(klass); if (!(p7ri = PKCS7_RECIP_INFO_new())) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7ri(obj, p7ri); @@ -1049,8 +1055,8 @@ ossl_pkcs7ri_initialize(VALUE self, VALUE cert) x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ GetPKCS7ri(self, p7ri); - if (!PKCS7_RECIP_INFO_set(p7ri, x509)) { - ossl_raise(ePKCS7Error, NULL); + if (PKCS7_RECIP_INFO_set(p7ri, x509) <= 0) { + ossl_raise(ePKCS7Error, NULL); } return self; @@ -1093,11 +1099,6 @@ void Init_ossl_pkcs7(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - cPKCS7 = rb_define_class_under(mOSSL, "PKCS7", rb_cObject); ePKCS7Error = rb_define_class_under(cPKCS7, "PKCS7Error", eOSSLError); rb_define_singleton_method(cPKCS7, "read_smime", ossl_pkcs7_s_read_smime, 1); @@ -1161,4 +1162,7 @@ Init_ossl_pkcs7(void) DefPKCS7Const(BINARY); DefPKCS7Const(NOATTR); DefPKCS7Const(NOSMIMECAP); + + id_md_holder = rb_intern_const("EVP_MD_holder"); + id_cipher_holder = rb_intern_const("EVP_CIPHER_holder"); } diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index e88074ddf2..a53332b17e 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -33,7 +33,7 @@ ossl_evp_pkey_free(void *ptr) const rb_data_type_t ossl_evp_pkey_type = { "OpenSSL/EVP_PKEY", { - 0, ossl_evp_pkey_free, + 0, ossl_evp_pkey_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -72,8 +72,8 @@ ossl_pkey_wrap(EVP_PKEY *pkey) obj = rb_protect(pkey_wrap0, (VALUE)pkey, &status); if (status) { - EVP_PKEY_free(pkey); - rb_jump_tag(status); + EVP_PKEY_free(pkey); + rb_jump_tag(status); } return obj; @@ -94,7 +94,8 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass) selection, NULL, NULL); if (!dctx) goto out; - if (OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, + if (selection == EVP_PKEY_KEYPAIR && + OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, ppass) != 1) goto out; while (1) { @@ -187,23 +188,23 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass) EVP_PKEY *pkey; if ((pkey = d2i_PrivateKey_bio(bio, NULL))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, ossl_pem_passwd_cb, ppass))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = d2i_PUBKEY_bio(bio, NULL))) - goto out; + goto out; OSSL_BIO_reset(bio); /* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */ if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, ppass))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = PEM_read_bio_Parameters(bio, NULL))) - goto out; + goto out; out: return pkey; @@ -238,7 +239,7 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(bio, ossl_pem_passwd_value(pass)); BIO_free(bio); if (!pkey) - ossl_raise(ePKeyError, "Could not parse PKey"); + ossl_raise(ePKeyError, "Could not parse PKey"); return ossl_pkey_wrap(pkey); } @@ -507,7 +508,7 @@ ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self) void ossl_pkey_check_public_key(const EVP_PKEY *pkey) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY if (EVP_PKEY_missing_parameters(pkey)) ossl_raise(ePKeyError, "parameters missing"); #else @@ -515,34 +516,34 @@ ossl_pkey_check_public_key(const EVP_PKEY *pkey) const BIGNUM *n, *e, *pubkey; if (EVP_PKEY_missing_parameters(pkey)) - ossl_raise(ePKeyError, "parameters missing"); + ossl_raise(ePKeyError, "parameters missing"); ptr = EVP_PKEY_get0(pkey); switch (EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: - RSA_get0_key(ptr, &n, &e, NULL); - if (n && e) - return; - break; + RSA_get0_key(ptr, &n, &e, NULL); + if (n && e) + return; + break; case EVP_PKEY_DSA: - DSA_get0_key(ptr, &pubkey, NULL); - if (pubkey) - return; - break; + DSA_get0_key(ptr, &pubkey, NULL); + if (pubkey) + return; + break; case EVP_PKEY_DH: - DH_get0_key(ptr, &pubkey, NULL); - if (pubkey) - return; - break; + DH_get0_key(ptr, &pubkey, NULL); + if (pubkey) + return; + break; #if !defined(OPENSSL_NO_EC) case EVP_PKEY_EC: - if (EC_KEY_get0_public_key(ptr)) - return; - break; + if (EC_KEY_get0_public_key(ptr)) + return; + break; #endif default: - /* unsupported type; assuming ok */ - return; + /* unsupported type; assuming ok */ + return; } ossl_raise(ePKeyError, "public key missing"); #endif @@ -609,7 +610,7 @@ static VALUE ossl_pkey_initialize(VALUE self) { if (rb_obj_is_instance_of(self, cPKey)) { - ossl_raise(rb_eTypeError, "OpenSSL::PKey::PKey can't be instantiated directly"); + ossl_raise(rb_eTypeError, "OpenSSL::PKey::PKey can't be instantiated directly"); } return self; } @@ -813,33 +814,33 @@ VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) { EVP_PKEY *pkey; - VALUE cipher, pass; + VALUE cipher, pass, cipher_holder; const EVP_CIPHER *enc = NULL; BIO *bio; GetPKey(self, pkey); rb_scan_args(argc, argv, "02", &cipher, &pass); if (!NIL_P(cipher)) { - enc = ossl_evp_get_cipherbyname(cipher); - pass = ossl_pem_passwd_value(pass); + enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); + pass = ossl_pem_passwd_value(pass); } bio = BIO_new(BIO_s_mem()); if (!bio) - ossl_raise(ePKeyError, "BIO_new"); + ossl_raise(ePKeyError, "BIO_new"); if (to_der) { - if (!i2d_PrivateKey_bio(bio, pkey)) { - BIO_free(bio); - ossl_raise(ePKeyError, "i2d_PrivateKey_bio"); - } + if (!i2d_PrivateKey_bio(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PrivateKey_bio"); + } } else { - if (!PEM_write_bio_PrivateKey_traditional(bio, pkey, enc, NULL, 0, - ossl_pem_passwd_cb, - (void *)pass)) { - BIO_free(bio); - ossl_raise(ePKeyError, "PEM_write_bio_PrivateKey_traditional"); - } + if (!PEM_write_bio_PrivateKey_traditional(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, + (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PrivateKey_traditional"); + } } return ossl_membio2str(bio); } @@ -848,37 +849,37 @@ static VALUE do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der) { EVP_PKEY *pkey; - VALUE cipher, pass; + VALUE cipher, pass, cipher_holder; const EVP_CIPHER *enc = NULL; BIO *bio; GetPKey(self, pkey); rb_scan_args(argc, argv, "02", &cipher, &pass); if (argc > 0) { - /* - * TODO: EncryptedPrivateKeyInfo actually has more options. - * Should they be exposed? - */ - enc = ossl_evp_get_cipherbyname(cipher); - pass = ossl_pem_passwd_value(pass); + /* + * TODO: EncryptedPrivateKeyInfo actually has more options. + * Should they be exposed? + */ + enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); + pass = ossl_pem_passwd_value(pass); } bio = BIO_new(BIO_s_mem()); if (!bio) - ossl_raise(ePKeyError, "BIO_new"); + ossl_raise(ePKeyError, "BIO_new"); if (to_der) { - if (!i2d_PKCS8PrivateKey_bio(bio, pkey, enc, NULL, 0, - ossl_pem_passwd_cb, (void *)pass)) { - BIO_free(bio); - ossl_raise(ePKeyError, "i2d_PKCS8PrivateKey_bio"); - } + if (!i2d_PKCS8PrivateKey_bio(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PKCS8PrivateKey_bio"); + } } else { - if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey, enc, NULL, 0, - ossl_pem_passwd_cb, (void *)pass)) { - BIO_free(bio); - ossl_raise(ePKeyError, "PEM_write_bio_PKCS8PrivateKey"); - } + if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PKCS8PrivateKey"); + } } return ossl_membio2str(bio); } @@ -962,18 +963,18 @@ ossl_pkey_export_spki(VALUE self, int to_der) ossl_pkey_check_public_key(pkey); bio = BIO_new(BIO_s_mem()); if (!bio) - ossl_raise(ePKeyError, "BIO_new"); + ossl_raise(ePKeyError, "BIO_new"); if (to_der) { - if (!i2d_PUBKEY_bio(bio, pkey)) { - BIO_free(bio); - ossl_raise(ePKeyError, "i2d_PUBKEY_bio"); - } + if (!i2d_PUBKEY_bio(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PUBKEY_bio"); + } } else { - if (!PEM_write_bio_PUBKEY(bio, pkey)) { - BIO_free(bio); - ossl_raise(ePKeyError, "PEM_write_bio_PUBKEY"); - } + if (!PEM_write_bio_PUBKEY(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PUBKEY"); + } } return ossl_membio2str(bio); } @@ -1110,7 +1111,7 @@ static VALUE ossl_pkey_sign(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, data, options, sig; + VALUE digest, data, options, sig, md_holder; const EVP_MD *md = NULL; EVP_MD_CTX *ctx; EVP_PKEY_CTX *pctx; @@ -1120,7 +1121,7 @@ ossl_pkey_sign(int argc, VALUE *argv, VALUE self) pkey = GetPrivPKeyPtr(self); rb_scan_args(argc, argv, "21", &digest, &data, &options); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); ctx = EVP_MD_CTX_new(); @@ -1189,7 +1190,7 @@ static VALUE ossl_pkey_verify(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, sig, data, options; + VALUE digest, sig, data, options, md_holder; const EVP_MD *md = NULL; EVP_MD_CTX *ctx; EVP_PKEY_CTX *pctx; @@ -1199,7 +1200,7 @@ ossl_pkey_verify(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); StringValue(data); @@ -1268,7 +1269,7 @@ static VALUE ossl_pkey_sign_raw(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, data, options, sig; + VALUE digest, data, options, sig, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; size_t outlen; @@ -1277,7 +1278,7 @@ ossl_pkey_sign_raw(int argc, VALUE *argv, VALUE self) GetPKey(self, pkey); rb_scan_args(argc, argv, "21", &digest, &data, &options); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); @@ -1344,7 +1345,7 @@ static VALUE ossl_pkey_verify_raw(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, sig, data, options; + VALUE digest, sig, data, options, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; int state, ret; @@ -1353,7 +1354,7 @@ ossl_pkey_verify_raw(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); StringValue(data); @@ -1407,7 +1408,7 @@ static VALUE ossl_pkey_verify_recover(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, sig, options, out; + VALUE digest, sig, options, out, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; int state; @@ -1417,7 +1418,7 @@ ossl_pkey_verify_recover(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "21", &digest, &sig, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); @@ -1495,8 +1496,10 @@ ossl_pkey_derive(int argc, VALUE *argv, VALUE self) EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_derive"); } - if (keylen > LONG_MAX) + if (keylen > LONG_MAX) { + EVP_PKEY_CTX_free(ctx); rb_raise(ePKeyError, "derived key would be too large"); + } str = ossl_str_new(NULL, (long)keylen, &state); if (state) { EVP_PKEY_CTX_free(ctx); @@ -1657,11 +1660,6 @@ void Init_ossl_pkey(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-module: OpenSSL::PKey * * == Asymmetric Public Key Algorithms @@ -1717,7 +1715,16 @@ Init_ossl_pkey(void) /* Document-class: OpenSSL::PKey::PKeyError * - *Raised when errors occur during PKey#sign or PKey#verify. + * Raised when errors occur during PKey#sign or PKey#verify. + * + * Before version 4.0.0, OpenSSL::PKey::PKeyError had the following + * subclasses. These subclasses have been removed and the constants are + * now defined as aliases of OpenSSL::PKey::PKeyError. + * + * * OpenSSL::PKey::DHError + * * OpenSSL::PKey::DSAError + * * OpenSSL::PKey::ECError + * * OpenSSL::PKey::RSAError */ ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index 6778381210..efba33b752 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -22,7 +22,7 @@ extern const rb_data_type_t ossl_evp_pkey_type; #define GetPKey(obj, pkey) do {\ TypedData_Get_Struct((obj), EVP_PKEY, &ossl_evp_pkey_type, (pkey)); \ if (!(pkey)) { \ - rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\ + rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\ } \ } while (0) @@ -45,7 +45,7 @@ VALUE ossl_pkey_export_spki(VALUE self, int to_der); * #to_der. */ VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, - int to_der); + int to_der); void Init_ossl_pkey(void); @@ -71,123 +71,122 @@ void Init_ossl_dh(void); * EC */ extern VALUE cEC; -VALUE ossl_ec_new(EVP_PKEY *); void Init_ossl_ec(void); -#define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get) \ -/* \ - * call-seq: \ - * _keytype##.##_name -> aBN \ - */ \ -static VALUE ossl_##_keytype##_get_##_name(VALUE self) \ -{ \ - const _type *obj; \ - const BIGNUM *bn; \ - \ - Get##_type(self, obj); \ - _get; \ - if (bn == NULL) \ - return Qnil; \ - return ossl_bn_new(bn); \ +#define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get) \ +/* \ + * call-seq: \ + * _keytype##.##_name -> aBN \ + */ \ +static VALUE ossl_##_keytype##_get_##_name(VALUE self) \ +{ \ + const _type *obj; \ + const BIGNUM *bn; \ + \ + Get##_type(self, obj); \ + _get; \ + if (bn == NULL) \ + return Qnil; \ + return ossl_bn_new(bn); \ } -#define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ - _type##_get0_##_group(obj, &bn, NULL, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ - _type##_get0_##_group(obj, NULL, &bn, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3, \ - _type##_get0_##_group(obj, NULL, NULL, &bn)) - -#define OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ - _type##_get0_##_group(obj, &bn, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ - _type##_get0_##_group(obj, NULL, &bn)) - -#if !OSSL_OPENSSL_PREREQ(3, 0, 0) -#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ -/* \ - * call-seq: \ - * _keytype##.set_##_group(a1, a2, a3) -> self \ - */ \ +#define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ + _type##_get0_##_group(obj, &bn, NULL, NULL)) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ + _type##_get0_##_group(obj, NULL, &bn, NULL)) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3, \ + _type##_get0_##_group(obj, NULL, NULL, &bn)) + +#define OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ + _type##_get0_##_group(obj, &bn, NULL)) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ + _type##_get0_##_group(obj, NULL, &bn)) + +#ifndef OSSL_HAVE_IMMUTABLE_PKEY +#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ +/* \ + * call-seq: \ + * _keytype##.set_##_group(a1, a2, a3) -> self \ + */ \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \ -{ \ - _type *obj; \ - BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ - BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ - BIGNUM *bn3 = NULL, *orig_bn3 = NIL_P(v3) ? NULL : GetBNPtr(v3);\ - \ - Get##_type(self, obj); \ - if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ - (orig_bn2 && !(bn2 = BN_dup(orig_bn2))) || \ - (orig_bn3 && !(bn3 = BN_dup(orig_bn3)))) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - BN_clear_free(bn3); \ - ossl_raise(ePKeyError, "BN_dup"); \ - } \ - \ - if (!_type##_set0_##_group(obj, bn1, bn2, bn3)) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - BN_clear_free(bn3); \ - ossl_raise(ePKeyError, #_type"_set0_"#_group); \ - } \ - return self; \ +{ \ + _type *obj; \ + BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ + BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ + BIGNUM *bn3 = NULL, *orig_bn3 = NIL_P(v3) ? NULL : GetBNPtr(v3);\ + \ + Get##_type(self, obj); \ + if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ + (orig_bn2 && !(bn2 = BN_dup(orig_bn2))) || \ + (orig_bn3 && !(bn3 = BN_dup(orig_bn3)))) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + BN_clear_free(bn3); \ + ossl_raise(ePKeyError, "BN_dup"); \ + } \ + \ + if (!_type##_set0_##_group(obj, bn1, bn2, bn3)) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + BN_clear_free(bn3); \ + ossl_raise(ePKeyError, #_type"_set0_"#_group); \ + } \ + return self; \ } -#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ -/* \ - * call-seq: \ - * _keytype##.set_##_group(a1, a2) -> self \ - */ \ +#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ +/* \ + * call-seq: \ + * _keytype##.set_##_group(a1, a2) -> self \ + */ \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \ -{ \ - _type *obj; \ - BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ - BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ - \ - Get##_type(self, obj); \ - if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ - (orig_bn2 && !(bn2 = BN_dup(orig_bn2)))) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - ossl_raise(ePKeyError, "BN_dup"); \ - } \ - \ - if (!_type##_set0_##_group(obj, bn1, bn2)) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - ossl_raise(ePKeyError, #_type"_set0_"#_group); \ - } \ - return self; \ +{ \ + _type *obj; \ + BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ + BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ + \ + Get##_type(self, obj); \ + if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ + (orig_bn2 && !(bn2 = BN_dup(orig_bn2)))) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + ossl_raise(ePKeyError, "BN_dup"); \ + } \ + \ + if (!_type##_set0_##_group(obj, bn1, bn2)) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + ossl_raise(ePKeyError, #_type"_set0_"#_group); \ + } \ + return self; \ } #else -#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ +#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \ -{ \ - rb_raise(ePKeyError, \ +{ \ + rb_raise(ePKeyError, \ #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \ } -#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ +#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \ -{ \ - rb_raise(ePKeyError, \ +{ \ + rb_raise(ePKeyError, \ #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \ } #endif -#define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) +#define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3) \ + OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ + OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) -#define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) +#define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2) \ + OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ + OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) -#define DEF_OSSL_PKEY_BN(class, keytype, name) \ - rb_define_method((class), #name, ossl_##keytype##_get_##name, 0) +#define DEF_OSSL_PKEY_BN(class, keytype, name) \ + rb_define_method((class), #name, ossl_##keytype##_get_##name, 0) #endif /* OSSL_PKEY_H */ diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 118d29f04f..3f2975c5a3 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -14,20 +14,21 @@ #define GetPKeyDH(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) { /* PARANOIA? */ \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A DH!") ; \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A DH!") ; \ } \ } while (0) #define GetDH(obj, dh) do { \ EVP_PKEY *_pkey; \ GetPKeyDH((obj), _pkey); \ (dh) = EVP_PKEY_get0_DH(_pkey); \ + if ((dh) == NULL) \ + ossl_raise(ePKeyError, "failed to get DH from EVP_PKEY"); \ } while (0) /* * Classes */ VALUE cDH; -static VALUE eDHError; /* * Private @@ -43,6 +44,7 @@ static VALUE eDHError; * If called without arguments, an empty instance without any parameter or key * components is created. Use #set_pqg to manually set the parameters afterwards * (and optionally #set_key to set private and public key components). + * This form is not compatible with OpenSSL 3.0 or later. * * If a String is given, tries to parse it as a DER- or PEM- encoded parameters. * See also OpenSSL::PKey.read which can parse keys of any kinds. @@ -58,14 +60,15 @@ static VALUE eDHError; * * Examples: * # Creating an instance from scratch - * # Note that this is deprecated and will not work on OpenSSL 3.0 or later. + * # Note that this is deprecated and will result in ArgumentError when + * # using OpenSSL 3.0 or later. * dh = OpenSSL::PKey::DH.new * dh.set_pqg(bn_p, nil, bn_g) * * # Generating a parameters and a key pair * dh = OpenSSL::PKey::DH.new(2048) # An alias of OpenSSL::PKey::DH.generate(2048) * - * # Reading DH parameters + * # Reading DH parameters from a PEM-encoded string * dh_params = OpenSSL::PKey::DH.new(File.read('parameters.pem')) # loads parameters only * dh = OpenSSL::PKey.generate_key(dh_params) # generates a key pair */ @@ -84,10 +87,15 @@ ossl_dh_initialize(int argc, VALUE *argv, VALUE self) /* The DH.new(size, generator) form is handled by lib/openssl/pkey.rb */ if (rb_scan_args(argc, argv, "01", &arg) == 0) { +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::DH.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else dh = DH_new(); if (!dh) - ossl_raise(eDHError, "DH_new"); + ossl_raise(ePKeyError, "DH_new"); goto legacy; +#endif } arg = ossl_to_der_if_possible(arg); @@ -105,12 +113,12 @@ ossl_dh_initialize(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(in, Qnil); BIO_free(in); if (!pkey) - ossl_raise(eDHError, "could not parse pkey"); + ossl_raise(ePKeyError, "could not parse pkey"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_DH) { EVP_PKEY_free(pkey); - rb_raise(eDHError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -121,7 +129,7 @@ ossl_dh_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) { EVP_PKEY_free(pkey); DH_free(dh); - ossl_raise(eDHError, "EVP_PKEY_assign_DH"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DH"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -143,26 +151,26 @@ ossl_dh_initialize_copy(VALUE self, VALUE other) dh = DHparams_dup(dh_other); if (!dh) - ossl_raise(eDHError, "DHparams_dup"); + ossl_raise(ePKeyError, "DHparams_dup"); DH_get0_key(dh_other, &pub, &priv); if (pub) { - BIGNUM *pub2 = BN_dup(pub); - BIGNUM *priv2 = BN_dup(priv); + BIGNUM *pub2 = BN_dup(pub); + BIGNUM *priv2 = BN_dup(priv); if (!pub2 || (priv && !priv2)) { - BN_clear_free(pub2); - BN_clear_free(priv2); - ossl_raise(eDHError, "BN_dup"); - } - DH_set0_key(dh, pub2, priv2); + BN_clear_free(pub2); + BN_clear_free(priv2); + ossl_raise(ePKeyError, "BN_dup"); + } + DH_set0_key(dh, pub2, priv2); } pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) { EVP_PKEY_free(pkey); DH_free(dh); - ossl_raise(eDHError, "EVP_PKEY_assign_DH"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DH"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -241,11 +249,11 @@ ossl_dh_export(VALUE self) GetDH(self, dh); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eDHError, NULL); + ossl_raise(ePKeyError, NULL); } if (!PEM_write_bio_DHparams(out, dh)) { - BIO_free(out); - ossl_raise(eDHError, NULL); + BIO_free(out); + ossl_raise(ePKeyError, NULL); } str = ossl_membio2str(out); @@ -275,11 +283,11 @@ ossl_dh_to_der(VALUE self) GetDH(self, dh); if((len = i2d_DHparams(dh, NULL)) <= 0) - ossl_raise(eDHError, NULL); + ossl_raise(ePKeyError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_DHparams(dh, &p) < 0) - ossl_raise(eDHError, NULL); + ossl_raise(ePKeyError, NULL); ossl_str_adjust(str, p); return str; @@ -306,7 +314,7 @@ ossl_dh_check_params(VALUE self) GetPKey(self, pkey); pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!pctx) - ossl_raise(eDHError, "EVP_PKEY_CTX_new"); + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); ret = EVP_PKEY_param_check(pctx); EVP_PKEY_CTX_free(pctx); #else @@ -349,19 +357,6 @@ OSSL_PKEY_BN_DEF2(dh, DH, key, pub_key, priv_key) void Init_ossl_dh(void) { -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - - /* Document-class: OpenSSL::PKey::DHError - * - * Generic exception that is raised if an operation on a DH PKey - * fails unexpectedly or in case an instantiation of an instance of DH - * fails due to non-conformant input data. - */ - eDHError = rb_define_class_under(mPKey, "DHError", ePKeyError); /* Document-class: OpenSSL::PKey::DH * * An implementation of the Diffie-Hellman key exchange protocol based on diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index 9f4f012cfe..041646a058 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -14,13 +14,15 @@ #define GetPKeyDSA(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DSA) { /* PARANOIA? */ \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A DSA!"); \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A DSA!"); \ } \ } while (0) #define GetDSA(obj, dsa) do { \ EVP_PKEY *_pkey; \ GetPKeyDSA((obj), _pkey); \ (dsa) = EVP_PKEY_get0_DSA(_pkey); \ + if ((dsa) == NULL) \ + ossl_raise(ePKeyError, "failed to get DSA from EVP_PKEY"); \ } while (0) static inline int @@ -41,7 +43,6 @@ DSA_PRIVATE(VALUE obj, OSSL_3_const DSA *dsa) * Classes */ VALUE cDSA; -static VALUE eDSAError; /* * Private @@ -56,6 +57,7 @@ static VALUE eDSAError; * * If called without arguments, creates a new instance with no key components * set. They can be set individually by #set_pqg and #set_key. + * This form is not compatible with OpenSSL 3.0 or later. * * If called with a String, tries to parse as DER or PEM encoding of a \DSA key. * See also OpenSSL::PKey.read which can parse keys of any kinds. @@ -96,10 +98,15 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) /* The DSA.new(size, generator) form is handled by lib/openssl/pkey.rb */ rb_scan_args(argc, argv, "02", &arg, &pass); if (argc == 0) { +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::DSA.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else dsa = DSA_new(); if (!dsa) - ossl_raise(eDSAError, "DSA_new"); + ossl_raise(ePKeyError, "DSA_new"); goto legacy; +#endif } pass = ossl_pem_passwd_value(pass); @@ -117,12 +124,12 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(in, pass); BIO_free(in); if (!pkey) - ossl_raise(eDSAError, "Neither PUB key nor PRIV key"); + ossl_raise(ePKeyError, "Neither PUB key nor PRIV key"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_DSA) { EVP_PKEY_free(pkey); - rb_raise(eDSAError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -133,7 +140,7 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_DSA(pkey, dsa) != 1) { EVP_PKEY_free(pkey); DSA_free(dsa); - ossl_raise(eDSAError, "EVP_PKEY_assign_DSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DSA"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -156,13 +163,13 @@ ossl_dsa_initialize_copy(VALUE self, VALUE other) (d2i_of_void *)d2i_DSAPrivateKey, (char *)dsa); if (!dsa_new) - ossl_raise(eDSAError, "ASN1_dup"); + ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DSA(pkey, dsa_new) != 1) { EVP_PKEY_free(pkey); DSA_free(dsa_new); - ossl_raise(eDSAError, "EVP_PKEY_assign_DSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DSA"); } RTYPEDDATA_DATA(self) = pkey; @@ -327,20 +334,6 @@ OSSL_PKEY_BN_DEF2(dsa, DSA, key, pub_key, priv_key) void Init_ossl_dsa(void) { -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - - /* Document-class: OpenSSL::PKey::DSAError - * - * Generic exception that is raised if an operation on a DSA PKey - * fails unexpectedly or in case an instantiation of an instance of DSA - * fails due to non-conformant input data. - */ - eDSAError = rb_define_class_under(mPKey, "DSAError", ePKeyError); - /* Document-class: OpenSSL::PKey::DSA * * DSA, the Digital Signature Algorithm, is specified in NIST's diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 1d20f63e0c..35f031819d 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -15,25 +15,27 @@ static const rb_data_type_t ossl_ec_point_type; #define GetPKeyEC(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A EC PKEY!"); \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A EC PKEY!"); \ } \ } while (0) #define GetEC(obj, key) do { \ EVP_PKEY *_pkey; \ GetPKeyEC(obj, _pkey); \ (key) = EVP_PKEY_get0_EC_KEY(_pkey); \ + if ((key) == NULL) \ + ossl_raise(ePKeyError, "failed to get EC_KEY from EVP_PKEY"); \ } while (0) #define GetECGroup(obj, group) do { \ TypedData_Get_Struct(obj, EC_GROUP, &ossl_ec_group_type, group); \ if ((group) == NULL) \ - ossl_raise(eEC_GROUP, "EC_GROUP is not initialized"); \ + ossl_raise(eEC_GROUP, "EC_GROUP is not initialized"); \ } while (0) #define GetECPoint(obj, point) do { \ TypedData_Get_Struct(obj, EC_POINT, &ossl_ec_point_type, point); \ if ((point) == NULL) \ - ossl_raise(eEC_POINT, "EC_POINT is not initialized"); \ + ossl_raise(eEC_POINT, "EC_POINT is not initialized"); \ } while (0) #define GetECPointGroup(obj, group) do { \ VALUE _group = rb_attr_get(obj, id_i_group); \ @@ -41,7 +43,6 @@ static const rb_data_type_t ossl_ec_point_type; } while (0) VALUE cEC; -static VALUE eECError; static VALUE cEC_GROUP; static VALUE eEC_GROUP; static VALUE cEC_POINT; @@ -65,27 +66,27 @@ ec_key_new_from_group(VALUE arg) EC_KEY *ec; if (rb_obj_is_kind_of(arg, cEC_GROUP)) { - EC_GROUP *group; + EC_GROUP *group; - GetECGroup(arg, group); - if (!(ec = EC_KEY_new())) - ossl_raise(eECError, NULL); + GetECGroup(arg, group); + if (!(ec = EC_KEY_new())) + ossl_raise(ePKeyError, NULL); - if (!EC_KEY_set_group(ec, group)) { - EC_KEY_free(ec); - ossl_raise(eECError, NULL); - } + if (!EC_KEY_set_group(ec, group)) { + EC_KEY_free(ec); + ossl_raise(ePKeyError, NULL); + } } else { - int nid = OBJ_sn2nid(StringValueCStr(arg)); + int nid = OBJ_sn2nid(StringValueCStr(arg)); - if (nid == NID_undef) - ossl_raise(eECError, "invalid curve name"); + if (nid == NID_undef) + ossl_raise(ePKeyError, "invalid curve name"); - if (!(ec = EC_KEY_new_by_curve_name(nid))) - ossl_raise(eECError, NULL); + if (!(ec = EC_KEY_new_by_curve_name(nid))) + ossl_raise(ePKeyError, NULL); - EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); - EC_KEY_set_conv_form(ec, POINT_CONVERSION_UNCOMPRESSED); + EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); + EC_KEY_set_conv_form(ec, POINT_CONVERSION_UNCOMPRESSED); } return ec; @@ -112,12 +113,12 @@ ossl_ec_key_s_generate(VALUE klass, VALUE arg) if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) { EVP_PKEY_free(pkey); EC_KEY_free(ec); - ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(obj) = pkey; if (!EC_KEY_generate_key(ec)) - ossl_raise(eECError, "EC_KEY_generate_key"); + ossl_raise(ePKeyError, "EC_KEY_generate_key"); return obj; } @@ -147,9 +148,14 @@ static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "02", &arg, &pass); if (NIL_P(arg)) { +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::EC.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else if (!(ec = EC_KEY_new())) - ossl_raise(eECError, "EC_KEY_new"); + ossl_raise(ePKeyError, "EC_KEY_new"); goto legacy; +#endif } else if (rb_obj_is_kind_of(arg, cEC_GROUP)) { ec = ec_key_new_from_group(arg); @@ -171,7 +177,7 @@ static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_EC) { EVP_PKEY_free(pkey); - rb_raise(eECError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -181,7 +187,7 @@ static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) { EVP_PKEY_free(pkey); EC_KEY_free(ec); - ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -202,12 +208,12 @@ ossl_ec_key_initialize_copy(VALUE self, VALUE other) ec_new = EC_KEY_dup(ec); if (!ec_new) - ossl_raise(eECError, "EC_KEY_dup"); + ossl_raise(ePKeyError, "EC_KEY_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec_new) != 1) { EC_KEY_free(ec_new); - ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(self) = pkey; @@ -231,7 +237,7 @@ ossl_ec_key_get_group(VALUE self) GetEC(self, ec); group = EC_KEY_get0_group(ec); if (!group) - return Qnil; + return Qnil; return ec_group_new(group); } @@ -246,7 +252,7 @@ ossl_ec_key_get_group(VALUE self) static VALUE ossl_ec_key_set_group(VALUE self, VALUE group_v) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; @@ -256,7 +262,7 @@ ossl_ec_key_set_group(VALUE self, VALUE group_v) GetECGroup(group_v, group); if (EC_KEY_set_group(ec, group) != 1) - ossl_raise(eECError, "EC_KEY_set_group"); + ossl_raise(ePKeyError, "EC_KEY_set_group"); return group_v; #endif @@ -288,7 +294,7 @@ static VALUE ossl_ec_key_get_private_key(VALUE self) */ static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; @@ -299,14 +305,14 @@ static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key) bn = GetBNPtr(private_key); switch (EC_KEY_set_private_key(ec, bn)) { - case 1: + case 1: break; - case 0: + case 0: if (bn == NULL) break; - /* fallthrough */ - default: - ossl_raise(eECError, "EC_KEY_set_private_key"); + /* fallthrough */ + default: + ossl_raise(ePKeyError, "EC_KEY_set_private_key"); } return private_key; @@ -339,7 +345,7 @@ static VALUE ossl_ec_key_get_public_key(VALUE self) */ static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; @@ -350,14 +356,14 @@ static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key) GetECPoint(public_key, point); switch (EC_KEY_set_public_key(ec, point)) { - case 1: + case 1: break; - case 0: + case 0: if (point == NULL) break; - /* fallthrough */ - default: - ossl_raise(eECError, "EC_KEY_set_public_key"); + /* fallthrough */ + default: + ossl_raise(ePKeyError, "EC_KEY_set_public_key"); } return public_key; @@ -461,7 +467,7 @@ ossl_ec_key_export(int argc, VALUE *argv, VALUE self) GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) - ossl_raise(eECError, "can't export - no public key set"); + ossl_raise(ePKeyError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(argc, argv, self, 0); else @@ -489,7 +495,7 @@ ossl_ec_key_to_der(VALUE self) GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) - ossl_raise(eECError, "can't export - no public key set"); + ossl_raise(ePKeyError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(0, NULL, self, 1); else @@ -511,14 +517,14 @@ ossl_ec_key_to_der(VALUE self) */ static VALUE ossl_ec_key_generate_key(VALUE self) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; GetEC(self, ec); if (EC_KEY_generate_key(ec) != 1) - ossl_raise(eECError, "EC_KEY_generate_key"); + ossl_raise(ePKeyError, "EC_KEY_generate_key"); return self; #endif @@ -543,18 +549,18 @@ static VALUE ossl_ec_key_check_key(VALUE self) GetEC(self, ec); pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!pctx) - ossl_raise(eECError, "EVP_PKEY_CTX_new"); + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EC_KEY_get0_private_key(ec) != NULL) { if (EVP_PKEY_check(pctx) != 1) { EVP_PKEY_CTX_free(pctx); - ossl_raise(eECError, "EVP_PKEY_check"); + ossl_raise(ePKeyError, "EVP_PKEY_check"); } } else { if (EVP_PKEY_public_check(pctx) != 1) { EVP_PKEY_CTX_free(pctx); - ossl_raise(eECError, "EVP_PKEY_public_check"); + ossl_raise(ePKeyError, "EVP_PKEY_public_check"); } } @@ -564,7 +570,7 @@ static VALUE ossl_ec_key_check_key(VALUE self) GetEC(self, ec); if (EC_KEY_check_key(ec) != 1) - ossl_raise(eECError, "EC_KEY_check_key"); + ossl_raise(ePKeyError, "EC_KEY_check_key"); #endif return Qtrue; @@ -582,7 +588,7 @@ ossl_ec_group_free(void *ptr) static const rb_data_type_t ossl_ec_group_type = { "OpenSSL/ec_group", { - 0, ossl_ec_group_free, + 0, ossl_ec_group_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -602,7 +608,7 @@ ec_group_new(const EC_GROUP *group) obj = ossl_ec_group_alloc(cEC_GROUP); group_new = EC_GROUP_dup(group); if (!group_new) - ossl_raise(eEC_GROUP, "EC_GROUP_dup"); + ossl_raise(eEC_GROUP, "EC_GROUP_dup"); RTYPEDDATA_DATA(obj) = group_new; return obj; @@ -630,7 +636,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) ossl_raise(rb_eRuntimeError, "EC_GROUP is already initialized"); switch (rb_scan_args(argc, argv, "13", &arg1, &arg2, &arg3, &arg4)) { - case 1: + case 1: if (rb_obj_is_kind_of(arg1, cEC_GROUP)) { const EC_GROUP *arg1_group; @@ -642,7 +648,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) group = PEM_read_bio_ECPKParameters(in, NULL, NULL, NULL); if (!group) { - OSSL_BIO_reset(in); + OSSL_BIO_reset(in); group = d2i_ECPKParameters_bio(in, NULL); } @@ -652,7 +658,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) const char *name = StringValueCStr(arg1); int nid = OBJ_sn2nid(name); - ossl_clear_error(); /* ignore errors in d2i_ECPKParameters_bio() */ + ossl_clear_error(); /* ignore errors in d2i_ECPKParameters_bio() */ if (nid == NID_undef) ossl_raise(eEC_GROUP, "unknown curve name (%"PRIsVALUE")", arg1); #if !defined(OPENSSL_IS_AWSLC) @@ -669,7 +675,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) } break; - case 4: + case 4: if (SYMBOL_P(arg1)) { EC_GROUP *(*new_curve)(const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; const BIGNUM *p = GetBNPtr(arg2); @@ -691,12 +697,12 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) if ((group = new_curve(p, a, b, ossl_bn_ctx)) == NULL) ossl_raise(eEC_GROUP, "EC_GROUP_new_by_GF*"); } else { - ossl_raise(rb_eArgError, "unknown argument, must be :GFp or :GF2m"); + ossl_raise(rb_eArgError, "unknown argument, must be :GFp or :GF2m"); } break; - default: - ossl_raise(rb_eArgError, "wrong number of arguments"); + default: + ossl_raise(rb_eArgError, "wrong number of arguments (given %d, expected 1 or 4)", argc); } ASSUME(group); @@ -713,12 +719,12 @@ ossl_ec_group_initialize_copy(VALUE self, VALUE other) TypedData_Get_Struct(self, EC_GROUP, &ossl_ec_group_type, group_new); if (group_new) - ossl_raise(eEC_GROUP, "EC::Group already initialized"); + ossl_raise(eEC_GROUP, "EC::Group already initialized"); GetECGroup(other, group); group_new = EC_GROUP_dup(group); if (!group_new) - ossl_raise(eEC_GROUP, "EC_GROUP_dup"); + ossl_raise(eEC_GROUP, "EC_GROUP_dup"); RTYPEDDATA_DATA(self) = group_new; return self; @@ -740,9 +746,9 @@ static VALUE ossl_ec_group_eql(VALUE a, VALUE b) GetECGroup(b, group2); switch (EC_GROUP_cmp(group1, group2, ossl_bn_ctx)) { - case 0: return Qtrue; - case 1: return Qfalse; - default: ossl_raise(eEC_GROUP, "EC_GROUP_cmp"); + case 0: return Qtrue; + case 1: return Qfalse; + default: ossl_raise(eEC_GROUP, "EC_GROUP_cmp"); } } @@ -762,7 +768,7 @@ static VALUE ossl_ec_group_get_generator(VALUE self) GetECGroup(self, group); generator = EC_GROUP_get0_generator(group); if (!generator) - return Qnil; + return Qnil; return ec_point_new(generator, group); } @@ -843,25 +849,23 @@ static VALUE ossl_ec_group_get_cofactor(VALUE self) /* * call-seq: - * group.curve_name => String + * group.curve_name -> string or nil * - * Returns the curve name (sn). + * Returns the curve name (short name) corresponding to this group, or +nil+ + * if \OpenSSL does not have an OID associated with the group. * * See the OpenSSL documentation for EC_GROUP_get_curve_name() */ static VALUE ossl_ec_group_get_curve_name(VALUE self) { - EC_GROUP *group = NULL; + EC_GROUP *group; int nid; GetECGroup(self, group); - if (group == NULL) - return Qnil; - nid = EC_GROUP_get_curve_name(group); - -/* BUG: an nid or asn1 object should be returned, maybe. */ - return rb_str_new2(OBJ_nid2sn(nid)); + if (nid == NID_undef) + return Qnil; + return rb_str_new_cstr(OBJ_nid2sn(nid)); } /* @@ -977,11 +981,11 @@ static point_conversion_form_t parse_point_conversion_form_symbol(VALUE sym) { if (sym == sym_uncompressed) - return POINT_CONVERSION_UNCOMPRESSED; + return POINT_CONVERSION_UNCOMPRESSED; if (sym == sym_compressed) - return POINT_CONVERSION_COMPRESSED; + return POINT_CONVERSION_COMPRESSED; if (sym == sym_hybrid) - return POINT_CONVERSION_HYBRID; + return POINT_CONVERSION_HYBRID; ossl_raise(rb_eArgError, "unsupported point conversion form %+"PRIsVALUE " (expected :compressed, :uncompressed, or :hybrid)", sym); } @@ -1088,20 +1092,20 @@ static VALUE ossl_ec_group_to_string(VALUE self, int format) ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); switch(format) { - case EXPORT_PEM: + case EXPORT_PEM: i = PEM_write_bio_ECPKParameters(out, group); - break; - case EXPORT_DER: + break; + case EXPORT_DER: i = i2d_ECPKParameters_bio(out, group); - break; - default: + break; + default: BIO_free(out); - ossl_raise(rb_eRuntimeError, "unknown format (internal error)"); + ossl_raise(rb_eRuntimeError, "unknown format (internal error)"); } if (i != 1) { BIO_free(out); - ossl_raise(eECError, NULL); + ossl_raise(ePKeyError, NULL); } str = ossl_membio2str(out); @@ -1145,11 +1149,11 @@ static VALUE ossl_ec_group_to_text(VALUE self) GetECGroup(self, group); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); + ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); } if (!ECPKParameters_print(out, group, 0)) { - BIO_free(out); - ossl_raise(eEC_GROUP, NULL); + BIO_free(out); + ossl_raise(eEC_GROUP, NULL); } str = ossl_membio2str(out); @@ -1169,7 +1173,7 @@ ossl_ec_point_free(void *ptr) static const rb_data_type_t ossl_ec_point_type = { "OpenSSL/EC_POINT", { - 0, ossl_ec_point_free, + 0, ossl_ec_point_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -1189,7 +1193,7 @@ ec_point_new(const EC_POINT *point, const EC_GROUP *group) obj = ossl_ec_point_alloc(cEC_POINT); point_new = EC_POINT_dup(point, group); if (!point_new) - ossl_raise(eEC_POINT, "EC_POINT_dup"); + ossl_raise(eEC_POINT, "EC_POINT_dup"); RTYPEDDATA_DATA(obj) = point_new; rb_ivar_set(obj, id_i_group, ec_group_new(group)); @@ -1217,39 +1221,39 @@ static VALUE ossl_ec_point_initialize(int argc, VALUE *argv, VALUE self) TypedData_Get_Struct(self, EC_POINT, &ossl_ec_point_type, point); if (point) - rb_raise(eEC_POINT, "EC_POINT already initialized"); + rb_raise(eEC_POINT, "EC_POINT already initialized"); rb_scan_args(argc, argv, "11", &group_v, &arg2); if (rb_obj_is_kind_of(group_v, cEC_POINT)) { - if (argc != 1) - rb_raise(rb_eArgError, "invalid second argument"); - return ossl_ec_point_initialize_copy(self, group_v); + if (argc != 1) + rb_raise(rb_eArgError, "invalid second argument"); + return ossl_ec_point_initialize_copy(self, group_v); } GetECGroup(group_v, group); if (argc == 1) { - point = EC_POINT_new(group); - if (!point) - ossl_raise(eEC_POINT, "EC_POINT_new"); + point = EC_POINT_new(group); + if (!point) + ossl_raise(eEC_POINT, "EC_POINT_new"); } else { - if (rb_obj_is_kind_of(arg2, cBN)) { - point = EC_POINT_bn2point(group, GetBNPtr(arg2), NULL, ossl_bn_ctx); - if (!point) - ossl_raise(eEC_POINT, "EC_POINT_bn2point"); - } - else { - StringValue(arg2); - point = EC_POINT_new(group); - if (!point) - ossl_raise(eEC_POINT, "EC_POINT_new"); - if (!EC_POINT_oct2point(group, point, - (unsigned char *)RSTRING_PTR(arg2), - RSTRING_LEN(arg2), ossl_bn_ctx)) { - EC_POINT_free(point); - ossl_raise(eEC_POINT, "EC_POINT_oct2point"); - } - } + if (rb_obj_is_kind_of(arg2, cBN)) { + point = EC_POINT_bn2point(group, GetBNPtr(arg2), NULL, ossl_bn_ctx); + if (!point) + ossl_raise(eEC_POINT, "EC_POINT_bn2point"); + } + else { + StringValue(arg2); + point = EC_POINT_new(group); + if (!point) + ossl_raise(eEC_POINT, "EC_POINT_new"); + if (!EC_POINT_oct2point(group, point, + (unsigned char *)RSTRING_PTR(arg2), + RSTRING_LEN(arg2), ossl_bn_ctx)) { + EC_POINT_free(point); + ossl_raise(eEC_POINT, "EC_POINT_oct2point"); + } + } } RTYPEDDATA_DATA(self) = point; @@ -1268,7 +1272,7 @@ ossl_ec_point_initialize_copy(VALUE self, VALUE other) TypedData_Get_Struct(self, EC_POINT, &ossl_ec_point_type, point_new); if (point_new) - ossl_raise(eEC_POINT, "EC::Point already initialized"); + ossl_raise(eEC_POINT, "EC::Point already initialized"); GetECPoint(other, point); group_v = rb_obj_dup(rb_attr_get(other, id_i_group)); @@ -1276,7 +1280,7 @@ ossl_ec_point_initialize_copy(VALUE self, VALUE other) point_new = EC_POINT_dup(point, group); if (!point_new) - ossl_raise(eEC_POINT, "EC_POINT_dup"); + ossl_raise(eEC_POINT, "EC_POINT_dup"); RTYPEDDATA_DATA(self) = point_new; rb_ivar_set(self, id_i_group, group_v); @@ -1303,9 +1307,9 @@ static VALUE ossl_ec_point_eql(VALUE a, VALUE b) GetECGroup(group_v1, group); switch (EC_POINT_cmp(group, point1, point2, ossl_bn_ctx)) { - case 0: return Qtrue; - case 1: return Qfalse; - default: ossl_raise(eEC_POINT, "EC_POINT_cmp"); + case 0: return Qtrue; + case 1: return Qfalse; + default: ossl_raise(eEC_POINT, "EC_POINT_cmp"); } UNREACHABLE; @@ -1324,9 +1328,9 @@ static VALUE ossl_ec_point_is_at_infinity(VALUE self) GetECPointGroup(self, group); switch (EC_POINT_is_at_infinity(group, point)) { - case 1: return Qtrue; - case 0: return Qfalse; - default: ossl_raise(eEC_POINT, "EC_POINT_is_at_infinity"); + case 1: return Qtrue; + case 0: return Qfalse; + default: ossl_raise(eEC_POINT, "EC_POINT_is_at_infinity"); } UNREACHABLE; @@ -1345,9 +1349,9 @@ static VALUE ossl_ec_point_is_on_curve(VALUE self) GetECPointGroup(self, group); switch (EC_POINT_is_on_curve(group, point, ossl_bn_ctx)) { - case 1: return Qtrue; - case 0: return Qfalse; - default: ossl_raise(eEC_POINT, "EC_POINT_is_on_curve"); + case 1: return Qtrue; + case 0: return Qfalse; + default: ossl_raise(eEC_POINT, "EC_POINT_is_on_curve"); } UNREACHABLE; @@ -1368,7 +1372,7 @@ static VALUE ossl_ec_point_make_affine(VALUE self) GetECPointGroup(self, group); rb_warn("OpenSSL::PKey::EC::Point#make_affine! is deprecated"); -#if !OSSL_OPENSSL_PREREQ(3, 0, 0) && !defined(OPENSSL_IS_AWSLC) +#if !defined(OSSL_HAVE_IMMUTABLE_PKEY) && !defined(OPENSSL_IS_AWSLC) if (EC_POINT_make_affine(group, point, ossl_bn_ctx) != 1) ossl_raise(eEC_POINT, "EC_POINT_make_affine"); #endif @@ -1439,12 +1443,12 @@ ossl_ec_point_to_octet_string(VALUE self, VALUE conversion_form) len = EC_POINT_point2oct(group, point, form, NULL, 0, ossl_bn_ctx); if (!len) - ossl_raise(eEC_POINT, "EC_POINT_point2oct"); + ossl_raise(eEC_POINT, "EC_POINT_point2oct"); str = rb_str_new(NULL, (long)len); if (!EC_POINT_point2oct(group, point, form, - (unsigned char *)RSTRING_PTR(str), len, - ossl_bn_ctx)) - ossl_raise(eEC_POINT, "EC_POINT_point2oct"); + (unsigned char *)RSTRING_PTR(str), len, + ossl_bn_ctx)) + ossl_raise(eEC_POINT, "EC_POINT_point2oct"); return str; } @@ -1522,15 +1526,6 @@ static VALUE ossl_ec_point_mul(int argc, VALUE *argv, VALUE self) void Init_ossl_ec(void) { #undef rb_intern -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - - eECError = rb_define_class_under(mPKey, "ECError", ePKeyError); - /* * Document-class: OpenSSL::PKey::EC * diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 185b47cbfb..039b2c6a34 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -14,13 +14,15 @@ #define GetPKeyRSA(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { /* PARANOIA? */ \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A RSA!") ; \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A RSA!") ; \ } \ } while (0) #define GetRSA(obj, rsa) do { \ EVP_PKEY *_pkey; \ GetPKeyRSA((obj), _pkey); \ (rsa) = EVP_PKEY_get0_RSA(_pkey); \ + if ((rsa) == NULL) \ + ossl_raise(ePKeyError, "failed to get RSA from EVP_PKEY"); \ } while (0) static inline int @@ -42,7 +44,6 @@ RSA_PRIVATE(VALUE obj, OSSL_3_const RSA *rsa) * Classes */ VALUE cRSA; -static VALUE eRSAError; /* * Private @@ -59,6 +60,7 @@ static VALUE eRSAError; * If called without arguments, creates a new instance with no key components * set. They can be set individually by #set_key, #set_factors, and * #set_crt_params. + * This form is not compatible with OpenSSL 3.0 or later. * * If called with a String, tries to parse as DER or PEM encoding of an \RSA key. * Note that if _password_ is not specified, but the key is encrypted with a @@ -89,10 +91,15 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) /* The RSA.new(size, generator) form is handled by lib/openssl/pkey.rb */ rb_scan_args(argc, argv, "02", &arg, &pass); if (argc == 0) { - rsa = RSA_new(); +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::RSA.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else + rsa = RSA_new(); if (!rsa) - ossl_raise(eRSAError, "RSA_new"); + ossl_raise(ePKeyError, "RSA_new"); goto legacy; +#endif } pass = ossl_pem_passwd_value(pass); @@ -113,12 +120,12 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(in, pass); BIO_free(in); if (!pkey) - ossl_raise(eRSAError, "Neither PUB key nor PRIV key"); + ossl_raise(ePKeyError, "Neither PUB key nor PRIV key"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_RSA) { EVP_PKEY_free(pkey); - rb_raise(eRSAError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -129,7 +136,7 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa) != 1) { EVP_PKEY_free(pkey); RSA_free(rsa); - ossl_raise(eRSAError, "EVP_PKEY_assign_RSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_RSA"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -152,12 +159,12 @@ ossl_rsa_initialize_copy(VALUE self, VALUE other) (d2i_of_void *)d2i_RSAPrivateKey, (char *)rsa); if (!rsa_new) - ossl_raise(eRSAError, "ASN1_dup"); + ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa_new) != 1) { RSA_free(rsa_new); - ossl_raise(eRSAError, "EVP_PKEY_assign_RSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_RSA"); } RTYPEDDATA_DATA(self) = pkey; @@ -312,7 +319,7 @@ ossl_rsa_to_der(VALUE self) * Signs _data_ using the Probabilistic Signature Scheme (RSA-PSS) and returns * the calculated signature. * - * RSAError will be raised if an error occurs. + * PKeyError will be raised if an error occurs. * * See #verify_pss for the verification operation. * @@ -341,7 +348,7 @@ ossl_rsa_to_der(VALUE self) static VALUE ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) { - VALUE digest, data, options, kwargs[2], signature; + VALUE digest, data, options, kwargs[2], signature, mgf1md_holder, md_holder; static ID kwargs_ids[2]; EVP_PKEY *pkey; EVP_PKEY_CTX *pkey_ctx; @@ -351,46 +358,46 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) int salt_len; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt_length"); - kwargs_ids[1] = rb_intern_const("mgf1_hash"); + kwargs_ids[0] = rb_intern_const("salt_length"); + kwargs_ids[1] = rb_intern_const("mgf1_hash"); } rb_scan_args(argc, argv, "2:", &digest, &data, &options); rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); if (kwargs[0] == ID2SYM(rb_intern("max"))) - salt_len = -2; /* RSA_PSS_SALTLEN_MAX_SIGN */ + salt_len = -2; /* RSA_PSS_SALTLEN_MAX_SIGN */ else if (kwargs[0] == ID2SYM(rb_intern("digest"))) - salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ + salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else - salt_len = NUM2INT(kwargs[0]); - mgf1md = ossl_evp_get_digestbyname(kwargs[1]); + salt_len = NUM2INT(kwargs[0]); + mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); pkey = GetPrivPKeyPtr(self); buf_len = EVP_PKEY_size(pkey); - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); signature = rb_str_new(NULL, (long)buf_len); md_ctx = EVP_MD_CTX_new(); if (!md_ctx) - goto err; + goto err; if (EVP_DigestSignInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) - goto err; + goto err; if (EVP_DigestSignUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) - goto err; + goto err; if (EVP_DigestSignFinal(md_ctx, (unsigned char *)RSTRING_PTR(signature), &buf_len) != 1) - goto err; + goto err; rb_str_set_len(signature, (long)buf_len); @@ -399,7 +406,7 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) err: EVP_MD_CTX_free(md_ctx); - ossl_raise(eRSAError, NULL); + ossl_raise(ePKeyError, NULL); } /* @@ -409,7 +416,7 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) * Verifies _data_ using the Probabilistic Signature Scheme (RSA-PSS). * * The return value is +true+ if the signature is valid, +false+ otherwise. - * RSAError will be raised if an error occurs. + * PKeyError will be raised if an error occurs. * * See #sign_pss for the signing operation and an example code. * @@ -428,7 +435,7 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) static VALUE ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) { - VALUE digest, signature, data, options, kwargs[2]; + VALUE digest, signature, data, options, kwargs[2], mgf1md_holder, md_holder; static ID kwargs_ids[2]; EVP_PKEY *pkey; EVP_PKEY_CTX *pkey_ctx; @@ -437,62 +444,61 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) int result, salt_len; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt_length"); - kwargs_ids[1] = rb_intern_const("mgf1_hash"); + kwargs_ids[0] = rb_intern_const("salt_length"); + kwargs_ids[1] = rb_intern_const("mgf1_hash"); } rb_scan_args(argc, argv, "3:", &digest, &signature, &data, &options); rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); if (kwargs[0] == ID2SYM(rb_intern("auto"))) - salt_len = -2; /* RSA_PSS_SALTLEN_AUTO */ + salt_len = -2; /* RSA_PSS_SALTLEN_AUTO */ else if (kwargs[0] == ID2SYM(rb_intern("digest"))) - salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ + salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else - salt_len = NUM2INT(kwargs[0]); - mgf1md = ossl_evp_get_digestbyname(kwargs[1]); + salt_len = NUM2INT(kwargs[0]); + mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); GetPKey(self, pkey); - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(signature); StringValue(data); md_ctx = EVP_MD_CTX_new(); if (!md_ctx) - goto err; + goto err; if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) - goto err; + goto err; if (EVP_DigestVerifyUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) - goto err; + goto err; result = EVP_DigestVerifyFinal(md_ctx, - (unsigned char *)RSTRING_PTR(signature), - RSTRING_LEN(signature)); + (unsigned char *)RSTRING_PTR(signature), + RSTRING_LEN(signature)); + EVP_MD_CTX_free(md_ctx); switch (result) { case 0: - ossl_clear_error(); - EVP_MD_CTX_free(md_ctx); - return Qfalse; + ossl_clear_error(); + return Qfalse; case 1: - EVP_MD_CTX_free(md_ctx); - return Qtrue; + return Qtrue; default: - goto err; + ossl_raise(ePKeyError, "EVP_DigestVerifyFinal"); } err: EVP_MD_CTX_free(md_ctx); - ossl_raise(eRSAError, NULL); + ossl_raise(ePKeyError, NULL); } /* @@ -530,20 +536,6 @@ OSSL_PKEY_BN_DEF3(rsa, RSA, crt_params, dmp1, dmq1, iqmp) void Init_ossl_rsa(void) { -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - - /* Document-class: OpenSSL::PKey::RSAError - * - * Generic exception that is raised if an operation on an RSA PKey - * fails unexpectedly or in case an instantiation of an instance of RSA - * fails due to non-conformant input data. - */ - eRSAError = rb_define_class_under(mPKey, "RSAError", ePKeyError); - /* Document-class: OpenSSL::PKey::RSA * * RSA is an asymmetric public key algorithm that has been formalized in diff --git a/ext/openssl/ossl_provider.c b/ext/openssl/ossl_provider.c index 529a5e1c72..ea5abb8e48 100644 --- a/ext/openssl/ossl_provider.c +++ b/ext/openssl/ossl_provider.c @@ -185,11 +185,6 @@ ossl_provider_inspect(VALUE self) void Init_ossl_provider(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - cProvider = rb_define_class_under(mOSSL, "Provider", rb_cObject); eProviderError = rb_define_class_under(cProvider, "ProviderError", eOSSLError); diff --git a/ext/openssl/ossl_rand.c b/ext/openssl/ossl_rand.c index 764900dfc6..753f8b25f7 100644 --- a/ext/openssl/ossl_rand.c +++ b/ext/openssl/ossl_rand.c @@ -68,7 +68,7 @@ static VALUE ossl_rand_load_file(VALUE self, VALUE filename) { if(!RAND_load_file(StringValueCStr(filename), -1)) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } @@ -85,14 +85,14 @@ static VALUE ossl_rand_write_file(VALUE self, VALUE filename) { if (RAND_write_file(StringValueCStr(filename)) == -1) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } /* * call-seq: - * random_bytes(length) -> string + * random_bytes(length) -> string * * Generates a String with _length_ number of cryptographically strong * pseudo-random bytes. @@ -112,9 +112,9 @@ ossl_rand_bytes(VALUE self, VALUE len) str = rb_str_new(0, n); ret = RAND_bytes((unsigned char *)RSTRING_PTR(str), n); if (ret == 0) { - ossl_raise(eRandomError, "RAND_bytes"); + ossl_raise(eRandomError, "RAND_bytes"); } else if (ret == -1) { - ossl_raise(eRandomError, "RAND_bytes is not supported"); + ossl_raise(eRandomError, "RAND_bytes is not supported"); } return str; @@ -131,7 +131,7 @@ static VALUE ossl_rand_egd(VALUE self, VALUE filename) { if (RAND_egd(StringValueCStr(filename)) == -1) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } @@ -151,7 +151,7 @@ ossl_rand_egd_bytes(VALUE self, VALUE filename, VALUE len) int n = NUM2INT(len); if (RAND_egd_bytes(StringValueCStr(filename), n) == -1) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } @@ -175,11 +175,6 @@ ossl_rand_status(VALUE self) void Init_ossl_rand(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - mRandom = rb_define_module_under(mOSSL, "Random"); eRandomError = rb_define_class_under(mRandom, "RandomError", eOSSLError); diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 29564a8139..3d913a3968 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -25,7 +25,7 @@ #endif #define GetSSLCTX(obj, ctx) do { \ - TypedData_Get_Struct((obj), SSL_CTX, &ossl_sslctx_type, (ctx)); \ + TypedData_Get_Struct((obj), SSL_CTX, &ossl_sslctx_type, (ctx)); \ } while (0) VALUE mSSL; @@ -36,19 +36,18 @@ VALUE cSSLSocket; static VALUE eSSLErrorWaitReadable; static VALUE eSSLErrorWaitWritable; -static ID id_call, ID_callback_state, id_tmp_dh_callback, - id_npn_protocols_encoded, id_each; +static ID id_call, ID_callback_state, id_npn_protocols_encoded, id_each; static VALUE sym_exception, sym_wait_readable, sym_wait_writable; static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, - id_i_verify_depth, id_i_verify_callback, id_i_client_ca, - id_i_renegotiation_cb, id_i_cert, id_i_key, id_i_extra_chain_cert, - id_i_client_cert_cb, id_i_timeout, - id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb, - id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, - id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, - id_i_verify_hostname, id_i_keylog_cb; -static ID id_i_io, id_i_context, id_i_hostname; + id_i_verify_depth, id_i_verify_callback, id_i_client_ca, + id_i_renegotiation_cb, id_i_cert, id_i_key, id_i_extra_chain_cert, + id_i_client_cert_cb, id_i_timeout, + id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb, + id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, + id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, + id_i_verify_hostname, id_i_keylog_cb, id_i_tmp_dh_callback; +static ID id_i_io, id_i_context, id_i_hostname, id_i_sync_close; static int ossl_ssl_ex_ptr_idx; static int ossl_sslctx_ex_ptr_idx; @@ -79,9 +78,9 @@ ossl_sslctx_s_alloc(VALUE klass) { SSL_CTX *ctx; long mode = 0 | - SSL_MODE_ENABLE_PARTIAL_WRITE | - SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | - SSL_MODE_RELEASE_BUFFERS; + SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | + SSL_MODE_RELEASE_BUFFERS; VALUE obj; obj = TypedData_Wrap_Struct(klass, &ossl_sslctx_type, 0); @@ -90,8 +89,10 @@ ossl_sslctx_s_alloc(VALUE klass) ossl_raise(eSSLError, "SSL_CTX_new"); } SSL_CTX_set_mode(ctx, mode); + SSL_CTX_set_dh_auto(ctx, 1); RTYPEDDATA_DATA(obj) = ctx; - SSL_CTX_set_ex_data(ctx, ossl_sslctx_ex_ptr_idx, (void *)obj); + if (!SSL_CTX_set_ex_data(ctx, ossl_sslctx_ex_ptr_idx, (void *)obj)) + ossl_raise(eSSLError, "SSL_CTX_set_ex_data"); return obj; } @@ -104,7 +105,7 @@ ossl_call_client_cert_cb(VALUE obj) ctx_obj = rb_attr_get(obj, id_i_context); cb = rb_attr_get(ctx_obj, id_i_client_cert_cb); if (NIL_P(cb)) - return Qnil; + return Qnil; ary = rb_funcallv(cb, id_call, 1, &obj); Check_Type(ary, T_ARRAY); @@ -122,7 +123,7 @@ ossl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); ret = rb_protect(ossl_call_client_cert_cb, obj, NULL); if (NIL_P(ret)) - return 0; + return 0; *x509 = DupX509CertPtr(RARRAY_AREF(ret, 0)); *pkey = DupPKeyPtr(RARRAY_AREF(ret, 1)); @@ -133,8 +134,6 @@ ossl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) #if !defined(OPENSSL_NO_DH) struct tmp_dh_callback_args { VALUE ssl_obj; - ID id; - int type; int is_export; int keylength; }; @@ -143,48 +142,36 @@ static VALUE ossl_call_tmp_dh_callback(VALUE arg) { struct tmp_dh_callback_args *args = (struct tmp_dh_callback_args *)arg; - VALUE cb, dh; - EVP_PKEY *pkey; + VALUE ctx_obj, cb, obj; + const DH *dh; - cb = rb_funcall(args->ssl_obj, args->id, 0); + ctx_obj = rb_attr_get(args->ssl_obj, id_i_context); + cb = rb_attr_get(ctx_obj, id_i_tmp_dh_callback); if (NIL_P(cb)) - return (VALUE)NULL; - dh = rb_funcall(cb, id_call, 3, args->ssl_obj, INT2NUM(args->is_export), - INT2NUM(args->keylength)); - pkey = GetPKeyPtr(dh); - if (EVP_PKEY_base_id(pkey) != args->type) - return (VALUE)NULL; + return (VALUE)NULL; - return (VALUE)pkey; + obj = rb_funcall(cb, id_call, 3, args->ssl_obj, INT2NUM(args->is_export), + INT2NUM(args->keylength)); + // TODO: We should riase if obj is not DH + dh = EVP_PKEY_get0_DH(GetPKeyPtr(obj)); + if (!dh) + ossl_clear_error(); + + return (VALUE)dh; } -#endif -#if !defined(OPENSSL_NO_DH) static DH * ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength) { - VALUE rb_ssl; - EVP_PKEY *pkey; - struct tmp_dh_callback_args args; int state; - - rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); - args.ssl_obj = rb_ssl; - args.id = id_tmp_dh_callback; - args.is_export = is_export; - args.keylength = keylength; - args.type = EVP_PKEY_DH; - - pkey = (EVP_PKEY *)rb_protect(ossl_call_tmp_dh_callback, - (VALUE)&args, &state); + VALUE rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + struct tmp_dh_callback_args args = {rb_ssl, is_export, keylength}; + VALUE ret = rb_protect(ossl_call_tmp_dh_callback, (VALUE)&args, &state); if (state) { - rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state)); - return NULL; + rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state)); + return NULL; } - if (!pkey) - return NULL; - - return (DH *)EVP_PKEY_get0_DH(pkey); + return (DH *)ret; } #endif /* OPENSSL_NO_DH */ @@ -200,13 +187,13 @@ call_verify_certificate_identity(VALUE ctx_v) hostname = rb_attr_get(ssl_obj, id_i_hostname); if (!RTEST(hostname)) { - rb_warning("verify_hostname requires hostname to be set"); - return Qtrue; + rb_warning("verify_hostname requires hostname to be set"); + return Qtrue; } cert_obj = ossl_x509_new(X509_STORE_CTX_get_current_cert(ctx)); return rb_funcall(mSSL, rb_intern("verify_certificate_identity"), 2, - cert_obj, hostname); + cert_obj, hostname); } static int @@ -223,12 +210,12 @@ ossl_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) verify_hostname = rb_attr_get(sslctx_obj, id_i_verify_hostname); if (preverify_ok && RTEST(verify_hostname) && !SSL_is_server(ssl) && - !X509_STORE_CTX_get_error_depth(ctx)) { - ret = rb_protect(call_verify_certificate_identity, (VALUE)ctx, &status); - if (status) { - rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); - return 0; - } + !X509_STORE_CTX_get_error_depth(ctx)) { + ret = rb_protect(call_verify_certificate_identity, (VALUE)ctx, &status); + if (status) { + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); + return 0; + } if (ret != Qtrue) { preverify_ok = 0; X509_STORE_CTX_set_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH); @@ -399,7 +386,7 @@ ossl_sslctx_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) * when SSL_CTX_free() is called. */ if (rb_during_gc()) - return; + return; OSSL_Debug("SSL SESSION remove callback entered"); @@ -462,8 +449,8 @@ ossl_call_servername_cb(VALUE arg) ossl_raise(eSSLError, "SSL_set_SSL_CTX"); rb_ivar_set(ssl_obj, id_i_context, ret_obj); } else if (!NIL_P(ret_obj)) { - ossl_raise(rb_eArgError, "servername_cb must return an " - "OpenSSL::SSL::SSLContext object or nil"); + ossl_raise(rb_eArgError, "servername_cb must return an " + "OpenSSL::SSL::SSLContext object or nil"); } return Qnil; @@ -503,7 +490,7 @@ ssl_npn_encode_protocol_i(RB_BLOCK_CALL_FUNC_ARGLIST(cur, encoded)) int len = RSTRING_LENINT(cur); char len_byte; if (len < 1 || len > 255) - ossl_raise(eSSLError, "Advertised protocol must have length 1..255"); + ossl_raise(eSSLError, "Advertised protocol must have length 1..255"); /* Encode the length byte */ len_byte = len; rb_str_buf_cat(encoded, &len_byte, 1); @@ -537,16 +524,16 @@ npn_select_cb_common_i(VALUE tmp) /* assume OpenSSL verifies this format */ /* The format is len_1|proto_1|...|len_n|proto_n */ while (in < in_end) { - l = *in++; - rb_ary_push(protocols, rb_str_new((const char *)in, l)); - in += l; + l = *in++; + rb_ary_push(protocols, rb_str_new((const char *)in, l)); + in += l; } selected = rb_funcallv(args->cb, id_call, 1, &protocols); StringValue(selected); len = RSTRING_LEN(selected); if (len < 1 || len >= 256) { - ossl_raise(eSSLError, "Selected protocol name must have length 1..255"); + ossl_raise(eSSLError, "Selected protocol name must have length 1..255"); } return selected; @@ -554,8 +541,8 @@ npn_select_cb_common_i(VALUE tmp) static int ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen) + unsigned char *outlen, const unsigned char *in, + unsigned int inlen) { VALUE selected; int status; @@ -567,10 +554,10 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, selected = rb_protect(npn_select_cb_common_i, (VALUE)&args, &status); if (status) { - VALUE ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + VALUE ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); - rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); - return SSL_TLSEXT_ERR_ALERT_FATAL; + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); + return SSL_TLSEXT_ERR_ALERT_FATAL; } *out = (unsigned char *)RSTRING_PTR(selected); @@ -582,7 +569,7 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, #ifdef OSSL_USE_NEXTPROTONEG static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, - void *arg) + void *arg) { VALUE protocols = rb_attr_get((VALUE)arg, id_npn_protocols_encoded); @@ -594,7 +581,7 @@ ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, static int ssl_npn_select_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, void *arg) + const unsigned char *in, unsigned int inlen, void *arg) { VALUE sslctx_obj, cb; @@ -602,13 +589,13 @@ ssl_npn_select_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, cb = rb_attr_get(sslctx_obj, id_i_npn_select_cb); return ssl_npn_select_cb_common(ssl, cb, (const unsigned char **)out, - outlen, in, inlen); + outlen, in, inlen); } #endif static int ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, void *arg) + const unsigned char *in, unsigned int inlen, void *arg) { VALUE sslctx_obj, cb; @@ -625,7 +612,7 @@ ssl_info_cb(const SSL *ssl, int where, int val) int is_server = SSL_is_server((SSL *)ssl); if (is_server && where & SSL_CB_HANDSHAKE_START) { - ssl_renegotiation_cb(ssl); + ssl_renegotiation_cb(ssl); } } @@ -671,9 +658,9 @@ ossl_sslctx_set_options(VALUE self, VALUE options) SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx)); if (NIL_P(options)) { - SSL_CTX_set_options(ctx, SSL_OP_ALL); + SSL_CTX_set_options(ctx, SSL_OP_ALL); } else { - SSL_CTX_set_options(ctx, NUM2ULONG(options)); + SSL_CTX_set_options(ctx, NUM2ULONG(options)); } return self; @@ -703,7 +690,10 @@ ossl_sslctx_setup(VALUE self) GetSSLCTX(self, ctx); #if !defined(OPENSSL_NO_DH) - SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback); + if (!NIL_P(rb_attr_get(self, id_i_tmp_dh_callback))) { + SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback); + SSL_CTX_set_dh_auto(ctx, 0); + } #endif #if !defined(OPENSSL_IS_AWSLC) /* AWS-LC has no support for TLS 1.3 PHA. */ @@ -712,14 +702,14 @@ ossl_sslctx_setup(VALUE self) val = rb_attr_get(self, id_i_cert_store); if (!NIL_P(val)) { - X509_STORE *store = GetX509StorePtr(val); /* NO NEED TO DUP */ - SSL_CTX_set_cert_store(ctx, store); - X509_STORE_up_ref(store); + X509_STORE *store = GetX509StorePtr(val); /* NO NEED TO DUP */ + SSL_CTX_set_cert_store(ctx, store); + X509_STORE_up_ref(store); } val = rb_attr_get(self, id_i_extra_chain_cert); if(!NIL_P(val)){ - rb_block_call(val, rb_intern("each"), 0, 0, ossl_sslctx_add_extra_chain_cert_i, self); + rb_block_call(val, rb_intern("each"), 0, 0, ossl_sslctx_add_extra_chain_cert_i, self); } /* private key may be bundled in certificate file. */ @@ -743,22 +733,22 @@ ossl_sslctx_setup(VALUE self) val = rb_attr_get(self, id_i_client_ca); if(!NIL_P(val)){ - if (RB_TYPE_P(val, T_ARRAY)) { - for(i = 0; i < RARRAY_LEN(val); i++){ - client_ca = GetX509CertPtr(RARRAY_AREF(val, i)); - if (!SSL_CTX_add_client_CA(ctx, client_ca)){ - /* Copies X509_NAME => FREE it. */ - ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); - } - } + if (RB_TYPE_P(val, T_ARRAY)) { + for(i = 0; i < RARRAY_LEN(val); i++){ + client_ca = GetX509CertPtr(RARRAY_AREF(val, i)); + if (!SSL_CTX_add_client_CA(ctx, client_ca)){ + /* Copies X509_NAME => FREE it. */ + ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); + } + } } - else{ - client_ca = GetX509CertPtr(val); /* NO DUP NEEDED. */ + else{ + client_ca = GetX509CertPtr(val); /* NO DUP NEEDED. */ if (!SSL_CTX_add_client_CA(ctx, client_ca)){ - /* Copies X509_NAME => FREE it. */ - ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); + /* Copies X509_NAME => FREE it. */ + ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); } - } + } } val = rb_attr_get(self, id_i_ca_file); @@ -781,7 +771,7 @@ ossl_sslctx_setup(VALUE self) verify_mode = NIL_P(val) ? SSL_VERIFY_NONE : NUM2INT(val); SSL_CTX_set_verify(ctx, verify_mode, ossl_ssl_verify_callback); if (RTEST(rb_attr_get(self, id_i_client_cert_cb))) - SSL_CTX_set_client_cert_cb(ctx, ossl_client_cert_cb); + SSL_CTX_set_client_cert_cb(ctx, ossl_client_cert_cb); val = rb_attr_get(self, id_i_timeout); if(!NIL_P(val)) SSL_CTX_set_timeout(ctx, NUM2LONG(val)); @@ -792,60 +782,60 @@ ossl_sslctx_setup(VALUE self) #ifdef OSSL_USE_NEXTPROTONEG val = rb_attr_get(self, id_i_npn_protocols); if (!NIL_P(val)) { - VALUE encoded = ssl_encode_npn_protocols(val); - rb_ivar_set(self, id_npn_protocols_encoded, encoded); - SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self); - OSSL_Debug("SSL NPN advertise callback added"); + VALUE encoded = ssl_encode_npn_protocols(val); + rb_ivar_set(self, id_npn_protocols_encoded, encoded); + SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self); + OSSL_Debug("SSL NPN advertise callback added"); } if (RTEST(rb_attr_get(self, id_i_npn_select_cb))) { - SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self); - OSSL_Debug("SSL NPN select callback added"); + SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self); + OSSL_Debug("SSL NPN select callback added"); } #endif val = rb_attr_get(self, id_i_alpn_protocols); if (!NIL_P(val)) { - VALUE rprotos = ssl_encode_npn_protocols(val); + VALUE rprotos = ssl_encode_npn_protocols(val); - /* returns 0 on success */ - if (SSL_CTX_set_alpn_protos(ctx, (unsigned char *)RSTRING_PTR(rprotos), - RSTRING_LENINT(rprotos))) - ossl_raise(eSSLError, "SSL_CTX_set_alpn_protos"); - OSSL_Debug("SSL ALPN values added"); + /* returns 0 on success */ + if (SSL_CTX_set_alpn_protos(ctx, (unsigned char *)RSTRING_PTR(rprotos), + RSTRING_LENINT(rprotos))) + ossl_raise(eSSLError, "SSL_CTX_set_alpn_protos"); + OSSL_Debug("SSL ALPN values added"); } if (RTEST(rb_attr_get(self, id_i_alpn_select_cb))) { - SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self); - OSSL_Debug("SSL ALPN select callback added"); + SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self); + OSSL_Debug("SSL ALPN select callback added"); } rb_obj_freeze(self); val = rb_attr_get(self, id_i_session_id_context); if (!NIL_P(val)){ - StringValue(val); - if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val), - RSTRING_LENINT(val))){ - ossl_raise(eSSLError, "SSL_CTX_set_session_id_context"); - } + StringValue(val); + if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val), + RSTRING_LENINT(val))){ + ossl_raise(eSSLError, "SSL_CTX_set_session_id_context"); + } } if (RTEST(rb_attr_get(self, id_i_session_get_cb))) { - SSL_CTX_sess_set_get_cb(ctx, ossl_sslctx_session_get_cb); - OSSL_Debug("SSL SESSION get callback added"); + SSL_CTX_sess_set_get_cb(ctx, ossl_sslctx_session_get_cb); + OSSL_Debug("SSL SESSION get callback added"); } if (RTEST(rb_attr_get(self, id_i_session_new_cb))) { - SSL_CTX_sess_set_new_cb(ctx, ossl_sslctx_session_new_cb); - OSSL_Debug("SSL SESSION new callback added"); + SSL_CTX_sess_set_new_cb(ctx, ossl_sslctx_session_new_cb); + OSSL_Debug("SSL SESSION new callback added"); } if (RTEST(rb_attr_get(self, id_i_session_remove_cb))) { - SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb); - OSSL_Debug("SSL SESSION remove callback added"); + SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb); + OSSL_Debug("SSL SESSION remove callback added"); } val = rb_attr_get(self, id_i_servername_cb); if (!NIL_P(val)) { SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb); - OSSL_Debug("SSL TLSEXT servername callback added"); + OSSL_Debug("SSL TLSEXT servername callback added"); } #if !OSSL_IS_LIBRESSL @@ -868,28 +858,28 @@ parse_proto_version(VALUE str) { int i; static const struct { - const char *name; - int version; + const char *name; + int version; } map[] = { - { "SSL2", SSL2_VERSION }, - { "SSL3", SSL3_VERSION }, - { "TLS1", TLS1_VERSION }, - { "TLS1_1", TLS1_1_VERSION }, - { "TLS1_2", TLS1_2_VERSION }, - { "TLS1_3", TLS1_3_VERSION }, + { "SSL2", SSL2_VERSION }, + { "SSL3", SSL3_VERSION }, + { "TLS1", TLS1_VERSION }, + { "TLS1_1", TLS1_1_VERSION }, + { "TLS1_2", TLS1_2_VERSION }, + { "TLS1_3", TLS1_3_VERSION }, }; if (NIL_P(str)) - return 0; + return 0; if (RB_INTEGER_TYPE_P(str)) - return NUM2INT(str); + return NUM2INT(str); if (SYMBOL_P(str)) - str = rb_sym2str(str); + str = rb_sym2str(str); StringValue(str); for (i = 0; i < numberof(map); i++) - if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) - return map[i].version; + if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) + return map[i].version; rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str); } @@ -1148,7 +1138,7 @@ ossl_sslctx_set_client_sigalgs(VALUE self, VALUE v) * contained in the key object, if any, are ignored. The server will always * generate a new key pair for each handshake. * - * Added in version 3.0. See also the man page SSL_set0_tmp_dh_pkey(3). + * Added in version 3.0. See also the man page SSL_CTX_set0_tmp_dh_pkey(3). * * Example: * ctx = OpenSSL::SSL::SSLContext.new @@ -1169,7 +1159,7 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) rb_raise(eSSLError, "invalid pkey type %s (expected DH)", OBJ_nid2sn(EVP_PKEY_base_id(pkey))); -#ifdef HAVE_SSL_SET0_TMP_DH_PKEY +#ifdef HAVE_SSL_CTX_SET0_TMP_DH_PKEY if (!SSL_CTX_set0_tmp_dh_pkey(ctx, pkey)) ossl_raise(eSSLError, "SSL_CTX_set0_tmp_dh_pkey"); EVP_PKEY_up_ref(pkey); @@ -1178,6 +1168,9 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) ossl_raise(eSSLError, "SSL_CTX_set_tmp_dh"); #endif + // Turn off the "auto" DH parameters set by ossl_sslctx_s_alloc() + SSL_CTX_set_dh_auto(ctx, 0); + return arg; } #endif @@ -1352,20 +1345,20 @@ ossl_sslctx_add_certificate(int argc, VALUE *argv, VALUE self) pub_pkey = X509_get_pubkey(x509); EVP_PKEY_free(pub_pkey); if (!pub_pkey) - rb_raise(rb_eArgError, "certificate does not contain public key"); + rb_raise(rb_eArgError, "certificate does not contain public key"); if (EVP_PKEY_eq(pub_pkey, pkey) != 1) - rb_raise(rb_eArgError, "public key mismatch"); + rb_raise(rb_eArgError, "public key mismatch"); if (argc >= 3) - extra_chain = ossl_x509_ary2sk(extra_chain_ary); + extra_chain = ossl_x509_ary2sk(extra_chain_ary); if (!SSL_CTX_use_certificate(ctx, x509)) { - sk_X509_pop_free(extra_chain, X509_free); - ossl_raise(eSSLError, "SSL_CTX_use_certificate"); + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_use_certificate"); } if (!SSL_CTX_use_PrivateKey(ctx, pkey)) { - sk_X509_pop_free(extra_chain, X509_free); - ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey"); + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey"); } if (extra_chain && !SSL_CTX_set0_chain(ctx, extra_chain)) { sk_X509_pop_free(extra_chain, X509_free); @@ -1598,32 +1591,31 @@ ossl_ssl_s_alloc(VALUE klass) } static VALUE -peer_ip_address(VALUE self) +peer_ip_address(VALUE io) { - VALUE remote_address = rb_funcall(rb_attr_get(self, id_i_io), rb_intern("remote_address"), 0); + VALUE remote_address = rb_funcall(io, rb_intern("remote_address"), 0); return rb_funcall(remote_address, rb_intern("inspect_sockaddr"), 0); } static VALUE -fallback_peer_ip_address(VALUE self, VALUE args) +fallback_peer_ip_address(VALUE self, VALUE exc) { return rb_str_new_cstr("(null)"); } static VALUE -peeraddr_ip_str(VALUE self) +peeraddr_ip_str(VALUE io) { - VALUE rb_mErrno = rb_const_get(rb_cObject, rb_intern("Errno")); - VALUE rb_eSystemCallError = rb_const_get(rb_mErrno, rb_intern("SystemCallError")); - - return rb_rescue2(peer_ip_address, self, fallback_peer_ip_address, (VALUE)0, rb_eSystemCallError, NULL); + return rb_rescue2(peer_ip_address, io, fallback_peer_ip_address, Qnil, + rb_eSystemCallError, (VALUE)0); } /* * call-seq: * SSLSocket.new(io) => aSSLSocket * SSLSocket.new(io, ctx) => aSSLSocket + * SSLSocket.new(io, ctx, sync_close:) => aSSLSocket * * Creates a new SSL socket from _io_ which must be a real IO object (not an * IO-like object that responds to read/write). @@ -1631,6 +1623,10 @@ peeraddr_ip_str(VALUE self) * If _ctx_ is provided the SSL Sockets initial params will be taken from * the context. * + * The optional _sync_close_ keyword parameter sets the _sync_close_ instance + * variable. Setting this to +true+ will cause the underlying socket to be + * closed when the SSL/TLS connection is shut down. + * * The OpenSSL::Buffering module provides additional IO methods. * * This method will freeze the SSLContext if one is provided; @@ -1639,32 +1635,46 @@ peeraddr_ip_str(VALUE self) static VALUE ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) { + static ID kw_ids[1]; + VALUE kw_args[1]; + VALUE opts; + VALUE io, v_ctx; SSL *ssl; SSL_CTX *ctx; TypedData_Get_Struct(self, SSL, &ossl_ssl_type, ssl); if (ssl) - ossl_raise(eSSLError, "SSL already initialized"); + ossl_raise(eSSLError, "SSL already initialized"); - if (rb_scan_args(argc, argv, "11", &io, &v_ctx) == 1) - v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); + if (rb_scan_args(argc, argv, "11:", &io, &v_ctx, &opts) == 1) + v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); + + if (!kw_ids[0]) { + kw_ids[0] = rb_intern_const("sync_close"); + } + + rb_get_kwargs(opts, kw_ids, 0, 1, kw_args); + if (kw_args[0] != Qundef) { + rb_ivar_set(self, id_i_sync_close, kw_args[0]); + } GetSSLCTX(v_ctx, ctx); rb_ivar_set(self, id_i_context, v_ctx); ossl_sslctx_setup(v_ctx); if (rb_respond_to(io, rb_intern("nonblock="))) - rb_funcall(io, rb_intern("nonblock="), 1, Qtrue); + rb_funcall(io, rb_intern("nonblock="), 1, Qtrue); Check_Type(io, T_FILE); rb_ivar_set(self, id_i_io, io); ssl = SSL_new(ctx); if (!ssl) - ossl_raise(eSSLError, NULL); + ossl_raise(eSSLError, NULL); RTYPEDDATA_DATA(self) = ssl; - SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self); + if (!SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self)) + ossl_raise(eSSLError, "SSL_set_ex_data"); SSL_set_info_callback(ssl, ssl_info_cb); rb_call_super(0, NULL); @@ -1692,7 +1702,7 @@ ossl_ssl_setup(VALUE self) GetSSL(self, ssl); if (ssl_started(ssl)) - return Qtrue; + return Qtrue; io = rb_attr_get(self, id_i_io); GetOpenFile(io, fptr); @@ -1704,24 +1714,28 @@ ossl_ssl_setup(VALUE self) return Qtrue; } +static int +errno_mapped(void) +{ #ifdef _WIN32 -#define ssl_get_error(ssl, ret) (errno = rb_w32_map_errno(WSAGetLastError()), SSL_get_error((ssl), (ret))) + return rb_w32_map_errno(WSAGetLastError()); #else -#define ssl_get_error(ssl, ret) SSL_get_error((ssl), (ret)) + return errno; #endif +} static void write_would_block(int nonblock) { if (nonblock) - ossl_raise(eSSLErrorWaitWritable, "write would block"); + ossl_raise(eSSLErrorWaitWritable, "write would block"); } static void read_would_block(int nonblock) { if (nonblock) - ossl_raise(eSSLErrorWaitReadable, "read would block"); + ossl_raise(eSSLErrorWaitReadable, "read would block"); } static int @@ -1729,7 +1743,7 @@ no_exception_p(VALUE opts) { if (RB_TYPE_P(opts, T_HASH) && rb_hash_lookup2(opts, sym_exception, Qundef) == Qfalse) - return 1; + return 1; return 0; } @@ -1749,13 +1763,13 @@ static void io_wait_writable(VALUE io) { #ifdef HAVE_RB_IO_MAYBE_WAIT - if (!rb_io_maybe_wait_writable(errno, io, RUBY_IO_TIMEOUT_DEFAULT)) { + if (!rb_io_wait(io, INT2NUM(RUBY_IO_WRITABLE), RUBY_IO_TIMEOUT_DEFAULT)) { rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become writable!"); } #else rb_io_t *fptr; GetOpenFile(io, fptr); - rb_io_wait_writable(fptr->fd); + rb_thread_fd_writable(fptr->fd); #endif } @@ -1763,13 +1777,13 @@ static void io_wait_readable(VALUE io) { #ifdef HAVE_RB_IO_MAYBE_WAIT - if (!rb_io_maybe_wait_readable(errno, io, RUBY_IO_TIMEOUT_DEFAULT)) { + if (!rb_io_wait(io, INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT)) { rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become readable!"); } #else rb_io_t *fptr; GetOpenFile(io, fptr); - rb_io_wait_readable(fptr->fd); + rb_thread_wait_fd(fptr->fd); #endif } @@ -1777,7 +1791,6 @@ static VALUE ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) { SSL *ssl; - int ret, ret2; VALUE cb_state; int nonblock = opts != Qfalse; @@ -1787,7 +1800,8 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) VALUE io = rb_attr_get(self, id_i_io); for (;;) { - ret = func(ssl); + int ret = func(ssl); + int saved_errno = errno_mapped(); cb_state = rb_attr_get(self, ID_callback_state); if (!NIL_P(cb_state)) { @@ -1799,7 +1813,8 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) if (ret > 0) break; - switch ((ret2 = ssl_get_error(ssl, ret))) { + int code = SSL_get_error(ssl, ret); + switch (code) { case SSL_ERROR_WANT_WRITE: if (no_exception_p(opts)) { return sym_wait_writable; } write_would_block(nonblock); @@ -1813,10 +1828,11 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) case SSL_ERROR_SYSCALL: #ifdef __APPLE__ /* See ossl_ssl_write_internal() */ - if (errno == EPROTOTYPE) + if (saved_errno == EPROTOTYPE) continue; #endif - if (errno) rb_sys_fail(funcname); + if (saved_errno) + rb_exc_raise(rb_syserr_new(saved_errno, funcname)); /* fallthrough */ default: { VALUE error_append = Qnil; @@ -1837,10 +1853,10 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) ossl_raise(eSSLError, "%s%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s%"PRIsVALUE, funcname, - ret2 == SSL_ERROR_SYSCALL ? " SYSCALL" : "", - ret2, - errno, - peeraddr_ip_str(self), + code == SSL_ERROR_SYSCALL ? " SYSCALL" : "", + code, + saved_errno, + peeraddr_ip_str(io), SSL_state_string_long(ssl), error_append); } @@ -1953,9 +1969,9 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) VALUE opts = Qnil; if (nonblock) { - rb_scan_args(argc, argv, "11:", &len, &str, &opts); + rb_scan_args(argc, argv, "11:", &len, &str, &opts); } else { - rb_scan_args(argc, argv, "11", &len, &str); + rb_scan_args(argc, argv, "11", &len, &str); } GetSSL(self, ssl); if (!ssl_started(ssl)) @@ -1963,13 +1979,13 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) ilen = NUM2INT(len); if (NIL_P(str)) - str = rb_str_new(0, ilen); + str = rb_str_new(0, ilen); else { - StringValue(str); - if (RSTRING_LEN(str) >= ilen) - rb_str_modify(str); - else - rb_str_modify_expand(str, ilen - RSTRING_LEN(str)); + StringValue(str); + if (RSTRING_LEN(str) >= ilen) + rb_str_modify(str); + else + rb_str_modify_expand(str, ilen - RSTRING_LEN(str)); } if (ilen == 0) { @@ -1982,6 +1998,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) for (;;) { rb_str_locktmp(str); int nread = SSL_read(ssl, RSTRING_PTR(str), ilen); + int saved_errno = errno_mapped(); rb_str_unlocktmp(str); cb_state = rb_attr_get(self, ID_callback_state); @@ -1991,7 +2008,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) rb_jump_tag(NUM2INT(cb_state)); } - switch (ssl_get_error(ssl, nread)) { + switch (SSL_get_error(ssl, nread)) { case SSL_ERROR_NONE: rb_str_set_len(str, nread); return str; @@ -2014,8 +2031,8 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) break; case SSL_ERROR_SYSCALL: if (!ERR_peek_error()) { - if (errno) - rb_sys_fail(0); + if (saved_errno) + rb_exc_raise(rb_syserr_new(saved_errno, "SSL_read")); else { /* * The underlying BIO returned 0. This is actually a @@ -2100,6 +2117,7 @@ ossl_ssl_write_internal_safe(VALUE _args) for (;;) { int nwritten = SSL_write(ssl, RSTRING_PTR(str), num); + int saved_errno = errno_mapped(); cb_state = rb_attr_get(self, ID_callback_state); if (!NIL_P(cb_state)) { @@ -2108,7 +2126,7 @@ ossl_ssl_write_internal_safe(VALUE _args) rb_jump_tag(NUM2INT(cb_state)); } - switch (ssl_get_error(ssl, nwritten)) { + switch (SSL_get_error(ssl, nwritten)) { case SSL_ERROR_NONE: return INT2NUM(nwritten); case SSL_ERROR_WANT_WRITE: @@ -2129,10 +2147,11 @@ ossl_ssl_write_internal_safe(VALUE _args) * make the error handling in line with the socket library. * [Bug #14713] https://bugs.ruby-lang.org/issues/14713 */ - if (errno == EPROTOTYPE) + if (saved_errno == EPROTOTYPE) continue; #endif - if (errno) rb_sys_fail(0); + if (saved_errno) + rb_exc_raise(rb_syserr_new(saved_errno, "SSL_write")); /* fallthrough */ default: ossl_raise(eSSLError, "SSL_write"); @@ -2177,9 +2196,12 @@ ossl_ssl_write(VALUE self, VALUE str) /* * call-seq: * ssl.syswrite_nonblock(string) => Integer + * ssl.syswrite_nonblock(string, opts) => Integer * * Writes _string_ to the SSL connection in a non-blocking manner. Raises an - * SSLError if writing would block. + * SSLError if writing would block. If "exception: false" is passed, this + * method returns a symbol of :wait_readable or :wait_writable, rather than + * raising an exception. */ static VALUE ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self) @@ -2206,12 +2228,12 @@ ossl_ssl_stop(VALUE self) GetSSL(self, ssl); if (!ssl_started(ssl)) - return Qnil; + return Qnil; ret = SSL_shutdown(ssl); if (ret == 1) /* Have already received close_notify */ - return Qnil; + return Qnil; if (ret == 0) /* Sent close_notify, but we don't wait for reply */ - return Qnil; + return Qnil; /* * XXX: Something happened. Possibly it failed because the underlying socket @@ -2297,20 +2319,20 @@ ossl_ssl_get_peer_cert_chain(VALUE self) num = sk_X509_num(chain); ary = rb_ary_new2(num); for (i = 0; i < num; i++){ - cert = sk_X509_value(chain, i); - rb_ary_push(ary, ossl_x509_new(cert)); + cert = sk_X509_value(chain, i); + rb_ary_push(ary, ossl_x509_new(cert)); } return ary; } /* -* call-seq: -* ssl.ssl_version => String -* -* Returns a String representing the SSL/TLS version that was negotiated -* for the connection, for example "TLSv1.2". -*/ + * call-seq: + * ssl.ssl_version => String + * + * Returns a String representing the SSL/TLS version that was negotiated + * for the connection, for example "TLSv1.2". + */ static VALUE ossl_ssl_get_version(VALUE self) { @@ -2431,10 +2453,10 @@ ossl_ssl_set_hostname(VALUE self, VALUE arg) GetSSL(self, ssl); if (!NIL_P(arg)) - hostname = StringValueCStr(arg); + hostname = StringValueCStr(arg); if (!SSL_set_tlsext_host_name(ssl, hostname)) - ossl_raise(eSSLError, NULL); + ossl_raise(eSSLError, NULL); /* for SSLSocket#hostname */ rb_ivar_set(self, id_i_hostname, arg); @@ -2463,16 +2485,15 @@ ossl_ssl_get_verify_result(VALUE self) /* * call-seq: - * ssl.finished_message => "finished message" - * - * Returns the last *Finished* message sent + * ssl.finished_message -> string or nil * + * Returns the contents of the last +Finished+ message sent to the peer. */ static VALUE ossl_ssl_get_finished(VALUE self) { SSL *ssl; - char sizer[1], *buf; + char sizer[1]; size_t len; GetSSL(self, ssl); @@ -2481,23 +2502,23 @@ ossl_ssl_get_finished(VALUE self) if (len == 0) return Qnil; - buf = ALLOCA_N(char, len); - SSL_get_finished(ssl, buf, len); - return rb_str_new(buf, len); + VALUE str = rb_str_new(NULL, len); + SSL_get_finished(ssl, RSTRING_PTR(str), len); + return str; } /* * call-seq: - * ssl.peer_finished_message => "peer finished message" - * - * Returns the last *Finished* message received + * ssl.peer_finished_message -> string or nil * + * Returns the contents of the last +Finished+ message expected to be sent + * by the peer. */ static VALUE ossl_ssl_get_peer_finished(VALUE self) { SSL *ssl; - char sizer[1], *buf; + char sizer[1]; size_t len; GetSSL(self, ssl); @@ -2506,9 +2527,9 @@ ossl_ssl_get_peer_finished(VALUE self) if (len == 0) return Qnil; - buf = ALLOCA_N(char, len); - SSL_get_peer_finished(ssl, buf, len); - return rb_str_new(buf, len); + VALUE str = rb_str_new(NULL, len); + SSL_get_peer_finished(ssl, RSTRING_PTR(str), len); + return str; } /* @@ -2555,9 +2576,9 @@ ossl_ssl_npn_protocol(VALUE self) SSL_get0_next_proto_negotiated(ssl, &out, &outlen); if (!outlen) - return Qnil; + return Qnil; else - return rb_str_new((const char *) out, outlen); + return rb_str_new((const char *) out, outlen); } # endif @@ -2579,9 +2600,9 @@ ossl_ssl_alpn_protocol(VALUE self) SSL_get0_alpn_selected(ssl, &out, &outlen); if (!outlen) - return Qnil; + return Qnil; else - return rb_str_new((const char *) out, outlen); + return rb_str_new((const char *) out, outlen); } /* @@ -2614,15 +2635,15 @@ ossl_ssl_export_keying_material(int argc, VALUE *argv, VALUE self) str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (!NIL_P(context)) { - use_ctx = 1; - StringValue(context); - ctx = (unsigned char *)RSTRING_PTR(context); - ctx_len = RSTRING_LEN(context); + use_ctx = 1; + StringValue(context); + ctx = (unsigned char *)RSTRING_PTR(context); + ctx_len = RSTRING_LEN(context); } ret = SSL_export_keying_material(ssl, p, len, (char *)RSTRING_PTR(label), - RSTRING_LENINT(label), ctx, ctx_len, use_ctx); + RSTRING_LENINT(label), ctx, ctx_len, use_ctx); if (ret == 0 || ret == -1) { - ossl_raise(eSSLError, "SSL_export_keying_material"); + ossl_raise(eSSLError, "SSL_export_keying_material"); } return str; } @@ -2641,7 +2662,7 @@ ossl_ssl_tmp_key(VALUE self) GetSSL(self, ssl); if (!SSL_get_server_tmp_key(ssl, &key)) - return Qnil; + return Qnil; return ossl_pkey_wrap(key); } @@ -2712,8 +2733,6 @@ void Init_ossl_ssl(void) { #if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); rb_mWaitReadable = rb_define_module_under(rb_cIO, "WaitReadable"); rb_mWaitWritable = rb_define_module_under(rb_cIO, "WaitWritable"); #endif @@ -2724,10 +2743,10 @@ Init_ossl_ssl(void) ossl_ssl_ex_ptr_idx = SSL_get_ex_new_index(0, (void *)"ossl_ssl_ex_ptr_idx", 0, 0, 0); if (ossl_ssl_ex_ptr_idx < 0) - ossl_raise(rb_eRuntimeError, "SSL_get_ex_new_index"); + ossl_raise(rb_eRuntimeError, "SSL_get_ex_new_index"); ossl_sslctx_ex_ptr_idx = SSL_CTX_get_ex_new_index(0, (void *)"ossl_sslctx_ex_ptr_idx", 0, 0, 0); if (ossl_sslctx_ex_ptr_idx < 0) - ossl_raise(rb_eRuntimeError, "SSL_CTX_get_ex_new_index"); + ossl_raise(rb_eRuntimeError, "SSL_CTX_get_ex_new_index"); /* Document-module: OpenSSL::SSL * @@ -2865,6 +2884,23 @@ Init_ossl_ssl(void) */ rb_attr(cSSLContext, rb_intern_const("client_cert_cb"), 1, 1, Qfalse); +#ifndef OPENSSL_NO_DH + /* + * A callback invoked when DH parameters are required for ephemeral DH key + * exchange. + * + * The callback is invoked with the SSLSocket, a + * flag indicating the use of an export cipher and the keylength + * required. + * + * The callback must return an OpenSSL::PKey::DH instance of the correct + * key length. + * + * <b>Deprecated in version 3.0.</b> Use #tmp_dh= instead. + */ + rb_attr(cSSLContext, rb_intern_const("tmp_dh_callback"), 1, 1, Qfalse); +#endif + /* * Sets the context in which a session can be reused. This allows * sessions for multiple applications to be distinguished, for example, by @@ -3258,7 +3294,6 @@ Init_ossl_ssl(void) sym_wait_readable = ID2SYM(rb_intern_const("wait_readable")); sym_wait_writable = ID2SYM(rb_intern_const("wait_writable")); - id_tmp_dh_callback = rb_intern_const("tmp_dh_callback"); id_npn_protocols_encoded = rb_intern_const("npn_protocols_encoded"); id_each = rb_intern_const("each"); @@ -3289,9 +3324,11 @@ Init_ossl_ssl(void) DefIVarID(servername_cb); DefIVarID(verify_hostname); DefIVarID(keylog_cb); + DefIVarID(tmp_dh_callback); DefIVarID(io); DefIVarID(context); DefIVarID(hostname); + DefIVarID(sync_close); #endif /* !defined(OPENSSL_NO_SOCK) */ } diff --git a/ext/openssl/ossl_ssl.h b/ext/openssl/ossl_ssl.h index a92985c601..a87e62d450 100644 --- a/ext/openssl/ossl_ssl.h +++ b/ext/openssl/ossl_ssl.h @@ -11,17 +11,17 @@ #define _OSSL_SSL_H_ #define GetSSL(obj, ssl) do { \ - TypedData_Get_Struct((obj), SSL, &ossl_ssl_type, (ssl)); \ - if (!(ssl)) { \ - ossl_raise(rb_eRuntimeError, "SSL is not initialized"); \ - } \ + TypedData_Get_Struct((obj), SSL, &ossl_ssl_type, (ssl)); \ + if (!(ssl)) { \ + ossl_raise(rb_eRuntimeError, "SSL is not initialized"); \ + } \ } while (0) #define GetSSLSession(obj, sess) do { \ - TypedData_Get_Struct((obj), SSL_SESSION, &ossl_ssl_session_type, (sess)); \ - if (!(sess)) { \ - ossl_raise(rb_eRuntimeError, "SSL Session wasn't initialized."); \ - } \ + TypedData_Get_Struct((obj), SSL_SESSION, &ossl_ssl_session_type, (sess)); \ + if (!(sess)) { \ + ossl_raise(rb_eRuntimeError, "SSL Session wasn't initialized."); \ + } \ } while (0) extern const rb_data_type_t ossl_ssl_type; diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c index 55b9d6c6d5..8a2fbf4100 100644 --- a/ext/openssl/ossl_ssl_session.c +++ b/ext/openssl/ossl_ssl_session.c @@ -17,14 +17,14 @@ ossl_ssl_session_free(void *ptr) const rb_data_type_t ossl_ssl_session_type = { "OpenSSL/SSL/Session", { - 0, ossl_ssl_session_free, + 0, ossl_ssl_session_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE ossl_ssl_session_alloc(VALUE klass) { - return TypedData_Wrap_Struct(klass, &ossl_ssl_session_type, NULL); + return TypedData_Wrap_Struct(klass, &ossl_ssl_session_type, NULL); } /* @@ -80,9 +80,9 @@ ossl_ssl_session_initialize_copy(VALUE self, VALUE other) GetSSLSession(other, sess_other); sess_new = ASN1_dup((i2d_of_void *)i2d_SSL_SESSION, (d2i_of_void *)d2i_SSL_SESSION, - (char *)sess_other); + (char *)sess_other); if (!sess_new) - ossl_raise(eSSLSession, "ASN1_dup"); + ossl_raise(eSSLSession, "ASN1_dup"); RTYPEDDATA_DATA(self) = sess_new; SSL_SESSION_free(sess); @@ -99,9 +99,9 @@ ossl_SSL_SESSION_cmp(const SSL_SESSION *a, const SSL_SESSION *b) const unsigned char *b_sid = SSL_SESSION_get_id(b, &b_len); if (SSL_SESSION_get_protocol_version(a) != SSL_SESSION_get_protocol_version(b)) - return 1; + return 1; if (a_len != b_len) - return 1; + return 1; return CRYPTO_memcmp(a_sid, b_sid, a_len); } @@ -114,15 +114,15 @@ ossl_SSL_SESSION_cmp(const SSL_SESSION *a, const SSL_SESSION *b) */ static VALUE ossl_ssl_session_eq(VALUE val1, VALUE val2) { - SSL_SESSION *ctx1, *ctx2; + SSL_SESSION *ctx1, *ctx2; - GetSSLSession(val1, ctx1); - GetSSLSession(val2, ctx2); + GetSSLSession(val1, ctx1); + GetSSLSession(val2, ctx2); - switch (ossl_SSL_SESSION_cmp(ctx1, ctx2)) { - case 0: return Qtrue; - default: return Qfalse; - } + switch (ossl_SSL_SESSION_cmp(ctx1, ctx2)) { + case 0: return Qtrue; + default: return Qfalse; + } } /* @@ -140,7 +140,7 @@ ossl_ssl_session_get_time(VALUE self) GetSSLSession(self, ctx); t = SSL_SESSION_get_time(ctx); if (t == 0) - return Qnil; + return Qnil; return rb_funcall(rb_cTime, rb_intern("at"), 1, LONG2NUM(t)); } @@ -175,16 +175,16 @@ ossl_ssl_session_get_timeout(VALUE self) */ static VALUE ossl_ssl_session_set_time(VALUE self, VALUE time_v) { - SSL_SESSION *ctx; - long t; - - GetSSLSession(self, ctx); - if (rb_obj_is_instance_of(time_v, rb_cTime)) { - time_v = rb_funcall(time_v, rb_intern("to_i"), 0); - } - t = NUM2LONG(time_v); - SSL_SESSION_set_time(ctx, t); - return ossl_ssl_session_get_time(self); + SSL_SESSION *ctx; + long t; + + GetSSLSession(self, ctx); + if (rb_obj_is_instance_of(time_v, rb_cTime)) { + time_v = rb_funcall(time_v, rb_intern("to_i"), 0); + } + t = NUM2LONG(time_v); + SSL_SESSION_set_time(ctx, t); + return ossl_ssl_session_get_time(self); } /* @@ -195,13 +195,13 @@ static VALUE ossl_ssl_session_set_time(VALUE self, VALUE time_v) */ static VALUE ossl_ssl_session_set_timeout(VALUE self, VALUE time_v) { - SSL_SESSION *ctx; - long t; + SSL_SESSION *ctx; + long t; - GetSSLSession(self, ctx); - t = NUM2LONG(time_v); - SSL_SESSION_set_timeout(ctx, t); - return ossl_ssl_session_get_timeout(self); + GetSSLSession(self, ctx); + t = NUM2LONG(time_v); + SSL_SESSION_set_timeout(ctx, t); + return ossl_ssl_session_get_timeout(self); } /* @@ -209,18 +209,18 @@ static VALUE ossl_ssl_session_set_timeout(VALUE self, VALUE time_v) * session.id -> String * * Returns the Session ID. -*/ + */ static VALUE ossl_ssl_session_get_id(VALUE self) { - SSL_SESSION *ctx; - const unsigned char *p = NULL; - unsigned int i = 0; + SSL_SESSION *ctx; + const unsigned char *p = NULL; + unsigned int i = 0; - GetSSLSession(self, ctx); + GetSSLSession(self, ctx); - p = SSL_SESSION_get_id(ctx, &i); + p = SSL_SESSION_get_id(ctx, &i); - return rb_str_new((const char *) p, i); + return rb_str_new((const char *) p, i); } /* @@ -231,22 +231,22 @@ static VALUE ossl_ssl_session_get_id(VALUE self) */ static VALUE ossl_ssl_session_to_der(VALUE self) { - SSL_SESSION *ctx; - unsigned char *p; - int len; - VALUE str; - - GetSSLSession(self, ctx); - len = i2d_SSL_SESSION(ctx, NULL); - if (len <= 0) { - ossl_raise(eSSLSession, "i2d_SSL_SESSION"); - } - - str = rb_str_new(0, len); - p = (unsigned char *)RSTRING_PTR(str); - i2d_SSL_SESSION(ctx, &p); - ossl_str_adjust(str, p); - return str; + SSL_SESSION *ctx; + unsigned char *p; + int len; + VALUE str; + + GetSSLSession(self, ctx); + len = i2d_SSL_SESSION(ctx, NULL); + if (len <= 0) { + ossl_raise(eSSLSession, "i2d_SSL_SESSION"); + } + + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + i2d_SSL_SESSION(ctx, &p); + ossl_str_adjust(str, p); + return str; } /* @@ -257,22 +257,22 @@ static VALUE ossl_ssl_session_to_der(VALUE self) */ static VALUE ossl_ssl_session_to_pem(VALUE self) { - SSL_SESSION *ctx; - BIO *out; + SSL_SESSION *ctx; + BIO *out; - GetSSLSession(self, ctx); + GetSSLSession(self, ctx); - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eSSLSession, "BIO_s_mem()"); - } + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eSSLSession, "BIO_s_mem()"); + } - if (!PEM_write_bio_SSL_SESSION(out, ctx)) { - BIO_free(out); - ossl_raise(eSSLSession, "SSL_SESSION_print()"); - } + if (!PEM_write_bio_SSL_SESSION(out, ctx)) { + BIO_free(out); + ossl_raise(eSSLSession, "SSL_SESSION_print()"); + } - return ossl_membio2str(out); + return ossl_membio2str(out); } @@ -284,49 +284,44 @@ static VALUE ossl_ssl_session_to_pem(VALUE self) */ static VALUE ossl_ssl_session_to_text(VALUE self) { - SSL_SESSION *ctx; - BIO *out; + SSL_SESSION *ctx; + BIO *out; - GetSSLSession(self, ctx); + GetSSLSession(self, ctx); - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eSSLSession, "BIO_s_mem()"); - } + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eSSLSession, "BIO_s_mem()"); + } - if (!SSL_SESSION_print(out, ctx)) { - BIO_free(out); - ossl_raise(eSSLSession, "SSL_SESSION_print()"); - } + if (!SSL_SESSION_print(out, ctx)) { + BIO_free(out); + ossl_raise(eSSLSession, "SSL_SESSION_print()"); + } - return ossl_membio2str(out); + return ossl_membio2str(out); } #endif /* !defined(OPENSSL_NO_SOCK) */ void Init_ossl_ssl_session(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - mSSL = rb_define_module_under(mOSSL, "SSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif #ifndef OPENSSL_NO_SOCK - cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject); - eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError); - - rb_define_alloc_func(cSSLSession, ossl_ssl_session_alloc); - rb_define_method(cSSLSession, "initialize", ossl_ssl_session_initialize, 1); - rb_define_method(cSSLSession, "initialize_copy", ossl_ssl_session_initialize_copy, 1); - - rb_define_method(cSSLSession, "==", ossl_ssl_session_eq, 1); - - rb_define_method(cSSLSession, "time", ossl_ssl_session_get_time, 0); - rb_define_method(cSSLSession, "time=", ossl_ssl_session_set_time, 1); - rb_define_method(cSSLSession, "timeout", ossl_ssl_session_get_timeout, 0); - rb_define_method(cSSLSession, "timeout=", ossl_ssl_session_set_timeout, 1); - rb_define_method(cSSLSession, "id", ossl_ssl_session_get_id, 0); - rb_define_method(cSSLSession, "to_der", ossl_ssl_session_to_der, 0); - rb_define_method(cSSLSession, "to_pem", ossl_ssl_session_to_pem, 0); - rb_define_method(cSSLSession, "to_text", ossl_ssl_session_to_text, 0); + cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject); + eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError); + + rb_define_alloc_func(cSSLSession, ossl_ssl_session_alloc); + rb_define_method(cSSLSession, "initialize", ossl_ssl_session_initialize, 1); + rb_define_method(cSSLSession, "initialize_copy", ossl_ssl_session_initialize_copy, 1); + + rb_define_method(cSSLSession, "==", ossl_ssl_session_eq, 1); + + rb_define_method(cSSLSession, "time", ossl_ssl_session_get_time, 0); + rb_define_method(cSSLSession, "time=", ossl_ssl_session_set_time, 1); + rb_define_method(cSSLSession, "timeout", ossl_ssl_session_get_timeout, 0); + rb_define_method(cSSLSession, "timeout=", ossl_ssl_session_set_timeout, 1); + rb_define_method(cSSLSession, "id", ossl_ssl_session_get_id, 0); + rb_define_method(cSSLSession, "to_der", ossl_ssl_session_to_der, 0); + rb_define_method(cSSLSession, "to_pem", ossl_ssl_session_to_pem, 0); + rb_define_method(cSSLSession, "to_text", ossl_ssl_session_to_text, 0); #endif /* !defined(OPENSSL_NO_SOCK) */ } diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index c7d2bd271b..317f7786aa 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -103,7 +103,7 @@ static const rb_data_type_t ossl_ts_resp_type = { static void ossl_ts_token_info_free(void *ptr) { - TS_TST_INFO_free(ptr); + TS_TST_INFO_free(ptr); } static const rb_data_type_t ossl_ts_token_info_type = { @@ -132,44 +132,10 @@ asn1_to_der(void *template, int (*i2d)(void *template, unsigned char **pp)) return str; } -static ASN1_OBJECT* -obj_to_asn1obj(VALUE obj) -{ - ASN1_OBJECT *a1obj; - - StringValue(obj); - a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 0); - if(!a1obj) a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 1); - if(!a1obj) ossl_raise(eASN1Error, "invalid OBJECT ID"); - - return a1obj; -} - static VALUE obj_to_asn1obj_i(VALUE obj) { - return (VALUE)obj_to_asn1obj(obj); -} - -static VALUE -get_asn1obj(const ASN1_OBJECT *obj) -{ - BIO *out; - VALUE ret; - int nid; - if ((nid = OBJ_obj2nid(obj)) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); - else{ - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eTimestampError, "BIO_new(BIO_s_mem())"); - if (i2a_ASN1_OBJECT(out, obj) <= 0) { - BIO_free(out); - ossl_raise(eTimestampError, "i2a_ASN1_OBJECT"); - } - ret = ossl_membio2str(out); - } - - return ret; + return (VALUE)ossl_to_asn1obj(obj); } static VALUE @@ -202,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; @@ -212,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; } @@ -242,7 +209,7 @@ ossl_ts_req_get_algorithm(VALUE self) mi = TS_REQ_get_msg_imprint(req); algor = TS_MSG_IMPRINT_get_algo(mi); X509_ALGOR_get0(&obj, NULL, NULL, algor); - return get_asn1obj(obj); + return ossl_asn1obj_to_string(obj); } /* @@ -264,7 +231,7 @@ ossl_ts_req_set_algorithm(VALUE self, VALUE algo) X509_ALGOR *algor; GetTSRequest(self, req); - obj = obj_to_asn1obj(algo); + obj = ossl_to_asn1obj(algo); mi = TS_REQ_get_msg_imprint(req); algor = TS_MSG_IMPRINT_get_algo(mi); if (!X509_ALGOR_set0(algor, obj, V_ASN1_NULL, NULL)) { @@ -293,7 +260,7 @@ ossl_ts_req_get_msg_imprint(VALUE self) mi = TS_REQ_get_msg_imprint(req); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); - ret = rb_str_new((const char *)hashed_msg->data, hashed_msg->length); + ret = asn1str_to_str(hashed_msg); return ret; } @@ -371,7 +338,7 @@ ossl_ts_req_get_policy_id(VALUE self) GetTSRequest(self, req); if (!TS_REQ_get_policy_id(req)) return Qnil; - return get_asn1obj(TS_REQ_get_policy_id(req)); + return ossl_asn1obj_to_string(TS_REQ_get_policy_id(req)); } /* @@ -394,7 +361,7 @@ ossl_ts_req_set_policy_id(VALUE self, VALUE oid) int ok; GetTSRequest(self, req); - obj = obj_to_asn1obj(oid); + obj = ossl_to_asn1obj(oid); ok = TS_REQ_set_policy_id(req, obj); ASN1_OBJECT_free(obj); if (!ok) @@ -504,7 +471,7 @@ ossl_ts_req_to_der(VALUE self) ossl_raise(eTimestampError, "Message imprint missing algorithm"); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); - if (!hashed_msg->length) + if (!ASN1_STRING_length(hashed_msg)) ossl_raise(eTimestampError, "Message imprint missing hashed message"); return asn1_to_der((void *)req, (int (*)(void *, unsigned char **))i2d_TS_REQ); @@ -556,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; } @@ -740,7 +708,7 @@ ossl_ts_resp_get_tsa_certificate(VALUE self) TS_RESP *resp; PKCS7 *p7; PKCS7_SIGNER_INFO *ts_info; - X509 *cert; + const X509 *cert; GetTSResponse(self, resp); if (!(p7 = TS_RESP_get_token(resp))) @@ -910,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; } @@ -961,7 +930,7 @@ ossl_ts_token_info_get_policy_id(VALUE self) TS_TST_INFO *info; GetTSTokenInfo(self, info); - return get_asn1obj(TS_TST_INFO_get_policy_id(info)); + return ossl_asn1obj_to_string(TS_TST_INFO_get_policy_id(info)); } /* @@ -989,7 +958,7 @@ ossl_ts_token_info_get_algorithm(VALUE self) mi = TS_TST_INFO_get_msg_imprint(info); algo = TS_MSG_IMPRINT_get_algo(mi); X509_ALGOR_get0(&obj, NULL, NULL, algo); - return get_asn1obj(obj); + return ossl_asn1obj_to_string(obj); } /* @@ -1015,7 +984,7 @@ ossl_ts_token_info_get_msg_imprint(VALUE self) GetTSTokenInfo(self, info); mi = TS_TST_INFO_get_msg_imprint(info); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); - ret = rb_str_new((const char *)hashed_msg->data, hashed_msg->length); + ret = asn1str_to_str(hashed_msg); return ret; } @@ -1155,9 +1124,14 @@ ossl_tsfac_time_cb(struct TS_resp_ctx *ctx, void *data, time_t *sec, long *usec) } static VALUE -ossl_evp_get_digestbyname_i(VALUE arg) +ossl_evp_md_fetch_i(VALUE args_) { - return (VALUE)ossl_evp_get_digestbyname(arg); + VALUE *args = (VALUE *)args_, md_holder; + const EVP_MD *md; + + md = ossl_evp_md_fetch(args[1], &md_holder); + rb_ary_push(args[0], md_holder); + return (VALUE)md; } static VALUE @@ -1193,7 +1167,8 @@ ossl_obj2bio_i(VALUE arg) static VALUE ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) { - VALUE serial_number, def_policy_id, gen_time, additional_certs, allowed_digests; + VALUE serial_number, def_policy_id, gen_time, additional_certs, + allowed_digests, allowed_digests_tmp = Qnil; VALUE str; STACK_OF(X509) *inter_certs; VALUE tsresp, ret = Qnil; @@ -1254,7 +1229,7 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) if (rb_obj_is_kind_of(additional_certs, rb_cArray)) { inter_certs = ossl_protect_x509_ary2sk(additional_certs, &status); if (status) - goto end; + goto end; /* this dups the sk_X509 and ups each cert's ref count */ TS_RESP_CTX_set_certs(ctx, inter_certs); @@ -1270,16 +1245,18 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) allowed_digests = ossl_tsfac_get_allowed_digests(self); if (rb_obj_is_kind_of(allowed_digests, rb_cArray)) { - int i; - VALUE rbmd; - const EVP_MD *md; - - for (i = 0; i < RARRAY_LEN(allowed_digests); i++) { - rbmd = rb_ary_entry(allowed_digests, i); - md = (const EVP_MD *)rb_protect(ossl_evp_get_digestbyname_i, rbmd, &status); + allowed_digests_tmp = rb_ary_new_capa(RARRAY_LEN(allowed_digests)); + for (long i = 0; i < RARRAY_LEN(allowed_digests); i++) { + VALUE args[] = { + allowed_digests_tmp, + rb_ary_entry(allowed_digests, i), + }; + const EVP_MD *md = (const EVP_MD *)rb_protect(ossl_evp_md_fetch_i, + (VALUE)args, &status); if (status) goto end; - TS_RESP_CTX_add_md(ctx, md); + if (!TS_RESP_CTX_add_md(ctx, md)) + goto end; } } @@ -1293,6 +1270,7 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) response = TS_RESP_create_response(ctx, req_bio); BIO_free(req_bio); + RB_GC_GUARD(allowed_digests_tmp); if (!response) { err_msg = "Error during response generation"; @@ -1306,7 +1284,7 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) SetTSResponse(tsresp, response); ret = tsresp; -end: + end: ASN1_INTEGER_free(asn1_serial); ASN1_OBJECT_free(def_policy_id_obj); TS_RESP_CTX_free(ctx); @@ -1323,10 +1301,6 @@ end: void Init_ossl_ts(void) { - #if 0 - mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ - #endif - /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server rejects the message imprint algorithm used in the @@ -1536,65 +1510,39 @@ Init_ossl_ts(void) * fac.default_policy_id = '1.2.3.4.5' * fac.additional_certificates = [ inter1, inter2 ] * timestamp = fac.create_timestamp(p12.key, p12.certificate, req) - * - * ==Attributes - * - * ===default_policy_id + */ + cTimestampFactory = rb_define_class_under(mTimestamp, "Factory", rb_cObject); + /* + * The list of digest algorithms that the factory is allowed + * create timestamps for. Known vulnerable or weak algorithms should not be + * allowed where possible. Must be an Array of String or OpenSSL::Digest + * subclass instances. + */ + rb_attr(cTimestampFactory, rb_intern_const("allowed_digests"), 1, 1, 0); + /* + * A String representing the default policy object identifier, or +nil+. * * Request#policy_id will always be preferred over this if present in the - * Request, only if Request#policy_id is nil default_policy will be used. + * Request, only if Request#policy_id is +nil+ default_policy will be used. * If none of both is present, a TimestampError will be raised when trying * to create a Response. - * - * call-seq: - * factory.default_policy_id = "string" -> string - * factory.default_policy_id -> string or nil - * - * ===serial_number - * - * Sets or retrieves the serial number to be used for timestamp creation. - * Must be present for timestamp creation. - * - * call-seq: - * factory.serial_number = number -> number - * factory.serial_number -> number or nil - * - * ===gen_time - * - * Sets or retrieves the Time value to be used in the Response. Must be - * present for timestamp creation. - * - * call-seq: - * factory.gen_time = Time -> Time - * factory.gen_time -> Time or nil - * - * ===additional_certs - * - * Sets or retrieves additional certificates apart from the timestamp - * certificate (e.g. intermediate certificates) to be added to the Response. - * Must be an Array of OpenSSL::X509::Certificate. - * - * call-seq: - * factory.additional_certs = [cert1, cert2] -> [ cert1, cert2 ] - * factory.additional_certs -> array or nil - * - * ===allowed_digests - * - * Sets or retrieves the digest algorithms that the factory is allowed - * create timestamps for. Known vulnerable or weak algorithms should not be - * allowed where possible. - * Must be an Array of String or OpenSSL::Digest subclass instances. - * - * call-seq: - * factory.allowed_digests = ["sha1", OpenSSL::Digest.new('SHA256').new] -> [ "sha1", OpenSSL::Digest) ] - * factory.allowed_digests -> array or nil - * */ - cTimestampFactory = rb_define_class_under(mTimestamp, "Factory", rb_cObject); - rb_attr(cTimestampFactory, rb_intern_const("allowed_digests"), 1, 1, 0); rb_attr(cTimestampFactory, rb_intern_const("default_policy_id"), 1, 1, 0); + /* + * The serial number to be used for timestamp creation. Must be present for + * timestamp creation. Must be an instance of OpenSSL::BN or Integer. + */ rb_attr(cTimestampFactory, rb_intern_const("serial_number"), 1, 1, 0); + /* + * The Time value to be used in the Response. Must be present for timestamp + * creation. + */ rb_attr(cTimestampFactory, rb_intern_const("gen_time"), 1, 1, 0); + /* + * Additional certificates apart from the timestamp certificate (e.g. + * intermediate certificates) to be added to the Response. + * Must be an Array of OpenSSL::X509::Certificate, or +nil+. + */ rb_attr(cTimestampFactory, rb_intern_const("additional_certs"), 1, 1, 0); rb_define_method(cTimestampFactory, "create_timestamp", ossl_tsfac_create_ts, 3); } diff --git a/ext/openssl/ossl_x509.c b/ext/openssl/ossl_x509.c index 2d552d7847..bc3914fda2 100644 --- a/ext/openssl/ossl_x509.c +++ b/ext/openssl/ossl_x509.c @@ -13,7 +13,8 @@ VALUE mX509; #define DefX509Const(x) rb_define_const(mX509, #x, INT2NUM(X509_##x)) #define DefX509Default(x,i) \ - rb_define_const(mX509, "DEFAULT_" #x, rb_str_new2(X509_get_default_##i())) + rb_define_const(mX509, "DEFAULT_" #x, \ + rb_obj_freeze(rb_str_new_cstr(X509_get_default_##i()))) ASN1_TIME * ossl_x509_time_adjust(ASN1_TIME *s, VALUE time) @@ -29,10 +30,6 @@ ossl_x509_time_adjust(ASN1_TIME *s, VALUE time) void Init_ossl_x509(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); -#endif - mX509 = rb_define_module_under(mOSSL, "X509"); Init_ossl_x509attr(); diff --git a/ext/openssl/ossl_x509.h b/ext/openssl/ossl_x509.h index d25167ee7b..71932ef1a9 100644 --- a/ext/openssl/ossl_x509.h +++ b/ext/openssl/ossl_x509.h @@ -29,7 +29,7 @@ void Init_ossl_x509(void); */ extern VALUE cX509Attr; -VALUE ossl_x509attr_new(X509_ATTRIBUTE *); +VALUE ossl_x509attr_new(const X509_ATTRIBUTE *); X509_ATTRIBUTE *GetX509AttrPtr(VALUE); void Init_ossl_x509attr(void); @@ -38,7 +38,7 @@ void Init_ossl_x509attr(void); */ extern VALUE cX509Cert; -VALUE ossl_x509_new(X509 *); +VALUE ossl_x509_new(const X509 *); X509 *GetX509CertPtr(VALUE); X509 *DupX509CertPtr(VALUE); void Init_ossl_x509cert(void); @@ -46,7 +46,7 @@ void Init_ossl_x509cert(void); /* * X509CRL */ -VALUE ossl_x509crl_new(X509_CRL *); +VALUE ossl_x509crl_new(const X509_CRL *); X509_CRL *GetX509CRLPtr(VALUE); void Init_ossl_x509crl(void); @@ -55,14 +55,14 @@ void Init_ossl_x509crl(void); */ extern VALUE cX509Ext; -VALUE ossl_x509ext_new(X509_EXTENSION *); +VALUE ossl_x509ext_new(const X509_EXTENSION *); X509_EXTENSION *GetX509ExtPtr(VALUE); void Init_ossl_x509ext(void); /* * X509Name */ -VALUE ossl_x509name_new(X509_NAME *); +VALUE ossl_x509name_new(const X509_NAME *); X509_NAME *GetX509NamePtr(VALUE); void Init_ossl_x509name(void); @@ -77,7 +77,7 @@ void Init_ossl_x509req(void); */ extern VALUE cX509Rev; -VALUE ossl_x509revoked_new(X509_REVOKED *); +VALUE ossl_x509revoked_new(const X509_REVOKED *); X509_REVOKED *DupX509RevokedPtr(VALUE); void Init_ossl_x509revoked(void); diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c index d983af5968..38600b9b00 100644 --- a/ext/openssl/ossl_x509attr.c +++ b/ext/openssl/ossl_x509attr.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509attr_type, 0) #define SetX509Attr(obj, attr) do { \ if (!(attr)) { \ - ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (attr); \ } while (0) #define GetX509Attr(obj, attr) do { \ TypedData_Get_Struct((obj), X509_ATTRIBUTE, &ossl_x509attr_type, (attr)); \ if (!(attr)) { \ - ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509attr_free(void *ptr) static const rb_data_type_t ossl_x509attr_type = { "OpenSSL/X509/ATTRIBUTE", { - 0, ossl_x509attr_free, + 0, ossl_x509attr_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -48,13 +48,14 @@ static const rb_data_type_t ossl_x509attr_type = { * Public */ VALUE -ossl_x509attr_new(X509_ATTRIBUTE *attr) +ossl_x509attr_new(const X509_ATTRIBUTE *attr) { X509_ATTRIBUTE *new; VALUE obj; obj = NewX509Attr(cX509Attr); - new = X509_ATTRIBUTE_dup(attr); + /* OpenSSL 1.1.1 takes a non-const pointer */ + new = X509_ATTRIBUTE_dup((X509_ATTRIBUTE *)attr); if (!new) ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup"); SetX509Attr(obj, new); @@ -83,7 +84,7 @@ ossl_x509attr_alloc(VALUE klass) obj = NewX509Attr(klass); if (!(attr = X509_ATTRIBUTE_new())) - ossl_raise(eX509AttrError, NULL); + ossl_raise(eX509AttrError, NULL); SetX509Attr(obj, attr); return obj; @@ -102,15 +103,15 @@ ossl_x509attr_initialize(int argc, VALUE *argv, VALUE self) GetX509Attr(self, attr); if(rb_scan_args(argc, argv, "11", &oid, &value) == 1){ - oid = ossl_to_der_if_possible(oid); - StringValue(oid); - p = (unsigned char *)RSTRING_PTR(oid); - x = d2i_X509_ATTRIBUTE(&attr, &p, RSTRING_LEN(oid)); - DATA_PTR(self) = attr; - if(!x){ - ossl_raise(eX509AttrError, NULL); - } - return self; + oid = ossl_to_der_if_possible(oid); + StringValue(oid); + p = (unsigned char *)RSTRING_PTR(oid); + x = d2i_X509_ATTRIBUTE(&attr, &p, RSTRING_LEN(oid)); + DATA_PTR(self) = attr; + if(!x){ + ossl_raise(eX509AttrError, NULL); + } + return self; } rb_funcall(self, rb_intern("oid="), 1, oid); rb_funcall(self, rb_intern("value="), 1, value); @@ -130,7 +131,7 @@ ossl_x509attr_initialize_copy(VALUE self, VALUE other) attr_new = X509_ATTRIBUTE_dup(attr_other); if (!attr_new) - ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup"); + ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup"); SetX509Attr(self, attr_new); X509_ATTRIBUTE_free(attr); @@ -154,8 +155,8 @@ ossl_x509attr_set_oid(VALUE self, VALUE oid) obj = OBJ_txt2obj(s, 0); if(!obj) ossl_raise(eX509AttrError, NULL); if (!X509_ATTRIBUTE_set1_object(attr, obj)) { - ASN1_OBJECT_free(obj); - ossl_raise(eX509AttrError, "X509_ATTRIBUTE_set1_object"); + ASN1_OBJECT_free(obj); + ossl_raise(eX509AttrError, "X509_ATTRIBUTE_set1_object"); } ASN1_OBJECT_free(obj); @@ -164,29 +165,18 @@ ossl_x509attr_set_oid(VALUE self, VALUE oid) /* * call-seq: - * attr.oid => string + * attr.oid -> string + * + * Returns the OID of the attribute. Returns the short name or the dotted + * decimal notation. */ static VALUE ossl_x509attr_get_oid(VALUE self) { X509_ATTRIBUTE *attr; - ASN1_OBJECT *oid; - BIO *out; - VALUE ret; - int nid; GetX509Attr(self, attr); - oid = X509_ATTRIBUTE_get0_object(attr); - if ((nid = OBJ_obj2nid(oid)) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); - else{ - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eX509AttrError, NULL); - i2a_ASN1_OBJECT(out, oid); - ret = ossl_membio2str(out); - } - - return ret; + return ossl_asn1obj_to_string(X509_ATTRIBUTE_get0_object(attr)); } /* @@ -207,7 +197,7 @@ ossl_x509attr_set_value(VALUE self, VALUE value) ossl_raise(eX509AttrError, "attribute value must be ASN1::Set"); if (X509_ATTRIBUTE_count(attr)) { /* populated, reset first */ - ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); + const ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); X509_ATTRIBUTE *new_attr = X509_ATTRIBUTE_create_by_OBJ(NULL, obj, 0, NULL, -1); if (!new_attr) { sk_ASN1_TYPE_pop_free(sk, ASN1_TYPE_free); @@ -245,23 +235,32 @@ ossl_x509attr_get_value(VALUE self) unsigned char *p; GetX509Attr(self, attr); + count = X509_ATTRIBUTE_count(attr); /* there is no X509_ATTRIBUTE_get0_set() :( */ +#ifdef HAVE_OPENSSL_SK_NEW_RESERVE + if (!(sk = sk_ASN1_TYPE_new_reserve(NULL, count))) + ossl_raise(eX509AttrError, "sk_new_reserve"); +#else if (!(sk = sk_ASN1_TYPE_new_null())) - ossl_raise(eX509AttrError, "sk_new"); + ossl_raise(eX509AttrError, "sk_new"); +#endif - count = X509_ATTRIBUTE_count(attr); - for (i = 0; i < count; i++) - sk_ASN1_TYPE_push(sk, X509_ATTRIBUTE_get0_type(attr, i)); + for (i = 0; i < count; i++) { + if (!sk_ASN1_TYPE_push(sk, (ASN1_TYPE *)X509_ATTRIBUTE_get0_type(attr, i))) { + sk_ASN1_TYPE_free(sk); + ossl_raise(eX509AttrError, NULL); + } + } if ((len = i2d_ASN1_SET_ANY(sk, NULL)) <= 0) { - sk_ASN1_TYPE_free(sk); - ossl_raise(eX509AttrError, NULL); + sk_ASN1_TYPE_free(sk); + ossl_raise(eX509AttrError, NULL); } str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_ASN1_SET_ANY(sk, &p) <= 0) { - sk_ASN1_TYPE_free(sk); - ossl_raise(eX509AttrError, NULL); + sk_ASN1_TYPE_free(sk); + ossl_raise(eX509AttrError, NULL); } ossl_str_adjust(str, p); sk_ASN1_TYPE_free(sk); @@ -283,11 +282,11 @@ ossl_x509attr_to_der(VALUE self) GetX509Attr(self, attr); if((len = i2d_X509_ATTRIBUTE(attr, NULL)) <= 0) - ossl_raise(eX509AttrError, NULL); + ossl_raise(eX509AttrError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_X509_ATTRIBUTE(attr, &p) <= 0) - ossl_raise(eX509AttrError, NULL); + ossl_raise(eX509AttrError, NULL); ossl_str_adjust(str, p); return str; @@ -299,12 +298,6 @@ ossl_x509attr_to_der(VALUE self) void Init_ossl_x509attr(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509AttrError = rb_define_class_under(mX509, "AttributeError", eOSSLError); cX509Attr = rb_define_class_under(mX509, "Attribute", rb_cObject); diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index 30e3c61753..08dd184a0c 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509_type, 0) #define SetX509(obj, x509) do { \ if (!(x509)) { \ - ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (x509); \ } while (0) #define GetX509(obj, x509) do { \ TypedData_Get_Struct((obj), X509, &ossl_x509_type, (x509)); \ if (!(x509)) { \ - ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509_free(void *ptr) static const rb_data_type_t ossl_x509_type = { "OpenSSL/X509", { - 0, ossl_x509_free, + 0, ossl_x509_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -48,13 +48,14 @@ static const rb_data_type_t ossl_x509_type = { * Public */ VALUE -ossl_x509_new(X509 *x509) +ossl_x509_new(const X509 *x509) { X509 *new; VALUE obj; obj = NewX509(cX509Cert); - new = X509_dup(x509); + /* OpenSSL 1.1.1 takes a non-const pointer */ + new = X509_dup((X509 *)x509); if (!new) ossl_raise(eX509CertError, "X509_dup"); SetX509(obj, new); @@ -115,8 +116,8 @@ ossl_x509_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { - /* create just empty X509Cert */ - return self; + /* create just empty X509Cert */ + return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); @@ -170,11 +171,11 @@ ossl_x509_to_der(VALUE self) GetX509(self, x509); if ((len = i2d_X509(x509, NULL)) <= 0) - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_X509(x509, &p) <= 0) - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); ossl_str_adjust(str, p); return str; @@ -196,8 +197,8 @@ ossl_x509_to_pem(VALUE self) if (!out) ossl_raise(eX509CertError, NULL); if (!PEM_write_bio_X509(out, x509)) { - BIO_free(out); - ossl_raise(eX509CertError, NULL); + BIO_free(out); + ossl_raise(eX509CertError, NULL); } str = ossl_membio2str(out); @@ -221,8 +222,8 @@ ossl_x509_to_text(VALUE self) if (!out) ossl_raise(eX509CertError, NULL); if (!X509_print(out, x509)) { - BIO_free(out); - ossl_raise(eX509CertError, NULL); + BIO_free(out); + ossl_raise(eX509CertError, NULL); } str = ossl_membio2str(out); @@ -242,7 +243,7 @@ ossl_x509_to_req(VALUE self) GetX509(self, x509); if (!(req = X509_to_X509_REQ(x509, NULL, EVP_md5()))) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } obj = ossl_x509req_new(req); X509_REQ_free(req); @@ -276,11 +277,11 @@ ossl_x509_set_version(VALUE self, VALUE version) long ver; if ((ver = NUM2LONG(version)) < 0) { - ossl_raise(eX509CertError, "version must be >= 0!"); + ossl_raise(eX509CertError, "version must be >= 0!"); } GetX509(self, x509); if (!X509_set_version(x509, ver)) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return version; @@ -310,7 +311,9 @@ ossl_x509_set_serial(VALUE self, VALUE num) X509 *x509; GetX509(self, x509); - X509_set_serialNumber(x509, num_to_asn1integer(num, X509_get_serialNumber(x509))); + if (!X509_set_serialNumber(x509, num_to_asn1integer(num, X509_get_serialNumber(x509)))) { + ossl_raise(eX509CertError, NULL); + } return num; } @@ -318,27 +321,23 @@ ossl_x509_set_serial(VALUE self, VALUE num) /* * call-seq: * cert.signature_algorithm => string + * + * Returns the signature algorithm used to sign this certificate. This returns + * the algorithm name found in the TBSCertificate structure, not the outer + * \Certificate structure. + * + * Returns the long name of the signature algorithm, or the dotted decimal + * notation if \OpenSSL does not define a long name for it. */ static VALUE ossl_x509_get_signature_algorithm(VALUE self) { X509 *x509; - BIO *out; const ASN1_OBJECT *obj; - VALUE str; GetX509(self, x509); - out = BIO_new(BIO_s_mem()); - if (!out) ossl_raise(eX509CertError, NULL); - X509_ALGOR_get0(&obj, NULL, NULL, X509_get0_tbs_sigalg(x509)); - if (!i2a_ASN1_OBJECT(out, obj)) { - BIO_free(out); - ossl_raise(eX509CertError, NULL); - } - str = ossl_membio2str(out); - - return str; + return ossl_asn1obj_to_string_long_name(obj); } /* @@ -349,11 +348,11 @@ static VALUE ossl_x509_get_subject(VALUE self) { X509 *x509; - X509_NAME *name; + const X509_NAME *name; GetX509(self, x509); if (!(name = X509_get_subject_name(x509))) { /* NO DUP - don't free! */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return ossl_x509name_new(name); @@ -370,7 +369,7 @@ ossl_x509_set_subject(VALUE self, VALUE subject) GetX509(self, x509); if (!X509_set_subject_name(x509, GetX509NamePtr(subject))) { /* DUPs name */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return subject; @@ -384,11 +383,11 @@ static VALUE ossl_x509_get_issuer(VALUE self) { X509 *x509; - X509_NAME *name; + const X509_NAME *name; GetX509(self, x509); if(!(name = X509_get_issuer_name(x509))) { /* NO DUP - don't free! */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return ossl_x509name_new(name); @@ -405,7 +404,7 @@ ossl_x509_set_issuer(VALUE self, VALUE issuer) GetX509(self, x509); if (!X509_set_issuer_name(x509, GetX509NamePtr(issuer))) { /* DUPs name */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return issuer; @@ -423,7 +422,7 @@ ossl_x509_get_not_before(VALUE self) GetX509(self, x509); if (!(asn1time = X509_get0_notBefore(x509))) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return asn1time_to_time(asn1time); @@ -442,8 +441,8 @@ ossl_x509_set_not_before(VALUE self, VALUE time) GetX509(self, x509); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_set1_notBefore(x509, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CertError, "X509_set_notBefore"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CertError, "X509_set_notBefore"); } ASN1_TIME_free(asn1time); @@ -462,7 +461,7 @@ ossl_x509_get_not_after(VALUE self) GetX509(self, x509); if (!(asn1time = X509_get0_notAfter(x509))) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return asn1time_to_time(asn1time); @@ -481,8 +480,8 @@ ossl_x509_set_not_after(VALUE self, VALUE time) GetX509(self, x509); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_set1_notAfter(x509, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CertError, "X509_set_notAfter"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CertError, "X509_set_notAfter"); } ASN1_TIME_free(asn1time); @@ -501,7 +500,7 @@ ossl_x509_get_public_key(VALUE self) GetX509(self, x509); if (!(pkey = X509_get_pubkey(x509))) { /* adds an reference */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return ossl_pkey_wrap(pkey); @@ -521,7 +520,7 @@ ossl_x509_set_public_key(VALUE self, VALUE key) pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!X509_set_pubkey(x509, pkey)) - ossl_raise(eX509CertError, "X509_set_pubkey"); + ossl_raise(eX509CertError, "X509_set_pubkey"); return key; } @@ -535,17 +534,14 @@ ossl_x509_sign(VALUE self, VALUE key, VALUE digest) X509 *x509; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - if (NIL_P(digest)) { - md = NULL; /* needed for some key types, e.g. Ed25519 */ - } else { - md = ossl_evp_get_digestbyname(digest); - } + /* NULL needed for some key types, e.g. Ed25519 */ + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); GetX509(self, x509); - if (!X509_sign(x509, pkey, md)) { - ossl_raise(eX509CertError, NULL); - } + if (!X509_sign(x509, pkey, md)) + ossl_raise(eX509CertError, "X509_sign"); return self; } @@ -568,12 +564,12 @@ ossl_x509_verify(VALUE self, VALUE key) ossl_pkey_check_public_key(pkey); switch (X509_verify(x509, pkey)) { case 1: - return Qtrue; + return Qtrue; case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; default: - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } } @@ -594,8 +590,8 @@ ossl_x509_check_private_key(VALUE self, VALUE key) pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ GetX509(self, x509); if (!X509_check_private_key(x509, pkey)) { - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; } return Qtrue; @@ -610,15 +606,14 @@ ossl_x509_get_extensions(VALUE self) { X509 *x509; int count, i; - X509_EXTENSION *ext; VALUE ary; GetX509(self, x509); count = X509_get_ext_count(x509); ary = rb_ary_new_capa(count); for (i=0; i<count; i++) { - ext = X509_get_ext(x509, i); /* NO DUP - don't free! */ - rb_ary_push(ary, ossl_x509ext_new(ext)); + const X509_EXTENSION *ext = X509_get_ext(x509, i); + rb_ary_push(ary, ossl_x509ext_new(ext)); } return ary; @@ -638,16 +633,16 @@ ossl_x509_set_extensions(VALUE self, VALUE ary) Check_Type(ary, T_ARRAY); /* All ary's members should be X509Extension */ for (i=0; i<RARRAY_LEN(ary); i++) { - OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); + OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); } GetX509(self, x509); for (i = X509_get_ext_count(x509); i > 0; i--) X509_EXTENSION_free(X509_delete_ext(x509, 0)); for (i=0; i<RARRAY_LEN(ary); i++) { - ext = GetX509ExtPtr(RARRAY_AREF(ary, i)); - if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext */ - ossl_raise(eX509CertError, "X509_add_ext"); - } + ext = GetX509ExtPtr(RARRAY_AREF(ary, i)); + if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext */ + ossl_raise(eX509CertError, "X509_add_ext"); + } } return ary; @@ -666,32 +661,24 @@ ossl_x509_add_extension(VALUE self, VALUE extension) GetX509(self, x509); ext = GetX509ExtPtr(extension); if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext - FREE it */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return extension; } -static VALUE -ossl_x509_inspect(VALUE self) -{ - return rb_sprintf("#<%"PRIsVALUE": subject=%+"PRIsVALUE", " - "issuer=%+"PRIsVALUE", serial=%+"PRIsVALUE", " - "not_before=%+"PRIsVALUE", not_after=%+"PRIsVALUE">", - rb_obj_class(self), - ossl_x509_get_subject(self), - ossl_x509_get_issuer(self), - ossl_x509_get_serial(self), - ossl_x509_get_not_before(self), - ossl_x509_get_not_after(self)); -} - /* * call-seq: * cert1 == cert2 -> true | false * * Compares the two certificates. Note that this takes into account all fields, * not just the issuer name and the serial number. + * + * This method uses X509_cmp() from OpenSSL, which compares certificates based + * on their cached DER encodings. The comparison can be unreliable if a + * certificate is incomplete. + * + * See also the man page X509_cmp(3). */ static VALUE ossl_x509_eq(VALUE self, VALUE other) @@ -700,7 +687,7 @@ ossl_x509_eq(VALUE self, VALUE other) GetX509(self, a); if (!rb_obj_is_kind_of(other, cX509Cert)) - return Qfalse; + return Qfalse; GetX509(other, b); return !X509_cmp(a, b) ? Qtrue : Qfalse; @@ -795,7 +782,7 @@ load_chained_certificates_PEM(BIO *in) { certificates = load_chained_certificates_append(Qnil, certificate); while ((certificate = PEM_read_bio_X509(in, NULL, NULL, NULL))) { - load_chained_certificates_append(certificates, certificate); + load_chained_certificates_append(certificates, certificate); } /* We tried to read one more certificate but could not read start line: */ @@ -893,12 +880,6 @@ ossl_x509_load(VALUE klass, VALUE buffer) void Init_ossl_x509cert(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509CertError = rb_define_class_under(mX509, "CertificateError", eOSSLError); /* Document-class: OpenSSL::X509::Certificate @@ -1026,7 +1007,6 @@ Init_ossl_x509cert(void) rb_define_method(cX509Cert, "extensions", ossl_x509_get_extensions, 0); rb_define_method(cX509Cert, "extensions=", ossl_x509_set_extensions, 1); rb_define_method(cX509Cert, "add_extension", ossl_x509_add_extension, 1); - rb_define_method(cX509Cert, "inspect", ossl_x509_inspect, 0); rb_define_method(cX509Cert, "==", ossl_x509_eq, 1); rb_define_method(cX509Cert, "tbs_bytes", ossl_x509_tbs_bytes, 0); } diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index 52174d1711..9b59bda9e2 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509crl_type, 0) #define SetX509CRL(obj, crl) do { \ if (!(crl)) { \ - ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (crl); \ } while (0) #define GetX509CRL(obj, crl) do { \ TypedData_Get_Struct((obj), X509_CRL, &ossl_x509crl_type, (crl)); \ if (!(crl)) { \ - ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509crl_free(void *ptr) static const rb_data_type_t ossl_x509crl_type = { "OpenSSL/X509/CRL", { - 0, ossl_x509crl_free, + 0, ossl_x509crl_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -58,13 +58,14 @@ GetX509CRLPtr(VALUE obj) } VALUE -ossl_x509crl_new(X509_CRL *crl) +ossl_x509crl_new(const X509_CRL *crl) { X509_CRL *tmp; VALUE obj; obj = NewX509CRL(cX509CRL); - tmp = X509_CRL_dup(crl); + /* OpenSSL 1.1.1 takes a non-const pointer */ + tmp = X509_CRL_dup((X509_CRL *)crl); if (!tmp) ossl_raise(eX509CRLError, "X509_CRL_dup"); SetX509CRL(obj, tmp); @@ -83,7 +84,7 @@ ossl_x509crl_alloc(VALUE klass) obj = NewX509CRL(klass); if (!(crl = X509_CRL_new())) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } SetX509CRL(obj, crl); @@ -99,7 +100,7 @@ ossl_x509crl_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { - return self; + return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); @@ -129,7 +130,7 @@ ossl_x509crl_copy(VALUE self, VALUE other) GetX509CRL(self, a); GetX509CRL(other, b); if (!(crl = X509_CRL_dup(b))) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } X509_CRL_free(a); DATA_PTR(self) = crl; @@ -156,36 +157,36 @@ ossl_x509crl_set_version(VALUE self, VALUE version) long ver; if ((ver = NUM2LONG(version)) < 0) { - ossl_raise(eX509CRLError, "version must be >= 0!"); + ossl_raise(eX509CRLError, "version must be >= 0!"); } GetX509CRL(self, crl); if (!X509_CRL_set_version(crl, ver)) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } return version; } +/* + * call-seq: + * crl.signature_algorithm -> string + * + * Returns the signature algorithm used to sign this CRL. + * + * Returns the long name of the signature algorithm, or the dotted decimal + * notation if \OpenSSL does not define a long name for it. + */ static VALUE ossl_x509crl_get_signature_algorithm(VALUE self) { X509_CRL *crl; const X509_ALGOR *alg; const ASN1_OBJECT *obj; - BIO *out; GetX509CRL(self, crl); - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509CRLError, NULL); - } X509_CRL_get0_signature(crl, NULL, &alg); X509_ALGOR_get0(&obj, NULL, NULL, alg); - if (!i2a_ASN1_OBJECT(out, obj)) { - BIO_free(out); - ossl_raise(eX509CRLError, NULL); - } - - return ossl_membio2str(out); + return ossl_asn1obj_to_string_long_name(obj); } static VALUE @@ -206,7 +207,7 @@ ossl_x509crl_set_issuer(VALUE self, VALUE issuer) GetX509CRL(self, crl); if (!X509_CRL_set_issuer_name(crl, GetX509NamePtr(issuer))) { /* DUPs name */ - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } return issuer; } @@ -220,7 +221,7 @@ ossl_x509crl_get_last_update(VALUE self) GetX509CRL(self, crl); time = X509_CRL_get0_lastUpdate(crl); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -234,8 +235,8 @@ ossl_x509crl_set_last_update(VALUE self, VALUE time) GetX509CRL(self, crl); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_CRL_set1_lastUpdate(crl, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CRLError, "X509_CRL_set_lastUpdate"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CRLError, "X509_CRL_set_lastUpdate"); } ASN1_TIME_free(asn1time); @@ -251,7 +252,7 @@ ossl_x509crl_get_next_update(VALUE self) GetX509CRL(self, crl); time = X509_CRL_get0_nextUpdate(crl); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -265,8 +266,8 @@ ossl_x509crl_set_next_update(VALUE self, VALUE time) GetX509CRL(self, crl); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_CRL_set1_nextUpdate(crl, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CRLError, "X509_CRL_set_nextUpdate"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CRLError, "X509_CRL_set_nextUpdate"); } ASN1_TIME_free(asn1time); @@ -289,8 +290,8 @@ ossl_x509crl_get_revoked(VALUE self) num = sk_X509_REVOKED_num(sk); ary = rb_ary_new_capa(num); for(i=0; i<num; i++) { - X509_REVOKED *rev = sk_X509_REVOKED_value(sk, i); - rb_ary_push(ary, ossl_x509revoked_new(rev)); + const X509_REVOKED *rev = sk_X509_REVOKED_value(sk, i); + rb_ary_push(ary, ossl_x509revoked_new(rev)); } return ary; @@ -307,19 +308,19 @@ ossl_x509crl_set_revoked(VALUE self, VALUE ary) Check_Type(ary, T_ARRAY); /* All ary members should be X509 Revoked */ for (i=0; i<RARRAY_LEN(ary); i++) { - OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Rev); + OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Rev); } GetX509CRL(self, crl); if ((sk = X509_CRL_get_REVOKED(crl))) { - while ((rev = sk_X509_REVOKED_pop(sk))) - X509_REVOKED_free(rev); + while ((rev = sk_X509_REVOKED_pop(sk))) + X509_REVOKED_free(rev); } for (i=0; i<RARRAY_LEN(ary); i++) { - rev = DupX509RevokedPtr(RARRAY_AREF(ary, i)); - if (!X509_CRL_add0_revoked(crl, rev)) { /* NO DUP - don't free! */ - X509_REVOKED_free(rev); - ossl_raise(eX509CRLError, "X509_CRL_add0_revoked"); - } + rev = DupX509RevokedPtr(RARRAY_AREF(ary, i)); + if (!X509_CRL_add0_revoked(crl, rev)) { /* NO DUP - don't free! */ + X509_REVOKED_free(rev); + ossl_raise(eX509CRLError, "X509_CRL_add0_revoked"); + } } X509_CRL_sort(crl); @@ -335,8 +336,8 @@ ossl_x509crl_add_revoked(VALUE self, VALUE revoked) GetX509CRL(self, crl); rev = DupX509RevokedPtr(revoked); if (!X509_CRL_add0_revoked(crl, rev)) { /* NO DUP - don't free! */ - X509_REVOKED_free(rev); - ossl_raise(eX509CRLError, "X509_CRL_add0_revoked"); + X509_REVOKED_free(rev); + ossl_raise(eX509CRLError, "X509_CRL_add0_revoked"); } X509_CRL_sort(crl); @@ -349,17 +350,14 @@ ossl_x509crl_sign(VALUE self, VALUE key, VALUE digest) X509_CRL *crl; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; GetX509CRL(self, crl); pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - if (NIL_P(digest)) { - md = NULL; /* needed for some key types, e.g. Ed25519 */ - } else { - md = ossl_evp_get_digestbyname(digest); - } - if (!X509_CRL_sign(crl, pkey, md)) { - ossl_raise(eX509CRLError, NULL); - } + /* NULL needed for some key types, e.g. Ed25519 */ + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); + if (!X509_CRL_sign(crl, pkey, md)) + ossl_raise(eX509CRLError, "X509_CRL_sign"); return self; } @@ -375,12 +373,12 @@ ossl_x509crl_verify(VALUE self, VALUE key) ossl_pkey_check_public_key(pkey); switch (X509_CRL_verify(crl, pkey)) { case 1: - return Qtrue; + return Qtrue; case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; default: - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } } @@ -392,11 +390,11 @@ ossl_x509crl_to_der(VALUE self) GetX509CRL(self, crl); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } if (!i2d_X509_CRL_bio(out, crl)) { - BIO_free(out); - ossl_raise(eX509CRLError, NULL); + BIO_free(out); + ossl_raise(eX509CRLError, NULL); } return ossl_membio2str(out); @@ -410,11 +408,11 @@ ossl_x509crl_to_pem(VALUE self) GetX509CRL(self, crl); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } if (!PEM_write_bio_X509_CRL(out, crl)) { - BIO_free(out); - ossl_raise(eX509CRLError, NULL); + BIO_free(out); + ossl_raise(eX509CRLError, NULL); } return ossl_membio2str(out); @@ -428,11 +426,11 @@ ossl_x509crl_to_text(VALUE self) GetX509CRL(self, crl); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } if (!X509_CRL_print(out, crl)) { - BIO_free(out); - ossl_raise(eX509CRLError, NULL); + BIO_free(out); + ossl_raise(eX509CRLError, NULL); } return ossl_membio2str(out); @@ -446,15 +444,14 @@ ossl_x509crl_get_extensions(VALUE self) { X509_CRL *crl; int count, i; - X509_EXTENSION *ext; VALUE ary; GetX509CRL(self, crl); count = X509_CRL_get_ext_count(crl); ary = rb_ary_new_capa(count); for (i=0; i<count; i++) { - ext = X509_CRL_get_ext(crl, i); /* NO DUP - don't free! */ - rb_ary_push(ary, ossl_x509ext_new(ext)); + const X509_EXTENSION *ext = X509_CRL_get_ext(crl, i); + rb_ary_push(ary, ossl_x509ext_new(ext)); } return ary; @@ -473,16 +470,16 @@ ossl_x509crl_set_extensions(VALUE self, VALUE ary) Check_Type(ary, T_ARRAY); /* All ary members should be X509 Extensions */ for (i=0; i<RARRAY_LEN(ary); i++) { - OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); + OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); } GetX509CRL(self, crl); for (i = X509_CRL_get_ext_count(crl); i > 0; i--) X509_EXTENSION_free(X509_CRL_delete_ext(crl, 0)); for (i=0; i<RARRAY_LEN(ary); i++) { - ext = GetX509ExtPtr(RARRAY_AREF(ary, i)); /* NO NEED TO DUP */ - if (!X509_CRL_add_ext(crl, ext, -1)) { - ossl_raise(eX509CRLError, "X509_CRL_add_ext"); - } + ext = GetX509ExtPtr(RARRAY_AREF(ary, i)); /* NO NEED TO DUP */ + if (!X509_CRL_add_ext(crl, ext, -1)) { + ossl_raise(eX509CRLError, "X509_CRL_add_ext"); + } } return ary; @@ -497,7 +494,7 @@ ossl_x509crl_add_extension(VALUE self, VALUE extension) GetX509CRL(self, crl); ext = GetX509ExtPtr(extension); if (!X509_CRL_add_ext(crl, ext, -1)) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } return extension; @@ -509,12 +506,6 @@ ossl_x509crl_add_extension(VALUE self, VALUE extension) void Init_ossl_x509crl(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509CRLError = rb_define_class_under(mX509, "CRLError", eOSSLError); cX509CRL = rb_define_class_under(mX509, "CRL", rb_cObject); diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c index 01aa3a8f51..1fe727d3f1 100644 --- a/ext/openssl/ossl_x509ext.c +++ b/ext/openssl/ossl_x509ext.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509ext_type, 0) #define SetX509Ext(obj, ext) do { \ if (!(ext)) { \ - ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (ext); \ } while (0) #define GetX509Ext(obj, ext) do { \ TypedData_Get_Struct((obj), X509_EXTENSION, &ossl_x509ext_type, (ext)); \ if (!(ext)) { \ - ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \ } \ } while (0) #define MakeX509ExtFactory(klass, obj, ctx) do { \ @@ -33,7 +33,7 @@ #define GetX509ExtFactory(obj, ctx) do { \ TypedData_Get_Struct((obj), X509V3_CTX, &ossl_x509extfactory_type, (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "CTX wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CTX wasn't initialized!"); \ } \ } while (0) @@ -53,7 +53,7 @@ ossl_x509ext_free(void *ptr) static const rb_data_type_t ossl_x509ext_type = { "OpenSSL/X509/EXTENSION", { - 0, ossl_x509ext_free, + 0, ossl_x509ext_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -62,13 +62,14 @@ static const rb_data_type_t ossl_x509ext_type = { * Public */ VALUE -ossl_x509ext_new(X509_EXTENSION *ext) +ossl_x509ext_new(const X509_EXTENSION *ext) { X509_EXTENSION *new; VALUE obj; obj = NewX509Ext(cX509Ext); - new = X509_EXTENSION_dup(ext); + /* OpenSSL 1.1.1 takes a non-const pointer */ + new = X509_EXTENSION_dup((X509_EXTENSION *)ext); if (!new) ossl_raise(eX509ExtError, "X509_EXTENSION_dup"); SetX509Ext(obj, new); @@ -101,7 +102,7 @@ ossl_x509extfactory_free(void *ctx) static const rb_data_type_t ossl_x509extfactory_type = { "OpenSSL/X509/EXTENSION/Factory", { - 0, ossl_x509extfactory_free, + 0, ossl_x509extfactory_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -175,15 +176,15 @@ ossl_x509extfactory_initialize(int argc, VALUE *argv, VALUE self) /*GetX509ExtFactory(self, ctx);*/ rb_scan_args(argc, argv, "04", - &issuer_cert, &subject_cert, &subject_req, &crl); + &issuer_cert, &subject_cert, &subject_req, &crl); if (!NIL_P(issuer_cert)) - ossl_x509extfactory_set_issuer_cert(self, issuer_cert); + ossl_x509extfactory_set_issuer_cert(self, issuer_cert); if (!NIL_P(subject_cert)) - ossl_x509extfactory_set_subject_cert(self, subject_cert); + ossl_x509extfactory_set_subject_cert(self, subject_cert); if (!NIL_P(subject_req)) - ossl_x509extfactory_set_subject_req(self, subject_req); + ossl_x509extfactory_set_subject_req(self, subject_req); if (!NIL_P(crl)) - ossl_x509extfactory_set_crl(self, crl); + ossl_x509extfactory_set_crl(self, crl); return self; } @@ -213,7 +214,7 @@ ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self) oid_cstr = StringValueCStr(oid); nid = OBJ_ln2nid(oid_cstr); if (nid != NID_undef) - oid_cstr = OBJ_nid2sn(nid); + oid_cstr = OBJ_nid2sn(nid); valstr = rb_str_new2(RTEST(critical) ? "critical," : ""); rb_str_append(valstr, value); @@ -228,7 +229,7 @@ ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self) ext = X509V3_EXT_nconf(conf, ctx, oid_cstr, RSTRING_PTR(valstr)); X509V3_set_ctx_nodb(ctx); if (!ext){ - ossl_raise(eX509ExtError, "%"PRIsVALUE" = %"PRIsVALUE, oid, valstr); + ossl_raise(eX509ExtError, "%"PRIsVALUE" = %"PRIsVALUE, oid, valstr); } SetX509Ext(obj, ext); @@ -246,7 +247,7 @@ ossl_x509ext_alloc(VALUE klass) obj = NewX509Ext(klass); if(!(ext = X509_EXTENSION_new())){ - ossl_raise(eX509ExtError, NULL); + ossl_raise(eX509ExtError, NULL); } SetX509Ext(obj, ext); @@ -274,14 +275,14 @@ ossl_x509ext_initialize(int argc, VALUE *argv, VALUE self) GetX509Ext(self, ext); if(rb_scan_args(argc, argv, "12", &oid, &value, &critical) == 1){ - oid = ossl_to_der_if_possible(oid); - StringValue(oid); - p = (unsigned char *)RSTRING_PTR(oid); - x = d2i_X509_EXTENSION(&ext, &p, RSTRING_LEN(oid)); - DATA_PTR(self) = ext; - if(!x) - ossl_raise(eX509ExtError, NULL); - return self; + oid = ossl_to_der_if_possible(oid); + StringValue(oid); + p = (unsigned char *)RSTRING_PTR(oid); + x = d2i_X509_EXTENSION(&ext, &p, RSTRING_LEN(oid)); + DATA_PTR(self) = ext; + if(!x) + ossl_raise(eX509ExtError, NULL); + return self; } rb_funcall(self, rb_intern("oid="), 1, oid); rb_funcall(self, rb_intern("value="), 1, value); @@ -302,7 +303,7 @@ ossl_x509ext_initialize_copy(VALUE self, VALUE other) ext_new = X509_EXTENSION_dup(ext_other); if (!ext_new) - ossl_raise(eX509ExtError, "X509_EXTENSION_dup"); + ossl_raise(eX509ExtError, "X509_EXTENSION_dup"); SetX509Ext(self, ext_new); X509_EXTENSION_free(ext); @@ -319,10 +320,10 @@ ossl_x509ext_set_oid(VALUE self, VALUE oid) GetX509Ext(self, ext); obj = OBJ_txt2obj(StringValueCStr(oid), 0); if (!obj) - ossl_raise(eX509ExtError, "OBJ_txt2obj"); + ossl_raise(eX509ExtError, "OBJ_txt2obj"); if (!X509_EXTENSION_set_object(ext, obj)) { - ASN1_OBJECT_free(obj); - ossl_raise(eX509ExtError, "X509_EXTENSION_set_object"); + ASN1_OBJECT_free(obj); + ossl_raise(eX509ExtError, "X509_EXTENSION_set_object"); } ASN1_OBJECT_free(obj); @@ -338,12 +339,20 @@ ossl_x509ext_set_value(VALUE self, VALUE data) GetX509Ext(self, ext); data = ossl_to_der_if_possible(data); StringValue(data); - asn1s = X509_EXTENSION_get_data(ext); + asn1s = ASN1_OCTET_STRING_new(); + if (!asn1s) + ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_new"); if (!ASN1_OCTET_STRING_set(asn1s, (unsigned char *)RSTRING_PTR(data), - RSTRING_LENINT(data))) { - ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_set"); + RSTRING_LENINT(data))) { + ASN1_OCTET_STRING_free(asn1s); + ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_set"); + } + if (!X509_EXTENSION_set_data(ext, asn1s)) { + ASN1_OCTET_STRING_free(asn1s); + ossl_raise(eX509ExtError, "X509_EXTENSION_set_data"); } + ASN1_OCTET_STRING_free(asn1s); return data; } @@ -359,27 +368,20 @@ ossl_x509ext_set_critical(VALUE self, VALUE flag) return flag; } +/* + * call-seq: + * ext.oid -> string + * + * Returns the OID of the extension. Returns the short name or the dotted + * decimal notation. + */ static VALUE ossl_x509ext_get_oid(VALUE obj) { X509_EXTENSION *ext; - ASN1_OBJECT *extobj; - BIO *out; - VALUE ret; - int nid; GetX509Ext(obj, ext); - extobj = X509_EXTENSION_get_object(ext); - if ((nid = OBJ_obj2nid(extobj)) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); - else{ - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eX509ExtError, NULL); - i2a_ASN1_OBJECT(out, extobj); - ret = ossl_membio2str(out); - } - - return ret; + return ossl_asn1obj_to_string(X509_EXTENSION_get_object(ext)); } static VALUE @@ -391,9 +393,9 @@ ossl_x509ext_get_value(VALUE obj) GetX509Ext(obj, ext); if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eX509ExtError, NULL); + ossl_raise(eX509ExtError, NULL); if (!X509V3_EXT_print(out, ext, 0, 0)) - ASN1_STRING_print(out, (ASN1_STRING *)X509_EXTENSION_get_data(ext)); + ASN1_STRING_print(out, X509_EXTENSION_get_data(ext)); ret = ossl_membio2str(out); return ret; @@ -403,13 +405,13 @@ static VALUE ossl_x509ext_get_value_der(VALUE obj) { X509_EXTENSION *ext; - ASN1_OCTET_STRING *value; + const ASN1_OCTET_STRING *value; GetX509Ext(obj, ext); if ((value = X509_EXTENSION_get_data(ext)) == NULL) - ossl_raise(eX509ExtError, NULL); + ossl_raise(eX509ExtError, NULL); - return rb_str_new((const char *)value->data, value->length); + return asn1str_to_str(value); } static VALUE @@ -431,11 +433,11 @@ ossl_x509ext_to_der(VALUE obj) GetX509Ext(obj, ext); if((len = i2d_X509_EXTENSION(ext, NULL)) <= 0) - ossl_raise(eX509ExtError, NULL); + ossl_raise(eX509ExtError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_X509_EXTENSION(ext, &p) < 0) - ossl_raise(eX509ExtError, NULL); + ossl_raise(eX509ExtError, NULL); ossl_str_adjust(str, p); return str; @@ -448,12 +450,6 @@ void Init_ossl_x509ext(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509ExtError = rb_define_class_under(mX509, "ExtensionError", eOSSLError); cX509ExtFactory = rb_define_class_under(mX509, "ExtensionFactory", rb_cObject); diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 7d0fd35247..eb97b23c02 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -13,21 +13,21 @@ TypedData_Wrap_Struct((klass), &ossl_x509name_type, 0) #define SetX509Name(obj, name) do { \ if (!(name)) { \ - ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (name); \ } while (0) #define GetX509Name(obj, name) do { \ TypedData_Get_Struct((obj), X509_NAME, &ossl_x509name_type, (name)); \ if (!(name)) { \ - ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ } \ } while (0) #define OBJECT_TYPE_TEMPLATE \ - rb_const_get(cX509Name, rb_intern("OBJECT_TYPE_TEMPLATE")) + rb_const_get(cX509Name, rb_intern("OBJECT_TYPE_TEMPLATE")) #define DEFAULT_OBJECT_TYPE \ - rb_const_get(cX509Name, rb_intern("DEFAULT_OBJECT_TYPE")) + rb_const_get(cX509Name, rb_intern("DEFAULT_OBJECT_TYPE")) /* * Classes @@ -44,7 +44,7 @@ ossl_x509name_free(void *ptr) static const rb_data_type_t ossl_x509name_type = { "OpenSSL/X509/NAME", { - 0, ossl_x509name_free, + 0, ossl_x509name_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -53,13 +53,14 @@ static const rb_data_type_t ossl_x509name_type = { * Public */ VALUE -ossl_x509name_new(X509_NAME *name) +ossl_x509name_new(const X509_NAME *name) { X509_NAME *new; VALUE obj; obj = NewX509Name(cX509Name); - new = X509_NAME_dup(name); + /* OpenSSL 1.1.1 takes a non-const pointer */ + new = X509_NAME_dup((X509_NAME *)name); if (!new) ossl_raise(eX509NameError, "X509_NAME_dup"); SetX509Name(obj, new); @@ -88,7 +89,7 @@ ossl_x509name_alloc(VALUE klass) obj = NewX509Name(klass); if (!(name = X509_NAME_new())) { - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); } SetX509Name(obj, name); @@ -145,28 +146,28 @@ ossl_x509name_initialize(int argc, VALUE *argv, VALUE self) GetX509Name(self, name); if (rb_scan_args(argc, argv, "02", &arg, &template) == 0) { - return self; + return self; } else { - VALUE tmp = rb_check_array_type(arg); - if (!NIL_P(tmp)) { - VALUE args; - if(NIL_P(template)) template = OBJECT_TYPE_TEMPLATE; - args = rb_ary_new3(2, self, template); - rb_block_call(tmp, rb_intern("each"), 0, 0, ossl_x509name_init_i, args); - } - else{ - const unsigned char *p; - VALUE str = ossl_to_der_if_possible(arg); - X509_NAME *x; - StringValue(str); - p = (unsigned char *)RSTRING_PTR(str); - x = d2i_X509_NAME(&name, &p, RSTRING_LEN(str)); - DATA_PTR(self) = name; - if(!x){ - ossl_raise(eX509NameError, NULL); - } - } + VALUE tmp = rb_check_array_type(arg); + if (!NIL_P(tmp)) { + VALUE args; + if(NIL_P(template)) template = OBJECT_TYPE_TEMPLATE; + args = rb_ary_new3(2, self, template); + rb_block_call(tmp, rb_intern("each"), 0, 0, ossl_x509name_init_i, args); + } + else{ + const unsigned char *p; + VALUE str = ossl_to_der_if_possible(arg); + X509_NAME *x; + StringValue(str); + p = (unsigned char *)RSTRING_PTR(str); + x = d2i_X509_NAME(&name, &p, RSTRING_LEN(str)); + DATA_PTR(self) = name; + if(!x){ + ossl_raise(eX509NameError, NULL); + } + } } return self; @@ -184,7 +185,7 @@ ossl_x509name_initialize_copy(VALUE self, VALUE other) name_new = X509_NAME_dup(name_other); if (!name_new) - ossl_raise(eX509NameError, "X509_NAME_dup"); + ossl_raise(eX509NameError, "X509_NAME_dup"); SetX509Name(self, name_new); X509_NAME_free(name); @@ -221,8 +222,8 @@ VALUE ossl_x509name_add_entry(int argc, VALUE *argv, VALUE self) int loc = -1, set = 0; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("loc"); - kwargs_ids[1] = rb_intern_const("set"); + kwargs_ids[0] = rb_intern_const("loc"); + kwargs_ids[1] = rb_intern_const("set"); } rb_scan_args(argc, argv, "21:", &oid, &value, &type, &opts); rb_get_kwargs(opts, kwargs_ids, 0, 2, kwargs); @@ -230,14 +231,14 @@ VALUE ossl_x509name_add_entry(int argc, VALUE *argv, VALUE self) StringValue(value); if(NIL_P(type)) type = rb_aref(OBJECT_TYPE_TEMPLATE, oid); if (kwargs[0] != Qundef) - loc = NUM2INT(kwargs[0]); + loc = NUM2INT(kwargs[0]); if (kwargs[1] != Qundef) - set = NUM2INT(kwargs[1]); + set = NUM2INT(kwargs[1]); GetX509Name(self, name); if (!X509_NAME_add_entry_by_txt(name, oid_name, NUM2INT(type), - (unsigned char *)RSTRING_PTR(value), - RSTRING_LENINT(value), loc, set)) - ossl_raise(eX509NameError, "X509_NAME_add_entry_by_txt"); + (unsigned char *)RSTRING_PTR(value), + RSTRING_LENINT(value), loc, set)) + ossl_raise(eX509NameError, "X509_NAME_add_entry_by_txt"); return self; } @@ -250,7 +251,7 @@ ossl_x509name_to_s_old(VALUE self) GetX509Name(self, name); buf = X509_NAME_oneline(name, NULL, 0); if (!buf) - ossl_raise(eX509NameError, "X509_NAME_oneline"); + ossl_raise(eX509NameError, "X509_NAME_oneline"); return ossl_buf2str(buf, rb_long2int(strlen(buf))); } @@ -264,11 +265,11 @@ x509name_print(VALUE self, unsigned long iflag) GetX509Name(self, name); out = BIO_new(BIO_s_mem()); if (!out) - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); ret = X509_NAME_print_ex(out, name, 0, iflag); if (ret < 0 || (iflag == XN_FLAG_COMPAT && ret == 0)) { - BIO_free(out); - ossl_raise(eX509NameError, "X509_NAME_print_ex"); + BIO_free(out); + ossl_raise(eX509NameError, "X509_NAME_print_ex"); } return ossl_membio2str(out); } @@ -302,9 +303,9 @@ ossl_x509name_to_s(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 0, 1); /* name.to_s(nil) was allowed */ if (!argc || NIL_P(argv[0])) - return ossl_x509name_to_s_old(self); + return ossl_x509name_to_s_old(self); else - return x509name_print(self, NUM2ULONG(argv[0])); + return x509name_print(self, NUM2ULONG(argv[0])); } /* @@ -327,7 +328,7 @@ static VALUE ossl_x509name_inspect(VALUE self) { return rb_enc_sprintf(rb_utf8_encoding(), "#<%"PRIsVALUE" %"PRIsVALUE">", - rb_obj_class(self), ossl_x509name_to_utf8(self)); + rb_obj_class(self), ossl_x509name_to_utf8(self)); } /* @@ -341,34 +342,22 @@ static VALUE ossl_x509name_to_a(VALUE self) { X509_NAME *name; - X509_NAME_ENTRY *entry; - int i,entries,nid; - char long_name[512]; - const char *short_name; - VALUE ary, vname, ret; - ASN1_STRING *value; + int entries; + VALUE ret; GetX509Name(self, name); entries = X509_NAME_entry_count(name); ret = rb_ary_new_capa(entries); - for (i=0; i<entries; i++) { - if (!(entry = X509_NAME_get_entry(name, i))) { - ossl_raise(eX509NameError, NULL); - } - if (!i2t_ASN1_OBJECT(long_name, sizeof(long_name), - X509_NAME_ENTRY_get_object(entry))) { - ossl_raise(eX509NameError, NULL); - } - nid = OBJ_ln2nid(long_name); - if (nid == NID_undef) { - vname = rb_str_new2((const char *) &long_name); - } else { - short_name = OBJ_nid2sn(nid); - vname = rb_str_new2(short_name); /*do not free*/ - } - value = X509_NAME_ENTRY_get_data(entry); - ary = rb_ary_new3(3, vname, asn1str_to_str(value), INT2NUM(value->type)); - rb_ary_push(ret, ary); + for (int i = 0; i < entries; i++) { + const X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); + if (!entry) + ossl_raise(eX509NameError, "X509_NAME_get_entry"); + const ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(entry); + VALUE vname = ossl_asn1obj_to_string(obj); + const ASN1_STRING *data = X509_NAME_ENTRY_get_data(entry); + VALUE vdata = asn1str_to_str(data); + VALUE type = INT2NUM(ASN1_STRING_type(data)); + rb_ary_push(ret, rb_ary_new_from_args(3, vname, vdata, type)); } return ret; } @@ -377,11 +366,17 @@ static int ossl_x509name_cmp0(VALUE self, VALUE other) { X509_NAME *name1, *name2; + int result; GetX509Name(self, name1); GetX509Name(other, name2); - return X509_NAME_cmp(name1, name2); + result = X509_NAME_cmp(name1, name2); + if (result == -2) { + ossl_raise(eX509NameError, NULL); + } + + return result; } /* @@ -399,7 +394,7 @@ ossl_x509name_cmp(VALUE self, VALUE other) int result; if (!rb_obj_is_kind_of(other, cX509Name)) - return Qnil; + return Qnil; result = ossl_x509name_cmp0(self, other); if (result < 0) return INT2FIX(-1); @@ -418,7 +413,7 @@ static VALUE ossl_x509name_eql(VALUE self, VALUE other) { if (!rb_obj_is_kind_of(other, cX509Name)) - return Qfalse; + return Qfalse; return ossl_x509name_cmp0(self, other) == 0 ? Qtrue : Qfalse; } @@ -478,11 +473,11 @@ ossl_x509name_to_der(VALUE self) GetX509Name(self, name); if((len = i2d_X509_NAME(name, NULL)) <= 0) - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_X509_NAME(name, &p) <= 0) - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); ossl_str_adjust(str, p); return str; @@ -508,12 +503,6 @@ Init_ossl_x509name(void) #undef rb_intern VALUE utf8str, ptrstr, ia5str, hash; -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - id_aref = rb_intern("[]"); eX509NameError = rb_define_class_under(mX509, "NameError", eOSSLError); cX509Name = rb_define_class_under(mX509, "Name", rb_cObject); @@ -552,6 +541,7 @@ Init_ossl_x509name(void) rb_hash_aset(hash, rb_str_new2("DC"), ia5str); rb_hash_aset(hash, rb_str_new2("domainComponent"), ia5str); rb_hash_aset(hash, rb_str_new2("emailAddress"), ia5str); + rb_obj_freeze(hash); /* * The default object type template for name entries. diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index b4c29f877e..ad5dd08033 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509req_type, 0) #define SetX509Req(obj, req) do { \ if (!(req)) { \ - ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (req); \ } while (0) #define GetX509Req(obj, req) do { \ TypedData_Get_Struct((obj), X509_REQ, &ossl_x509req_type, (req)); \ if (!(req)) { \ - ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509req_free(void *ptr) static const rb_data_type_t ossl_x509req_type = { "OpenSSL/X509/REQ", { - 0, ossl_x509req_free, + 0, ossl_x509req_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -68,7 +68,7 @@ ossl_x509req_alloc(VALUE klass) obj = NewX509Req(klass); if (!(req = X509_REQ_new())) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } SetX509Req(obj, req); @@ -84,7 +84,7 @@ ossl_x509req_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { - return self; + return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); @@ -114,7 +114,7 @@ ossl_x509req_copy(VALUE self, VALUE other) GetX509Req(self, a); GetX509Req(other, b); if (!(req = X509_REQ_dup(b))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } X509_REQ_free(a); DATA_PTR(self) = req; @@ -130,11 +130,11 @@ ossl_x509req_to_pem(VALUE self) GetX509Req(self, req); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } if (!PEM_write_bio_X509_REQ(out, req)) { - BIO_free(out); - ossl_raise(eX509ReqError, NULL); + BIO_free(out); + ossl_raise(eX509ReqError, NULL); } return ossl_membio2str(out); @@ -150,11 +150,11 @@ ossl_x509req_to_der(VALUE self) GetX509Req(self, req); if ((len = i2d_X509_REQ(req, NULL)) <= 0) - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_X509_REQ(req, &p) <= 0) - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); ossl_str_adjust(str, p); return str; @@ -168,11 +168,11 @@ ossl_x509req_to_text(VALUE self) GetX509Req(self, req); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } if (!X509_REQ_print(out, req)) { - BIO_free(out); - ossl_raise(eX509ReqError, NULL); + BIO_free(out); + ossl_raise(eX509ReqError, NULL); } return ossl_membio2str(out); @@ -191,7 +191,7 @@ ossl_x509req_to_x509(VALUE self, VALUE days, VALUE key) GetX509Req(self, req); ... if (!(x509 = X509_REQ_to_X509(req, d, pkey))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return ossl_x509_new(x509); @@ -217,11 +217,11 @@ ossl_x509req_set_version(VALUE self, VALUE version) long ver; if ((ver = NUM2LONG(version)) < 0) { - ossl_raise(eX509ReqError, "version must be >= 0!"); + ossl_raise(eX509ReqError, "version must be >= 0!"); } GetX509Req(self, req); if (!X509_REQ_set_version(req, ver)) { - ossl_raise(eX509ReqError, "X509_REQ_set_version"); + ossl_raise(eX509ReqError, "X509_REQ_set_version"); } return version; @@ -231,11 +231,11 @@ static VALUE ossl_x509req_get_subject(VALUE self) { X509_REQ *req; - X509_NAME *name; + const X509_NAME *name; GetX509Req(self, req); if (!(name = X509_REQ_get_subject_name(req))) { /* NO DUP - don't free */ - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return ossl_x509name_new(name); @@ -249,33 +249,32 @@ ossl_x509req_set_subject(VALUE self, VALUE subject) GetX509Req(self, req); /* DUPs name */ if (!X509_REQ_set_subject_name(req, GetX509NamePtr(subject))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return subject; } +/* + * call-seq: + * req.signature_algorithm -> string + * + * Returns the signature algorithm used to sign this request. + * + * Returns the long name of the signature algorithm, or the dotted decimal + * notation if \OpenSSL does not define a long name for it. + */ static VALUE ossl_x509req_get_signature_algorithm(VALUE self) { X509_REQ *req; const X509_ALGOR *alg; const ASN1_OBJECT *obj; - BIO *out; GetX509Req(self, req); - - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509ReqError, NULL); - } X509_REQ_get0_signature(req, NULL, &alg); X509_ALGOR_get0(&obj, NULL, NULL, alg); - if (!i2a_ASN1_OBJECT(out, obj)) { - BIO_free(out); - ossl_raise(eX509ReqError, NULL); - } - - return ossl_membio2str(out); + return ossl_asn1obj_to_string_long_name(obj); } static VALUE @@ -286,7 +285,7 @@ ossl_x509req_get_public_key(VALUE self) GetX509Req(self, req); if (!(pkey = X509_REQ_get_pubkey(req))) { /* adds reference */ - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return ossl_pkey_wrap(pkey); @@ -302,7 +301,7 @@ ossl_x509req_set_public_key(VALUE self, VALUE key) pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!X509_REQ_set_pubkey(req, pkey)) - ossl_raise(eX509ReqError, "X509_REQ_set_pubkey"); + ossl_raise(eX509ReqError, "X509_REQ_set_pubkey"); return key; } @@ -312,17 +311,14 @@ ossl_x509req_sign(VALUE self, VALUE key, VALUE digest) X509_REQ *req; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; GetX509Req(self, req); pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - if (NIL_P(digest)) { - md = NULL; /* needed for some key types, e.g. Ed25519 */ - } else { - md = ossl_evp_get_digestbyname(digest); - } - if (!X509_REQ_sign(req, pkey, md)) { - ossl_raise(eX509ReqError, NULL); - } + /* NULL needed for some key types, e.g. Ed25519 */ + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); + if (!X509_REQ_sign(req, pkey, md)) + ossl_raise(eX509ReqError, "X509_REQ_sign"); return self; } @@ -341,12 +337,12 @@ ossl_x509req_verify(VALUE self, VALUE key) ossl_pkey_check_public_key(pkey); switch (X509_REQ_verify(req, pkey)) { case 1: - return Qtrue; + return Qtrue; case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; default: - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } } @@ -355,20 +351,20 @@ ossl_x509req_get_attributes(VALUE self) { X509_REQ *req; int count, i; - X509_ATTRIBUTE *attr; + const X509_ATTRIBUTE *attr; VALUE ary; GetX509Req(self, req); count = X509_REQ_get_attr_count(req); if (count < 0) { - OSSL_Debug("count < 0???"); - return rb_ary_new(); + OSSL_Debug("count < 0???"); + return rb_ary_new(); } ary = rb_ary_new2(count); for (i=0; i<count; i++) { - attr = X509_REQ_get_attr(req, i); - rb_ary_push(ary, ossl_x509attr_new(attr)); + attr = X509_REQ_get_attr(req, i); + rb_ary_push(ary, ossl_x509attr_new(attr)); } return ary; @@ -384,17 +380,17 @@ ossl_x509req_set_attributes(VALUE self, VALUE ary) Check_Type(ary, T_ARRAY); for (i=0;i<RARRAY_LEN(ary); i++) { - OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Attr); + OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Attr); } GetX509Req(self, req); for (i = X509_REQ_get_attr_count(req); i > 0; i--) X509_ATTRIBUTE_free(X509_REQ_delete_attr(req, 0)); for (i=0;i<RARRAY_LEN(ary); i++) { - item = RARRAY_AREF(ary, i); - attr = GetX509AttrPtr(item); - if (!X509_REQ_add1_attr(req, attr)) { - ossl_raise(eX509ReqError, "X509_REQ_add1_attr"); - } + item = RARRAY_AREF(ary, i); + attr = GetX509AttrPtr(item); + if (!X509_REQ_add1_attr(req, attr)) { + ossl_raise(eX509ReqError, "X509_REQ_add1_attr"); + } } return ary; } @@ -406,7 +402,7 @@ ossl_x509req_add_attribute(VALUE self, VALUE attr) GetX509Req(self, req); if (!X509_REQ_add1_attr(req, GetX509AttrPtr(attr))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return attr; @@ -418,12 +414,6 @@ ossl_x509req_add_attribute(VALUE self, VALUE attr) void Init_ossl_x509req(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509ReqError = rb_define_class_under(mX509, "RequestError", eOSSLError); cX509Req = rb_define_class_under(mX509, "Request", rb_cObject); diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c index 9496c4bf1b..0151961e9f 100644 --- a/ext/openssl/ossl_x509revoked.c +++ b/ext/openssl/ossl_x509revoked.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509rev_type, 0) #define SetX509Rev(obj, rev) do { \ if (!(rev)) { \ - ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (rev); \ } while (0) #define GetX509Rev(obj, rev) do { \ TypedData_Get_Struct((obj), X509_REVOKED, &ossl_x509rev_type, (rev)); \ if (!(rev)) { \ - ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509rev_free(void *ptr) static const rb_data_type_t ossl_x509rev_type = { "OpenSSL/X509/REV", { - 0, ossl_x509rev_free, + 0, ossl_x509rev_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -48,15 +48,16 @@ static const rb_data_type_t ossl_x509rev_type = { * PUBLIC */ VALUE -ossl_x509revoked_new(X509_REVOKED *rev) +ossl_x509revoked_new(const X509_REVOKED *rev) { X509_REVOKED *new; VALUE obj; obj = NewX509Rev(cX509Rev); - new = X509_REVOKED_dup(rev); + /* OpenSSL 1.1.1 takes a non-const pointer */ + new = X509_REVOKED_dup((X509_REVOKED *)rev); if (!new) - ossl_raise(eX509RevError, "X509_REVOKED_dup"); + ossl_raise(eX509RevError, "X509_REVOKED_dup"); SetX509Rev(obj, new); return obj; @@ -69,7 +70,7 @@ DupX509RevokedPtr(VALUE obj) GetX509Rev(obj, rev); if (!(new = X509_REVOKED_dup(rev))) { - ossl_raise(eX509RevError, NULL); + ossl_raise(eX509RevError, NULL); } return new; @@ -86,7 +87,7 @@ ossl_x509revoked_alloc(VALUE klass) obj = NewX509Rev(klass); if (!(rev = X509_REVOKED_new())) { - ossl_raise(eX509RevError, NULL); + ossl_raise(eX509RevError, NULL); } SetX509Rev(obj, rev); @@ -112,7 +113,7 @@ ossl_x509revoked_initialize_copy(VALUE self, VALUE other) rev_new = X509_REVOKED_dup(rev_other); if (!rev_new) - ossl_raise(eX509RevError, "X509_REVOKED_dup"); + ossl_raise(eX509RevError, "X509_REVOKED_dup"); SetX509Rev(self, rev_new); X509_REVOKED_free(rev); @@ -139,8 +140,8 @@ ossl_x509revoked_set_serial(VALUE self, VALUE num) GetX509Rev(self, rev); asn1int = num_to_asn1integer(num, NULL); if (!X509_REVOKED_set_serialNumber(rev, asn1int)) { - ASN1_INTEGER_free(asn1int); - ossl_raise(eX509RevError, "X509_REVOKED_set_serialNumber"); + ASN1_INTEGER_free(asn1int); + ossl_raise(eX509RevError, "X509_REVOKED_set_serialNumber"); } ASN1_INTEGER_free(asn1int); @@ -156,7 +157,7 @@ ossl_x509revoked_get_time(VALUE self) GetX509Rev(self, rev); time = X509_REVOKED_get0_revocationDate(rev); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -170,8 +171,8 @@ ossl_x509revoked_set_time(VALUE self, VALUE time) GetX509Rev(self, rev); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_REVOKED_set_revocationDate(rev, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509RevError, "X509_REVOKED_set_revocationDate"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509RevError, "X509_REVOKED_set_revocationDate"); } ASN1_TIME_free(asn1time); @@ -185,15 +186,15 @@ ossl_x509revoked_get_extensions(VALUE self) { X509_REVOKED *rev; int count, i; - X509_EXTENSION *ext; + const X509_EXTENSION *ext; VALUE ary; GetX509Rev(self, rev); count = X509_REVOKED_get_ext_count(rev); ary = rb_ary_new_capa(count); for (i=0; i<count; i++) { - ext = X509_REVOKED_get_ext(rev, i); - rb_ary_push(ary, ossl_x509ext_new(ext)); + ext = X509_REVOKED_get_ext(rev, i); + rb_ary_push(ary, ossl_x509ext_new(ext)); } return ary; @@ -212,17 +213,17 @@ ossl_x509revoked_set_extensions(VALUE self, VALUE ary) Check_Type(ary, T_ARRAY); for (i=0; i<RARRAY_LEN(ary); i++) { - OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); + OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext); } GetX509Rev(self, rev); for (i = X509_REVOKED_get_ext_count(rev); i > 0; i--) X509_EXTENSION_free(X509_REVOKED_delete_ext(rev, 0)); for (i=0; i<RARRAY_LEN(ary); i++) { - item = RARRAY_AREF(ary, i); - ext = GetX509ExtPtr(item); - if(!X509_REVOKED_add_ext(rev, ext, -1)) { - ossl_raise(eX509RevError, "X509_REVOKED_add_ext"); - } + item = RARRAY_AREF(ary, i); + ext = GetX509ExtPtr(item); + if(!X509_REVOKED_add_ext(rev, ext, -1)) { + ossl_raise(eX509RevError, "X509_REVOKED_add_ext"); + } } return ary; @@ -235,7 +236,7 @@ ossl_x509revoked_add_extension(VALUE self, VALUE ext) GetX509Rev(self, rev); if (!X509_REVOKED_add_ext(rev, GetX509ExtPtr(ext), -1)) { - ossl_raise(eX509RevError, NULL); + ossl_raise(eX509RevError, NULL); } return ext; @@ -252,11 +253,11 @@ ossl_x509revoked_to_der(VALUE self) GetX509Rev(self, rev); len = i2d_X509_REVOKED(rev, NULL); if (len <= 0) - ossl_raise(eX509RevError, "i2d_X509_REVOKED"); + ossl_raise(eX509RevError, "i2d_X509_REVOKED"); str = rb_str_new(NULL, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_X509_REVOKED(rev, &p) <= 0) - ossl_raise(eX509RevError, "i2d_X509_REVOKED"); + ossl_raise(eX509RevError, "i2d_X509_REVOKED"); ossl_str_adjust(str, p); return str; } @@ -267,12 +268,6 @@ ossl_x509revoked_to_der(VALUE self) void Init_ossl_x509revoked(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509RevError = rb_define_class_under(mX509, "RevokedError", eOSSLError); cX509Rev = rb_define_class_under(mX509, "Revoked", rb_cObject); diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c index c18596cbf5..9e43336c44 100644 --- a/ext/openssl/ossl_x509store.c +++ b/ext/openssl/ossl_x509store.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509store_type, 0) #define SetX509Store(obj, st) do { \ if (!(st)) { \ - ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (st); \ } while (0) #define GetX509Store(obj, st) do { \ TypedData_Get_Struct((obj), X509_STORE, &ossl_x509store_type, (st)); \ if (!(st)) { \ - ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \ } \ } while (0) @@ -28,14 +28,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509stctx_type, 0) #define SetX509StCtx(obj, ctx) do { \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "STORE_CTX wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "STORE_CTX wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (ctx); \ } while (0) #define GetX509StCtx(obj, ctx) do { \ TypedData_Get_Struct((obj), X509_STORE_CTX, &ossl_x509stctx_type, (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "STORE_CTX is out of scope!"); \ + ossl_raise(rb_eRuntimeError, "STORE_CTX is out of scope!"); \ } \ } while (0) @@ -62,7 +62,7 @@ call_verify_cb_proc(VALUE arg) { struct ossl_verify_cb_args *args = (struct ossl_verify_cb_args *)arg; return rb_funcall(args->proc, rb_intern("call"), 2, - args->preverify_ok, args->store_ctx); + args->preverify_ok, args->store_ctx); } int @@ -73,33 +73,33 @@ ossl_verify_cb_call(VALUE proc, int ok, X509_STORE_CTX *ctx) int state; if (NIL_P(proc)) - return ok; + return ok; ret = Qfalse; rctx = rb_protect(ossl_x509stctx_new_i, (VALUE)ctx, &state); if (state) { - rb_set_errinfo(Qnil); - rb_warn("StoreContext initialization failure"); + rb_set_errinfo(Qnil); + rb_warn("StoreContext initialization failure"); } else { - args.proc = proc; - args.preverify_ok = ok ? Qtrue : Qfalse; - args.store_ctx = rctx; - ret = rb_protect(call_verify_cb_proc, (VALUE)&args, &state); - if (state) { - rb_set_errinfo(Qnil); - rb_warn("exception in verify_callback is ignored"); - } - RTYPEDDATA_DATA(rctx) = NULL; + args.proc = proc; + args.preverify_ok = ok ? Qtrue : Qfalse; + args.store_ctx = rctx; + ret = rb_protect(call_verify_cb_proc, (VALUE)&args, &state); + if (state) { + rb_set_errinfo(Qnil); + rb_warn("exception in verify_callback is ignored"); + } + RTYPEDDATA_DATA(rctx) = NULL; } if (ret == Qtrue) { - X509_STORE_CTX_set_error(ctx, X509_V_OK); - ok = 1; + X509_STORE_CTX_set_error(ctx, X509_V_OK); + ok = 1; } else { - if (X509_STORE_CTX_get_error(ctx) == X509_V_OK) - X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED); - ok = 0; + if (X509_STORE_CTX_get_error(ctx) == X509_V_OK) + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED); + ok = 0; } return ok; @@ -159,10 +159,10 @@ x509store_verify_cb(int ok, X509_STORE_CTX *ctx) proc = (VALUE)X509_STORE_CTX_get_ex_data(ctx, stctx_ex_verify_cb_idx); if (!proc) - proc = (VALUE)X509_STORE_get_ex_data(X509_STORE_CTX_get0_store(ctx), - store_ex_verify_cb_idx); + proc = (VALUE)X509_STORE_get_ex_data(X509_STORE_CTX_get0_store(ctx), + store_ex_verify_cb_idx); if (!proc) - return ok; + return ok; return ossl_verify_cb_call(proc, ok, ctx); } @@ -190,8 +190,9 @@ ossl_x509store_set_vfy_cb(VALUE self, VALUE cb) X509_STORE *store; GetX509Store(self, store); + if (!X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb)) + ossl_raise(eX509StoreError, "X509_STORE_set_ex_data"); rb_iv_set(self, "@verify_callback", cb); - X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb); RB_OBJ_WRITTEN(self, Qundef, cb); return cb; @@ -484,7 +485,7 @@ ossl_x509store_verify(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "11", &cert, &chain); ctx = rb_funcall(cX509StoreContext, rb_intern("new"), 3, self, cert, chain); proc = rb_block_given_p() ? rb_block_proc() : - rb_iv_get(self, "@verify_callback"); + rb_iv_get(self, "@verify_callback"); rb_iv_set(ctx, "@verify_callback", proc); result = rb_funcall(ctx, rb_intern("verify"), 0); @@ -512,10 +513,8 @@ static void ossl_x509stctx_free(void *ptr) { X509_STORE_CTX *ctx = ptr; - if (X509_STORE_CTX_get0_untrusted(ctx)) - sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free); - if (X509_STORE_CTX_get0_cert(ctx)) - X509_free(X509_STORE_CTX_get0_cert(ctx)); + sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free); + X509_free((X509 *)X509_STORE_CTX_get0_cert(ctx)); X509_STORE_CTX_free(ctx); } @@ -610,7 +609,8 @@ ossl_x509stctx_verify(VALUE self) GetX509StCtx(self, ctx); VALUE cb = rb_iv_get(self, "@verify_callback"); - X509_STORE_CTX_set_ex_data(ctx, stctx_ex_verify_cb_idx, (void *)cb); + if (!X509_STORE_CTX_set_ex_data(ctx, stctx_ex_verify_cb_idx, (void *)cb)) + ossl_raise(eX509StoreError, "X509_STORE_CTX_set_ex_data"); RB_OBJ_WRITTEN(self, Qundef, cb); switch (X509_verify_cert(ctx)) { @@ -736,7 +736,7 @@ static VALUE ossl_x509stctx_get_curr_cert(VALUE self) { X509_STORE_CTX *ctx; - X509 *x509; + const X509 *x509; GetX509StCtx(self, ctx); x509 = X509_STORE_CTX_get_current_cert(ctx); @@ -758,12 +758,12 @@ static VALUE ossl_x509stctx_get_curr_crl(VALUE self) { X509_STORE_CTX *ctx; - X509_CRL *crl; + const X509_CRL *crl; GetX509StCtx(self, ctx); crl = X509_STORE_CTX_get0_current_crl(ctx); if (!crl) - return Qnil; + return Qnil; return ossl_x509crl_new(crl); } @@ -859,19 +859,13 @@ void Init_ossl_x509store(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - /* Register ext_data slot for verify callback Proc */ stctx_ex_verify_cb_idx = X509_STORE_CTX_get_ex_new_index(0, (void *)"stctx_ex_verify_cb_idx", 0, 0, 0); if (stctx_ex_verify_cb_idx < 0) - ossl_raise(eOSSLError, "X509_STORE_CTX_get_ex_new_index"); + ossl_raise(eOSSLError, "X509_STORE_CTX_get_ex_new_index"); store_ex_verify_cb_idx = X509_STORE_get_ex_new_index(0, (void *)"store_ex_verify_cb_idx", 0, 0, 0); if (store_ex_verify_cb_idx < 0) - ossl_raise(eOSSLError, "X509_STORE_get_ex_new_index"); + ossl_raise(eOSSLError, "X509_STORE_get_ex_new_index"); eX509StoreError = rb_define_class_under(mX509, "StoreError", eOSSLError); diff --git a/ext/psych/extconf.rb b/ext/psych/extconf.rb index e7dd0bb60a..589e201c1c 100644 --- a/ext/psych/extconf.rb +++ b/ext/psych/extconf.rb @@ -36,8 +36,11 @@ if yaml_source libyaml = "libyaml.#$LIBEXT" $cleanfiles << libyaml $LOCAL_LIBS.prepend("$(LIBYAML) ") -else # default to pre-installed libyaml - pkg_config('yaml-0.1') + + # default to pre-installed libyaml +elsif pkg_config('yaml-0.1') + # found with pkg-config +else dir_config('libyaml') find_header('yaml.h') or abort "yaml.h not found" find_library('yaml', 'yaml_get_version') or abort "libyaml not found" diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb index 0c158c9ff3..850a6d1937 100644 --- a/ext/psych/lib/psych.rb +++ b/ext/psych/lib/psych.rb @@ -269,10 +269,10 @@ module Psych # YAML documents that are supplied via user input. Instead, please use the # load method or the safe_load method. # - def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false + def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true result = parse(yaml, filename: filename) return fallback unless result - result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer) + result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, parse_symbols: parse_symbols) end ### @@ -320,13 +320,13 @@ module Psych # Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"} # Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"} # - def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false + def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true result = parse(yaml, filename: filename) return fallback unless result class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s), permitted_symbols.map(&:to_s)) - scanner = ScalarScanner.new class_loader, strict_integer: strict_integer + scanner = ScalarScanner.new class_loader, strict_integer: strict_integer, parse_symbols: parse_symbols visitor = if aliases Visitors::ToRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze else @@ -366,7 +366,7 @@ module Psych # Raises a TypeError when `yaml` parameter is NilClass. This method is # similar to `safe_load` except that `Symbol` objects are allowed by default. # - def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false + def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true safe_load yaml, permitted_classes: permitted_classes, permitted_symbols: permitted_symbols, aliases: aliases, @@ -374,7 +374,8 @@ module Psych fallback: fallback, symbolize_names: symbolize_names, freeze: freeze, - strict_integer: strict_integer + strict_integer: strict_integer, + parse_symbols: parse_symbols end ### diff --git a/ext/psych/lib/psych/core_ext.rb b/ext/psych/lib/psych/core_ext.rb index fc259fd4a2..6dfd0f1696 100644 --- a/ext/psych/lib/psych/core_ext.rb +++ b/ext/psych/lib/psych/core_ext.rb @@ -14,13 +14,9 @@ class Object end end -if defined?(::IRB) - require_relative 'y' -end - # Up to Ruby 3.4, Set was a regular object and was dumped as such # by Pysch. -# Starting from Ruby 3.5 it's a core class written in C, so we have to implement +# Starting from Ruby 4.0 it's a core class written in C, so we have to implement # #encode_with / #init_with to preserve backward compatibility. if defined?(::Set) && Set.new.instance_variables.empty? class Set diff --git a/ext/psych/lib/psych/nodes/node.rb b/ext/psych/lib/psych/nodes/node.rb index 6ae5c59148..fc27448f2e 100644 --- a/ext/psych/lib/psych/nodes/node.rb +++ b/ext/psych/lib/psych/nodes/node.rb @@ -45,8 +45,8 @@ module Psych # Convert this node to Ruby. # # See also Psych::Visitors::ToRuby - def to_ruby(symbolize_names: false, freeze: false, strict_integer: false) - Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer).accept(self) + def to_ruby(symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true) + Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, parse_symbols: parse_symbols).accept(self) end alias :transform :to_ruby diff --git a/ext/psych/lib/psych/scalar_scanner.rb b/ext/psych/lib/psych/scalar_scanner.rb index 8cf868f863..6a556fb3b8 100644 --- a/ext/psych/lib/psych/scalar_scanner.rb +++ b/ext/psych/lib/psych/scalar_scanner.rb @@ -27,10 +27,11 @@ module Psych attr_reader :class_loader # Create a new scanner - def initialize class_loader, strict_integer: false + def initialize class_loader, strict_integer: false, parse_symbols: true @symbol_cache = {} @class_loader = class_loader @strict_integer = strict_integer + @parse_symbols = parse_symbols end # Tokenize +string+ returning the Ruby object @@ -72,7 +73,7 @@ module Psych -Float::INFINITY elsif string.match?(/^\.nan$/i) Float::NAN - elsif string.match?(/^:./) + elsif @parse_symbols && string.match?(/^:./) if string =~ /^:(["'])(.*)\1/ @symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, '')) else diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index 3202b10296..6c1679bf65 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -2,9 +2,9 @@ module Psych # The version of Psych you are using - VERSION = '5.2.6' + VERSION = '5.4.0' if RUBY_ENGINE == 'jruby' - DEFAULT_SNAKEYAML_VERSION = '2.9'.freeze + DEFAULT_SNAKEYAML_VERSION = '2.10'.freeze end end diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb index 580a74e9fb..475444e589 100644 --- a/ext/psych/lib/psych/visitors/to_ruby.rb +++ b/ext/psych/lib/psych/visitors/to_ruby.rb @@ -12,9 +12,13 @@ module Psych ### # This class walks a YAML AST, converting each node to Ruby class ToRuby < Psych::Visitors::Visitor - def self.create(symbolize_names: false, freeze: false, strict_integer: false) + unless RUBY_VERSION < "3.2" + DATA_INITIALIZE = Data.instance_method(:initialize) + end + + def self.create(symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true) class_loader = ClassLoader.new - scanner = ScalarScanner.new class_loader, strict_integer: strict_integer + scanner = ScalarScanner.new class_loader, strict_integer: strict_integer, parse_symbols: parse_symbols new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze) end @@ -219,7 +223,7 @@ module Psych revive_data_members(members, o) end data ||= allocate_anon_data(o, members) - init_struct(data, **members) + DATA_INITIALIZE.bind_call(data, **members) data.freeze data diff --git a/ext/psych/psych_parser.c b/ext/psych/psych_parser.c index d973496284..00a2207b58 100644 --- a/ext/psych/psych_parser.c +++ b/ext/psych/psych_parser.c @@ -32,9 +32,20 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read) *read = 0; if(! NIL_P(string)) { - void * str = (void *)StringValuePtr(string); - *read = (size_t)RSTRING_LEN(string); - memcpy(buf, str, *read); + char * str = StringValuePtr(string); + size_t len = (size_t)RSTRING_LEN(string); + + /* IO#read(size) is documented to return at most `size` bytes, but a + * misbehaving IO-like object may return more. Clamp the copy to the + * buffer libyaml gave us to avoid writing past its end, rounding down + * to a character boundary so a multibyte character is never split. */ + if(len > size) { + rb_encoding * enc = rb_enc_get(string); + len = (size_t)(rb_enc_left_char_head(str, str + size, str + len, enc) - str); + } + + *read = len; + memcpy(buf, str, len); } return 1; diff --git a/ext/psych/psych_to_ruby.c b/ext/psych/psych_to_ruby.c index d473a5f840..3ab0138b52 100644 --- a/ext/psych/psych_to_ruby.c +++ b/ext/psych/psych_to_ruby.c @@ -10,7 +10,11 @@ static VALUE build_exception(VALUE self, VALUE klass, VALUE mesg) { VALUE e = rb_obj_alloc(klass); +#ifdef TRUFFLERUBY + rb_exc_set_message(e, mesg); +#else rb_iv_set(e, "mesg", mesg); +#endif return e; } @@ -24,15 +28,6 @@ static VALUE path2class(VALUE self, VALUE path) return rb_path_to_class(path); } -static VALUE init_struct(VALUE self, VALUE data, VALUE attrs) -{ - VALUE args = rb_ary_new2(1); - rb_ary_push(args, attrs); - rb_struct_initialize(data, args); - - return data; -} - void Init_psych_to_ruby(void) { VALUE psych = rb_define_module("Psych"); @@ -42,7 +37,6 @@ void Init_psych_to_ruby(void) VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor); - rb_define_private_method(cPsychVisitorsToRuby, "init_struct", init_struct, 2); rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2); rb_define_private_method(class_loader, "path2class", path2class, 1); } diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index da3655cf4d..ae2cb45d3c 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -21,6 +21,8 @@ if /mswin|mingw|bccwin/ !~ RUBY_PLATFORM (util or have_func("openpty")) or have_func("_getpty") or have_func("ioctl") + have_macro("HAVE_FCHMOD") or have_func("fchmod") + have_macro("HAVE_FCHOWN") or have_func("fchown") create_makefile('pty') end end diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 7c79f81e33..3d5977707f 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -274,12 +274,23 @@ ptsname_r(int fd, char *buf, size_t buflen) # define HAVE_PTSNAME_R 1 #endif +#ifdef HAVE_FCHMOD +# define change_mode(name, fd, mode) fchmod(fd, mode) +#else +# define change_mode(name, fd, mode) chmod(name, mode) +#endif +#ifdef HAVE_FCHOWN +# define change_owner(name, fd, uid, gid) fchown(fd, uid, gid) +#else +# define change_owner(name, fd, uid, gid) chown(name, uid, gid) +#endif + #if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME_R) -static int -no_mesg(char *slavedevice, int nomesg) +static inline int +prevent_messages(const char *slavedevice, int fd, int nomesg) { if (nomesg) - return chmod(slavedevice, 0600); + return change_mode(slavedevice, fd, 0600); else return 0; } @@ -340,8 +351,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if (unlockpt(masterfd) == -1) goto error; if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; slavedevice = SlaveName; - if (no_mesg(slavedevice, nomesg) == -1) goto error; if ((slavefd = rb_cloexec_open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error; + if (prevent_messages(slavedevice, slavefd, nomesg) == -1) goto error; rb_update_max_fd(slavefd); #if defined(I_PUSH) && !defined(__linux__) && !defined(_AIX) @@ -375,7 +386,9 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, } rb_fd_fix_cloexec(*master); rb_fd_fix_cloexec(*slave); - if (no_mesg(SlaveName, nomesg) == -1) { + if (prevent_messages(SlaveName, *slave, nomesg) == -1) { + close(*master); + close(*slave); if (!fail) return -1; rb_raise(rb_eRuntimeError, "can't chmod slave pty"); } @@ -424,8 +437,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if(unlockpt(masterfd) == -1) goto error; if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; slavedevice = SlaveName; - if (no_mesg(slavedevice, nomesg) == -1) goto error; if((slavefd = rb_cloexec_open(slavedevice, O_RDWR, 0)) == -1) goto error; + if (prevent_messages(slavedevice, slavefd, nomesg) == -1) goto error; rb_update_max_fd(slavefd); #if defined(I_PUSH) && !defined(__linux__) && !defined(_AIX) if(ioctl_I_PUSH(slavefd, "ptem") == -1) goto error; @@ -478,8 +491,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if ((slavefd = rb_cloexec_open(SlaveName,O_RDWR,0)) >= 0) { rb_update_max_fd(slavefd); *slave = slavefd; - if (chown(SlaveName, getuid(), getgid()) != 0) goto error; - if (chmod(SlaveName, nomesg ? 0600 : 0622) != 0) goto error; + if (change_owner(SlaveName, slavefd, getuid(), getgid()) != 0) goto error; + if (change_mode(SlaveName, slavefd, nomesg ? 0600 : 0622) != 0) goto error; return 0; } close(masterfd); diff --git a/ext/ripper/depend b/ext/ripper/depend index c8ba34962c..db83378a1d 100644 --- a/ext/ripper/depend +++ b/ext/ripper/depend @@ -474,6 +474,7 @@ ripper.o: $(hdrdir)/ruby/internal/core/rclass.h ripper.o: $(hdrdir)/ruby/internal/core/rdata.h ripper.o: $(hdrdir)/ruby/internal/core/rfile.h ripper.o: $(hdrdir)/ruby/internal/core/rhash.h +ripper.o: $(hdrdir)/ruby/internal/core/rmatch.h ripper.o: $(hdrdir)/ruby/internal/core/robject.h ripper.o: $(hdrdir)/ruby/internal/core/rregexp.h ripper.o: $(hdrdir)/ruby/internal/core/rstring.h @@ -566,6 +567,7 @@ ripper.o: $(hdrdir)/ruby/missing.h ripper.o: $(hdrdir)/ruby/onigmo.h ripper.o: $(hdrdir)/ruby/oniguruma.h ripper.o: $(hdrdir)/ruby/ractor.h +ripper.o: $(hdrdir)/ruby/re.h ripper.o: $(hdrdir)/ruby/regex.h ripper.o: $(hdrdir)/ruby/ruby.h ripper.o: $(hdrdir)/ruby/st.h @@ -578,12 +580,15 @@ ripper.o: $(top_srcdir)/ccan/container_of/container_of.h ripper.o: $(top_srcdir)/ccan/list/list.h ripper.o: $(top_srcdir)/ccan/str/str.h ripper.o: $(top_srcdir)/constant.h +ripper.o: $(top_srcdir)/encindex.h ripper.o: $(top_srcdir)/id_table.h ripper.o: $(top_srcdir)/internal.h ripper.o: $(top_srcdir)/internal/array.h ripper.o: $(top_srcdir)/internal/basic_operators.h ripper.o: $(top_srcdir)/internal/bignum.h ripper.o: $(top_srcdir)/internal/bits.h +ripper.o: $(top_srcdir)/internal/box.h +ripper.o: $(top_srcdir)/internal/compar.h ripper.o: $(top_srcdir)/internal/compile.h ripper.o: $(top_srcdir)/internal/compilers.h ripper.o: $(top_srcdir)/internal/complex.h @@ -594,7 +599,6 @@ ripper.o: $(top_srcdir)/internal/gc.h ripper.o: $(top_srcdir)/internal/hash.h ripper.o: $(top_srcdir)/internal/imemo.h ripper.o: $(top_srcdir)/internal/io.h -ripper.o: $(top_srcdir)/internal/namespace.h ripper.o: $(top_srcdir)/internal/numeric.h ripper.o: $(top_srcdir)/internal/parse.h ripper.o: $(top_srcdir)/internal/rational.h @@ -605,6 +609,7 @@ ripper.o: $(top_srcdir)/internal/serial.h ripper.o: $(top_srcdir)/internal/set_table.h ripper.o: $(top_srcdir)/internal/static_assert.h ripper.o: $(top_srcdir)/internal/string.h +ripper.o: $(top_srcdir)/internal/struct.h ripper.o: $(top_srcdir)/internal/symbol.h ripper.o: $(top_srcdir)/internal/thread.h ripper.o: $(top_srcdir)/internal/variable.h @@ -810,8 +815,10 @@ ripper_init.o: $(hdrdir)/ruby/st.h ripper_init.o: $(hdrdir)/ruby/subst.h ripper_init.o: $(top_srcdir)/internal.h ripper_init.o: $(top_srcdir)/internal/array.h +ripper_init.o: $(top_srcdir)/internal/basic_operators.h ripper_init.o: $(top_srcdir)/internal/bignum.h ripper_init.o: $(top_srcdir)/internal/bits.h +ripper_init.o: $(top_srcdir)/internal/compar.h ripper_init.o: $(top_srcdir)/internal/compilers.h ripper_init.o: $(top_srcdir)/internal/complex.h ripper_init.o: $(top_srcdir)/internal/fixnum.h diff --git a/ext/ripper/lib/ripper/lexer.rb b/ext/ripper/lib/ripper/lexer.rb index 870c79857b..9b849dfeae 100644 --- a/ext/ripper/lib/ripper/lexer.rb +++ b/ext/ripper/lib/ripper/lexer.rb @@ -68,7 +68,7 @@ class Ripper when 0, :to_int @to_int when 1, :to_s - @event + @to_s else nil end diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c index f1e9e42524..0e17d9e873 100644 --- a/ext/socket/ancdata.c +++ b/ext/socket/ancdata.c @@ -711,6 +711,9 @@ anc_inspect_passcred_credentials(int level, int type, VALUE data, VALUE ret) static int anc_inspect_socket_creds(int level, int type, VALUE data, VALUE ret) { + long len; + const char *ptr; + if (level != SOL_SOCKET && type != SCM_CREDS) return 0; @@ -725,48 +728,59 @@ anc_inspect_socket_creds(int level, int type, VALUE data, VALUE ret) * This heuristics works well except when sc_ngroups == CMGROUP_MAX. */ + RSTRING_GETMEM(data, ptr, len); #if defined(HAVE_TYPE_STRUCT_CMSGCRED) /* FreeBSD */ - if (RSTRING_LEN(data) == sizeof(struct cmsgcred)) { + if (len == sizeof(struct cmsgcred)) { struct cmsgcred cred; - memcpy(&cred, RSTRING_PTR(data), sizeof(struct cmsgcred)); + int ngroups; + memcpy(&cred, ptr, sizeof(struct cmsgcred)); rb_str_catf(ret, " pid=%u", cred.cmcred_pid); rb_str_catf(ret, " uid=%u", cred.cmcred_uid); rb_str_catf(ret, " euid=%u", cred.cmcred_euid); rb_str_catf(ret, " gid=%u", cred.cmcred_gid); - if (cred.cmcred_ngroups) { + rb_str_catf(ret, " groups[%d]=[", cred.cmcred_ngroups); + ngroups = cred.cmcred_ngroups; + if (ngroups > 0) { int i; - const char *sep = " groups="; - for (i = 0; i < cred.cmcred_ngroups; i++) { - rb_str_catf(ret, "%s%u", sep, cred.cmcred_groups[i]); - sep = ","; + rb_str_catf(ret, "%u", cred.cmcred_groups[0]); + if (ngroups > CMGROUP_MAX) ngroups = CMGROUP_MAX; + for (i = 1; i < ngroups; i++) { + rb_str_catf(ret, ",%u", cred.cmcred_groups[i]); } } - rb_str_cat2(ret, " (cmsgcred)"); + rb_str_cat2(ret, "] (cmsgcred)"); return 1; } #endif #if defined(HAVE_TYPE_STRUCT_SOCKCRED) /* FreeBSD, NetBSD */ - if ((size_t)RSTRING_LEN(data) >= SOCKCREDSIZE(0)) { - struct sockcred cred0, *cred; - memcpy(&cred0, RSTRING_PTR(data), SOCKCREDSIZE(0)); - if ((size_t)RSTRING_LEN(data) == SOCKCREDSIZE(cred0.sc_ngroups)) { - cred = (struct sockcred *)ALLOCA_N(char, SOCKCREDSIZE(cred0.sc_ngroups)); - memcpy(cred, RSTRING_PTR(data), SOCKCREDSIZE(cred0.sc_ngroups)); - rb_str_catf(ret, " uid=%u", cred->sc_uid); - rb_str_catf(ret, " euid=%u", cred->sc_euid); - rb_str_catf(ret, " gid=%u", cred->sc_gid); - rb_str_catf(ret, " egid=%u", cred->sc_egid); - if (cred0.sc_ngroups) { - int i; - const char *sep = " groups="; - for (i = 0; i < cred0.sc_ngroups; i++) { - rb_str_catf(ret, "%s%u", sep, cred->sc_groups[i]); - sep = ","; - } + if ((size_t)len >= SOCKCREDSIZE(0)) { + struct sockcred cred; + int ngroups; + memcpy(&cred, ptr, SOCKCREDSIZE(0)); + rb_str_catf(ret, " uid=%u", cred.sc_uid); + rb_str_catf(ret, " euid=%u", cred.sc_euid); + rb_str_catf(ret, " gid=%u", cred.sc_gid); + rb_str_catf(ret, " egid=%u", cred.sc_egid); + rb_str_catf(ret, " groups[%d]=[", cred.sc_ngroups); + ngroups = cred.sc_ngroups; + if (ngroups <= 0) { + ngroups = 0; + } + else { + size_t max = ((size_t)len - SOCKCREDSIZE(0)) / sizeof(gid_t); + if ((size_t)ngroups > max) ngroups = (int)max; + } + if (ngroups > 0) { + int i; + const void *gp = ptr + offsetof(struct sockcred, sc_groups); + const gid_t *groups = MEMCPY(ALLOCA_N(gid_t, ngroups), gp, gid_t, ngroups); + rb_str_catf(ret, "%u", groups[0]); + for (i = 1; i < ngroups; i++) { + rb_str_catf(ret, ",%u", groups[i]); } - rb_str_cat2(ret, " (sockcred)"); - return 1; } + rb_str_cat2(ret, "] (sockcred)"); + return 1; } #endif return 0; @@ -1000,6 +1014,7 @@ ancillary_inspect(VALUE self) rb_str_catf(ret, " %"PRIsVALUE, rb_sym2str(vtype)); else rb_str_catf(ret, " cmsg_type:%d", type); + RB_GC_GUARD(vtype); } else { rb_str_catf(ret, " cmsg_level:%d", level); diff --git a/ext/socket/depend b/ext/socket/depend index 6f2d280bfd..77f6239a3d 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -193,16 +193,17 @@ ancdata.o: $(top_srcdir)/ccan/check_type/check_type.h ancdata.o: $(top_srcdir)/ccan/container_of/container_of.h ancdata.o: $(top_srcdir)/ccan/list/list.h ancdata.o: $(top_srcdir)/ccan/str/str.h +ancdata.o: $(top_srcdir)/encindex.h ancdata.o: $(top_srcdir)/id_table.h ancdata.o: $(top_srcdir)/internal.h ancdata.o: $(top_srcdir)/internal/array.h ancdata.o: $(top_srcdir)/internal/basic_operators.h +ancdata.o: $(top_srcdir)/internal/box.h ancdata.o: $(top_srcdir)/internal/compilers.h ancdata.o: $(top_srcdir)/internal/error.h ancdata.o: $(top_srcdir)/internal/gc.h ancdata.o: $(top_srcdir)/internal/imemo.h ancdata.o: $(top_srcdir)/internal/io.h -ancdata.o: $(top_srcdir)/internal/namespace.h ancdata.o: $(top_srcdir)/internal/sanitizers.h ancdata.o: $(top_srcdir)/internal/serial.h ancdata.o: $(top_srcdir)/internal/set_table.h @@ -408,16 +409,17 @@ basicsocket.o: $(top_srcdir)/ccan/check_type/check_type.h basicsocket.o: $(top_srcdir)/ccan/container_of/container_of.h basicsocket.o: $(top_srcdir)/ccan/list/list.h basicsocket.o: $(top_srcdir)/ccan/str/str.h +basicsocket.o: $(top_srcdir)/encindex.h basicsocket.o: $(top_srcdir)/id_table.h basicsocket.o: $(top_srcdir)/internal.h basicsocket.o: $(top_srcdir)/internal/array.h basicsocket.o: $(top_srcdir)/internal/basic_operators.h +basicsocket.o: $(top_srcdir)/internal/box.h basicsocket.o: $(top_srcdir)/internal/compilers.h basicsocket.o: $(top_srcdir)/internal/error.h basicsocket.o: $(top_srcdir)/internal/gc.h basicsocket.o: $(top_srcdir)/internal/imemo.h basicsocket.o: $(top_srcdir)/internal/io.h -basicsocket.o: $(top_srcdir)/internal/namespace.h basicsocket.o: $(top_srcdir)/internal/sanitizers.h basicsocket.o: $(top_srcdir)/internal/serial.h basicsocket.o: $(top_srcdir)/internal/set_table.h @@ -623,16 +625,17 @@ constants.o: $(top_srcdir)/ccan/check_type/check_type.h constants.o: $(top_srcdir)/ccan/container_of/container_of.h constants.o: $(top_srcdir)/ccan/list/list.h constants.o: $(top_srcdir)/ccan/str/str.h +constants.o: $(top_srcdir)/encindex.h constants.o: $(top_srcdir)/id_table.h constants.o: $(top_srcdir)/internal.h constants.o: $(top_srcdir)/internal/array.h constants.o: $(top_srcdir)/internal/basic_operators.h +constants.o: $(top_srcdir)/internal/box.h constants.o: $(top_srcdir)/internal/compilers.h constants.o: $(top_srcdir)/internal/error.h constants.o: $(top_srcdir)/internal/gc.h constants.o: $(top_srcdir)/internal/imemo.h constants.o: $(top_srcdir)/internal/io.h -constants.o: $(top_srcdir)/internal/namespace.h constants.o: $(top_srcdir)/internal/sanitizers.h constants.o: $(top_srcdir)/internal/serial.h constants.o: $(top_srcdir)/internal/set_table.h @@ -839,16 +842,17 @@ ifaddr.o: $(top_srcdir)/ccan/check_type/check_type.h ifaddr.o: $(top_srcdir)/ccan/container_of/container_of.h ifaddr.o: $(top_srcdir)/ccan/list/list.h ifaddr.o: $(top_srcdir)/ccan/str/str.h +ifaddr.o: $(top_srcdir)/encindex.h ifaddr.o: $(top_srcdir)/id_table.h ifaddr.o: $(top_srcdir)/internal.h ifaddr.o: $(top_srcdir)/internal/array.h ifaddr.o: $(top_srcdir)/internal/basic_operators.h +ifaddr.o: $(top_srcdir)/internal/box.h ifaddr.o: $(top_srcdir)/internal/compilers.h ifaddr.o: $(top_srcdir)/internal/error.h ifaddr.o: $(top_srcdir)/internal/gc.h ifaddr.o: $(top_srcdir)/internal/imemo.h ifaddr.o: $(top_srcdir)/internal/io.h -ifaddr.o: $(top_srcdir)/internal/namespace.h ifaddr.o: $(top_srcdir)/internal/sanitizers.h ifaddr.o: $(top_srcdir)/internal/serial.h ifaddr.o: $(top_srcdir)/internal/set_table.h @@ -1054,16 +1058,17 @@ init.o: $(top_srcdir)/ccan/check_type/check_type.h init.o: $(top_srcdir)/ccan/container_of/container_of.h init.o: $(top_srcdir)/ccan/list/list.h init.o: $(top_srcdir)/ccan/str/str.h +init.o: $(top_srcdir)/encindex.h init.o: $(top_srcdir)/id_table.h init.o: $(top_srcdir)/internal.h init.o: $(top_srcdir)/internal/array.h init.o: $(top_srcdir)/internal/basic_operators.h +init.o: $(top_srcdir)/internal/box.h init.o: $(top_srcdir)/internal/compilers.h init.o: $(top_srcdir)/internal/error.h init.o: $(top_srcdir)/internal/gc.h init.o: $(top_srcdir)/internal/imemo.h init.o: $(top_srcdir)/internal/io.h -init.o: $(top_srcdir)/internal/namespace.h init.o: $(top_srcdir)/internal/sanitizers.h init.o: $(top_srcdir)/internal/serial.h init.o: $(top_srcdir)/internal/set_table.h @@ -1269,16 +1274,17 @@ ipsocket.o: $(top_srcdir)/ccan/check_type/check_type.h ipsocket.o: $(top_srcdir)/ccan/container_of/container_of.h ipsocket.o: $(top_srcdir)/ccan/list/list.h ipsocket.o: $(top_srcdir)/ccan/str/str.h +ipsocket.o: $(top_srcdir)/encindex.h ipsocket.o: $(top_srcdir)/id_table.h ipsocket.o: $(top_srcdir)/internal.h ipsocket.o: $(top_srcdir)/internal/array.h ipsocket.o: $(top_srcdir)/internal/basic_operators.h +ipsocket.o: $(top_srcdir)/internal/box.h ipsocket.o: $(top_srcdir)/internal/compilers.h ipsocket.o: $(top_srcdir)/internal/error.h ipsocket.o: $(top_srcdir)/internal/gc.h ipsocket.o: $(top_srcdir)/internal/imemo.h ipsocket.o: $(top_srcdir)/internal/io.h -ipsocket.o: $(top_srcdir)/internal/namespace.h ipsocket.o: $(top_srcdir)/internal/sanitizers.h ipsocket.o: $(top_srcdir)/internal/serial.h ipsocket.o: $(top_srcdir)/internal/set_table.h @@ -1484,16 +1490,17 @@ option.o: $(top_srcdir)/ccan/check_type/check_type.h option.o: $(top_srcdir)/ccan/container_of/container_of.h option.o: $(top_srcdir)/ccan/list/list.h option.o: $(top_srcdir)/ccan/str/str.h +option.o: $(top_srcdir)/encindex.h option.o: $(top_srcdir)/id_table.h option.o: $(top_srcdir)/internal.h option.o: $(top_srcdir)/internal/array.h option.o: $(top_srcdir)/internal/basic_operators.h +option.o: $(top_srcdir)/internal/box.h option.o: $(top_srcdir)/internal/compilers.h option.o: $(top_srcdir)/internal/error.h option.o: $(top_srcdir)/internal/gc.h option.o: $(top_srcdir)/internal/imemo.h option.o: $(top_srcdir)/internal/io.h -option.o: $(top_srcdir)/internal/namespace.h option.o: $(top_srcdir)/internal/sanitizers.h option.o: $(top_srcdir)/internal/serial.h option.o: $(top_srcdir)/internal/set_table.h @@ -1699,16 +1706,17 @@ raddrinfo.o: $(top_srcdir)/ccan/check_type/check_type.h raddrinfo.o: $(top_srcdir)/ccan/container_of/container_of.h raddrinfo.o: $(top_srcdir)/ccan/list/list.h raddrinfo.o: $(top_srcdir)/ccan/str/str.h +raddrinfo.o: $(top_srcdir)/encindex.h raddrinfo.o: $(top_srcdir)/id_table.h raddrinfo.o: $(top_srcdir)/internal.h raddrinfo.o: $(top_srcdir)/internal/array.h raddrinfo.o: $(top_srcdir)/internal/basic_operators.h +raddrinfo.o: $(top_srcdir)/internal/box.h raddrinfo.o: $(top_srcdir)/internal/compilers.h raddrinfo.o: $(top_srcdir)/internal/error.h raddrinfo.o: $(top_srcdir)/internal/gc.h raddrinfo.o: $(top_srcdir)/internal/imemo.h raddrinfo.o: $(top_srcdir)/internal/io.h -raddrinfo.o: $(top_srcdir)/internal/namespace.h raddrinfo.o: $(top_srcdir)/internal/sanitizers.h raddrinfo.o: $(top_srcdir)/internal/serial.h raddrinfo.o: $(top_srcdir)/internal/set_table.h @@ -1914,16 +1922,17 @@ socket.o: $(top_srcdir)/ccan/check_type/check_type.h socket.o: $(top_srcdir)/ccan/container_of/container_of.h socket.o: $(top_srcdir)/ccan/list/list.h socket.o: $(top_srcdir)/ccan/str/str.h +socket.o: $(top_srcdir)/encindex.h socket.o: $(top_srcdir)/id_table.h socket.o: $(top_srcdir)/internal.h socket.o: $(top_srcdir)/internal/array.h socket.o: $(top_srcdir)/internal/basic_operators.h +socket.o: $(top_srcdir)/internal/box.h socket.o: $(top_srcdir)/internal/compilers.h socket.o: $(top_srcdir)/internal/error.h socket.o: $(top_srcdir)/internal/gc.h socket.o: $(top_srcdir)/internal/imemo.h socket.o: $(top_srcdir)/internal/io.h -socket.o: $(top_srcdir)/internal/namespace.h socket.o: $(top_srcdir)/internal/sanitizers.h socket.o: $(top_srcdir)/internal/serial.h socket.o: $(top_srcdir)/internal/set_table.h @@ -2129,16 +2138,17 @@ sockssocket.o: $(top_srcdir)/ccan/check_type/check_type.h sockssocket.o: $(top_srcdir)/ccan/container_of/container_of.h sockssocket.o: $(top_srcdir)/ccan/list/list.h sockssocket.o: $(top_srcdir)/ccan/str/str.h +sockssocket.o: $(top_srcdir)/encindex.h sockssocket.o: $(top_srcdir)/id_table.h sockssocket.o: $(top_srcdir)/internal.h sockssocket.o: $(top_srcdir)/internal/array.h sockssocket.o: $(top_srcdir)/internal/basic_operators.h +sockssocket.o: $(top_srcdir)/internal/box.h sockssocket.o: $(top_srcdir)/internal/compilers.h sockssocket.o: $(top_srcdir)/internal/error.h sockssocket.o: $(top_srcdir)/internal/gc.h sockssocket.o: $(top_srcdir)/internal/imemo.h sockssocket.o: $(top_srcdir)/internal/io.h -sockssocket.o: $(top_srcdir)/internal/namespace.h sockssocket.o: $(top_srcdir)/internal/sanitizers.h sockssocket.o: $(top_srcdir)/internal/serial.h sockssocket.o: $(top_srcdir)/internal/set_table.h @@ -2344,16 +2354,17 @@ tcpserver.o: $(top_srcdir)/ccan/check_type/check_type.h tcpserver.o: $(top_srcdir)/ccan/container_of/container_of.h tcpserver.o: $(top_srcdir)/ccan/list/list.h tcpserver.o: $(top_srcdir)/ccan/str/str.h +tcpserver.o: $(top_srcdir)/encindex.h tcpserver.o: $(top_srcdir)/id_table.h tcpserver.o: $(top_srcdir)/internal.h tcpserver.o: $(top_srcdir)/internal/array.h tcpserver.o: $(top_srcdir)/internal/basic_operators.h +tcpserver.o: $(top_srcdir)/internal/box.h tcpserver.o: $(top_srcdir)/internal/compilers.h tcpserver.o: $(top_srcdir)/internal/error.h tcpserver.o: $(top_srcdir)/internal/gc.h tcpserver.o: $(top_srcdir)/internal/imemo.h tcpserver.o: $(top_srcdir)/internal/io.h -tcpserver.o: $(top_srcdir)/internal/namespace.h tcpserver.o: $(top_srcdir)/internal/sanitizers.h tcpserver.o: $(top_srcdir)/internal/serial.h tcpserver.o: $(top_srcdir)/internal/set_table.h @@ -2559,16 +2570,17 @@ tcpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h tcpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h tcpsocket.o: $(top_srcdir)/ccan/list/list.h tcpsocket.o: $(top_srcdir)/ccan/str/str.h +tcpsocket.o: $(top_srcdir)/encindex.h tcpsocket.o: $(top_srcdir)/id_table.h tcpsocket.o: $(top_srcdir)/internal.h tcpsocket.o: $(top_srcdir)/internal/array.h tcpsocket.o: $(top_srcdir)/internal/basic_operators.h +tcpsocket.o: $(top_srcdir)/internal/box.h tcpsocket.o: $(top_srcdir)/internal/compilers.h tcpsocket.o: $(top_srcdir)/internal/error.h tcpsocket.o: $(top_srcdir)/internal/gc.h tcpsocket.o: $(top_srcdir)/internal/imemo.h tcpsocket.o: $(top_srcdir)/internal/io.h -tcpsocket.o: $(top_srcdir)/internal/namespace.h tcpsocket.o: $(top_srcdir)/internal/sanitizers.h tcpsocket.o: $(top_srcdir)/internal/serial.h tcpsocket.o: $(top_srcdir)/internal/set_table.h @@ -2774,16 +2786,17 @@ udpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h udpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h udpsocket.o: $(top_srcdir)/ccan/list/list.h udpsocket.o: $(top_srcdir)/ccan/str/str.h +udpsocket.o: $(top_srcdir)/encindex.h udpsocket.o: $(top_srcdir)/id_table.h udpsocket.o: $(top_srcdir)/internal.h udpsocket.o: $(top_srcdir)/internal/array.h udpsocket.o: $(top_srcdir)/internal/basic_operators.h +udpsocket.o: $(top_srcdir)/internal/box.h udpsocket.o: $(top_srcdir)/internal/compilers.h udpsocket.o: $(top_srcdir)/internal/error.h udpsocket.o: $(top_srcdir)/internal/gc.h udpsocket.o: $(top_srcdir)/internal/imemo.h udpsocket.o: $(top_srcdir)/internal/io.h -udpsocket.o: $(top_srcdir)/internal/namespace.h udpsocket.o: $(top_srcdir)/internal/sanitizers.h udpsocket.o: $(top_srcdir)/internal/serial.h udpsocket.o: $(top_srcdir)/internal/set_table.h @@ -2989,16 +3002,17 @@ unixserver.o: $(top_srcdir)/ccan/check_type/check_type.h unixserver.o: $(top_srcdir)/ccan/container_of/container_of.h unixserver.o: $(top_srcdir)/ccan/list/list.h unixserver.o: $(top_srcdir)/ccan/str/str.h +unixserver.o: $(top_srcdir)/encindex.h unixserver.o: $(top_srcdir)/id_table.h unixserver.o: $(top_srcdir)/internal.h unixserver.o: $(top_srcdir)/internal/array.h unixserver.o: $(top_srcdir)/internal/basic_operators.h +unixserver.o: $(top_srcdir)/internal/box.h unixserver.o: $(top_srcdir)/internal/compilers.h unixserver.o: $(top_srcdir)/internal/error.h unixserver.o: $(top_srcdir)/internal/gc.h unixserver.o: $(top_srcdir)/internal/imemo.h unixserver.o: $(top_srcdir)/internal/io.h -unixserver.o: $(top_srcdir)/internal/namespace.h unixserver.o: $(top_srcdir)/internal/sanitizers.h unixserver.o: $(top_srcdir)/internal/serial.h unixserver.o: $(top_srcdir)/internal/set_table.h @@ -3204,16 +3218,17 @@ unixsocket.o: $(top_srcdir)/ccan/check_type/check_type.h unixsocket.o: $(top_srcdir)/ccan/container_of/container_of.h unixsocket.o: $(top_srcdir)/ccan/list/list.h unixsocket.o: $(top_srcdir)/ccan/str/str.h +unixsocket.o: $(top_srcdir)/encindex.h unixsocket.o: $(top_srcdir)/id_table.h unixsocket.o: $(top_srcdir)/internal.h unixsocket.o: $(top_srcdir)/internal/array.h unixsocket.o: $(top_srcdir)/internal/basic_operators.h +unixsocket.o: $(top_srcdir)/internal/box.h unixsocket.o: $(top_srcdir)/internal/compilers.h unixsocket.o: $(top_srcdir)/internal/error.h unixsocket.o: $(top_srcdir)/internal/gc.h unixsocket.o: $(top_srcdir)/internal/imemo.h unixsocket.o: $(top_srcdir)/internal/io.h -unixsocket.o: $(top_srcdir)/internal/namespace.h unixsocket.o: $(top_srcdir)/internal/sanitizers.h unixsocket.o: $(top_srcdir)/internal/serial.h unixsocket.o: $(top_srcdir)/internal/set_table.h diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index d44ce31b0a..a814e21c3a 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -704,6 +704,7 @@ SRC have_func("pthread_create") have_func("pthread_detach") + have_func("pthread_attr_setdetachstate") $VPATH << '$(topdir)' << '$(top_srcdir)' create_makefile("socket") diff --git a/ext/socket/getaddrinfo.c b/ext/socket/getaddrinfo.c index bf0d90129f..9a65490b1d 100644 --- a/ext/socket/getaddrinfo.c +++ b/ext/socket/getaddrinfo.c @@ -62,9 +62,6 @@ #endif #include <unistd.h> #else -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#include <windows.h> -#endif #include <winsock2.h> #include <ws2tcpip.h> #include <io.h> @@ -171,9 +168,7 @@ static const char *const ai_errlist[] = { #define GET_CANONNAME(ai, str) \ if (pai->ai_flags & AI_CANONNAME) {\ - if (((ai)->ai_canonname = (char *)malloc(strlen(str) + 1)) != NULL) {\ - strcpy((ai)->ai_canonname, (str));\ - } else {\ + if (((ai)->ai_canonname = strdup(str)) == NULL) {\ error = EAI_MEMORY;\ goto free;\ }\ diff --git a/ext/socket/getnameinfo.c b/ext/socket/getnameinfo.c index ae5284fab6..98da8c1647 100644 --- a/ext/socket/getnameinfo.c +++ b/ext/socket/getnameinfo.c @@ -55,9 +55,6 @@ #endif #endif #ifdef _WIN32 -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#include <windows.h> -#endif #include <winsock2.h> #include <ws2tcpip.h> #define snprintf _snprintf @@ -158,16 +155,14 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho /* what we should do? */ } else if (flags & NI_NUMERICSERV) { snprintf(numserv, sizeof(numserv), "%d", ntohs(port)); - if (strlen(numserv) + 1 > servlen) + if (strlcpy(serv, numserv, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, numserv); } else { #if defined(HAVE_GETSERVBYPORT) struct servent *sp = getservbyport(port, (flags & NI_DGRAM) ? "udp" : "tcp"); if (sp) { - if (strlen(sp->s_name) + 1 > servlen) + if (strlcpy(serv, sp->s_name, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, sp->s_name); } else return ENI_NOSERVNAME; #else @@ -202,9 +197,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_SYSTEM; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } else { #ifdef INET6 hp = getipnodebyaddr(addr, afd->a_addrlen, afd->a_af, &h_error); @@ -218,13 +212,12 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho p = strchr(hp->h_name, '.'); if (p) *p = '\0'; } - if (strlen(hp->h_name) + 1 > hostlen) { + if (strlcpy(host, hp->h_name, hostlen) >= hostlen) { #ifdef INET6 freehostent(hp); #endif return ENI_MEMORY; } - strcpy(host, hp->h_name); #ifdef INET6 freehostent(hp); #endif @@ -234,9 +227,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_NOHOSTNAME; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } } return SUCCESS; diff --git a/ext/socket/init.c b/ext/socket/init.c index 8e405609ba..6091385561 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -473,7 +473,7 @@ rsock_socket(int domain, int type, int proto) /* emulate blocking connect behavior on EINTR or non-blocking socket */ static int -wait_connectable(VALUE self, VALUE timeout) +wait_connectable(VALUE self, VALUE timeout, const struct sockaddr *sockaddr, int len) { int sockerr; socklen_t sockerrlen; @@ -514,7 +514,9 @@ wait_connectable(VALUE self, VALUE timeout) VALUE result = rb_io_wait(self, RB_INT2NUM(RUBY_IO_READABLE|RUBY_IO_WRITABLE), timeout); if (result == Qfalse) { - rb_raise(rb_eIOTimeoutError, "Connect timed out!"); + VALUE rai = rsock_addrinfo_new((struct sockaddr *)sockaddr, len, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + rb_raise(rb_eIOTimeoutError, "user specified timeout for %" PRIsVALUE, addr_str); } int revents = RB_NUM2INT(result); @@ -603,7 +605,7 @@ rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, V #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(self, timeout); + return wait_connectable(self, timeout, sockaddr, len); } } return status; diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index edd270c3ad..e1943b8496 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -27,11 +27,19 @@ struct inetsock_arg }; void -rsock_raise_user_specified_timeout(void) +rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port) { - VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno")); - VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT")); - rb_raise(etimedout_error, "user specified timeout"); + VALUE message; + + if (ai && ai->ai_addr) { + VALUE rai = rsock_addrinfo_new((struct sockaddr *)ai->ai_addr, (socklen_t)ai->ai_addrlen, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str); + } else { + message = rb_sprintf("user specified timeout for %" PRIsVALUE " port %" PRIsVALUE, host, port); + } + + rb_exc_raise(rb_exc_new_str(rb_eIOTimeoutError, message)); } static VALUE @@ -75,15 +83,13 @@ init_inetsock_internal(VALUE v) VALUE open_timeout = arg->open_timeout; VALUE timeout; VALUE starts_at; - unsigned int timeout_msec; timeout = NIL_P(open_timeout) ? resolv_timeout : open_timeout; - timeout_msec = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); starts_at = current_clocktime(); arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, family, SOCK_STREAM, - (type == INET_SERVER) ? AI_PASSIVE : 0, timeout_msec); + (type == INET_SERVER) ? AI_PASSIVE : 0, timeout); /* * Maybe also accept a local address @@ -91,7 +97,7 @@ init_inetsock_internal(VALUE v) if (type != INET_SERVER && (!NIL_P(arg->local.host) || !NIL_P(arg->local.serv))) { arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv, - family, SOCK_STREAM, 0, 0); + family, SOCK_STREAM, 0, timeout); } VALUE io = Qnil; @@ -151,7 +157,9 @@ init_inetsock_internal(VALUE v) } else { VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); timeout = rb_funcall(open_timeout, '-', 1, elapsed); - if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) rsock_raise_user_specified_timeout(); + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(res, arg->remote.host, arg->remote.serv); + } } if (status >= 0) { @@ -208,8 +216,8 @@ rsock_init_inetsock( VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout, - VALUE _fast_fallback, VALUE _test_mode_settings -) { + VALUE _fast_fallback, VALUE _test_mode_settings) +{ if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); } @@ -250,6 +258,21 @@ is_specified_ip_address(const char *hostname) inet_pton(AF_INET, hostname, &ipv4addr) == 1); } +static int +is_local_port_fixed(const char *portp) +{ + if (!portp) return 0; + + char *endp; + errno = 0; + long port = strtol(portp, &endp, 10); + + if (endp == portp) return 0; + if (errno == ERANGE) return 0; + + return port > 0; +} + struct fast_fallback_inetsock_arg { VALUE self; @@ -423,8 +446,8 @@ select_expires_at( struct timeval *connection_attempt_delay, struct timeval *user_specified_resolv_timeout_at, struct timeval *user_specified_connect_timeout_at, - struct timeval *user_specified_open_timeout_at -) { + struct timeval *user_specified_open_timeout_at) +{ if (any_addrinfos(resolution_store)) { struct timeval *delay; delay = resolution_delay ? resolution_delay : connection_attempt_delay; @@ -526,7 +549,8 @@ in_progress_fds(int fds_size) } static void -remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) { +remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) +{ int i, j; for (i = 0; i < *fds_size; i++) { @@ -605,6 +629,7 @@ init_fast_fallback_inetsock_internal(VALUE v) struct timeval user_specified_open_timeout_storage; struct timeval *user_specified_open_timeout_at = NULL; struct timespec now = current_clocktime_ts(); + VALUE starts_at = current_clocktime(); if (!NIL_P(open_timeout)) { struct timeval open_timeout_tv = rb_time_interval(open_timeout); @@ -618,7 +643,7 @@ init_fast_fallback_inetsock_internal(VALUE v) arg->getaddrinfo_shared = NULL; int family = arg->families[0]; - unsigned int t = NIL_P(resolv_timeout) ? 0 : rsock_value_timeout_to_msec(resolv_timeout); + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; arg->remote.res = rsock_addrinfo( arg->remote.host, @@ -704,7 +729,6 @@ init_fast_fallback_inetsock_internal(VALUE v) if (raddrinfo_pthread_create(&threads[i], fork_safe_do_fast_fallback_getaddrinfo, arg->getaddrinfo_entries[i]) != 0) { rsock_raise_resolution_error("getaddrinfo(3)", EAI_AGAIN); } - pthread_detach(threads[i]); } if (NIL_P(resolv_timeout)) { @@ -833,14 +857,26 @@ init_fast_fallback_inetsock_internal(VALUE v) status = connect(fd, remote_ai->ai_addr, remote_ai->ai_addrlen); last_family = remote_ai->ai_family; } else { - if (!NIL_P(connect_timeout)) { - user_specified_connect_timeout_storage = rb_time_interval(connect_timeout); - user_specified_connect_timeout_at = &user_specified_connect_timeout_storage; + VALUE timeout = Qnil; + + if (!NIL_P(open_timeout)) { + VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); + timeout = rb_funcall(open_timeout, '-', 1, elapsed); + + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } + } + if (NIL_P(timeout)) { + if (!NIL_P(connect_timeout)) { + user_specified_connect_timeout_storage = rb_time_interval(connect_timeout); + user_specified_connect_timeout_at = &user_specified_connect_timeout_storage; + } + timeout = + (user_specified_connect_timeout_at && is_infinity(*user_specified_connect_timeout_at)) ? + Qnil : tv_to_seconds(user_specified_connect_timeout_at); } - VALUE timeout = - (user_specified_connect_timeout_at && is_infinity(*user_specified_connect_timeout_at)) ? - Qnil : tv_to_seconds(user_specified_connect_timeout_at); io = arg->io = rsock_init_sock(arg->self, fd); status = rsock_connect(io, remote_ai->ai_addr, remote_ai->ai_addrlen, 0, timeout); } @@ -1166,7 +1202,9 @@ init_fast_fallback_inetsock_internal(VALUE v) } } - if (is_timeout_tv(user_specified_open_timeout_at, now)) rsock_raise_user_specified_timeout(); + if (is_timeout_tv(user_specified_open_timeout_at, now)) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } if (!any_addrinfos(&resolution_store)) { if (!in_progress_fds(arg->connection_attempt_fds_size) && @@ -1189,7 +1227,7 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.is_all_finished) && (is_timeout_tv(user_specified_connect_timeout_at, now) || !in_progress_fds(arg->connection_attempt_fds_size))) { - rsock_raise_user_specified_timeout(); + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); } } } @@ -1283,21 +1321,23 @@ rsock_init_inetsock( VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout, - VALUE fast_fallback, VALUE test_mode_settings -) { + VALUE fast_fallback, VALUE test_mode_settings) +{ if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); } if (type == INET_CLIENT && FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 && RTEST(fast_fallback)) { struct rb_addrinfo *local_res = NULL; - char *hostp, *portp; - char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; + char *hostp, *portp, *local_portp; + char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV], local_pbuf[NI_MAXSERV]; int additional_flags = 0; + int local_flags = 0; hostp = raddrinfo_host_str(remote_host, hbuf, sizeof(hbuf), &additional_flags); portp = raddrinfo_port_str(remote_serv, pbuf, sizeof(pbuf), &additional_flags); + local_portp = raddrinfo_port_str(local_serv, local_pbuf, sizeof(local_pbuf), &local_flags); - if (!is_specified_ip_address(hostp)) { + if (!is_specified_ip_address(hostp) && !is_local_port_fixed(local_portp)) { int target_families[2] = { 0, 0 }; int resolving_family_size = 0; @@ -1305,17 +1345,18 @@ rsock_init_inetsock( * Maybe also accept a local address */ if (!NIL_P(local_host) || !NIL_P(local_serv)) { + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; local_res = rsock_addrinfo( local_host, local_serv, AF_UNSPEC, SOCK_STREAM, 0, - 0 + t ); struct addrinfo *tmp_p = local_res->ai; - for (tmp_p; tmp_p != NULL; tmp_p = tmp_p->ai_next) { + for (; tmp_p != NULL; tmp_p = tmp_p->ai_next) { if (target_families[0] == 0 && tmp_p->ai_family == AF_INET6) { target_families[0] = AF_INET6; resolving_family_size++; @@ -1568,7 +1609,7 @@ static VALUE ip_s_getaddress(VALUE obj, VALUE host) { union_sockaddr addr; - struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, Qnil); socklen_t len = res->ai->ai_addrlen; /* just take the first one */ diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 1e30861efa..465b74964f 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -62,7 +62,7 @@ class Addrinfo break when :wait_writable sock.wait_writable(timeout) or - raise Errno::ETIMEDOUT, 'user specified timeout' + raise Errno::ETIMEDOUT, "user specified timeout for #{self.ip_address}:#{self.ip_port}" end while true else sock.connect(self) @@ -599,6 +599,7 @@ class Socket < BasicSocket __accept_nonblock(exception) end + # :stopdoc: RESOLUTION_DELAY = 0.05 private_constant :RESOLUTION_DELAY @@ -616,6 +617,7 @@ class Socket < BasicSocket IPV6_ADDRESS_FORMAT = /\A(?i:(?:(?:[0-9A-F]{1,4}:){7}(?:[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){6}(?:[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:){1,5}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){5}(?:(?::[0-9A-F]{1,4}){1,2}|:(?:[0-9A-F]{1,4}:){1,4}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){4}(?:(?::[0-9A-F]{1,4}){1,3}|:(?:[0-9A-F]{1,4}:){1,3}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){3}(?:(?::[0-9A-F]{1,4}){1,4}|:(?:[0-9A-F]{1,4}:){1,2}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){2}(?:(?::[0-9A-F]{1,4}){1,5}|:(?:[0-9A-F]{1,4}:)[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){1}(?:(?::[0-9A-F]{1,4}){1,6}|:(?:[0-9A-F]{1,4}:){0,5}[0-9A-F]{1,4}|:)|(?:::(?:[0-9A-F]{1,4}:){0,7}[0-9A-F]{1,4}|::)))(?:%.+)?\z/ private_constant :IPV6_ADDRESS_FORMAT + # :startdoc: # :call-seq: # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... } @@ -658,12 +660,11 @@ class Socket < BasicSocket # puts sock.read # } def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket - if open_timeout && (connect_timeout || resolv_timeout) raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout" end - sock = if fast_fallback && !(host && ip_address?(host)) + sock = if fast_fallback && !(host && ip_address?(host)) && !(local_port && local_port.to_i != 0) tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) else tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) @@ -680,9 +681,10 @@ class Socket < BasicSocket end end + # :stopdoc: def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil) if local_host || local_port - local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: resolv_timeout) + local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: open_timeout || resolv_timeout) resolving_family_names = local_addrinfos.map { |lai| ADDRESS_FAMILIES.key(lai.afamily) }.uniq else local_addrinfos = [] @@ -695,6 +697,7 @@ class Socket < BasicSocket is_windows_environment ||= (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) now = current_clock_time + starts_at = now resolution_delay_expires_at = nil connection_attempt_delay_expires_at = nil user_specified_connect_timeout_at = nil @@ -704,7 +707,7 @@ class Socket < BasicSocket if resolving_family_names.size == 1 family_name = resolving_family_names.first - addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[:family_name], :STREAM, timeout: resolv_timeout) + addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[:family_name], :STREAM, timeout: open_timeout || resolv_timeout) resolution_store.add_resolved(family_name, addrinfos) hostname_resolution_result = nil hostname_resolution_notifier = nil @@ -721,7 +724,6 @@ class Socket < BasicSocket thread } ) - user_specified_resolv_timeout_at = resolv_timeout ? now + resolv_timeout : Float::INFINITY end @@ -733,7 +735,7 @@ class Socket < BasicSocket if local_addrinfos.any? local_addrinfo = local_addrinfos.find { |lai| lai.afamily == addrinfo.afamily } - if local_addrinfo.nil? # Connecting addrinfoと同じアドレスファミリのLocal addrinfoがない + if local_addrinfo.nil? if resolution_store.any_addrinfos? # Try other Addrinfo in next "while" next @@ -755,9 +757,16 @@ class Socket < BasicSocket socket.bind(local_addrinfo) if local_addrinfo result = socket.connect_nonblock(addrinfo, exception: false) else + timeout = + if open_timeout + t = open_timeout - (current_clock_time - starts_at) + t.negative? ? 0 : t + else + connect_timeout + end result = socket = local_addrinfo ? - addrinfo.connect_from(local_addrinfo, timeout: connect_timeout) : - addrinfo.connect(timeout: connect_timeout) + addrinfo.connect_from(local_addrinfo, timeout:) : + addrinfo.connect(timeout:) end if result == :wait_writable @@ -895,7 +904,9 @@ class Socket < BasicSocket end end - raise(Errno::ETIMEDOUT, 'user specified timeout') if expired?(now, user_specified_open_timeout_at) + if expired?(now, user_specified_open_timeout_at) + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") + end if resolution_store.empty_addrinfos? if connecting_sockets.empty? && resolution_store.resolved_all_families? @@ -908,21 +919,18 @@ class Socket < BasicSocket if (expired?(now, user_specified_resolv_timeout_at) || resolution_store.resolved_all_families?) && (expired?(now, user_specified_connect_timeout_at) || connecting_sockets.empty?) - raise Errno::ETIMEDOUT, 'user specified timeout' + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") end end end ensure - hostname_resolution_threads.each do |thread| - thread.exit - end + hostname_resolution_threads.each(&:exit).each(&:join) hostname_resolution_result&.close - connecting_sockets.each_key do |connecting_socket| - connecting_socket.close - end + connecting_sockets.each_key(&:close) end + private_class_method :tcp_with_fast_fallback def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) last_error = nil @@ -930,7 +938,7 @@ class Socket < BasicSocket local_addr_list = nil if local_host != nil || local_port != nil - local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil) + local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil, timeout: open_timeout || resolv_timeout) end timeout = open_timeout ? open_timeout : resolv_timeout @@ -944,7 +952,14 @@ class Socket < BasicSocket local_addr = nil end begin - timeout = open_timeout ? open_timeout - (current_clock_time - starts_at) : connect_timeout + timeout = + if open_timeout + t = open_timeout - (current_clock_time - starts_at) + t.negative? ? 0 : t + else + connect_timeout + end + sock = local_addr ? ai.connect_from(local_addr, timeout:) : ai.connect(timeout:) @@ -1106,7 +1121,6 @@ class Socket < BasicSocket end private_constant :HostnameResolutionStore - # :stopdoc: def self.ip_sockets_port0(ai_list, reuseaddr) sockets = [] begin @@ -1139,9 +1153,7 @@ class Socket < BasicSocket end sockets end - class << self - private :ip_sockets_port0 - end + private_class_method :ip_sockets_port0 def self.tcp_server_sockets_port0(host) ai_list = Addrinfo.getaddrinfo(host, 0, nil, :STREAM, nil, Socket::AI_PASSIVE) @@ -1569,13 +1581,18 @@ class Socket < BasicSocket end end - class << self - private - - def unix_socket_abstract_name?(path) - /linux/ =~ RUBY_PLATFORM && /\A(\0|\z)/ =~ path + # :stopdoc: + if RUBY_PLATFORM.include?("linux") + def self.unix_socket_abstract_name?(path) + path.empty? or path.start_with?("\0") + end + else + def self.unix_socket_abstract_name?(path) + false end end + private_class_method :unix_socket_abstract_name? + # :startdoc: # creates a UNIX socket server on _path_. # It calls the block for each socket accepted. diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index ca00e51ee7..f1fbcc35de 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -293,7 +293,7 @@ rb_freeaddrinfo(struct rb_addrinfo *ai) xfree(ai); } -unsigned int +static int rsock_value_timeout_to_msec(VALUE timeout) { double seconds = NUM2DBL(timeout); @@ -308,7 +308,7 @@ rsock_value_timeout_to_msec(VALUE timeout) #if GETADDRINFO_IMPL == 0 static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) { return getaddrinfo(hostp, portp, hints, ai); } @@ -346,7 +346,7 @@ fork_safe_getaddrinfo(void *arg) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) { struct getaddrinfo_arg arg; MEMZERO(&arg, struct getaddrinfo_arg, 1); @@ -367,11 +367,11 @@ struct getaddrinfo_arg int err, gai_errno, refcount, done, cancelled, timedout; rb_nativethread_lock_t lock; rb_nativethread_cond_t cond; - unsigned int timeout; + int timeout; }; static struct getaddrinfo_arg * -allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, unsigned int timeout) +allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, int timeout) { size_t hostp_offset = sizeof(struct getaddrinfo_arg); size_t portp_offset = hostp_offset + (hostp ? strlen(hostp) + 1 : 0); @@ -387,7 +387,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (hostp) { arg->node = buf + hostp_offset; - strcpy(arg->node, hostp); + memcpy(arg->node, hostp, portp_offset - hostp_offset); } else { arg->node = NULL; @@ -395,7 +395,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (portp) { arg->service = buf + portp_offset; - strcpy(arg->service, portp); + memcpy(arg->service, portp, bufsize - portp_offset); } else { arg->service = NULL; @@ -465,8 +465,11 @@ wait_getaddrinfo(void *ptr) struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; rb_nativethread_lock_lock(&arg->lock); while (!arg->done && !arg->cancelled) { - unsigned long msec = arg->timeout; - if (msec > 0) { + long msec = arg->timeout; + if (msec == 0) { + arg->cancelled = 1; + arg->timedout = 1; + } else if (msec > 0) { rb_native_cond_timedwait(&arg->cond, &arg->lock, msec); if (!arg->done) { arg->cancelled = 1; @@ -496,13 +499,49 @@ int raddrinfo_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg) { int limit = 3, ret; + int saved_errno; +#ifdef HAVE_PTHREAD_ATTR_SETDETACHSTATE + pthread_attr_t attr; + pthread_attr_t *attr_p = &attr; + int err; + int init_retries = 0; + int init_retries_max = 3; +retry_attr_init: + if ((err = pthread_attr_init(attr_p)) != 0) { + if (err == ENOMEM && init_retries < init_retries_max) { + init_retries++; + rb_gc(); + goto retry_attr_init; + } + return err; + } + if ((err = pthread_attr_setdetachstate(attr_p, PTHREAD_CREATE_DETACHED)) != 0) { + saved_errno = errno; + pthread_attr_destroy(attr_p); + errno = saved_errno; + return err; // EINVAL - shouldn't happen + } +#else + pthread_attr_t *attr_p = NULL; +#endif do { // It is said that pthread_create may fail spuriously, so we follow the JDK and retry several times. // // https://bugs.openjdk.org/browse/JDK-8268605 // https://github.com/openjdk/jdk/commit/e35005d5ce383ddd108096a3079b17cb0bcf76f1 - ret = pthread_create(th, 0, start_routine, arg); + ret = pthread_create(th, attr_p, start_routine, arg); } while (ret == EAGAIN && limit-- > 0); +#ifdef HAVE_PTHREAD_ATTR_SETDETACHSTATE + saved_errno = errno; + pthread_attr_destroy(attr_p); + if (ret != 0) { + errno = saved_errno; + } +#else + if (ret == 0) { + pthread_detach(th); // this can race with shutdown routine of thread in some glibc versions + } +#endif return ret; } @@ -513,7 +552,7 @@ fork_safe_do_getaddrinfo(void *ptr) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int timeout) { int retry; struct getaddrinfo_arg *arg; @@ -534,7 +573,6 @@ start: errno = err; return EAI_SYSTEM; } - pthread_detach(th); rb_thread_call_without_gvl2(wait_getaddrinfo, arg, cancel_getaddrinfo, arg); @@ -562,7 +600,11 @@ start: if (need_free) free_getaddrinfo_arg(arg); - if (timedout) rsock_raise_user_specified_timeout(); + if (timedout) { + VALUE host = rb_str_new_cstr(hostp); + VALUE port = rb_str_new_cstr(portp); + rsock_raise_user_specified_timeout(NULL, host, port); + } // If the current thread is interrupted by asynchronous exception, the following raises the exception. // But if the current thread is interrupted by timer thread, the following returns; we need to manually retry. @@ -589,7 +631,7 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) { - return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + return getnameinfo(sa, salen, host, (socklen_t)hostlen, serv, (socklen_t)servlen, flags); } #elif GETADDRINFO_IMPL == 1 @@ -752,7 +794,7 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, int err = 0, gni_errno = 0; if (GETNAMEINFO_WONT_BLOCK(host, serv, flags)) { - return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + return getnameinfo(sa, salen, host, (socklen_t)hostlen, serv, (socklen_t)servlen, flags); } start: @@ -770,7 +812,6 @@ start: errno = err; return EAI_SYSTEM; } - pthread_detach(th); rb_thread_call_without_gvl2(wait_getnameinfo, arg, cancel_getnameinfo, arg); @@ -979,7 +1020,7 @@ rb_scheduler_getaddrinfo(VALUE scheduler, VALUE host, const char *service, } struct rb_addrinfo* -rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout) +rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout) { struct rb_addrinfo* res = NULL; struct addrinfo *ai; @@ -1014,7 +1055,8 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h } if (!resolved) { - error = rb_getaddrinfo(hostp, portp, hints, &ai, timeout); + int t = NIL_P(timeout) ? -1 : rsock_value_timeout_to_msec(timeout); + error = rb_getaddrinfo(hostp, portp, hints, &ai, t); if (error == 0) { res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo)); res->allocated_by_malloc = 0; @@ -1047,7 +1089,7 @@ rsock_fd_family(int fd) } struct rb_addrinfo* -rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout) +rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout) { struct addrinfo hints; @@ -1338,8 +1380,7 @@ call_getaddrinfo(VALUE node, VALUE service, hints.ai_flags = NUM2INT(flags); } - unsigned int t = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); - res = rsock_getaddrinfo(node, service, &hints, socktype_hack, t); + res = rsock_getaddrinfo(node, service, &hints, socktype_hack, timeout); if (res == NULL) rb_raise(rb_eSocket, "host not found"); diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 29b8afc163..2ec3ab335a 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -327,8 +327,8 @@ void rb_freeaddrinfo(struct rb_addrinfo *ai); VALUE rsock_freeaddrinfo(VALUE arg); int rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); int rsock_fd_family(int fd); -struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout); -struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout); +struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout); +struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout); VALUE rsock_fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len); VALUE rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len); @@ -453,8 +453,7 @@ void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shar # endif #endif -unsigned int rsock_value_timeout_to_msec(VALUE); -NORETURN(void rsock_raise_user_specified_timeout(void)); +NORETURN(void rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port)); void rsock_init_basicsocket(void); void rsock_init_ipsocket(void); @@ -510,8 +509,6 @@ extern ID tcp_fast_fallback; const char *inet_ntop(int, const void *, char *, size_t); #elif defined __MINGW32__ # define inet_ntop(f,a,n,l) rb_w32_inet_ntop(f,a,n,l) -#elif defined _MSC_VER && RUBY_MSVCRT_VERSION < 90 -const char *WSAAPI inet_ntop(int, const void *, char *, size_t); #endif #endif diff --git a/ext/socket/socket.c b/ext/socket/socket.c index 26bf0bae8c..a8e5ae8119 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -965,7 +965,7 @@ sock_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("Socket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, sock_sockaddr); } @@ -1183,7 +1183,7 @@ sock_s_getaddrinfo(int argc, VALUE *argv, VALUE _) norevlookup = rsock_do_not_reverse_lookup; } - res = rsock_getaddrinfo(host, port, &hints, 0, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); ret = make_addrinfo(res, norevlookup); rb_freeaddrinfo(res); @@ -1279,7 +1279,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) hints.ai_socktype = (fl & NI_DGRAM) ? SOCK_DGRAM : SOCK_STREAM; /* af */ hints.ai_family = NIL_P(af) ? PF_UNSPEC : rsock_family_arg(af); - res = rsock_getaddrinfo(host, port, &hints, 0, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); sap = res->ai->ai_addr; salen = res->ai->ai_addrlen; } @@ -1335,7 +1335,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) static VALUE sock_s_pack_sockaddr_in(VALUE self, VALUE port, VALUE host) { - struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, Qnil); VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen); rb_freeaddrinfo(res); diff --git a/ext/socket/tcpsocket.c b/ext/socket/tcpsocket.c index 22c9f28ab7..7ce536e0af 100644 --- a/ext/socket/tcpsocket.c +++ b/ext/socket/tcpsocket.c @@ -12,7 +12,7 @@ /* * call-seq: - * TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil, fast_fallback: true) + * TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil, open_timeout: nil, fast_fallback: true) * * Opens a TCP connection to +remote_host+ on +remote_port+. If +local_host+ * and +local_port+ are specified, then those parameters are used on the local @@ -113,7 +113,7 @@ tcp_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("TCPSocket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, tcp_sockaddr); } diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index 5538f24523..b2bc925538 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -84,7 +84,7 @@ udp_connect(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); int result = (int)rb_ensure(udp_connect_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -129,7 +129,7 @@ udp_bind(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); VALUE result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -212,7 +212,7 @@ udp_send(int argc, VALUE *argv, VALUE sock) GetOpenFile(sock, arg.fptr); arg.sarg.fd = arg.fptr->fd; arg.sarg.flags = NUM2INT(flags); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, Qnil); ret = rb_ensure(udp_send_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port); diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c index 31e2acee04..2ec9376074 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -42,11 +42,12 @@ unixsock_path_value(VALUE path) } } #endif + path = rb_get_path(path); #ifdef _WIN32 /* UNIXSocket requires UTF-8 per spec. */ path = rb_str_export_to_enc(path, rb_utf8_encoding()); #endif - return rb_get_path(path); + return path; } VALUE diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 0493c8cd50..41aa71f893 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -13,7 +13,7 @@ **********************************************************************/ static const char *const -STRINGIO_VERSION = "3.1.8.dev"; +STRINGIO_VERSION = "3.2.1.dev"; #include <stdbool.h> @@ -62,6 +62,7 @@ struct StringIO { int count; }; +static struct StringIO *get_strio_for_read(VALUE self); static VALUE strio_init(int, VALUE *, struct StringIO *, VALUE); static VALUE strio_unget_bytes(struct StringIO *, const char *, long); static long strio_write(VALUE self, VALUE str); @@ -126,8 +127,14 @@ static const rb_data_type_t strio_data_type = { static struct StringIO* get_strio(VALUE self) { - struct StringIO *ptr = check_strio(rb_io_taint_check(self)); + rb_check_frozen(self); + return get_strio_for_read(self); +} +static struct StringIO* +get_strio_for_read(VALUE self) +{ + struct StringIO *ptr = check_strio(self); if (!ptr) { rb_raise(rb_eIOError, "uninitialized stream"); } @@ -154,14 +161,38 @@ strio_substr(struct StringIO *ptr, long pos, long len, rb_encoding *enc) return enc_subseq(str, pos, len, enc); } +static VALUE +strio_readbuf(struct StringIO *ptr, VALUE str) +{ + if (!NIL_P(str)) { + StringValue(str); + rb_str_modify(str); + if (str == ptr->string) { + rb_raise(rb_eArgError, "cannot read into the underlying string"); + } + } + return str; +} + #define StringIO(obj) get_strio(obj) +#define StringIOForRead(obj) get_strio_for_read(obj) #define STRIO_READABLE FL_USER4 #define STRIO_WRITABLE FL_USER5 #define STRIO_READWRITE (STRIO_READABLE|STRIO_WRITABLE) typedef char strio_flags_check[(STRIO_READABLE/FMODE_READABLE == STRIO_WRITABLE/FMODE_WRITABLE) * 2 - 1]; +#ifndef RB_FL_TEST_RAW +# define RB_FL_TEST_RAW(obj, bits) (RBASIC(obj)->flags & (bits)) +#endif +#ifndef RB_FL_SET_RAW +# define RB_FL_SET_RAW(obj, bits) (RBASIC(obj)->flags |= (bits)) +#endif +#ifndef RB_FL_UNSET_RAW +# define RB_FL_UNSET_RAW(obj, bits) (RBASIC(obj)->flags &= ~(bits)) +#endif + #define STRIO_MODE_SET_P(strio, mode) \ - ((RBASIC(strio)->flags & STRIO_##mode) && \ + (RB_FL_TEST_RAW(strio, STRIO_##mode) && \ ((struct StringIO*)DATA_PTR(strio))->flags & FMODE_##mode) #define CLOSED(strio) (!STRIO_MODE_SET_P(strio, READWRITE)) #define READABLE(strio) STRIO_MODE_SET_P(strio, READABLE) @@ -172,7 +203,7 @@ static VALUE sym_exception; static struct StringIO* readable(VALUE strio) { - struct StringIO *ptr = StringIO(strio); + struct StringIO *ptr = StringIOForRead(strio); if (!READABLE(strio)) { rb_raise(rb_eIOError, "not opened for reading"); } @@ -225,17 +256,32 @@ strio_s_allocate(VALUE klass) * call-seq: * StringIO.new(string = '', mode = 'r+') -> new_stringio * - * Note that +mode+ defaults to <tt>'r'</tt> if +string+ is frozen. - * * Returns a new \StringIO instance formed from +string+ and +mode+; - * see {Access Modes}[rdoc-ref:File@Access+Modes]: + * the instance should be closed when no longer needed: + * + * strio = StringIO.new + * strio.string # => "" + * strio.closed_read? # => false + * strio.closed_write? # => false + * strio.close + * + * If +string+ is frozen, the default +mode+ is <tt>'r'</tt>: * - * strio = StringIO.new # => #<StringIO> + * strio = StringIO.new('foo'.freeze) + * strio.string # => "foo" + * strio.closed_read? # => false + * strio.closed_write? # => true * strio.close * - * The instance should be closed when no longer needed. + * Argument +mode+ must be a valid + * {Access Mode}[rdoc-ref:File@Access+Modes], + * which may be a string or an integer constant: * - * Related: StringIO.open (accepts block; closes automatically). + * StringIO.new('foo', 'w+') + * StringIO.new('foo', File::RDONLY) + * + * Related: StringIO.open + * (passes the \StringIO object to the block; closes the object automatically on block exit). */ static VALUE strio_initialize(int argc, VALUE *argv, VALUE self) @@ -355,14 +401,15 @@ strio_init(int argc, VALUE *argv, struct StringIO *ptr, VALUE self) ptr->pos = 0; ptr->lineno = 0; if (ptr->flags & FMODE_SETENC_BY_BOM) set_encoding_by_bom(ptr); - RBASIC(self)->flags |= (ptr->flags & FMODE_READWRITE) * (STRIO_READABLE / FMODE_READABLE); + RB_FL_SET_RAW(self, (ptr->flags & FMODE_READWRITE) * (STRIO_READABLE / FMODE_READABLE)); return self; } static VALUE strio_finalize(VALUE self) { - struct StringIO *ptr = StringIO(self); + struct StringIO *ptr = check_strio(self); + if (!ptr) return Qnil; RB_OBJ_WRITE(self, &ptr->string, Qnil); ptr->flags &= ~FMODE_READWRITE; return self; @@ -370,23 +417,20 @@ strio_finalize(VALUE self) /* * call-seq: - * StringIO.open(string = '', mode = 'r+') {|strio| ... } + * StringIO.open(string = '', mode = 'r+') -> new_stringio + * StringIO.open(string = '', mode = 'r+') {|strio| ... } -> object * - * Note that +mode+ defaults to <tt>'r'</tt> if +string+ is frozen. + * Creates new \StringIO instance by calling <tt>StringIO.new(string, mode)</tt>. * - * Creates a new \StringIO instance formed from +string+ and +mode+; - * see {Access Modes}[rdoc-ref:File@Access+Modes]. - * - * With no block, returns the new instance: + * With no block given, returns the new instance: * * strio = StringIO.open # => #<StringIO> * - * With a block, calls the block with the new instance + * With a block given, calls the block with the new instance * and returns the block's value; - * closes the instance on block exit. + * closes the instance on block exit: * - * StringIO.open {|strio| p strio } - * # => #<StringIO> + * StringIO.open('foo') {|strio| strio.string.upcase } # => "FOO" * * Related: StringIO.new. */ @@ -412,42 +456,42 @@ strio_s_new(int argc, VALUE *argv, VALUE klass) } /* - * Returns +false+. Just for compatibility to IO. + * Returns +false+; for compatibility with IO. */ static VALUE strio_false(VALUE self) { - StringIO(self); + StringIOForRead(self); return Qfalse; } /* - * Returns +nil+. Just for compatibility to IO. + * Returns +nil+; for compatibility with IO. */ static VALUE strio_nil(VALUE self) { - StringIO(self); + StringIOForRead(self); return Qnil; } /* - * Returns an object itself. Just for compatibility to IO. + * Returns +self+; for compatibility with IO. */ static VALUE strio_self(VALUE self) { - StringIO(self); + StringIOForRead(self); return self; } /* - * Returns 0. Just for compatibility to IO. + * Returns 0; for compatibility with IO. */ static VALUE strio_0(VALUE self) { - StringIO(self); + StringIOForRead(self); return INT2FIX(0); } @@ -457,7 +501,7 @@ strio_0(VALUE self) static VALUE strio_first(VALUE self, VALUE arg) { - StringIO(self); + StringIOForRead(self); return arg; } @@ -467,7 +511,7 @@ strio_first(VALUE self, VALUE arg) static VALUE strio_unimpl(int argc, VALUE *argv, VALUE self) { - StringIO(self); + StringIOForRead(self); rb_notimplement(); UNREACHABLE; @@ -495,14 +539,14 @@ strio_unimpl(int argc, VALUE *argv, VALUE self) static VALUE strio_get_string(VALUE self) { - return StringIO(self)->string; + return StringIOForRead(self)->string; } /* * call-seq: * string = other_string -> other_string * - * Assigns the underlying string as +other_string+, and sets position to zero; + * Replaces the stored string with +other_string+, and sets the position to zero; * returns +other_string+: * * StringIO.open('foo') do |strio| @@ -516,7 +560,7 @@ strio_get_string(VALUE self) * "foo" * "bar" * - * Related: StringIO#string (returns the underlying string). + * Related: StringIO#string (returns the stored string). */ static VALUE strio_set_string(VALUE self, VALUE string) @@ -537,17 +581,22 @@ strio_set_string(VALUE self, VALUE string) * call-seq: * close -> nil * - * Closes +self+ for both reading and writing. + * Closes +self+ for both reading and writing; returns +nil+: * - * Raises IOError if reading or writing is attempted. + * strio = StringIO.new + * strio.closed? # => false + * strio.close # => nil + * strio.closed? # => true + * strio.read # Raises IOError: not opened for reading + * strio.write # Raises IOError: not opened for writing * - * Related: StringIO#close_read, StringIO#close_write. + * Related: StringIO#close_read, StringIO#close_write, StringIO.closed?. */ static VALUE strio_close(VALUE self) { StringIO(self); - RBASIC(self)->flags &= ~STRIO_READWRITE; + RB_FL_UNSET_RAW(self, STRIO_READWRITE); return Qnil; } @@ -555,9 +604,16 @@ strio_close(VALUE self) * call-seq: * close_read -> nil * - * Closes +self+ for reading; closed-write setting remains unchanged. + * Closes +self+ for reading; + * closed-write setting remains unchanged; + * returns +nil+: * - * Raises IOError if reading is attempted. + * strio = StringIO.new + * strio.closed_read? # => false + * strio.close_read # => nil + * strio.closed_read? # => true + * strio.closed_write? # => false + * strio.read # Raises IOError: not opened for reading * * Related: StringIO#close, StringIO#close_write. */ @@ -568,7 +624,7 @@ strio_close_read(VALUE self) if (!(ptr->flags & FMODE_READABLE)) { rb_raise(rb_eIOError, "closing non-duplex IO for reading"); } - RBASIC(self)->flags &= ~STRIO_READABLE; + RB_FL_UNSET_RAW(self, STRIO_READABLE); return Qnil; } @@ -576,11 +632,16 @@ strio_close_read(VALUE self) * call-seq: * close_write -> nil * - * Closes +self+ for writing; closed-read setting remains unchanged. + * Closes +self+ for writing; closed-read setting remains unchanged; returns +nil+: * - * Raises IOError if writing is attempted. + * strio = StringIO.new + * strio.closed_write? # => false + * strio.close_write # => nil + * strio.closed_write? # => true + * strio.closed_read? # => false + * strio.write('foo') # Raises IOError: not opened for writing * - * Related: StringIO#close, StringIO#close_read. + * Related: StringIO#close, StringIO#close_read, StringIO#closed_write?. */ static VALUE strio_close_write(VALUE self) @@ -589,7 +650,7 @@ strio_close_write(VALUE self) if (!(ptr->flags & FMODE_WRITABLE)) { rb_raise(rb_eIOError, "closing non-duplex IO for writing"); } - RBASIC(self)->flags &= ~STRIO_WRITABLE; + RB_FL_UNSET_RAW(self, STRIO_WRITABLE); return Qnil; } @@ -597,13 +658,21 @@ strio_close_write(VALUE self) * call-seq: * closed? -> true or false * - * Returns +true+ if +self+ is closed for both reading and writing, - * +false+ otherwise. + * Returns whether +self+ is closed for both reading and writing: + * + * strio = StringIO.new + * strio.closed? # => false # Open for reading and writing. + * strio.close_read + * strio.closed? # => false # Still open for writing. + * strio.close_write + * strio.closed? # => true # Now closed for both. + * + * Related: StringIO.closed_read?, StringIO.closed_write?. */ static VALUE strio_closed(VALUE self) { - StringIO(self); + StringIOForRead(self); if (!CLOSED(self)) return Qfalse; return Qtrue; } @@ -612,12 +681,19 @@ strio_closed(VALUE self) * call-seq: * closed_read? -> true or false * - * Returns +true+ if +self+ is closed for reading, +false+ otherwise. + * Returns whether +self+ is closed for reading: + * + * strio = StringIO.new + * strio.closed_read? # => false + * strio.close_read + * strio.closed_read? # => true + * + * Related: StringIO#closed?, StringIO#closed_write?, StringIO#close_read. */ static VALUE strio_closed_read(VALUE self) { - StringIO(self); + StringIOForRead(self); if (READABLE(self)) return Qfalse; return Qtrue; } @@ -626,12 +702,19 @@ strio_closed_read(VALUE self) * call-seq: * closed_write? -> true or false * - * Returns +true+ if +self+ is closed for writing, +false+ otherwise. + * Returns whether +self+ is closed for writing: + * + * strio = StringIO.new + * strio.closed_write? # => false + * strio.close_write + * strio.closed_write? # => true + * + * Related: StringIO#close_write, StringIO#closed?, StringIO#closed_read?. */ static VALUE strio_closed_write(VALUE self) { - StringIO(self); + StringIOForRead(self); if (WRITABLE(self)) return Qfalse; return Qtrue; } @@ -648,10 +731,18 @@ strio_to_read(VALUE self) * call-seq: * eof? -> true or false * - * Returns +true+ if positioned at end-of-stream, +false+ otherwise; - * see {Position}[rdoc-ref:IO@Position]. + * Returns whether +self+ is positioned at end-of-stream: + * + * strio = StringIO.new('foo') + * strio.pos # => 0 + * strio.eof? # => false + * strio.read # => "foo" + * strio.pos # => 3 + * strio.eof? # => true + * strio.close_read + * strio.eof? # Raises IOError: not opened for reading * - * Raises IOError if the stream is not opened for reading. + * Related: StringIO#pos. */ static VALUE strio_eof(VALUE self) @@ -669,7 +760,7 @@ strio_copy(VALUE copy, VALUE orig) orig = rb_convert_type(orig, T_DATA, "StringIO", "to_strio"); if (copy == orig) return copy; - ptr = StringIO(orig); + ptr = StringIOForRead(orig); old_ptr = check_strio(copy); if (old_ptr) { old_string = old_ptr->string; @@ -677,8 +768,8 @@ strio_copy(VALUE copy, VALUE orig) } DATA_PTR(copy) = ptr; RB_OBJ_WRITTEN(copy, old_string, ptr->string); - RBASIC(copy)->flags &= ~STRIO_READWRITE; - RBASIC(copy)->flags |= RBASIC(orig)->flags & STRIO_READWRITE; + RB_FL_UNSET_RAW(copy, STRIO_READWRITE); + RB_FL_SET_RAW(copy, RB_FL_TEST_RAW(orig, STRIO_READWRITE)); ++ptr->count; return copy; } @@ -688,12 +779,12 @@ strio_copy(VALUE copy, VALUE orig) * lineno -> current_line_number * * Returns the current line number in +self+; - * see {Line Number}[rdoc-ref:IO@Line+Number]. + * see {Line Number}[rdoc-ref:StringIO@Line+Number]. */ static VALUE strio_get_lineno(VALUE self) { - return LONG2NUM(StringIO(self)->lineno); + return LONG2NUM(StringIOForRead(self)->lineno); } /* @@ -701,7 +792,7 @@ strio_get_lineno(VALUE self) * lineno = new_line_number -> new_line_number * * Sets the current line number in +self+ to the given +new_line_number+; - * see {Line Number}[rdoc-ref:IO@Line+Number]. + * see {Line Number}[rdoc-ref:StringIO@Line+Number]. */ static VALUE strio_set_lineno(VALUE self, VALUE lineno) @@ -715,7 +806,7 @@ strio_set_lineno(VALUE self, VALUE lineno) * binmode -> self * * Sets the data mode in +self+ to binary mode; - * see {Data Mode}[rdoc-ref:File@Data+Mode]. + * see {Data Mode}[rdoc-ref:StringIO@Data+Mode]. * */ static VALUE @@ -776,12 +867,12 @@ strio_reopen(int argc, VALUE *argv, VALUE self) * pos -> stream_position * * Returns the current position (in bytes); - * see {Position}[rdoc-ref:IO@Position]. + * see {Position}[rdoc-ref:StringIO@Position]. */ static VALUE strio_get_pos(VALUE self) { - return LONG2NUM(StringIO(self)->pos); + return LONG2NUM(StringIOForRead(self)->pos); } /* @@ -789,7 +880,7 @@ strio_get_pos(VALUE self) * pos = new_position -> new_position * * Sets the current position (in bytes); - * see {Position}[rdoc-ref:IO@Position]. + * see {Position}[rdoc-ref:StringIO@Position]. */ static VALUE strio_set_pos(VALUE self, VALUE pos) @@ -824,9 +915,9 @@ strio_rewind(VALUE self) * call-seq: * seek(offset, whence = SEEK_SET) -> 0 * - * Sets the current position to the given integer +offset+ (in bytes), + * Sets the position to the given integer +offset+ (in bytes), * with respect to a given constant +whence+; - * see {Position}[rdoc-ref:IO@Position]. + * see {IO#seek}[rdoc-ref:IO#seek]. */ static VALUE strio_seek(int argc, VALUE *argv, VALUE self) @@ -873,7 +964,7 @@ strio_seek(int argc, VALUE *argv, VALUE self) static VALUE strio_get_sync(VALUE self) { - StringIO(self); + StringIOForRead(self); return Qtrue; } @@ -885,10 +976,9 @@ strio_get_sync(VALUE self) * call-seq: * each_byte {|byte| ... } -> self * - * With a block given, calls the block with each remaining byte in the stream; - * see {Byte IO}[rdoc-ref:IO@Byte+IO]. + * :include: stringio/each_byte.rdoc * - * With no block given, returns an enumerator. + * Related: StringIO#each_char, StringIO#each_codepoint, StringIO#each_line. */ static VALUE strio_each_byte(VALUE self) @@ -906,10 +996,10 @@ strio_each_byte(VALUE self) /* * call-seq: - * getc -> character or nil + * getc -> character, byte, or nil + * + * :include: stringio/getc.rdoc * - * Reads and returns the next character from the stream; - * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_getc(VALUE self) @@ -932,10 +1022,10 @@ strio_getc(VALUE self) /* * call-seq: - * getbyte -> byte or nil + * getbyte -> integer or nil + * + * :include: stringio/getbyte.rdoc * - * Reads and returns the next 8-bit byte from the stream; - * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_getbyte(VALUE self) @@ -1111,12 +1201,11 @@ strio_readbyte(VALUE self) /* * call-seq: - * each_char {|c| ... } -> self + * each_char {|char| ... } -> self * - * With a block given, calls the block with each remaining character in the stream; - * see {Character IO}[rdoc-ref:IO@Character+IO]. + * :include: stringio/each_char.rdoc * - * With no block given, returns an enumerator. + * Related: StringIO#each_byte, StringIO#each_codepoint, StringIO#each_line. */ static VALUE strio_each_char(VALUE self) @@ -1135,10 +1224,9 @@ strio_each_char(VALUE self) * call-seq: * each_codepoint {|codepoint| ... } -> self * - * With a block given, calls the block with each remaining codepoint in the stream; - * see {Codepoint IO}[rdoc-ref:IO@Codepoint+IO]. + * :include: stringio/each_codepoint.rdoc * - * With no block given, returns an enumerator. + * Related: StringIO#each_byte, StringIO#each_char, StringIO#each_line. */ static VALUE strio_each_codepoint(VALUE self) @@ -1372,9 +1460,8 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) * gets(limit, chomp: false) -> string or nil * gets(sep, limit, chomp: false) -> string or nil * - * Reads and returns a line from the stream; - * assigns the return value to <tt>$_</tt>; - * see {Line IO}[rdoc-ref:IO@Line+IO]. + * :include: stringio/gets.rdoc + * */ static VALUE strio_gets(int argc, VALUE *argv, VALUE self) @@ -1411,15 +1498,15 @@ strio_readline(int argc, VALUE *argv, VALUE self) } /* + * :markup: markdown + * * call-seq: * each_line(sep = $/, chomp: false) {|line| ... } -> self * each_line(limit, chomp: false) {|line| ... } -> self * each_line(sep, limit, chomp: false) {|line| ... } -> self * - * Calls the block with each remaining line read from the stream; - * does nothing if already at end-of-file; - * returns +self+. - * See {Line IO}[rdoc-ref:IO@Line+IO]. + * :include: stringio/each_line.md + * */ static VALUE strio_each(int argc, VALUE *argv, VALUE self) @@ -1560,9 +1647,10 @@ strio_write(VALUE self, VALUE str) /* * call-seq: - * strio.putc(obj) -> obj + * putc(object) -> object + * + * :include: stringio/putc.rdoc * - * See IO#putc. */ static VALUE strio_putc(VALUE self, VALUE ch) @@ -1594,9 +1682,10 @@ strio_putc(VALUE self, VALUE ch) /* * call-seq: - * strio.read([length [, outbuf]]) -> string, outbuf, or nil + * read(maxlen = nil, out_string = nil) → new_string, out_string, or nil + * + * :include: stringio/read.rdoc * - * See IO#read. */ static VALUE strio_read(int argc, VALUE *argv, VALUE self) @@ -1608,11 +1697,7 @@ strio_read(int argc, VALUE *argv, VALUE self) switch (argc) { case 2: - str = argv[1]; - if (!NIL_P(str)) { - StringValue(str); - rb_str_modify(str); - } + str = strio_readbuf(ptr, argv[1]); /* fall through */ case 1: if (!NIL_P(argv[0])) { @@ -1668,15 +1753,17 @@ strio_read(int argc, VALUE *argv, VALUE self) /* * call-seq: - * pread(maxlen, offset) -> string - * pread(maxlen, offset, out_string) -> string + * pread(maxlen, offset, out_string = nil) -> new_string or out_string + * + * :include: stringio/pread.rdoc * - * See IO#pread. */ static VALUE strio_pread(int argc, VALUE *argv, VALUE self) { VALUE rb_len, rb_offset, rb_buf; + struct StringIO *ptr = readable(self); + rb_scan_args(argc, argv, "21", &rb_len, &rb_offset, &rb_buf); long len = NUM2LONG(rb_len); long offset = NUM2LONG(rb_offset); @@ -1685,6 +1772,12 @@ strio_pread(int argc, VALUE *argv, VALUE self) rb_raise(rb_eArgError, "negative string size (or size too big): %" PRIsVALUE, rb_len); } + if (offset < 0) { + rb_syserr_fail_str(EINVAL, rb_sprintf("pread: Invalid offset argument: %" PRIsVALUE, rb_offset)); + } + + rb_buf = strio_readbuf(ptr, rb_buf); + if (len == 0) { if (NIL_P(rb_buf)) { return rb_str_new("", 0); @@ -1692,12 +1785,6 @@ strio_pread(int argc, VALUE *argv, VALUE self) return rb_buf; } - if (offset < 0) { - rb_syserr_fail_str(EINVAL, rb_sprintf("pread: Invalid offset argument: %" PRIsVALUE, rb_offset)); - } - - struct StringIO *ptr = readable(self); - if (outside_p(ptr, offset)) { rb_eof_error(); } @@ -1788,15 +1875,15 @@ strio_syswrite_nonblock(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.length -> integer - * strio.size -> integer + * size -> integer + * + * :include: stringio/size.rdoc * - * Returns the size of the buffer string. */ static VALUE strio_size(VALUE self) { - VALUE string = StringIO(self)->string; + VALUE string = StringIOForRead(self)->string; if (NIL_P(string)) { return INT2FIX(0); } @@ -1829,27 +1916,34 @@ strio_truncate(VALUE self, VALUE len) } /* - * call-seq: - * strio.external_encoding => encoding + * call-seq: + * external_encoding -> encoding or nil + * + * Returns an Encoding object that represents the encoding of the string; + * see {Encodings}[rdoc-ref:StringIO@Encodings]: + * + * strio = StringIO.new('foo') + * strio.external_encoding # => #<Encoding:UTF-8> + * + * Returns +nil+ if +self+ has no string and is in write mode: + * + * strio = StringIO.new(nil, 'w+') + * strio.external_encoding # => nil * - * Returns the Encoding object that represents the encoding of the file. - * If the stream is write mode and no encoding is specified, returns - * +nil+. */ static VALUE strio_external_encoding(VALUE self) { - struct StringIO *ptr = StringIO(self); + struct StringIO *ptr = StringIOForRead(self); return rb_enc_from_encoding(get_enc(ptr)); } /* * call-seq: - * strio.internal_encoding => encoding + * internal_encoding -> nil * - * Returns the Encoding of the internal string if conversion is - * specified. Otherwise returns +nil+. + * Returns +nil+; for compatibility with IO. */ static VALUE @@ -1918,16 +2012,9 @@ strio_set_encoding_by_bom(VALUE self) } /* - * \IO streams for strings, with access similar to - * {IO}[rdoc-ref:IO]; - * see {IO}[rdoc-ref:IO]. - * - * === About the Examples - * - * Examples on this page assume that \StringIO has been required: - * - * require 'stringio' + * :markup: markdown * + * :include: stringio/stringio.md */ void Init_stringio(void) @@ -2027,7 +2114,9 @@ Init_stringio(void) rb_define_method(StringIO, "set_encoding_by_bom", strio_set_encoding_by_bom, 0); { + /* :stopdoc: */ VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable"); + /* :startdoc: */ rb_define_method(mReadable, "readchar", strio_readchar, 0); rb_define_method(mReadable, "readbyte", strio_readbyte, 0); rb_define_method(mReadable, "readline", strio_readline, -1); @@ -2037,7 +2126,9 @@ Init_stringio(void) rb_include_module(StringIO, mReadable); } { + /* :stopdoc: */ VALUE mWritable = rb_define_module_under(rb_cIO, "generic_writable"); + /* :startdoc: */ rb_define_method(mWritable, "<<", strio_addstr, 1); rb_define_method(mWritable, "print", strio_print, -1); rb_define_method(mWritable, "printf", strio_printf, -1); diff --git a/ext/strscan/extconf.rb b/ext/strscan/extconf.rb index 3c311d2364..4e8d851fdb 100644 --- a/ext/strscan/extconf.rb +++ b/ext/strscan/extconf.rb @@ -4,6 +4,10 @@ if RUBY_ENGINE == 'ruby' $INCFLAGS << " -I$(top_srcdir)" if $extmk have_func("onig_region_memsize(NULL)") have_func("rb_reg_onig_match", "ruby/re.h") + have_func("rb_deprecate_constant") + have_func("rb_int_parse_cstr", "ruby.h") # RUBY_VERSION >= 2.5 + have_func("rb_gc_location", "ruby.h") # RUBY_VERSION >= 2.7 + have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 create_makefile 'strscan' else File.write('Makefile', dummy_makefile("").join) diff --git a/ext/strscan/lib/strscan.rb b/ext/strscan/lib/strscan.rb new file mode 100644 index 0000000000..4e8910d141 --- /dev/null +++ b/ext/strscan/lib/strscan.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +case RUBY_ENGINE +when 'ruby' + require 'strscan.so' + require_relative 'strscan/strscan' +when 'jruby' + require 'strscan.jar' + JRuby::Util.load_ext('org.jruby.ext.strscan.StringScannerLibrary') + require_relative 'strscan/strscan' +when 'truffleruby' + if RUBY_ENGINE_VERSION.to_i >= 34 + require 'strscan/truffleruby' + else + $LOAD_PATH.delete __dir__ + require 'strscan' + end +else + raise NotImplementedError, "Unknown Ruby: #{RUBY_ENGINE}" +end diff --git a/ext/strscan/lib/strscan/strscan.rb b/ext/strscan/lib/strscan/strscan.rb index 46acc7ea82..5e262f4007 100644 --- a/ext/strscan/lib/strscan/strscan.rb +++ b/ext/strscan/lib/strscan/strscan.rb @@ -1,17 +1,47 @@ # frozen_string_literal: true class StringScanner + unless method_defined?(:integer_at) # For JRuby + def integer_at(specifier, *to_i_args) + self[specifier]&.to_i(*to_i_args) + end + end + + # :markup: markdown + # # call-seq: - # scan_integer(base: 10) + # scan_integer(base: 10) -> integer or nil + # + # Returns an integer scanned from `self`, + # beginning at the current position; + # returns `nil` if no such integer was available. + # + # When `base` is `10` (the default), + # equivalent to calling #scan with argument +pattern+ + # as `'[+-]?\d+'`: + # + # ```ruby + # scanner = StringScanner.new('Form 27B/6') + # scanner.scan_integer # => nil # No integer at position 0. + # scanner.pos = 5 + # scanner.scan_integer # => 27 + # scanner.matched # => "27" + # scanner.pos # => 7 + # ``` # - # If `base` isn't provided or is `10`, then it is equivalent to calling `#scan` with a `[+-]?\d+` pattern, - # and returns an Integer or nil. + # When `base` is `16` (the only other value allowed), + # equivalent to calling #scan with argument `pattern` + # as `'[+-]?(0x)?[0-9a-fA-F]+'`: # - # If `base` is `16`, then it is equivalent to calling `#scan` with a `[+-]?(0x)?[0-9a-fA-F]+` pattern, - # and returns an Integer or nil. + # ```ruby + # scanner.pos = 5 + # scanner.scan_integer(base: 16) # => 635 + # scanner.matched # => "27B" + # scanner.pos # => 8 + # ``` # - # The scanned string must be encoded with an ASCII compatible encoding, otherwise - # Encoding::CompatibilityError will be raised. + # Raises Encoding::CompatibilityError if `self` does not have + # an ASCII compatible encoding. def scan_integer(base: 10) case base when 10 diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index bc543f62b1..dede57218b 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,15 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include <stdbool.h> -#define STRSCAN_VERSION "3.1.6.dev" +#define STRSCAN_VERSION "3.1.9.dev" + + +#ifdef HAVE_RB_DEPRECATE_CONSTANT +/* In ruby 3.0, defined but exposed in external headers */ +extern void rb_deprecate_constant(VALUE mod, const char *name); +#else +# define rb_deprecate_constant(mod, name) ((void)0) +#endif /* ======================================================================= Data Type Definitions @@ -30,7 +38,6 @@ extern size_t onig_region_memsize(const struct re_registers *regs); static VALUE StringScanner; static VALUE ScanError; -static ID id_byteslice; static int usascii_encindex, utf8_encindex, binary_encindex; @@ -97,7 +104,6 @@ static VALUE strscan_init_copy _((VALUE vself, VALUE vorig)); static VALUE strscan_s_mustc _((VALUE self)); static VALUE strscan_terminate _((VALUE self)); -static VALUE strscan_clear _((VALUE self)); static VALUE strscan_get_string _((VALUE self)); static VALUE strscan_set_string _((VALUE self, VALUE str)); static VALUE strscan_concat _((VALUE self, VALUE str)); @@ -119,14 +125,11 @@ static VALUE strscan_search_full _((VALUE self, VALUE re, static void adjust_registers_to_matched _((struct strscanner *p)); static VALUE strscan_getch _((VALUE self)); static VALUE strscan_get_byte _((VALUE self)); -static VALUE strscan_getbyte _((VALUE self)); static VALUE strscan_peek _((VALUE self, VALUE len)); -static VALUE strscan_peep _((VALUE self, VALUE len)); static VALUE strscan_scan_base10_integer _((VALUE self)); static VALUE strscan_unscan _((VALUE self)); static VALUE strscan_bol_p _((VALUE self)); static VALUE strscan_eos_p _((VALUE self)); -static VALUE strscan_empty_p _((VALUE self)); static VALUE strscan_rest_p _((VALUE self)); static VALUE strscan_matched_p _((VALUE self)); static VALUE strscan_matched _((VALUE self)); @@ -179,12 +182,35 @@ extract_beg_len(struct strscanner *p, long beg_i, long len) Constructor ======================================================================= */ +#ifdef RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +#else +# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE +# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +# else +# define RUBY_TYPED_EMBEDDABLE 0 +# endif +#endif + +#ifdef HAVE_RB_GC_LOCATION +static void +strscan_compact(void *ptr) +{ + struct strscanner *p = ptr; + p->str = rb_gc_location(p->str); + p->regex = rb_gc_location(p->regex); +} +#else +#define rb_gc_mark_movable rb_gc_mark +#endif + static void strscan_mark(void *ptr) { struct strscanner *p = ptr; - rb_gc_mark(p->str); - rb_gc_mark(p->regex); + rb_gc_mark_movable(p->str); + rb_gc_mark_movable(p->regex); } static void @@ -192,24 +218,37 @@ strscan_free(void *ptr) { struct strscanner *p = ptr; onig_region_free(&(p->regs), 0); +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE ruby_xfree(p); +#endif } static size_t strscan_memsize(const void *ptr) { - const struct strscanner *p = ptr; - size_t size = sizeof(*p) - sizeof(p->regs); + size_t size = 0; +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + size += sizeof(struct strscanner); +#endif + #ifdef HAVE_ONIG_REGION_MEMSIZE - size += onig_region_memsize(&p->regs); + const struct strscanner *p = ptr; + size += onig_region_memsize(&p->regs) - sizeof(p->regs); #endif return size; } static const rb_data_type_t strscanner_type = { - "StringScanner", - {strscan_mark, strscan_free, strscan_memsize}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED + .wrap_struct_name = "StringScanner", + .function = { + .dmark = strscan_mark, + .dfree = strscan_free, + .dsize = strscan_memsize, +#ifdef HAVE_RB_GC_LOCATION + .dcompact = strscan_compact, +#endif + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE }; static VALUE @@ -371,6 +410,9 @@ strscan_reset(VALUE self) /* * :markup: markdown + * :call-seq: + * terminate -> self + * * :include: strscan/link_refs.txt * :include: strscan/methods/terminate.md */ @@ -386,21 +428,6 @@ strscan_terminate(VALUE self) } /* - * call-seq: - * clear -> self - * - * This method is obsolete; use the equivalent method StringScanner#terminate. - */ - - /* :nodoc: */ -static VALUE -strscan_clear(VALUE self) -{ - rb_warning("StringScanner#clear is obsolete; use #terminate instead"); - return strscan_terminate(self); -} - -/* * :markup: markdown * :include: strscan/link_refs.txt * @@ -515,6 +542,9 @@ strscan_concat(VALUE self, VALUE str) /* * :markup: markdown + * :call-seq: + * pos -> byte_position + * * :include: strscan/link_refs.txt * :include: strscan/methods/get_pos.md */ @@ -529,6 +559,9 @@ strscan_get_pos(VALUE self) /* * :markup: markdown + * :call-seq: + * charpos -> character_position + * * :include: strscan/link_refs.txt * :include: strscan/methods/get_charpos.md */ @@ -544,6 +577,10 @@ strscan_get_charpos(VALUE self) /* * :markup: markdown + * :call-seq: + * pos = n -> n + * pointer = n -> n + * * :include: strscan/link_refs.txt * :include: strscan/methods/set_pos.md */ @@ -768,6 +805,9 @@ strscan_do_scan(VALUE self, VALUE pattern, int succptr, int getstr, int headonly /* * :markup: markdown + * :call-seq: + * scan(pattern) -> substring or nil + * * :include: strscan/link_refs.txt * :include: strscan/methods/scan.md */ @@ -782,7 +822,7 @@ strscan_scan(VALUE self, VALUE re) * :include: strscan/link_refs.txt * * call-seq: - * match?(pattern) -> updated_position or nil + * match?(pattern) -> match_size or nil * * Attempts to [match][17] the given `pattern` * at the beginning of the [target substring][3]; @@ -841,6 +881,9 @@ strscan_match_p(VALUE self, VALUE re) /* * :markup: markdown + * call-seq: + * skip(pattern) -> match_size or nil + * * :include: strscan/link_refs.txt * :include: strscan/methods/skip.md */ @@ -913,7 +956,7 @@ strscan_check(VALUE self, VALUE re) /* * call-seq: - * scan_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or nil + * scan_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or length or nil * * Equivalent to one of the following: * @@ -938,6 +981,9 @@ strscan_scan_full(VALUE self, VALUE re, VALUE s, VALUE f) /* * :markup: markdown + * :call-seq: + * scan_until(pattern) -> substring or nil + * * :include: strscan/link_refs.txt * :include: strscan/methods/scan_until.md */ @@ -1012,6 +1058,9 @@ strscan_exist_p(VALUE self, VALUE re) /* * :markup: markdown + * :call-seq: + * skip_until(pattern) -> matched_substring_size or nil + * * :include: strscan/link_refs.txt * :include: strscan/methods/skip_until.md */ @@ -1123,6 +1172,9 @@ adjust_registers_to_matched(struct strscanner *p) /* * :markup: markdown + * :call-seq: + * getch -> character or nil + * * :include: strscan/link_refs.txt * :include: strscan/methods/getch.md */ @@ -1150,7 +1202,7 @@ strscan_getch(VALUE self) /* * call-seq: - * scan_byte -> integer_byte + * scan_byte -> integer_byte or nil * * Scans one byte and returns it as an integer. * This method is not multibyte character sensitive. @@ -1196,6 +1248,9 @@ strscan_peek_byte(VALUE self) /* * :markup: markdown + * :call-seq: + * get_byte -> byte_as_character or nil + * * :include: strscan/link_refs.txt * :include: strscan/methods/get_byte.md */ @@ -1219,22 +1274,6 @@ strscan_get_byte(VALUE self) } /* - * call-seq: - * getbyte - * - * Equivalent to #get_byte. - * This method is obsolete; use #get_byte instead. - */ - - /* :nodoc: */ -static VALUE -strscan_getbyte(VALUE self) -{ - rb_warning("StringScanner#getbyte is obsolete; use #get_byte instead"); - return strscan_get_byte(self); -} - -/* * :markup: markdown * :include: strscan/link_refs.txt * @@ -1269,22 +1308,6 @@ strscan_peek(VALUE self, VALUE vlen) return extract_beg_len(p, p->curr, len); } -/* - * call-seq: - * peep - * - * Equivalent to #peek. - * This method is obsolete; use #peek instead. - */ - - /* :nodoc: */ -static VALUE -strscan_peep(VALUE self, VALUE vlen) -{ - rb_warning("StringScanner#peep is obsolete; use #peek instead"); - return strscan_peek(self, vlen); -} - static VALUE strscan_parse_integer(struct strscanner *p, int base, long len) { @@ -1305,16 +1328,17 @@ strscan_parse_integer(struct strscanner *p, int base, long len) } static inline bool -strscan_ascii_compat_fastpath(VALUE str) { +strscan_ascii_compat_fastpath(VALUE str) +{ int encindex = ENCODING_GET_INLINED(str); - // The overwhelming majority of strings are in one of these 3 encodings. + /* The overwhelming majority of strings are in one of these 3 encodings. */ return encindex == utf8_encindex || encindex == binary_encindex || encindex == usascii_encindex; } static inline void strscan_must_ascii_compat(VALUE str) { - // The overwhelming majority of strings are in one of these 3 encodings. + /* The overwhelming majority of strings are in one of these 3 encodings. */ if (RB_LIKELY(strscan_ascii_compat_fastpath(str))) { return; } @@ -1322,11 +1346,12 @@ strscan_must_ascii_compat(VALUE str) rb_must_asciicompat(str); } +/* :nodoc: */ static VALUE strscan_scan_base10_integer(VALUE self) { char *ptr; - long len = 0; + long len = 0, remaining_len; struct strscanner *p; GET_SCANNER(self, p); @@ -1336,7 +1361,7 @@ strscan_scan_base10_integer(VALUE self) ptr = CURPTR(p); - long remaining_len = S_RESTLEN(p); + remaining_len = S_RESTLEN(p); if (remaining_len <= 0) { return Qnil; @@ -1359,11 +1384,12 @@ strscan_scan_base10_integer(VALUE self) return strscan_parse_integer(p, 10, len); } +/* :nodoc: */ static VALUE strscan_scan_base16_integer(VALUE self) { char *ptr; - long len = 0; + long len = 0, remaining_len; struct strscanner *p; GET_SCANNER(self, p); @@ -1373,7 +1399,7 @@ strscan_scan_base16_integer(VALUE self) ptr = CURPTR(p); - long remaining_len = S_RESTLEN(p); + remaining_len = S_RESTLEN(p); if (remaining_len <= 0) { return Qnil; @@ -1526,26 +1552,9 @@ strscan_eos_p(VALUE self) /* * call-seq: - * empty? - * - * Equivalent to #eos?. - * This method is obsolete, use #eos? instead. - */ - - /* :nodoc: */ -static VALUE -strscan_empty_p(VALUE self) -{ - rb_warning("StringScanner#empty? is obsolete; use #eos? instead"); - return strscan_eos_p(self); -} - -/* - * call-seq: * rest? * * Returns true if and only if there is more data in the string. See #eos?. - * This method is obsolete; use #eos? instead. * * s = StringScanner.new('test string') * # These two are opposites @@ -1673,7 +1682,7 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name (const unsigned char* )name_end, regs); if (num >= 1) { - return num; + return num; } } rb_enc_raise(enc, rb_eIndexError, "undefined group name reference: %.*s", @@ -1681,6 +1690,38 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name } /* + * Resolve capture group index from Integer, Symbol, or String. + * Returns the resolved register index, or -1 if unmatched/out of range. + * For Symbol/String specifiers, raises IndexError if the named group + * does not exist. + */ +static long +resolve_capture_index(struct strscanner *p, VALUE specifier) +{ + const char *name; + long i; + if (! MATCHED_P(p)) return -1; + switch (TYPE(specifier)) { + case T_SYMBOL: + specifier = rb_sym2str(specifier); + /* fall through */ + case T_STRING: + RSTRING_GETMEM(specifier, name, i); + i = name_to_backref_number(&(p->regs), p->regex, name, name + i, + rb_enc_get(specifier)); + break; + default: + i = NUM2LONG(specifier); + } + if (i < 0) + i += p->regs.num_regs; + if (i < 0) return -1; + if (i >= p->regs.num_regs) return -1; + if (p->regs.beg[i] == -1) return -1; + return i; +} + +/* * * :markup: markdown * :include: strscan/link_refs.txt @@ -1754,30 +1795,12 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name static VALUE strscan_aref(VALUE self, VALUE idx) { - const char *name; struct strscanner *p; long i; GET_SCANNER(self, p); - if (! MATCHED_P(p)) return Qnil; - - switch (TYPE(idx)) { - case T_SYMBOL: - idx = rb_sym2str(idx); - /* fall through */ - case T_STRING: - RSTRING_GETMEM(idx, name, i); - i = name_to_backref_number(&(p->regs), p->regex, name, name + i, rb_enc_get(idx)); - break; - default: - i = NUM2LONG(idx); - } - - if (i < 0) - i += p->regs.num_regs; - if (i < 0) return Qnil; - if (i >= p->regs.num_regs) return Qnil; - if (p->regs.beg[i] == -1) return Qnil; + i = resolve_capture_index(p, idx); + if (i < 0) return Qnil; return extract_range(p, adjust_register_position(p, p->regs.beg[i]), @@ -1786,6 +1809,81 @@ strscan_aref(VALUE self, VALUE idx) /* * :markup: markdown + * + * call-seq: + * integer_at(specifier, base=10) -> integer or nil + * + * Returns the captured substring at the given `specifier` as an Integer, + * following the behavior of `String#to_i(base)`. + * + * `specifier` can be an Integer (positive, negative, or zero), a Symbol, + * or a String for named capture groups. + * + * Returns `nil` if: + * - No match has been performed or the last match failed + * - The `specifier` is an Integer and is out of range + * - The group at `specifier` did not participate in the match + * + * Raises IndexError if `specifier` is a Symbol or String that does not + * correspond to a named capture group, consistent with + * `StringScanner#[]`. + * + * This is semantically equivalent to `self[specifier]&.to_i(base)` + * but avoids the allocation of a temporary String when possible. + * + * ```rb + * scanner = StringScanner.new("2024-06-15") + * scanner.scan(/(\d{4})-(\d{2})-(\d{2})/) + * scanner.integer_at(1) # => 2024 + * scanner.integer_at(1, 16) # => 8228 + * ``` + */ +static VALUE +strscan_integer_at(int argc, VALUE *argv, VALUE self) +{ + struct strscanner *p; + long i; + long beg, end, len; + const char *ptr; + VALUE rb_specifier; + VALUE rb_base; + int base = 10; + + GET_SCANNER(self, p); + rb_scan_args(argc, argv, "11", &rb_specifier, &rb_base); + if (argc > 1) + base = NUM2INT(rb_base); + i = resolve_capture_index(p, rb_specifier); + if (i < 0) + return Qnil; + + beg = adjust_register_position(p, p->regs.beg[i]); + end = adjust_register_position(p, p->regs.end[i]); + len = end - beg; + ptr = S_PBEG(p) + beg; +#ifdef HAVE_RB_INT_PARSE_CSTR + { + /* + * Ruby 2.5 or later export the rb_int_parse_cstr() symbol but + * prototype definition isn't provided. Ruby 4.1 or later + * provide prototype definition. + */ +# ifndef RB_INT_PARSE_DEFAULT + VALUE rb_int_parse_cstr(const char *str, ssize_t len, char **endp, + size_t *ndigits, int base, int flags); +# define RB_INT_PARSE_DEFAULT 0x07 +# endif + char *endp; + return rb_int_parse_cstr(ptr, len, &endp, NULL, base, + RB_INT_PARSE_DEFAULT); + } +#else + return rb_str_to_inum(rb_str_new(ptr, len), base, 0); +#endif +} + +/* + * :markup: markdown * :include: strscan/link_refs.txt * * call-seq: @@ -2053,22 +2151,6 @@ strscan_rest_size(VALUE self) return INT2FIX(i); } -/* - * call-seq: - * restsize - * - * <tt>s.restsize</tt> is equivalent to <tt>s.rest_size</tt>. - * This method is obsolete; use #rest_size instead. - */ - - /* :nodoc: */ -static VALUE -strscan_restsize(VALUE self) -{ - rb_warning("StringScanner#restsize is obsolete; use #rest_size instead"); - return strscan_rest_size(self); -} - #define INSPECT_LENGTH 5 /* @@ -2227,8 +2309,8 @@ named_captures_iter(const OnigUChar *name, * call-seq: * named_captures -> hash * - * Returns the array of captured match values at indexes (1..) - * if the most recent match attempt succeeded, or nil otherwise; + * Returns a hash of named captures for the most recent regexp match, + * or an empty hash if there are no named captures; * see [Captured Match Values][13]: * * ```rb @@ -2268,6 +2350,13 @@ strscan_named_captures(VALUE self) ======================================================================= */ /* + * Document-class: StringScanner::Error + * + * The error class for StringScanner. + * See StringScanner#unscan. + */ + +/* * Document-class: StringScanner * * :markup: markdown @@ -2287,8 +2376,6 @@ Init_strscan(void) ID id_scanerr = rb_intern("ScanError"); VALUE tmp; - id_byteslice = rb_intern("byteslice"); - usascii_encindex = rb_usascii_encindex(); utf8_encindex = rb_utf8_encindex(); binary_encindex = rb_ascii8bit_encindex(); @@ -2297,6 +2384,7 @@ Init_strscan(void) ScanError = rb_define_class_under(StringScanner, "Error", rb_eStandardError); if (!rb_const_defined(rb_cObject, id_scanerr)) { rb_const_set(rb_cObject, id_scanerr, ScanError); + rb_deprecate_constant(rb_cObject, "ScanError"); } tmp = rb_str_new2(STRSCAN_VERSION); rb_obj_freeze(tmp); @@ -2304,6 +2392,7 @@ Init_strscan(void) tmp = rb_str_new2("$Id$"); rb_obj_freeze(tmp); rb_const_set(StringScanner, rb_intern("Id"), tmp); + rb_deprecate_constant(StringScanner, "Id"); rb_define_alloc_func(StringScanner, strscan_s_allocate); rb_define_private_method(StringScanner, "initialize", strscan_initialize, -1); @@ -2311,7 +2400,6 @@ Init_strscan(void) rb_define_singleton_method(StringScanner, "must_C_version", strscan_s_mustc, 0); rb_define_method(StringScanner, "reset", strscan_reset, 0); rb_define_method(StringScanner, "terminate", strscan_terminate, 0); - rb_define_method(StringScanner, "clear", strscan_clear, 0); rb_define_method(StringScanner, "string", strscan_get_string, 0); rb_define_method(StringScanner, "string=", strscan_set_string, 1); rb_define_method(StringScanner, "concat", strscan_concat, 1); @@ -2336,11 +2424,9 @@ Init_strscan(void) rb_define_method(StringScanner, "getch", strscan_getch, 0); rb_define_method(StringScanner, "get_byte", strscan_get_byte, 0); - rb_define_method(StringScanner, "getbyte", strscan_getbyte, 0); rb_define_method(StringScanner, "scan_byte", strscan_scan_byte, 0); rb_define_method(StringScanner, "peek", strscan_peek, 1); rb_define_method(StringScanner, "peek_byte", strscan_peek_byte, 0); - rb_define_method(StringScanner, "peep", strscan_peep, 1); rb_define_private_method(StringScanner, "scan_base10_integer", strscan_scan_base10_integer, 0); rb_define_private_method(StringScanner, "scan_base16_integer", strscan_scan_base16_integer, 0); @@ -2350,13 +2436,13 @@ Init_strscan(void) rb_define_method(StringScanner, "beginning_of_line?", strscan_bol_p, 0); rb_alias(StringScanner, rb_intern("bol?"), rb_intern("beginning_of_line?")); rb_define_method(StringScanner, "eos?", strscan_eos_p, 0); - rb_define_method(StringScanner, "empty?", strscan_empty_p, 0); rb_define_method(StringScanner, "rest?", strscan_rest_p, 0); rb_define_method(StringScanner, "matched?", strscan_matched_p, 0); rb_define_method(StringScanner, "matched", strscan_matched, 0); rb_define_method(StringScanner, "matched_size", strscan_matched_size, 0); rb_define_method(StringScanner, "[]", strscan_aref, 1); + rb_define_method(StringScanner, "integer_at", strscan_integer_at, -1); rb_define_method(StringScanner, "pre_match", strscan_pre_match, 0); rb_define_method(StringScanner, "post_match", strscan_post_match, 0); rb_define_method(StringScanner, "size", strscan_size, 0); @@ -2365,13 +2451,10 @@ Init_strscan(void) rb_define_method(StringScanner, "rest", strscan_rest, 0); rb_define_method(StringScanner, "rest_size", strscan_rest_size, 0); - rb_define_method(StringScanner, "restsize", strscan_restsize, 0); rb_define_method(StringScanner, "inspect", strscan_inspect, 0); rb_define_method(StringScanner, "fixed_anchor?", strscan_fixed_anchor_p, 0); rb_define_method(StringScanner, "named_captures", strscan_named_captures, 0); - - rb_require("strscan/strscan"); } diff --git a/ext/strscan/strscan.gemspec b/ext/strscan/strscan.gemspec index 47180bb8d8..a51285fa7e 100644 --- a/ext/strscan/strscan.gemspec +++ b/ext/strscan/strscan.gemspec @@ -16,18 +16,18 @@ Gem::Specification.new do |s| s.summary = "Provides lexical scanning operations on a String." s.description = "Provides lexical scanning operations on a String." - files = [ - "COPYING", - "LICENSE.txt", - "lib/strscan/strscan.rb" + files = %w[ + COPYING + LICENSE.txt + lib/strscan.rb + lib/strscan/strscan.rb + lib/strscan/truffleruby.rb ] s.require_paths = %w{lib} if RUBY_ENGINE == "jruby" files << "lib/strscan.jar" - files << "ext/jruby/lib/strscan.rb" - s.require_paths += %w{ext/jruby/lib} s.platform = "java" else files << "ext/strscan/extconf.rb" diff --git a/ext/win32/lib/win32/registry.rb b/ext/win32/lib/win32/registry.rb deleted file mode 100644 index 8e2c8b2e1a..0000000000 --- a/ext/win32/lib/win32/registry.rb +++ /dev/null @@ -1,925 +0,0 @@ -# frozen_string_literal: true -require 'fiddle/import' - -module Win32 - -=begin rdoc -= Win32 Registry - -win32/registry is registry accessor library for Win32 platform. -It uses importer to call Win32 Registry APIs. - -== example - Win32::Registry::HKEY_CURRENT_USER.open('SOFTWARE\foo') do |reg| - value = reg['foo'] # read a value - value = reg['foo', Win32::Registry::REG_SZ] # read a value with type - type, value = reg.read('foo') # read a value - reg['foo'] = 'bar' # write a value - reg['foo', Win32::Registry::REG_SZ] = 'bar' # write a value with type - reg.write('foo', Win32::Registry::REG_SZ, 'bar') # write a value - - reg.each_value { |name, type, data| ... } # Enumerate values - reg.each_key { |key, wtime| ... } # Enumerate subkeys - - reg.delete_value(name) # Delete a value - reg.delete_key(name) # Delete a subkey - reg.delete_key(name, true) # Delete a subkey recursively - end - -= Reference - -== Win32::Registry class - ---- info - ---- num_keys - ---- max_key_length - ---- num_values - ---- max_value_name_length - ---- max_value_length - ---- descriptor_length - ---- wtime - Returns an item of key information. - -=== constants ---- HKEY_CLASSES_ROOT - ---- HKEY_CURRENT_USER - ---- HKEY_LOCAL_MACHINE - ---- HKEY_PERFORMANCE_DATA - ---- HKEY_CURRENT_CONFIG - ---- HKEY_DYN_DATA - - Win32::Registry object whose key is predefined key. -For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/predefined_keys.asp] article. - -=end rdoc - - WCHAR = Encoding::UTF_16LE - WCHAR_NUL = "\0".encode(WCHAR).freeze - WCHAR_CR = "\r".encode(WCHAR).freeze - WCHAR_SIZE = WCHAR_NUL.bytesize - LOCALE = Encoding::UTF_8 - - class Registry - - # - # For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/registry.asp]. - # - # --- HKEY_* - # - # Predefined key ((*handle*)). - # These are Integer, not Win32::Registry. - # - # --- REG_* - # - # Registry value type. - # - # --- KEY_* - # - # Security access mask. - # - # --- KEY_OPTIONS_* - # - # Key options. - # - # --- REG_CREATED_NEW_KEY - # - # --- REG_OPENED_EXISTING_KEY - # - # If the key is created newly or opened existing key. - # See also Registry#disposition method. - module Constants - HKEY_CLASSES_ROOT = 0x80000000 - HKEY_CURRENT_USER = 0x80000001 - HKEY_LOCAL_MACHINE = 0x80000002 - HKEY_USERS = 0x80000003 - HKEY_PERFORMANCE_DATA = 0x80000004 - HKEY_PERFORMANCE_TEXT = 0x80000050 - HKEY_PERFORMANCE_NLSTEXT = 0x80000060 - HKEY_CURRENT_CONFIG = 0x80000005 - HKEY_DYN_DATA = 0x80000006 - - REG_NONE = 0 - REG_SZ = 1 - REG_EXPAND_SZ = 2 - REG_BINARY = 3 - REG_DWORD = 4 - REG_DWORD_LITTLE_ENDIAN = 4 - REG_DWORD_BIG_ENDIAN = 5 - REG_LINK = 6 - REG_MULTI_SZ = 7 - REG_RESOURCE_LIST = 8 - REG_FULL_RESOURCE_DESCRIPTOR = 9 - REG_RESOURCE_REQUIREMENTS_LIST = 10 - REG_QWORD = 11 - REG_QWORD_LITTLE_ENDIAN = 11 - - STANDARD_RIGHTS_READ = 0x00020000 - STANDARD_RIGHTS_WRITE = 0x00020000 - KEY_QUERY_VALUE = 0x0001 - KEY_SET_VALUE = 0x0002 - KEY_CREATE_SUB_KEY = 0x0004 - KEY_ENUMERATE_SUB_KEYS = 0x0008 - KEY_NOTIFY = 0x0010 - KEY_CREATE_LINK = 0x0020 - KEY_READ = STANDARD_RIGHTS_READ | - KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY - KEY_WRITE = STANDARD_RIGHTS_WRITE | - KEY_SET_VALUE | KEY_CREATE_SUB_KEY - KEY_EXECUTE = KEY_READ - KEY_ALL_ACCESS = KEY_READ | KEY_WRITE | KEY_CREATE_LINK - - REG_OPTION_RESERVED = 0x0000 - REG_OPTION_NON_VOLATILE = 0x0000 - REG_OPTION_VOLATILE = 0x0001 - REG_OPTION_CREATE_LINK = 0x0002 - REG_OPTION_BACKUP_RESTORE = 0x0004 - REG_OPTION_OPEN_LINK = 0x0008 - REG_LEGAL_OPTION = REG_OPTION_RESERVED | - REG_OPTION_NON_VOLATILE | REG_OPTION_CREATE_LINK | - REG_OPTION_BACKUP_RESTORE | REG_OPTION_OPEN_LINK - - REG_CREATED_NEW_KEY = 1 - REG_OPENED_EXISTING_KEY = 2 - - REG_WHOLE_HIVE_VOLATILE = 0x0001 - REG_REFRESH_HIVE = 0x0002 - REG_NO_LAZY_FLUSH = 0x0004 - REG_FORCE_RESTORE = 0x0008 - - MAX_KEY_LENGTH = 514 - MAX_VALUE_LENGTH = 32768 - end - include Constants - include Enumerable - - # - # Error - # - class Error < ::StandardError - module Kernel32 - extend Fiddle::Importer - dlload "kernel32.dll" - end - FormatMessageW = Kernel32.extern "int FormatMessageW(int, void *, int, int, void *, int, void *)", :stdcall - def initialize(code) - @code = code - buff = WCHAR_NUL * 1024 - lang = 0 - begin - len = FormatMessageW.call(0x1200, nil, code, lang, buff, 1024, nil) - msg = buff.byteslice(0, len * WCHAR_SIZE) - msg.delete!(WCHAR_CR) - msg.chomp! - msg.encode!(LOCALE) - rescue EncodingError - raise unless lang == 0 - lang = 0x0409 # en_US - retry - end - super msg - end - attr_reader :code - end - - # - # Predefined Keys - # - class PredefinedKey < Registry - def initialize(hkey, keyname) - @hkey = Fiddle::Pointer.new(hkey) - @parent = nil - @keyname = keyname - @disposition = REG_OPENED_EXISTING_KEY - end - - # Predefined keys cannot be closed - def close - raise Error.new(5) ## ERROR_ACCESS_DENIED - end - - # Fake #class method for Registry#open, Registry#create - def class - Registry - end - - # Make all - Constants.constants.grep(/^HKEY_/) do |c| - Registry.const_set c, new(Constants.const_get(c), c.to_s) - end - end - - # - # Win32 APIs - # - module API - include Constants - extend Fiddle::Importer - dlload "advapi32.dll" - [ - "long RegOpenKeyExW(void *, void *, long, long, void *)", - "long RegCreateKeyExW(void *, void *, long, long, long, long, void *, void *, void *)", - "long RegEnumValueW(void *, long, void *, void *, void *, void *, void *, void *)", - "long RegEnumKeyExW(void *, long, void *, void *, void *, void *, void *, void *)", - "long RegQueryValueExW(void *, void *, void *, void *, void *, void *)", - "long RegSetValueExW(void *, void *, long, long, void *, long)", - "long RegDeleteValueW(void *, void *)", - "long RegDeleteKeyW(void *, void *)", - "long RegFlushKey(void *)", - "long RegCloseKey(void *)", - "long RegQueryInfoKeyW(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *)", - ].each do |fn| - cfunc = extern fn, :stdcall - const_set cfunc.name.intern, cfunc - end - - module_function - - def check(result) - raise Error, result, caller(1) if result != 0 - end - - def win64? - /^(?:x64|x86_64)/ =~ RUBY_PLATFORM - end - - TEMPLATE_HANDLE = 'J<' - - def packhandle(h) - [h].pack(TEMPLATE_HANDLE) - end - - def unpackhandle(h) - (h + [0].pack(TEMPLATE_HANDLE)).unpack1(TEMPLATE_HANDLE) - end - - TEMPLATE_DWORD = 'V' - - def packdw(dw) - [dw].pack(TEMPLATE_DWORD) - end - - def unpackdw(dw) - (dw + [0].pack(TEMPLATE_DWORD)).unpack1(TEMPLATE_DWORD) - end - - TEMPLATE_QWORD = 'Q<' - - def packqw(qw) - [qw].pack(TEMPLATE_QWORD) - end - - def unpackqw(qw) - (qw + [0].pack(TEMPLATE_QWORD)).unpack1(TEMPLATE_QWORD) - end - - def make_wstr(str) - (str+"\0").encode(WCHAR) - end - - def OpenKey(hkey, name, opt, desired) - result = packhandle(0) - check RegOpenKeyExW.call(hkey, make_wstr(name), opt, desired, result) - unpackhandle(result) - end - - def CreateKey(hkey, name, opt, desired) - result = packhandle(0) - disp = packdw(0) - check RegCreateKeyExW.call(hkey, make_wstr(name), 0, 0, opt, desired, - nil, result, disp) - [ unpackhandle(result), unpackdw(disp) ] - end - - def EnumValue(hkey, index) - name = WCHAR_NUL * Constants::MAX_KEY_LENGTH - size = packdw(Constants::MAX_KEY_LENGTH) - check RegEnumValueW.call(hkey, index, name, size, nil, nil, nil, nil) - name.byteslice(0, unpackdw(size) * WCHAR_SIZE) - end - - def EnumKey(hkey, index) - name = WCHAR_NUL * Constants::MAX_KEY_LENGTH - size = packdw(Constants::MAX_KEY_LENGTH) - wtime = ' ' * 8 - check RegEnumKeyExW.call(hkey, index, name, size, nil, nil, nil, wtime) - [ name.byteslice(0, unpackdw(size) * WCHAR_SIZE), unpackqw(wtime) ] - end - - def QueryValue(hkey, name) - type = packdw(0) - size = packdw(0) - name = make_wstr(name) - check RegQueryValueExW.call(hkey, name, nil, type, nil, size) - data = "\0".b * unpackdw(size) - check RegQueryValueExW.call(hkey, name, nil, type, data, size) - [ unpackdw(type), data[0, unpackdw(size)] ] - end - - def SetValue(hkey, name, type, data, size) - case type - when REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ - data = data.encode(WCHAR) - size ||= data.bytesize + WCHAR_SIZE - end - check RegSetValueExW.call(hkey, make_wstr(name), 0, type, data, size) - end - - def DeleteValue(hkey, name) - check RegDeleteValueW.call(hkey, make_wstr(name)) - end - - def DeleteKey(hkey, name) - check RegDeleteKeyW.call(hkey, make_wstr(name)) - end - - def FlushKey(hkey) - check RegFlushKey.call(hkey) - end - - def CloseKey(hkey) - check RegCloseKey.call(hkey) - end - - def QueryInfoKey(hkey) - subkeys = packdw(0) - maxsubkeylen = packdw(0) - values = packdw(0) - maxvaluenamelen = packdw(0) - maxvaluelen = packdw(0) - secdescs = packdw(0) - wtime = ' ' * 8 - check RegQueryInfoKeyW.call(hkey, 0, 0, 0, subkeys, maxsubkeylen, 0, - values, maxvaluenamelen, maxvaluelen, secdescs, wtime) - [ unpackdw(subkeys), unpackdw(maxsubkeylen), unpackdw(values), - unpackdw(maxvaluenamelen), unpackdw(maxvaluelen), - unpackdw(secdescs), unpackqw(wtime) ] - end - end - - # - # Replace %\w+% into the environment value of what is contained between the %'s - # This method is used for REG_EXPAND_SZ. - # - # For detail, see expandEnvironmentStrings[https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-expandenvironmentstringsa] \Win32 \API. - # - def self.expand_environ(str) - str.gsub(Regexp.compile("%([^%]+)%".encode(str.encoding))) { - v = $1.encode(LOCALE) - (ENV[v] || ENV[v.upcase])&.encode(str.encoding) || $& - } - end - - @@type2name = %w[ - REG_NONE REG_SZ REG_EXPAND_SZ REG_BINARY REG_DWORD - REG_DWORD_BIG_ENDIAN REG_LINK REG_MULTI_SZ - REG_RESOURCE_LIST REG_FULL_RESOURCE_DESCRIPTOR - REG_RESOURCE_REQUIREMENTS_LIST REG_QWORD - ].inject([]) do |ary, type| - ary[Constants.const_get(type)] = type - ary - end.freeze - - # - # Convert registry type value to readable string. - # - def self.type2name(type) - @@type2name[type] || type.to_s - end - - # - # Convert 64-bit FILETIME integer into Time object. - # - def self.wtime2time(wtime) - Time.at((wtime - 116444736000000000) / 10000000) - end - - # - # Convert Time object or Integer object into 64-bit FILETIME. - # - def self.time2wtime(time) - time.to_i * 10000000 + 116444736000000000 - end - - # - # constructor - # - private_class_method :new - - # - # --- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) - # - # --- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) { |reg| ... } - # - # Open the registry key subkey under key. - # key is Win32::Registry object of parent key. - # You can use predefined key HKEY_* (see Constants) - # desired and opt is access mask and key option. - # For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/regopenkeyex.asp]. - # If block is given, the key is closed automatically. - def self.open(hkey, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) - subkey = subkey.chomp('\\') - newkey = API.OpenKey(hkey.instance_variable_get(:@hkey), subkey, opt, desired) - obj = new(newkey, hkey, subkey, REG_OPENED_EXISTING_KEY) - if block_given? - begin - yield obj - ensure - obj.close - end - else - obj - end - end - - # - # --- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) - # - # --- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) { |reg| ... } - # - # Create or open the registry key subkey under key. - # You can use predefined key HKEY_* (see Constants) - # - # If subkey is already exists, key is opened and Registry#created? - # method will return false. - # - # If block is given, the key is closed automatically. - # - def self.create(hkey, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) - newkey, disp = API.CreateKey(hkey.instance_variable_get(:@hkey), subkey, opt, desired) - obj = new(newkey, hkey, subkey, disp) - if block_given? - begin - yield obj - ensure - obj.close - end - else - obj - end - end - - # - # finalizer - # - @@final = proc { |hkey| proc { API.CloseKey(hkey[0]) if hkey[0] } } - - # - # initialize - # - def initialize(hkey, parent, keyname, disposition) - @hkey = Fiddle::Pointer.new(hkey) - @parent = parent - @keyname = keyname - @disposition = disposition - @hkeyfinal = [ hkey ] - ObjectSpace.define_finalizer self, @@final.call(@hkeyfinal) - end - - # Win32::Registry object of parent key, or nil if predefined key. - attr_reader :parent - # Same as subkey value of Registry.open or - # Registry.create method. - attr_reader :keyname - # Disposition value (REG_CREATED_NEW_KEY or REG_OPENED_EXISTING_KEY). - attr_reader :disposition - - # Returns key handle value. - def hkey - @hkey.to_i - end - - # - # Returns if key is created ((*newly*)). - # (see Registry.create) -- basically you call create - # then when you call created? on the instance returned - # it will tell if it was successful or not - # - def created? - @disposition == REG_CREATED_NEW_KEY - end - - # - # Returns if key is not closed. - # - def open? - !@hkey.nil? - end - - # - # Full path of key such as 'HKEY_CURRENT_USER\SOFTWARE\foo\bar'. - # - def name - parent = self - name = @keyname - while parent = parent.parent - name = parent.keyname + '\\' + name - end - name - end - - def inspect - "\#<Win32::Registry key=#{name.inspect}>" - end - - # - # marshalling is not allowed - # - def _dump(depth) - raise TypeError, "can't dump Win32::Registry" - end - - # - # Same as Win32::Registry.open (self, subkey, desired, opt) - # - def open(subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED, &blk) - self.class.open(self, subkey, desired, opt, &blk) - end - - # - # Same as Win32::Registry.create (self, subkey, desired, opt) - # - def create(subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED, &blk) - self.class.create(self, subkey, desired, opt, &blk) - end - - # - # Close key. - # - # After close, most method raise an error. - # - def close - API.CloseKey(@hkey) - @hkey = @parent = @keyname = nil - @hkeyfinal[0] = nil - end - - # - # Enumerate all values in this registry path. - # - # For each value it yields key, type and data. - # - # key is a String which contains name of key. - # type is a type constant kind of Win32::Registry::REG_* - # data is the value of this key. - # - def each_value - return enum_for(:each_value) unless block_given? - index = 0 - while true - begin - subkey = API.EnumValue(@hkey, index) - rescue Error - break - end - subkey = export_string(subkey) - begin - type, data = read(subkey) - rescue Error - else - yield subkey, type, data - end - index += 1 - end - index - end - alias each each_value - - # - # return values as an array - # - def values - vals_ary = [] - each_value { |*, val| vals_ary << val } - vals_ary - end - - # - # Enumerate all subkeys. - # - # For each subkey it yields subkey and wtime. - # - # subkey is String which contains name of subkey. - # wtime is last write time as FILETIME (64-bit integer). - # (see Registry.wtime2time) - # - def each_key - return enum_for(:each_key) unless block_given? - index = 0 - while true - begin - subkey, wtime = API.EnumKey(@hkey, index) - rescue Error - break - end - subkey = export_string(subkey) - yield subkey, wtime - index += 1 - end - index - end - - # - # return keys as an array - # - def keys - keys_ary = [] - each_key { |key,| keys_ary << key } - keys_ary - end - - # Read a registry value named name and return array of - # [ type, data ]. - # When name is nil, the `default' value is read. - # type is value type. (see Win32::Registry::Constants module) - # data is value data, its class is: - # :REG_SZ, REG_EXPAND_SZ - # String - # :REG_MULTI_SZ - # Array of String - # :REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD - # Integer - # :REG_BINARY, REG_NONE - # String (contains binary data) - # - # When rtype is specified, the value type must be included by - # rtype array, or TypeError is raised. - def read(name, *rtype) - type, data = API.QueryValue(@hkey, name) - unless rtype.empty? or rtype.include?(type) - raise TypeError, "Type mismatch (expect [#{ - rtype.map{|t|Registry.type2name(t)}.join(', ')}] but #{ - Registry.type2name(type)} present)" - end - case type - when REG_SZ, REG_EXPAND_SZ - [ type, data.encode(name.encoding, WCHAR).chop ] - when REG_MULTI_SZ - [ type, data.encode(name.encoding, WCHAR).split(/\0/) ] - when REG_BINARY, REG_NONE - [ type, data ] - when REG_DWORD - [ type, API.unpackdw(data) ] - when REG_DWORD_BIG_ENDIAN - [ type, data.unpack1('N') ] - when REG_QWORD - [ type, API.unpackqw(data) ] - else - raise TypeError, "Type #{Registry.type2name(type)} is not supported." - end - end - - # - # Read a registry value named name and return its value data. - # The class of the value is the same as the #read method returns. - # - # If the value type is REG_EXPAND_SZ, returns value data whose environment - # variables are replaced. - # If the value type is neither REG_SZ, REG_MULTI_SZ, REG_DWORD, - # REG_DWORD_BIG_ENDIAN, nor REG_QWORD, TypeError is raised. - # - # The meaning of rtype is the same as for the #read method. - # - def [](name, *rtype) - type, data = read(name, *rtype) - case type - when REG_SZ, REG_DWORD, REG_QWORD, REG_MULTI_SZ - data - when REG_EXPAND_SZ - Registry.expand_environ(data) - else - raise TypeError, "Type #{Registry.type2name(type)} is not supported." - end - end - - # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) - # registry value named name. - # - # If the values type does not match, TypeError is raised. - def read_s(name) - read(name, REG_SZ)[1] - end - - # - # Read a REG_SZ or REG_EXPAND_SZ registry value named name. - # - # If the value type is REG_EXPAND_SZ, environment variables are replaced. - # Unless the value type is REG_SZ or REG_EXPAND_SZ, TypeError is raised. - # - def read_s_expand(name) - type, data = read(name, REG_SZ, REG_EXPAND_SZ) - if type == REG_EXPAND_SZ - Registry.expand_environ(data) - else - data - end - end - - # - # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) - # registry value named name. - # - # If the values type does not match, TypeError is raised. - # - def read_i(name) - read(name, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD)[1] - end - - # - # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) - # registry value named name. - # - # If the values type does not match, TypeError is raised. - # - def read_bin(name) - read(name, REG_BINARY)[1] - end - - # - # Write data to a registry value named name. - # When name is nil, write to the `default' value. - # - # type is type value. (see Registry::Constants module) - # Class of data must be same as which #read - # method returns. - # - def write(name, type, data) - case type - when REG_SZ, REG_EXPAND_SZ - data = data.encode(WCHAR) << WCHAR_NUL - when REG_MULTI_SZ - data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL - when REG_BINARY, REG_NONE - data = data.to_s - when REG_DWORD - data = API.packdw(data.to_i) - when REG_DWORD_BIG_ENDIAN - data = [data.to_i].pack('N') - when REG_QWORD - data = API.packqw(data.to_i) - else - raise TypeError, "Unsupported type #{Registry.type2name(type)}" - end - API.SetValue(@hkey, name, type, data, data.bytesize) - end - - # - # Write value to a registry value named name. - # - # If wtype is specified, the value type is it. - # Otherwise, the value type is depend on class of value: - # :Integer - # REG_DWORD - # :String - # REG_SZ - # :Array - # REG_MULTI_SZ - # - def []=(name, rtype, value = nil) - if value - write name, rtype, value - else - case value = rtype - when Integer - write name, REG_DWORD, value - when String - write name, REG_SZ, value - when Array - write name, REG_MULTI_SZ, value - else - raise TypeError, "Unexpected type #{value.class}" - end - end - value - end - - # - # Write value to a registry value named name. - # - # The value type is REG_SZ(write_s), REG_DWORD(write_i), or - # REG_BINARY(write_bin). - # - def write_s(name, value) - write name, REG_SZ, value.to_s - end - - # - # Write value to a registry value named name. - # - # The value type is REG_SZ(write_s), REG_DWORD(write_i), or - # REG_BINARY(write_bin). - # - def write_i(name, value) - write name, REG_DWORD, value.to_i - end - - # - # Write value to a registry value named name. - # - # The value type is REG_SZ(write_s), REG_DWORD(write_i), or - # REG_BINARY(write_bin). - # - def write_bin(name, value) - write name, REG_BINARY, value.to_s - end - - # - # Delete a registry value named name. - # We can not delete the `default' value. - # - def delete_value(name) - API.DeleteValue(@hkey, name) - end - alias delete delete_value - - # - # Delete a subkey named name and all its values. - # - # If recursive is false, the subkey must not have subkeys. - # Otherwise, this method deletes all subkeys and values recursively. - # - def delete_key(name, recursive = false) - if recursive - open(name, KEY_ALL_ACCESS) do |reg| - reg.keys.each do |key| - begin - reg.delete_key(key, true) - rescue Error - # - end - end - end - API.DeleteKey(@hkey, name) - else - begin - API.EnumKey @hkey, 0 - rescue Error - return API.DeleteKey(@hkey, name) - end - raise Error.new(5) ## ERROR_ACCESS_DENIED - end - end - - # - # Write all the attributes into the registry file. - # - def flush - API.FlushKey @hkey - end - - # - # Returns key information as Array of: - # :num_keys - # The number of subkeys. - # :max_key_length - # Maximum length of name of subkeys. - # :num_values - # The number of values. - # :max_value_name_length - # Maximum length of name of values. - # :max_value_length - # Maximum length of value of values. - # :descriptor_length - # Length of security descriptor. - # :wtime - # Last write time as FILETIME(64-bit integer) - # - # For detail, see RegQueryInfoKey[http://msdn.microsoft.com/library/en-us/sysinfo/base/regqueryinfokey.asp] Win32 API. - # - def info - API.QueryInfoKey(@hkey) - end - - # - # Returns an item of key information. - # - %w[ - num_keys max_key_length - num_values max_value_name_length max_value_length - descriptor_length wtime - ].each_with_index do |s, i| - eval <<-__END__ - def #{s} - info[#{i}] - end - __END__ - end - - private - - def export_string(str, enc = Encoding.default_internal || LOCALE) # :nodoc: - str.encode(enc) - end - end -end diff --git a/ext/win32/lib/win32/resolv.rb b/ext/win32/lib/win32/resolv.rb index 67762da375..08fed08563 100644 --- a/ext/win32/lib/win32/resolv.rb +++ b/ext/win32/lib/win32/resolv.rb @@ -4,8 +4,24 @@ =end +require 'win32/resolv.so' + +# Generic namespace for Windows platform-specific features. module Win32 - module Resolv + module Resolv # :nodoc: + # Error at Win32 API + class Error < StandardError + # +code+ Win32 Error code + # +message+ Formatted message for +code+ + def initialize(code, message) + super(message) + @code = code + end + + # Win32 error code + attr_reader :code + end + def self.get_hosts_path path = get_hosts_dir path = File.expand_path('hosts', path) @@ -29,115 +45,62 @@ module Win32 end [ search, nameserver ] end - end -end - -begin - require 'win32/resolv.so' -rescue LoadError -end - -module Win32 -#==================================================================== -# Windows NT -#==================================================================== - module Resolv - begin - require 'win32/registry' - module SZ - refine Registry do - # ad hoc workaround for broken registry - def read_s(key) - type, str = read(key) - unless type == Registry::REG_SZ - warn "Broken registry, #{name}\\#{key} was #{Registry.type2name(type)}, ignored" - return String.new - end - str - end - end - end - using SZ - rescue LoadError - require "open3" - end - - TCPIP_NT = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' class << self private def get_hosts_dir - get_item_property(TCPIP_NT, 'DataBasePath', expand: true) + tcpip_params do |params| + params.value('DataBasePath') + end end def get_info search = nil nameserver = get_dns_server_list - slist = get_item_property(TCPIP_NT, 'SearchList') - search = slist.split(/,\s*/) unless slist.empty? + tcpip_params do |params| + slist = params.value('SearchList') + search = slist.split(/,\s*/) if slist and !slist.empty? - if add_search = search.nil? - search = [] - nvdom = get_item_property(TCPIP_NT, 'NV Domain') + if add_search = search.nil? + search = [] + domain = params.value('Domain') - unless nvdom.empty? - @search = [ nvdom ] - udmnd = get_item_property(TCPIP_NT, 'UseDomainNameDevolution').to_i - if udmnd != 0 - if /^\w+\./ =~ nvdom - devo = $' + if domain and !domain.empty? + search = [ domain ] + udmnd = params.value('UseDomainNameDevolution') + if udmnd&.nonzero? + if /^\w+\./ =~ domain + devo = $' + end end end end - end - ifs = if defined?(Win32::Registry) - Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT + '\Interfaces') do |reg| - reg.keys - rescue Registry::Error - [] - end - else - cmd = "Get-ChildItem 'HKLM:\\#{TCPIP_NT}\\Interfaces' | ForEach-Object { $_.PSChildName }" - output, _ = Open3.capture2('powershell', '-Command', cmd) - output.split(/\n+/) + params.open('Interfaces') do |reg| + reg.each_key do |iface| + next unless ns = %w[NameServer DhcpNameServer].find do |key| + ns = iface.value(key) + break ns.split(/[,\s]\s*/) if ns and !ns.empty? end - ifs.each do |iface| - next unless ns = %w[NameServer DhcpNameServer].find do |key| - ns = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key) - break ns.split(/[,\s]\s*/) unless ns.empty? - end - - next if (nameserver & ns).empty? + next if (nameserver & ns).empty? - if add_search - [ 'Domain', 'DhcpDomain' ].each do |key| - dom = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key) - unless dom.empty? - search.concat(dom.split(/,\s*/)) - break + if add_search + [ 'Domain', 'DhcpDomain' ].each do |key| + dom = iface.value(key) + if dom and !dom.empty? + search.concat(dom.split(/,\s*/)) + break + end + end end end end - end - search << devo if add_search and devo - [ search.uniq, nameserver.uniq ] - end - def get_item_property(path, name, expand: false) - if defined?(Win32::Registry) - Registry::HKEY_LOCAL_MACHINE.open(path) do |reg| - expand ? reg.read_s_expand(name) : reg.read_s(name) - rescue Registry::Error - "" - end - else - cmd = "Get-ItemProperty -Path 'HKLM:\\#{path}' -Name '#{name}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty '#{name}'" - output, _ = Open3.capture2('powershell', '-Command', cmd) - output.strip + search << devo if add_search and devo end + [ search.uniq, nameserver.uniq ] end end end diff --git a/ext/win32/resolv/extconf.rb b/ext/win32/resolv/extconf.rb index a5f8cc279d..5ee4c0d7c4 100644 --- a/ext/win32/resolv/extconf.rb +++ b/ext/win32/resolv/extconf.rb @@ -1,5 +1,6 @@ require 'mkmf' if RUBY_ENGINE == "ruby" and have_library('iphlpapi', 'GetNetworkParams', ['windows.h', 'iphlpapi.h']) + have_library('advapi32', 'RegGetValueW', ['windows.h']) create_makefile('win32/resolv') else File.write('Makefile', "all clean install:\n\t@echo Done: $(@)\n") diff --git a/ext/win32/resolv/resolv.c b/ext/win32/resolv/resolv.c index af678e1f17..9150df5dc1 100644 --- a/ext/win32/resolv/resolv.c +++ b/ext/win32/resolv/resolv.c @@ -1,26 +1,59 @@ #include <ruby.h> #include <ruby/encoding.h> #include <windows.h> +#include <windns.h> #ifndef NTDDI_VERSION #define NTDDI_VERSION 0x06000000 #endif #include <iphlpapi.h> +#ifndef numberof +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#endif + static VALUE w32error_make_error(DWORD e) { - VALUE code = ULONG2NUM(e); - return rb_class_new_instance(1, &code, rb_path2class("Win32::Resolv::Error")); + char buffer[512], *p; + DWORD source = 0; + VALUE args[2]; + if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, &source, e, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + buffer, sizeof(buffer), NULL)) { + snprintf(buffer, sizeof(buffer), "Unknown Error %lu", (unsigned long)e); + } + p = buffer; + while ((p = strpbrk(p, "\r\n")) != NULL) { + memmove(p, p + 1, strlen(p)); + if (!p[1]) { + p[0] = '\0'; + break; + } + } + args[0] = ULONG2NUM(e); + args[1] = rb_str_new_cstr(buffer); + return rb_class_new_instance(2, args, rb_path2class("Win32::Resolv::Error")); } -NORETURN(static void w32error_raise(DWORD e)); - static void -w32error_raise(DWORD e) +w32error_check(DWORD e) +{ + if (e != NO_ERROR) { + rb_exc_raise(w32error_make_error(e)); + } +} + +static VALUE +wchar_to_utf8(const WCHAR *w, int n) { - rb_exc_raise(w32error_make_error(e)); + int clen = WideCharToMultiByte(CP_UTF8, 0, w, n, NULL, 0, NULL, NULL); + VALUE str = rb_enc_str_new(NULL, clen, rb_utf8_encoding()); + WideCharToMultiByte(CP_UTF8, 0, w, n, RSTRING_PTR(str), clen, NULL, NULL); + return str; } +/* :nodoc: */ static VALUE get_dns_server_list(VALUE self) { @@ -30,9 +63,7 @@ get_dns_server_list(VALUE self) VALUE buf, nameservers = Qnil; ret = GetNetworkParams(NULL, &buflen); - if (ret != NO_ERROR && ret != ERROR_BUFFER_OVERFLOW) { - w32error_raise(ret); - } + if (ret != ERROR_BUFFER_OVERFLOW) w32error_check(ret); fixedinfo = ALLOCV(buf, buflen); ret = GetNetworkParams(fixedinfo, &buflen); if (ret == NO_ERROR) { @@ -46,18 +77,181 @@ get_dns_server_list(VALUE self) } while ((ipaddr = ipaddr->Next) != NULL); } ALLOCV_END(buf); - if (ret != NO_ERROR) w32error_raise(ret); + w32error_check(ret); return nameservers; } +static const WCHAR TCPIP_Params[] = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; + +static void +hkey_finalize(void *p) +{ + RegCloseKey((HKEY)p); +} + +static const rb_data_type_t hkey_type = { + "RegKey", + {0, hkey_finalize}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE +hkey_close(VALUE self) +{ + RegCloseKey((HKEY)DATA_PTR(self)); + DATA_PTR(self) = 0; + return self; +} + +static VALUE reg_key_class; + +static VALUE +reg_open_key(VALUE klass, HKEY hkey, const WCHAR *wname) +{ + VALUE k = TypedData_Wrap_Struct(klass, &hkey_type, NULL); + DWORD e = RegOpenKeyExW(hkey, wname, 0, KEY_READ, (HKEY *)&DATA_PTR(k)); + if (e == ERROR_FILE_NOT_FOUND) return Qnil; + w32error_check(e); + return rb_ensure(rb_yield, k, hkey_close, k); +} + +/* :nodoc: */ +static VALUE +tcpip_params_open(VALUE klass) +{ + return reg_open_key(reg_key_class, HKEY_LOCAL_MACHINE, TCPIP_Params); +} + +static int +to_wname(VALUE *name, WCHAR *wname, int wlen) +{ + const char *n = StringValueCStr(*name); + int nlen = RSTRING_LEN(*name); + int len = MultiByteToWideChar(CP_UTF8, 0, n, nlen, wname, wlen - 1); + if (len == 0) w32error_check(GetLastError()); + if (len >= wlen) rb_raise(rb_eArgError, "too long name"); + wname[len] = L'\0'; + return len; +} + +static VALUE +reg_open(VALUE self, VALUE name) +{ + HKEY hkey = DATA_PTR(self); + WCHAR wname[256]; + to_wname(&name, wname, numberof(wname)); + return reg_open_key(CLASS_OF(self), hkey, wname); +} + + +static VALUE +reg_each_key(VALUE self) +{ + WCHAR wname[256]; + HKEY hkey = DATA_PTR(self); + VALUE k = TypedData_Wrap_Struct(CLASS_OF(self), &hkey_type, NULL); + DWORD i, e, n; + for (i = 0; n = numberof(wname), (e = RegEnumKeyExW(hkey, i, wname, &n, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS; i++) { + e = RegOpenKeyExW(hkey, wname, 0, KEY_READ, (HKEY *)&DATA_PTR(k)); + w32error_check(e); + rb_ensure(rb_yield, k, hkey_close, k); + } + if (e != ERROR_NO_MORE_ITEMS) w32error_check(e); + return self; +} + +static inline DWORD +swap_dw(DWORD x) +{ +#if defined(_MSC_VER) + return _byteswap_ulong(x); +#else + return __builtin_bswap32(x); +#endif +} + +static VALUE +reg_value(VALUE self, VALUE name) +{ + HKEY hkey = DATA_PTR(self); + DWORD type = 0, size = 0, e; + VALUE result, value_buffer; + void *buffer; + WCHAR wname[256]; + to_wname(&name, wname, numberof(wname)); + e = RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type, NULL, &size); + if (e == ERROR_FILE_NOT_FOUND) return Qnil; + w32error_check(e); +# define get_value_2nd(data, dsize) do { \ + DWORD type2 = type; \ + w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type2, data, dsize)); \ + if (type != type2) { \ + rb_raise(rb_eRuntimeError, "registry value type changed %lu -> %lu", \ + (unsigned long)type, (unsigned long)type2); \ + } \ + } while (0) + + switch (type) { + case REG_DWORD: case REG_DWORD_BIG_ENDIAN: + { + DWORD d; + if (size != sizeof(d)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size); + w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_DWORD, &type, &d, &size)); + if (type == REG_DWORD_BIG_ENDIAN) d = swap_dw(d); + return ULONG2NUM(d); + } + case REG_QWORD: + { + QWORD q; + if (size != sizeof(q)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size); + w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_QWORD, &type, &q, &size)); + return ULL2NUM(q); + } + case REG_SZ: case REG_MULTI_SZ: case REG_EXPAND_SZ: + if (size % sizeof(WCHAR)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size); + buffer = ALLOCV_N(char, value_buffer, size); + get_value_2nd(buffer, &size); + if (type == REG_MULTI_SZ) { + const WCHAR *w = (WCHAR *)buffer; + result = rb_ary_new(); + size /= sizeof(WCHAR); + size -= 1; + for (size_t i = 0; i < size; ++i) { + int n = lstrlenW(w+i); + rb_ary_push(result, wchar_to_utf8(w+i, n)); + i += n; + } + } + else { + result = wchar_to_utf8((WCHAR *)buffer, lstrlenW((WCHAR *)buffer)); + } + ALLOCV_END(value_buffer); + break; + default: + result = rb_str_new(0, size); + get_value_2nd(RSTRING_PTR(result), &size); + rb_str_set_len(result, size); + break; + } + return result; +} + void InitVM_resolv(void) { VALUE mWin32 = rb_define_module("Win32"); VALUE resolv = rb_define_module_under(mWin32, "Resolv"); VALUE singl = rb_singleton_class(resolv); + VALUE regkey = rb_define_class_under(resolv, "registry key", rb_cObject); + + reg_key_class = regkey; + rb_undef_alloc_func(regkey); rb_define_private_method(singl, "get_dns_server_list", get_dns_server_list, 0); + rb_define_private_method(singl, "tcpip_params", tcpip_params_open, 0); + rb_define_method(regkey, "open", reg_open, 1); + rb_define_method(regkey, "each_key", reg_each_key, 0); + rb_define_method(regkey, "value", reg_value, 1); } void diff --git a/ext/win32/win32-registry.gemspec b/ext/win32/win32-registry.gemspec deleted file mode 100644 index d747daf458..0000000000 --- a/ext/win32/win32-registry.gemspec +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true -Gem::Specification.new do |spec| - spec.name = "win32-registry" - spec.version = "0.1.0" - spec.authors = ["U.Nakamura"] - spec.email = ["usa@garbagecollect.jp"] - - spec.summary = %q{Provides an interface to the Windows Registry in Ruby} - spec.description = spec.summary - spec.homepage = "https://github.com/ruby/win32-registry" - spec.required_ruby_version = ">= 2.6.0" - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - excludes = %w[ - bin/ test/ spec/ features/ rakelib/ - .git .github .mailmap appveyor Rakefile Gemfile - ] - spec.files = Dir.chdir(__dir__) do - `git ls-files -z`.split("\x0").reject do |f| - File.identical?(f, __FILE__) || f.start_with?(*excludes) - end - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] - - spec.add_dependency "fiddle", "~> 1.0" -end diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 0b9c4d65ee..481d74b2b6 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -25,7 +25,7 @@ # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif -#define RUBY_ZLIB_VERSION "3.2.1" +#define RUBY_ZLIB_VERSION "3.2.3" #ifndef RB_PASS_CALLED_KEYWORDS # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) @@ -860,9 +860,7 @@ zstream_buffer_ungets(struct zstream *z, const Bytef *b, unsigned long len) char *bufptr; long filled; - if (NIL_P(z->buf) || (long)rb_str_capacity(z->buf) <= ZSTREAM_BUF_FILLED(z)) { - zstream_expand_buffer_into(z, len); - } + zstream_expand_buffer_into(z, len); RSTRING_GETMEM(z->buf, bufptr, filled); memmove(bufptr + len, bufptr, filled); @@ -1094,8 +1092,9 @@ zstream_run_func(struct zstream_run_args *args) break; } - if (err != Z_OK && err != Z_BUF_ERROR) + if (err != Z_OK && err != Z_BUF_ERROR) { break; + } if (z->stream.avail_out > 0) { z->flags |= ZSTREAM_FLAG_IN_STREAM; @@ -1170,12 +1169,17 @@ loop: /* retry if no exception is thrown */ if (err == Z_OK && args->interrupt) { args->interrupt = 0; - goto loop; + + /* Retry only if both avail_in > 0 (more input to process) and avail_out > 0 + * (output buffer has space). If avail_out == 0, the buffer is full and should + * be consumed by the caller first. If avail_in == 0, there's nothing more to process. */ + if (z->stream.avail_in > 0 && z->stream.avail_out > 0) { + goto loop; + } } - if (flush != Z_FINISH && err == Z_BUF_ERROR - && z->stream.avail_out > 0) { - z->flags |= ZSTREAM_FLAG_IN_STREAM; + if (flush != Z_FINISH && err == Z_BUF_ERROR && z->stream.avail_out > 0) { + z->flags |= ZSTREAM_FLAG_IN_STREAM; } zstream_reset_input(z); @@ -1456,6 +1460,7 @@ rb_zstream_finish(VALUE obj) * call-seq: * flush_next_in -> input * + * Flushes input buffer and returns all data in that buffer. */ static VALUE rb_zstream_flush_next_in(VALUE obj) @@ -2444,17 +2449,16 @@ struct gzfile { #define GZFILE_READ_SIZE 2048 +enum { read_raw_arg_len, read_raw_arg_buf, read_raw_arg__count}; struct read_raw_arg { VALUE io; - union { - const VALUE argv[2]; /* for rb_funcallv */ - struct { - VALUE len; - VALUE buf; - } in; - } as; + const VALUE argv[read_raw_arg__count]; /* for rb_funcallv */ }; +#define read_raw_arg_argc(ra) \ + ((int)read_raw_arg__count - NIL_P((ra)->argv[read_raw_arg__count - 1])) +#define read_raw_arg_init(io, len, buf) { io, { len, buf } } + static void gzfile_mark(void *p) { @@ -2580,9 +2584,9 @@ gzfile_read_raw_partial(VALUE arg) { struct read_raw_arg *ra = (struct read_raw_arg *)arg; VALUE str; - int argc = NIL_P(ra->as.argv[1]) ? 1 : 2; + int argc = read_raw_arg_argc(ra); - str = rb_funcallv(ra->io, id_readpartial, argc, ra->as.argv); + str = rb_funcallv(ra->io, id_readpartial, argc, ra->argv); Check_Type(str, T_STRING); return str; } @@ -2593,8 +2597,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _) struct read_raw_arg *ra = (struct read_raw_arg *)arg; VALUE str = Qnil; if (rb_obj_is_kind_of(rb_errinfo(), rb_eNoMethodError)) { - int argc = NIL_P(ra->as.argv[1]) ? 1 : 2; - str = rb_funcallv(ra->io, id_read, argc, ra->as.argv); + int argc = read_raw_arg_argc(ra); + str = rb_funcallv(ra->io, id_read, argc, ra->argv); if (!NIL_P(str)) { Check_Type(str, T_STRING); } @@ -2605,11 +2609,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _) static VALUE gzfile_read_raw(struct gzfile *gz, VALUE outbuf) { - struct read_raw_arg ra; - - ra.io = gz->io; - ra.as.in.len = INT2FIX(GZFILE_READ_SIZE); - ra.as.in.buf = outbuf; + struct read_raw_arg ra = + read_raw_arg_init(gz->io, INT2FIX(GZFILE_READ_SIZE), outbuf); return rb_rescue2(gzfile_read_raw_partial, (VALUE)&ra, gzfile_read_raw_rescue, (VALUE)&ra, diff --git a/ext/zlib/zlib.gemspec b/ext/zlib/zlib.gemspec index 345dc5f225..ba7114476f 100644 --- a/ext/zlib/zlib.gemspec +++ b/ext/zlib/zlib.gemspec @@ -27,5 +27,5 @@ Gem::Specification.new do |spec| spec.executables = [] spec.require_paths = ["lib"] spec.extensions = "ext/zlib/extconf.rb" - spec.required_ruby_version = ">= 2.5.0" + spec.required_ruby_version = ">= 2.7.0" end |
