diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2013-09-25 00:53:19 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2013-09-25 00:53:19 +0000 |
commit | 8eb39185810a59ad8d3aa874ba8f6c9a7b0949ac (patch) | |
tree | 790b26abda56b06c1d25a7a0036d882c32df7609 /lib/rubygems/dependency_resolver.rb | |
parent | 61f3a787f6f12c794299871d5739cfdfa01ec617 (diff) | |
download | ruby-8eb39185810a59ad8d3aa874ba8f6c9a7b0949ac.tar.gz |
* lib/rubygems: Fix CVE-2013-4363. Miscellaneous minor improvements.
* test/rubygems: Tests for the above.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43039 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rubygems/dependency_resolver.rb')
-rw-r--r-- | lib/rubygems/dependency_resolver.rb | 209 |
1 files changed, 119 insertions, 90 deletions
diff --git a/lib/rubygems/dependency_resolver.rb b/lib/rubygems/dependency_resolver.rb index 9f3af38ae5..bbb5408a55 100644 --- a/lib/rubygems/dependency_resolver.rb +++ b/lib/rubygems/dependency_resolver.rb @@ -92,6 +92,32 @@ class Gem::DependencyResolver res.to_a end + ## + # Finds the State in +states+ that matches the +conflict+ so that we can try + # other possible sets. + + def find_conflict_state conflict, states # :nodoc: + until states.empty? do + if conflict.for_spec? states.last.spec + state = states.last + state.conflicts << [state.spec, conflict] + return state + else + states.pop + end + end + + nil + end + + ## + # Extracts the specifications that may be able to fulfill +dependency+ + + def find_possible dependency # :nodoc: + possible = @set.find_all dependency + select_local_platforms possible + end + def handle_conflict(dep, existing) # There is a conflict! We return the conflict # object which will be seen by the caller and be @@ -144,116 +170,119 @@ class Gem::DependencyResolver # If there is already a spec activated for the requested name... if specs && existing = specs.find { |s| dep.name == s.name } - - # then we're done since this new dep matches the - # existing spec. + # then we're done since this new dep matches the existing spec. next if dep.matches_spec? existing conflict = handle_conflict dep, existing - # Look through the state array and pop State objects - # until we get back to the State that matches the conflict - # so that we can try other possible sets. + state = find_conflict_state conflict, states - i = nil + return conflict unless state - until states.empty? - if conflict.for_spec? states.last.spec - i = states.last - i.conflicts << [i.spec, conflict] - break - else - states.pop - end - end + needed, specs = resolve_for_conflict needed, specs, state - if i - # We exhausted the possibles so it's definitely not going to - # work out, bail out. + next + end - if i.possibles.empty? - raise Gem::ImpossibleDependenciesError.new(i.dep, i.conflicts) - end + possible = find_possible dep - spec = i.possibles.pop + case possible.size + when 0 + resolve_for_zero dep + when 1 + needed, specs = + resolve_for_single needed, specs, dep, possible + else + needed, specs = + resolve_for_multiple needed, specs, states, dep, possible + end + end - # Recursively call #resolve_for with this spec - # and add it's dependencies into the picture... + specs + end - act = Gem::DependencyResolver::ActivationRequest.new spec, i.dep + ## + # Rewinds +needed+ and +specs+ to a previous state in +state+ for a conflict + # between +dep+ and +existing+. - needed = requests(spec, act, i.needed) - specs = Gem::List.prepend(i.specs, act) + def resolve_for_conflict needed, specs, state # :nodoc: + # We exhausted the possibles so it's definitely not going to work out, + # bail out. + raise Gem::ImpossibleDependenciesError.new state.dep, state.conflicts if + state.possibles.empty? - next - else - return conflict - end - end + spec = state.possibles.pop - # Get a list of all specs that satisfy dep and platform - possible = @set.find_all dep - possible = select_local_platforms possible + # Retry resolution with this spec and add it's dependencies + act = Gem::DependencyResolver::ActivationRequest.new spec, state.dep - case possible.size - when 0 - @missing << dep + needed = requests spec, act, state.needed + specs = Gem::List.prepend state.specs, act - unless @soft_missing - # If there are none, then our work here is done. - raise Gem::UnsatisfiableDependencyError, dep - end - when 1 - # If there is one, then we just add it to specs - # and process the specs dependencies by adding - # them to needed. + return needed, specs + end - spec = possible.first - act = Gem::DependencyResolver::ActivationRequest.new spec, dep, false + ## + # There are multiple +possible+ specifications for this +dep+. Updates + # +needed+, +specs+ and +states+ for further resolution of the +possible+ + # choices. + + def resolve_for_multiple needed, specs, states, dep, possible # :nodoc: + # Sort them so that we try the highest versions first. + possible = possible.sort_by do |s| + [s.source, s.version, s.platform == Gem::Platform::RUBY ? -1 : 1] + end - specs = Gem::List.prepend specs, act + # To figure out which to pick, we keep resolving given each one being + # activated and if there isn't a conflict, we know we've found a full set. + # + # We use an until loop rather than reverse_each to keep the stack short + # since we're using a recursive algorithm. + spec = possible.pop - # Put the deps for at the beginning of needed - # rather than the end to match the depth first - # searching done by the multiple case code below. - # - # This keeps the error messages consistent. - needed = requests(spec, act, needed) - else - # There are multiple specs for this dep. This is - # the case that this class is built to handle. - - # Sort them so that we try the highest versions - # first. - possible = possible.sort_by do |s| - [s.source, s.version, s.platform == Gem::Platform::RUBY ? -1 : 1] - end - - # To figure out which to pick, we keep resolving - # given each one being activated and if there isn't - # a conflict, we know we've found a full set. - # - # We use an until loop rather than #reverse_each - # to keep the stack short since we're using a recursive - # algorithm. - # - spec = possible.pop - - # We're may need to try all of +possible+, so we setup - # state to unwind back to current +needed+ and +specs+ - # so we can try another. This is code is what makes the above - # code in conflict resolution possible. - - act = Gem::DependencyResolver::ActivationRequest.new spec, dep - - states << State.new(needed, specs, dep, spec, possible, []) - - needed = requests(spec, act, needed) - specs = Gem::List.prepend(specs, act) - end - end + # We may need to try all of +possible+, so we setup state to unwind back + # to current +needed+ and +specs+ so we can try another. This is code is + # what makes conflict resolution possible. - specs + act = Gem::DependencyResolver::ActivationRequest.new spec, dep + + states << State.new(needed, specs, dep, spec, possible, []) + + needed = requests spec, act, needed + specs = Gem::List.prepend specs, act + + return needed, specs + end + + ## + # Add the spec from the +possible+ list to +specs+ and process the spec's + # dependencies by adding them to +needed+. + + def resolve_for_single needed, specs, dep, possible # :nodoc: + spec = possible.first + act = Gem::DependencyResolver::ActivationRequest.new spec, dep, false + + specs = Gem::List.prepend specs, act + + # Put the deps for at the beginning of needed + # rather than the end to match the depth first + # searching done by the multiple case code below. + # + # This keeps the error messages consistent. + needed = requests spec, act, needed + + return needed, specs + end + + ## + # When there are no possible specifications for +dep+ our work is done. + + def resolve_for_zero dep # :nodoc: + @missing << dep + + unless @soft_missing + raise Gem::UnsatisfiableDependencyError, dep + end end ## |