From 24af1d4cd0eab611e4b4ebb383cac679c7cdd396 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 13 Jan 2022 12:51:39 +0100 Subject: [PATCH] Handle YAML.load_file on Psych 4 --- .github/workflows/ci.yaml | 3 + CHANGELOG.md | 8 + ext/bootsnap/bootsnap.c | 56 ++++-- lib/bootsnap/cli.rb | 6 +- lib/bootsnap/compile_cache/iseq.rb | 8 +- lib/bootsnap/compile_cache/json.rb | 9 +- lib/bootsnap/compile_cache/yaml.rb | 270 +++++++++++++++++++------- test/cli_test.rb | 10 +- test/compile_cache/yaml_test.rb | 76 ++++++-- test/compile_cache_key_format_test.rb | 2 +- test/compile_cache_test.rb | 2 +- test/helper_test.rb | 2 +- 12 files changed, 335 insertions(+), 117 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 494379fe..67eda5e1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,6 +13,7 @@ on: jobs: platforms: strategy: + fail-fast: false matrix: os: [ubuntu, macos, windows] ruby: ['2.5'] @@ -40,6 +41,7 @@ jobs: psych4: strategy: + fail-fast: false matrix: os: [ubuntu] ruby: ['3.0'] @@ -57,6 +59,7 @@ jobs: minimal: strategy: + fail-fast: false matrix: os: [ubuntu] ruby: ['jruby', 'truffleruby'] diff --git a/CHANGELOG.md b/CHANGELOG.md index e40b466c..f1d4b4e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Unreleased +* Improve support of Psych 4. (#392) + Since `1.8.0`, `YAML.load_file` was no longer cached when Psych 4 was used. This is because `load_file` loads + in safe mode by default, so the Bootsnap cache could defeat that safety. + Now when precompiling YAML files, Bootsnap first try to parse them in safe mode, and if it can't fallback to unsafe mode, + and the cache contains a flag that records wether it was generated in safe mode or not. + `YAML.unsafe_load_file` will use safe caches just fine, but `YAML.load_file` will fallback to uncached YAML parsing + if the cache was generated using unsafe parsing. + * Minimize the Kernel.require extra stack frames. (#393) This should reduce the noise generated by bootsnap on `LoadError`. diff --git a/ext/bootsnap/bootsnap.c b/ext/bootsnap/bootsnap.c index fc2279c4..fadd4508 100644 --- a/ext/bootsnap/bootsnap.c +++ b/ext/bootsnap/bootsnap.c @@ -75,7 +75,7 @@ struct bs_cache_key { STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE); /* Effectively a schema version. Bumping invalidates all previous caches */ -static const uint32_t current_version = 3; +static const uint32_t current_version = 4; /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a * new OS ABI, etc. */ @@ -426,6 +426,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr #define ERROR_WITH_ERRNO -1 #define CACHE_MISS -2 #define CACHE_STALE -3 +#define CACHE_UNCOMPILABLE -4 /* * Read the cache key from the given fd, which must have position 0 (e.g. @@ -507,14 +508,14 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * if (data_size > 100000000000) { *errno_provenance = "bs_fetch:fetch_cached_data:datasize"; errno = EINVAL; /* because wtf? */ - ret = -1; + ret = ERROR_WITH_ERRNO; goto done; } data = ALLOC_N(char, data_size); nread = read(fd, data, data_size); if (nread < 0) { *errno_provenance = "bs_fetch:fetch_cached_data:read"; - ret = -1; + ret = ERROR_WITH_ERRNO; goto done; } if (nread != data_size) { @@ -525,6 +526,10 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * storage_data = rb_str_new(data, data_size); *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data); + if (*output_data == uncompilable) { + ret = CACHE_UNCOMPILABLE; + goto done; + } ret = 0; done: if (data != NULL) xfree(data); @@ -737,7 +742,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args &output_data, &exception_tag, &errno_provenance ); if (exception_tag != 0) goto raise; - else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0; + else if (res == CACHE_UNCOMPILABLE) { + /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output` + This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */ + if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno; + input_data = rb_str_new(contents, current_key.size); + bs_input_to_output(handler, args, input_data, &output_data, &exception_tag); + if (exception_tag != 0) goto raise; + goto succeed; + } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0; else if (res == ERROR_WITH_ERRNO) goto fail_errno; else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */ } @@ -772,9 +785,13 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data); if (exception_tag != 0) goto raise; - /* If output_data is nil, delete the cache entry and generate the output - * using input_to_output */ - if (NIL_P(output_data)) { + if (output_data == uncompilable) { + /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */ + bs_input_to_output(handler, args, input_data, &output_data, &exception_tag); + if (exception_tag != 0) goto raise; + } else if (NIL_P(output_data)) { + /* If output_data is nil, delete the cache entry and generate the output + * using input_to_output */ if (unlink(cache_path) < 0) { errno_provenance = "bs_fetch:unlink"; goto fail_errno; @@ -919,12 +936,27 @@ struct i2s_data { }; static VALUE -prot_storage_to_output(VALUE arg) +rescue_uncompilable(VALUE arg, VALUE e) +{ + return uncompilable; +} + +static VALUE +try_storage_to_output(VALUE arg) { struct s2o_data * data = (struct s2o_data *)arg; return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args); } +static VALUE +prot_storage_to_output(VALUE arg) +{ + return rb_rescue2( + try_storage_to_output, arg, + rescue_uncompilable, Qnil, + rb_eBootsnap_CompileCache_Uncompilable, 0); +} + static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data) { @@ -963,19 +995,13 @@ try_input_to_storage(VALUE arg) return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval); } -static VALUE -rescue_input_to_storage(VALUE arg, VALUE e) -{ - return uncompilable; -} - static VALUE prot_input_to_storage(VALUE arg) { struct i2s_data * data = (struct i2s_data *)arg; return rb_rescue2( try_input_to_storage, (VALUE)data, - rescue_input_to_storage, Qnil, + rescue_uncompilable, Qnil, rb_eBootsnap_CompileCache_Uncompilable, 0); } diff --git a/lib/bootsnap/cli.rb b/lib/bootsnap/cli.rb index 11a886d7..f307ecad 100644 --- a/lib/bootsnap/cli.rb +++ b/lib/bootsnap/cli.rb @@ -138,7 +138,7 @@ def precompile_yaml_files(load_paths, exclude: self.exclude) def precompile_yaml(*yaml_files) Array(yaml_files).each do |yaml_file| - if CompileCache::YAML.precompile(yaml_file, cache_dir: cache_dir) + if CompileCache::YAML.precompile(yaml_file) STDERR.puts(yaml_file) if verbose end end @@ -161,7 +161,7 @@ def precompile_json_files(load_paths, exclude: self.exclude) def precompile_json(*json_files) Array(json_files).each do |json_file| - if CompileCache::JSON.precompile(json_file, cache_dir: cache_dir) + if CompileCache::JSON.precompile(json_file) STDERR.puts(json_file) if verbose end end @@ -183,7 +183,7 @@ def precompile_ruby_files(load_paths, exclude: self.exclude) def precompile_ruby(*ruby_files) Array(ruby_files).each do |ruby_file| - if CompileCache::ISeq.precompile(ruby_file, cache_dir: cache_dir) + if CompileCache::ISeq.precompile(ruby_file) STDERR.puts(ruby_file) if verbose end end diff --git a/lib/bootsnap/compile_cache/iseq.rb b/lib/bootsnap/compile_cache/iseq.rb index e926eb4f..5e1ce80f 100644 --- a/lib/bootsnap/compile_cache/iseq.rb +++ b/lib/bootsnap/compile_cache/iseq.rb @@ -7,7 +7,11 @@ module Bootsnap module CompileCache module ISeq class << self - attr_accessor(:cache_dir) + attr_reader(:cache_dir) + + def cache_dir=(cache_dir) + @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq" + end end has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250 @@ -61,7 +65,7 @@ def self.fetch(path, cache_dir: ISeq.cache_dir) ) end - def self.precompile(path, cache_dir: ISeq.cache_dir) + def self.precompile(path) Bootsnap::CompileCache::Native.precompile( cache_dir, path.to_s, diff --git a/lib/bootsnap/compile_cache/json.rb b/lib/bootsnap/compile_cache/json.rb index 36d0c6be..7f81152c 100644 --- a/lib/bootsnap/compile_cache/json.rb +++ b/lib/bootsnap/compile_cache/json.rb @@ -6,7 +6,12 @@ module Bootsnap module CompileCache module JSON class << self - attr_accessor(:msgpack_factory, :cache_dir, :supported_options) + attr_accessor(:msgpack_factory, :supported_options) + attr_reader(:cache_dir) + + def cache_dir=(cache_dir) + @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}json" : "#{cache_dir}-json" + end def input_to_storage(payload, _) obj = ::JSON.parse(payload) @@ -24,7 +29,7 @@ def input_to_output(data, kwargs) ::JSON.parse(data, **(kwargs || {})) end - def precompile(path, cache_dir: self.cache_dir) + def precompile(path) Bootsnap::CompileCache::Native.precompile( cache_dir, path.to_s, diff --git a/lib/bootsnap/compile_cache/yaml.rb b/lib/bootsnap/compile_cache/yaml.rb index 1088e924..da8e33d2 100644 --- a/lib/bootsnap/compile_cache/yaml.rb +++ b/lib/bootsnap/compile_cache/yaml.rb @@ -6,51 +6,25 @@ module Bootsnap module CompileCache module YAML class << self - attr_accessor(:msgpack_factory, :cache_dir, :supported_options) + attr_accessor(:msgpack_factory, :supported_options) + attr_reader(:implementation, :cache_dir) - def input_to_storage(contents, _) - obj = strict_load(contents) - msgpack_factory.dump(obj) - rescue NoMethodError, RangeError - # The object included things that we can't serialize - raise(Uncompilable) - end - - def storage_to_output(data, kwargs) - if kwargs&.key?(:symbolize_names) - kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names) - end - msgpack_factory.load(data, kwargs) + def cache_dir=(cache_dir) + @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}yaml" : "#{cache_dir}-yaml" end - def input_to_output(data, kwargs) - if ::YAML.respond_to?(:unsafe_load) - ::YAML.unsafe_load(data, **(kwargs || {})) - else - ::YAML.load(data, **(kwargs || {})) - end - end - - def strict_load(payload, *args) - ast = ::YAML.parse(payload) - return ast unless ast - - strict_visitor.create(*args).visit(ast) - end - ruby2_keywords :strict_load if respond_to?(:ruby2_keywords, true) - - def precompile(path, cache_dir: YAML.cache_dir) + def precompile(path) Bootsnap::CompileCache::Native.precompile( cache_dir, path.to_s, - Bootsnap::CompileCache::YAML, + @implementation, ) end def install!(cache_dir) self.cache_dir = cache_dir init! - ::YAML.singleton_class.prepend(Patch) + ::YAML.singleton_class.prepend(@implementation::Patch) end def init! @@ -58,11 +32,9 @@ def init! require("msgpack") require("date") - if Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file) - Patch.send(:remove_method, :unsafe_load_file) - end - if Patch.method_defined?(:load_file) && ::YAML::VERSION >= "4" - Patch.send(:remove_method, :load_file) + @implementation = ::YAML::VERSION >= "4" ? Psych4 : Psych3 + if @implementation::Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file) + @implementation::Patch.send(:remove_method, :unsafe_load_file) end # MessagePack serializes symbols as strings by default. @@ -106,6 +78,17 @@ def init! supported_options.freeze end + def patch + @implementation::Patch + end + + def strict_load(payload) + ast = ::YAML.parse(payload) + return ast unless ast + + strict_visitor.create.visit(ast) + end + def strict_visitor self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do def visit(target) @@ -119,50 +102,199 @@ def visit(target) end end - module Patch - def load_file(path, *args) - return super if args.size > 1 + module Psych4 + extend self - if (kwargs = args.first) - return super unless kwargs.is_a?(Hash) - return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty? + def input_to_storage(contents, _) + SafeLoad.input_to_storage(contents, nil) + rescue Uncompilable + UnsafeLoad.input_to_storage(contents, nil) + end + + module UnsafeLoad + extend self + + def input_to_storage(contents, _) + obj = CompileCache::YAML.strict_load(contents) + packer = CompileCache::YAML.msgpack_factory.packer + packer.pack(false) # not safe loaded + packer.pack(obj) + packer.to_s + rescue NoMethodError, RangeError + # The object included things that we can't serialize + raise(Uncompilable) end - begin - ::Bootsnap::CompileCache::Native.fetch( - Bootsnap::CompileCache::YAML.cache_dir, - File.realpath(path), - ::Bootsnap::CompileCache::YAML, - kwargs, - ) - rescue Errno::EACCES - ::Bootsnap::CompileCache.permission_error(path) + def storage_to_output(data, kwargs) + if kwargs&.key?(:symbolize_names) + kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names) + end + + unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs) + unpacker.feed(data) + _safe_loaded = unpacker.unpack + unpacker.unpack + end + + def input_to_output(data, kwargs) + ::YAML.unsafe_load(data, **(kwargs || {})) end end - ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true) + module SafeLoad + extend self - def unsafe_load_file(path, *args) - return super if args.size > 1 + def input_to_storage(contents, _) + obj = ::YAML.load(contents) + packer = CompileCache::YAML.msgpack_factory.packer + packer.pack(true) # safe loaded + packer.pack(obj) + packer.to_s + rescue NoMethodError, RangeError, Psych::DisallowedClass, Psych::BadAlias + # The object included things that we can't serialize + raise(Uncompilable) + end - if (kwargs = args.first) - return super unless kwargs.is_a?(Hash) - return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty? + def storage_to_output(data, kwargs) + if kwargs&.key?(:symbolize_names) + kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names) + end + + unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs) + unpacker.feed(data) + safe_loaded = unpacker.unpack + if safe_loaded + unpacker.unpack + else + raise(Uncompilable) + end end - begin - ::Bootsnap::CompileCache::Native.fetch( - Bootsnap::CompileCache::YAML.cache_dir, - File.realpath(path), - ::Bootsnap::CompileCache::YAML, - kwargs, - ) - rescue Errno::EACCES - ::Bootsnap::CompileCache.permission_error(path) + def input_to_output(data, kwargs) + ::YAML.load(data, **(kwargs || {})) end end - ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true) + module Patch + def load_file(path, *args) + return super if args.size > 1 + + if (kwargs = args.first) + return super unless kwargs.is_a?(Hash) + return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty? + end + + begin + ::Bootsnap::CompileCache::Native.fetch( + Bootsnap::CompileCache::YAML.cache_dir, + File.realpath(path), + ::Bootsnap::CompileCache::YAML::Psych4::SafeLoad, + kwargs, + ) + rescue Errno::EACCES + ::Bootsnap::CompileCache.permission_error(path) + end + end + + ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true) + + def unsafe_load_file(path, *args) + return super if args.size > 1 + + if (kwargs = args.first) + return super unless kwargs.is_a?(Hash) + return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty? + end + + begin + ::Bootsnap::CompileCache::Native.fetch( + Bootsnap::CompileCache::YAML.cache_dir, + File.realpath(path), + ::Bootsnap::CompileCache::YAML::Psych4::UnsafeLoad, + kwargs, + ) + rescue Errno::EACCES + ::Bootsnap::CompileCache.permission_error(path) + end + end + + ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true) + end + end + + module Psych3 + extend self + + def input_to_storage(contents, _) + obj = CompileCache::YAML.strict_load(contents) + packer = CompileCache::YAML.msgpack_factory.packer + packer.pack(false) # not safe loaded + packer.pack(obj) + packer.to_s + rescue NoMethodError, RangeError + # The object included things that we can't serialize + raise(Uncompilable) + end + + def storage_to_output(data, kwargs) + if kwargs&.key?(:symbolize_names) + kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names) + end + unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs) + unpacker.feed(data) + _safe_loaded = unpacker.unpack + unpacker.unpack + end + + def input_to_output(data, kwargs) + ::YAML.load(data, **(kwargs || {})) + end + + module Patch + def load_file(path, *args) + return super if args.size > 1 + + if (kwargs = args.first) + return super unless kwargs.is_a?(Hash) + return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty? + end + + begin + ::Bootsnap::CompileCache::Native.fetch( + Bootsnap::CompileCache::YAML.cache_dir, + File.realpath(path), + ::Bootsnap::CompileCache::YAML::Psych3, + kwargs, + ) + rescue Errno::EACCES + ::Bootsnap::CompileCache.permission_error(path) + end + end + + ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true) + + def unsafe_load_file(path, *args) + return super if args.size > 1 + + if (kwargs = args.first) + return super unless kwargs.is_a?(Hash) + return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty? + end + + begin + ::Bootsnap::CompileCache::Native.fetch( + Bootsnap::CompileCache::YAML.cache_dir, + File.realpath(path), + ::Bootsnap::CompileCache::YAML::Psych3, + kwargs, + ) + rescue Errno::EACCES + ::Bootsnap::CompileCache.permission_error(path) + end + end + + ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true) + end end end end diff --git a/test/cli_test.rb b/test/cli_test.rb index 3f33c996..59e08a8c 100644 --- a/test/cli_test.rb +++ b/test/cli_test.rb @@ -14,7 +14,7 @@ def setup def test_precompile_single_file path = Help.set_file("a.rb", "a = a = 3", 100) - CompileCache::ISeq.expects(:precompile).with(File.expand_path(path), cache_dir: @cache_dir) + CompileCache::ISeq.expects(:precompile).with(File.expand_path(path)) assert_equal 0, CLI.new(["precompile", "-j", "0", path]).run end @@ -28,8 +28,8 @@ def test_precompile_directory path_a = Help.set_file("foo/a.rb", "a = a = 3", 100) path_b = Help.set_file("foo/b.rb", "b = b = 3", 100) - CompileCache::ISeq.expects(:precompile).with(File.expand_path(path_a), cache_dir: @cache_dir) - CompileCache::ISeq.expects(:precompile).with(File.expand_path(path_b), cache_dir: @cache_dir) + CompileCache::ISeq.expects(:precompile).with(File.expand_path(path_a)) + CompileCache::ISeq.expects(:precompile).with(File.expand_path(path_b)) assert_equal 0, CLI.new(["precompile", "-j", "0", "foo"]).run end @@ -37,7 +37,7 @@ def test_precompile_exclude path_a = Help.set_file("foo/a.rb", "a = a = 3", 100) Help.set_file("foo/b.rb", "b = b = 3", 100) - CompileCache::ISeq.expects(:precompile).with(File.expand_path(path_a), cache_dir: @cache_dir) + CompileCache::ISeq.expects(:precompile).with(File.expand_path(path_a)) assert_equal 0, CLI.new(["precompile", "-j", "0", "--exclude", "b.rb", "foo"]).run end @@ -47,7 +47,7 @@ def test_precompile_gemfile def test_precompile_yaml path = Help.set_file("a.yaml", "foo: bar", 100) - CompileCache::YAML.expects(:precompile).with(File.expand_path(path), cache_dir: @cache_dir) + CompileCache::YAML.expects(:precompile).with(File.expand_path(path)) assert_equal 0, CLI.new(["precompile", "-j", "0", path]).run end diff --git a/test/compile_cache/yaml_test.rb b/test/compile_cache/yaml_test.rb index f9b6d6fd..734f1c45 100644 --- a/test/compile_cache/yaml_test.rb +++ b/test/compile_cache/yaml_test.rb @@ -21,7 +21,7 @@ def unsafe_load_file(_path, symbolize_names: false, freeze: false, fallback: nil def setup super Bootsnap::CompileCache::YAML.init! - FakeYaml.singleton_class.prepend(Bootsnap::CompileCache::YAML::Patch) + FakeYaml.singleton_class.prepend(Bootsnap::CompileCache::YAML.patch) end def test_yaml_strict_load @@ -37,19 +37,6 @@ def test_yaml_strict_load assert_equal expected, document end - def test_yaml_input_to_output - document = ::Bootsnap::CompileCache::YAML.input_to_output(<<~YAML, {}) - --- - :foo: 42 - bar: [1] - YAML - expected = { - foo: 42, - "bar" => [1], - } - assert_equal expected, document - end - def test_yaml_tags error = assert_raises Bootsnap::CompileCache::Uncompilable do ::Bootsnap::CompileCache::YAML.strict_load("!many Boolean") @@ -63,15 +50,68 @@ def test_yaml_tags end if YAML::VERSION >= "4" - def test_load_psych_4 - # Until we figure out a proper strategy, only `YAML.unsafe_load_file` - # is cached with Psych >= 4 + def test_load_psych_4_with_alias Help.set_file("a.yml", "foo: &foo\n bar: 42\nplop:\n <<: *foo", 100) - assert_raises FakeYaml::Fallback do + + foo = {"bar" => 42} + expected = {"foo" => foo, "plop" => foo} + assert_equal(expected, FakeYaml.unsafe_load_file("a.yml")) + + assert_raises Psych::BadAlias do FakeYaml.load_file("a.yml") end end + + def test_load_psych_4_with_unsafe_class + Help.set_file("a.yml", "---\nfoo: !ruby/regexp /bar/\n", 100) + + expected = {"foo" => /bar/} + assert_equal(expected, FakeYaml.unsafe_load_file("a.yml")) + + assert_raises Psych::DisallowedClass do + FakeYaml.load_file("a.yml") + end + end + + def test_yaml_input_to_output_safe + document = ::Bootsnap::CompileCache::YAML::Psych4::SafeLoad.input_to_output(<<~YAML, {}) + --- + :foo: 42 + bar: [1] + YAML + expected = { + foo: 42, + "bar" => [1], + } + assert_equal expected, document + end + + def test_yaml_input_to_output_unsafe + document = ::Bootsnap::CompileCache::YAML::Psych4::UnsafeLoad.input_to_output(<<~YAML, {}) + --- + :foo: 42 + bar: [1] + YAML + expected = { + foo: 42, + "bar" => [1], + } + assert_equal expected, document + end else + def test_yaml_input_to_output + document = ::Bootsnap::CompileCache::YAML::Psych3.input_to_output(<<~YAML, {}) + --- + :foo: 42 + bar: [1] + YAML + expected = { + foo: 42, + "bar" => [1], + } + assert_equal expected, document + end + def test_load_file Help.set_file("a.yml", "---\nfoo: bar", 100) assert_equal({"foo" => "bar"}, FakeYaml.load_file("a.yml")) diff --git a/test/compile_cache_key_format_test.rb b/test/compile_cache_key_format_test.rb index 522475ee..bf7f2e3c 100644 --- a/test/compile_cache_key_format_test.rb +++ b/test/compile_cache_key_format_test.rb @@ -21,7 +21,7 @@ class CompileCacheKeyFormatTest < Minitest::Test def test_key_version key = cache_key_for_file(FILE) - exp = [3].pack("L") + exp = [4].pack("L") assert_equal(exp, key[R[:version]]) end diff --git a/test/compile_cache_test.rb b/test/compile_cache_test.rb index 98174a2c..5a8636f4 100644 --- a/test/compile_cache_test.rb +++ b/test/compile_cache_test.rb @@ -114,7 +114,7 @@ def test_recache_when_size_different def test_invalid_cache_file path = Help.set_file("a.rb", "a = a = 3", 100) - cp = Help.cache_path(@tmp_dir, path) + cp = Help.cache_path("#{@tmp_dir}-iseq", path) FileUtils.mkdir_p(File.dirname(cp)) File.write(cp, "nope") load(path) diff --git a/test/helper_test.rb b/test/helper_test.rb index 1de619e8..12c83ee3 100644 --- a/test/helper_test.rb +++ b/test/helper_test.rb @@ -7,7 +7,7 @@ class HelperTest < MiniTest::Test def test_validate_cache_path path = Help.set_file("a.rb", "a = a = 3", 100) - cp = Help.cache_path(@tmp_dir, path) + cp = Help.cache_path("#{@tmp_dir}-iseq", path) load(path) assert_equal(true, File.file?(cp)) end