From 37cbfc40bf8c93509544ae6f9f56affb514e1eec Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Wed, 1 May 2024 21:22:51 -0700 Subject: [PATCH] Fix internal formula json v3 frozen hash parsing bug This caused formulae with uses from macos bounds to not load correctly because they tried to modify a frozen hash. It wasn't obvious from the tests because I didn't replicate the real world JSON parsing conditions closely enough. I also had to modify `Cachable#clear_cache` so that it can clear frozen hashes. Error: ``` Error: can't modify frozen Hash: {"since"=>"catalina"} Warning: Removed Sorbet lines from backtrace! Rerun with `--verbose` to see the original backtrace /usr/local/Homebrew/Library/Homebrew/extend/hash/keys.rb:123:in `delete' /usr/local/Homebrew/Library/Homebrew/extend/hash/keys.rb:123:in `block in _deep_transform_keys_in_object!' /usr/local/Homebrew/Library/Homebrew/extend/hash/keys.rb:122:in `each' /usr/local/Homebrew/Library/Homebrew/extend/hash/keys.rb:122:in `_deep_transform_keys_in_object!' /usr/local/Homebrew/Library/Homebrew/extend/hash/keys.rb:48:in `deep_transform_keys!' /usr/local/Homebrew/Library/Homebrew/formulary.rb:230:in `block (2 levels) in load_formula_from_api' /usr/local/Homebrew/Library/Homebrew/formulary.rb:218:in `each' /usr/local/Homebrew/Library/Homebrew/formulary.rb:218:in `block in load_formula_from_api' /usr/local/Homebrew/Library/Homebrew/formulary.rb:304:in `instance_exec' /usr/local/Homebrew/Library/Homebrew/formulary.rb:304:in `block (2 levels) in load_formula_from_api' /usr/local/Homebrew/Library/Homebrew/formula.rb:3664:in `instance_eval' /usr/local/Homebrew/Library/Homebrew/formula.rb:3664:in `stable' /usr/local/Homebrew/Library/Homebrew/formulary.rb:293:in `block in load_formula_from_api' /usr/local/Homebrew/Library/Homebrew/formulary.rb:283:in `initialize' /usr/local/Homebrew/Library/Homebrew/formulary.rb:283:in `new' /usr/local/Homebrew/Library/Homebrew/formulary.rb:283:in `load_formula_from_api' /usr/local/Homebrew/Library/Homebrew/formulary.rb:962:in `load_from_api' /usr/local/Homebrew/Library/Homebrew/formulary.rb:955:in `klass' /usr/local/Homebrew/Library/Homebrew/formulary.rb:569:in `get_formula' /usr/local/Homebrew/Library/Homebrew/formulary.rb:1009:in `factory' /usr/local/Homebrew/Library/Homebrew/dependency.rb:41:in `to_formula' /usr/local/Homebrew/Library/Homebrew/utils/autoremove.rb:46:in `block (2 levels) in formulae_with_no_formula_dependents' /usr/local/Homebrew/Library/Homebrew/utils/autoremove.rb:45:in `each' /usr/local/Homebrew/Library/Homebrew/utils/autoremove.rb:45:in `block in formulae_with_no_formula_dependents' /usr/local/Homebrew/Library/Homebrew/utils/autoremove.rb:39:in `each' /usr/local/Homebrew/Library/Homebrew/utils/autoremove.rb:39:in `formulae_with_no_formula_dependents' /usr/local/Homebrew/Library/Homebrew/utils/autoremove.rb:59:in `unused_formulae_with_no_formula_dependents' /usr/local/Homebrew/Library/Homebrew/utils/autoremove.rb:16:in `removable_formulae' /usr/local/Homebrew/Library/Homebrew/cleanup.rb:693:in `autoremove' /usr/local/Homebrew/Library/Homebrew/cleanup.rb:291:in `clean!' /usr/local/Homebrew/Library/Homebrew/cmd/cleanup.rb:52:in `run' /usr/local/Homebrew/Library/Homebrew/brew.rb:92:in `
' ``` --- Library/Homebrew/extend/cachable.rb | 3 ++- Library/Homebrew/formulary.rb | 2 +- Library/Homebrew/test/api/internal_tap_json/formula_spec.rb | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/extend/cachable.rb b/Library/Homebrew/extend/cachable.rb index 6472cca03f25d..d3d2794bc38a4 100644 --- a/Library/Homebrew/extend/cachable.rb +++ b/Library/Homebrew/extend/cachable.rb @@ -7,9 +7,10 @@ def cache @cache ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped])) end + # NOTE: We overwrite here instead of using `Hash#clear` to handle frozen hashes. sig { void } def clear_cache - cache.clear + overwrite_cache!({}) end private diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index c479ddacdf32c..8810dc4e7be50 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -226,7 +226,7 @@ def self.load_formula_from_api(name, flags:) end if info&.key?("uses_from_macos") - bounds = info["uses_from_macos"] || {} + bounds = info["uses_from_macos"].dup || {} bounds.deep_transform_keys!(&:to_sym) bounds.deep_transform_values!(&:to_sym) diff --git a/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb b/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb index 2cfe78426dcb7..0b6f3c04fdf29 100644 --- a/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb +++ b/Library/Homebrew/test/api/internal_tap_json/formula_spec.rb @@ -33,7 +33,7 @@ allow(Homebrew::API).to receive(:fetch_json_api_file) .with("internal/v3/homebrew-core.jws.json") - .and_return([JSON.parse(internal_tap_json), false]) + .and_return([JSON.parse(internal_tap_json, freeze: true), false]) # `Tap.tap_migration_oldnames` looks for renames in every # tap so `CoreCaskTap.tap_migrations` gets called and tries to