-
-
Notifications
You must be signed in to change notification settings - Fork 34
Memoize ruby-macho's internals #942
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| .ruby-version | ||
| .idea/ | ||
| .vscode/ | ||
| mise.toml | ||
|
|
||
| # macOS metadata file | ||
| .DS_Store | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,4 +67,4 @@ DEPENDENCIES | |
| simplecov-cobertura | ||
|
|
||
| BUNDLED WITH | ||
| 2.3.5 | ||
| 4.0.6 | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -147,7 +147,7 @@ def cpusubtype | |||||||
| # @return [Array<LoadCommands::LoadCommand>] an array of load commands | ||||||||
| # corresponding to `name` | ||||||||
| def command(name) | ||||||||
| load_commands.select { |lc| lc.type == name.to_sym } | ||||||||
| @load_commands_by_type.fetch(name.to_sym, []) | ||||||||
| end | ||||||||
|
|
||||||||
| alias [] command | ||||||||
|
|
@@ -245,21 +245,22 @@ def delete_command(lc, options = {}) | |||||||
| # The exception to this rule is when methods like {#add_command} and | ||||||||
| # {#delete_command} have been called with `repopulate = false`. | ||||||||
| def populate_fields | ||||||||
| clear_memoization_cache | ||||||||
| @header = populate_mach_header | ||||||||
| @load_commands = populate_load_commands | ||||||||
| end | ||||||||
|
|
||||||||
| # All load commands responsible for loading dylibs. | ||||||||
| # @return [Array<LoadCommands::DylibCommand>] an array of DylibCommands | ||||||||
| def dylib_load_commands | ||||||||
| load_commands.select { |lc| LoadCommands::DYLIB_LOAD_COMMANDS.include?(lc.type) } | ||||||||
| @dylib_load_commands ||= load_commands.select { |lc| LoadCommands::DYLIB_LOAD_COMMANDS.include?(lc.type) } | ||||||||
| end | ||||||||
|
Comment on lines
255
to
257
|
||||||||
|
|
||||||||
| # All segment load commands in the Mach-O. | ||||||||
| # @return [Array<LoadCommands::SegmentCommand>] if the Mach-O is 32-bit | ||||||||
| # @return [Array<LoadCommands::SegmentCommand64>] if the Mach-O is 64-bit | ||||||||
| def segments | ||||||||
| if magic32? | ||||||||
| @segments ||= if magic32? | ||||||||
| command(:LC_SEGMENT) | ||||||||
| else | ||||||||
| command(:LC_SEGMENT_64) | ||||||||
|
|
@@ -271,26 +272,7 @@ def segments | |||||||
| # @note This is **not** the same as {#alignment}! | ||||||||
| # @note See `get_align` and `get_align_64` in `cctools/misc/lipo.c` | ||||||||
| def segment_alignment | ||||||||
| # special cases: 12 for x86/64/PPC/PP64, 14 for ARM/ARM64 | ||||||||
| return 12 if %i[i386 x86_64 ppc ppc64].include?(cputype) | ||||||||
| return 14 if %i[arm arm64].include?(cputype) | ||||||||
|
|
||||||||
| cur_align = Sections::MAX_SECT_ALIGN | ||||||||
|
|
||||||||
| segments.each do |segment| | ||||||||
| if filetype == :object | ||||||||
| # start with the smallest alignment, and work our way up | ||||||||
| align = magic32? ? 2 : 3 | ||||||||
| segment.sections.each do |section| | ||||||||
| align = section.align unless section.align <= align | ||||||||
| end | ||||||||
| else | ||||||||
| align = segment.guess_align | ||||||||
| end | ||||||||
| cur_align = align if align < cur_align | ||||||||
| end | ||||||||
|
|
||||||||
| cur_align | ||||||||
| @segment_alignment ||= calculate_segment_alignment | ||||||||
| end | ||||||||
|
|
||||||||
| # The Mach-O's dylib ID, or `nil` if not a dylib. | ||||||||
|
|
@@ -338,7 +320,7 @@ def linked_dylibs | |||||||
| # library, but at this point we're really only interested in a list of | ||||||||
| # unique libraries this Mach-O file links to, thus: `uniq`. (This is also | ||||||||
| # for consistency with `FatFile` that merges this list across all archs.) | ||||||||
| dylib_load_commands.map(&:name).map(&:to_s).uniq | ||||||||
| @linked_dylibs ||= dylib_load_commands.map { |lc| lc.name.to_s }.uniq | ||||||||
|
||||||||
| @linked_dylibs ||= dylib_load_commands.map { |lc| lc.name.to_s }.uniq | |
| @linked_dylibs ||= dylib_load_commands.map { |lc| lc.name.to_s }.uniq | |
| @linked_dylibs.dup |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rpaths is now memoized and returns a cached array instance. This can be an observable API change if any caller mutates the array, since subsequent calls will reflect that mutation (previously each call built a new array). Consider returning a defensive copy of the cached value.
| @rpaths ||= command(:LC_RPATH).map { |lc| lc.path.to_s } | |
| @rpaths ||= command(:LC_RPATH).map { |lc| lc.path.to_s } | |
| @rpaths.dup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commandnow returns the underlying cached array from@load_commands_by_type. Previously this method returned a fresh array viaselect, so callers could mutate the result without affecting internal state. To avoid an unintended public API behavior change (and accidental corruption of the type index), return a copy (e.g.,dup) of the cached array.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this and the others are probably fine in practice, since callers have no reason to ever mutate these returned arrays in place (and we never guaranteed that they'd be fresh values for callers).
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a reasonable point to be fair. We could alternatively
.freezeon these to force the caller to make an explicit.dupif they need to mutate but that would be an API change to do that now though.Looking at Homebrew, I already see cases where we are mutating so we probably can't ignore this:
https://github.com/Homebrew/brew/blob/8ab39821c51ff591456be48a0ff5f8f55f44511b/Library/Homebrew/os/mac/mach.rb#L119
https://github.com/Homebrew/brew/blob/8ab39821c51ff591456be48a0ff5f8f55f44511b/Library/Homebrew/os/mac/mach.rb#L108
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh well. Okay, I'll change it to copies for now, and we can always make them frozen with the next major release.