-
Notifications
You must be signed in to change notification settings - Fork 604
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
repl.rb: guard against negative overhang #1771
Conversation
Hey @DamienRobert, thanks for the patch. I noticed your code snippet is not valid Ruby. Is this on purpose? The description makes sense, however when I tried copy-pasting it in my pry but I don't see any difference between [1] pry(main)> def aaa
[1] pry(main)* foo do
bar #note the tab here
[1] pry(main)* bar #note the tab herehere
[1] pry(main)* donedone
[1] pry(main)* end That said, I don't see any exceptions. I'm running it on OSX. Is there any extra information to be shared? |
@kyrylo sorry, the code should be correct ruby, replace the 'done' by 'end'. I guess I wrote the commit message after writing some shell code... Its strange that you don't get any exception, when I copy paste the snippet (even though it is not valid code) into pry, I get: |
What's the Ruby version you're using? |
Even when I paste the corrected version of the snippet, I still see broken output. If this is a bug on older Rubies, we probably need a test to confirm that. |
@kyrylo I use ruby 2.5.1p57, but this is definitively not a bug from ruby, but from pry; when overhang is negative, This pr is not about correcting the output, but preventing an exception in To correct the output, one would need to replace But I think it is more urgent to prevent the exception than to correct the displayed output. |
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.
Would it be possible to add a test for this? It's not obvious from the code how this can occur. If it's unachievable, I'd be inclined to have a comment instead.
lib/pry/repl.rb
Outdated
@@ -107,7 +107,7 @@ def read | |||
if output.tty? && pry.config.correct_indent && Pry::Helpers::BaseHelpers.use_ansi_codes? | |||
output.print @indent.correct_indentation( | |||
current_prompt, indented_val, | |||
original_val.length - indented_val.length | |||
[0, original_val.length - indented_val.length].max |
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.
Not trying to be nitpicky here but this code is executed frequently, therefore it's better to make it more performant. I believe if we used a temp variable with an if
check it would accomplish that goal. What do you think?
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.
Sorry, but I am not sure I understand your review. This line is executed only once for every read
, so I don't know how I could use a temp variable?
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.
Sorry for not being clear enough. I think we could put the result of evaluation of original_val.length - indented_val.length
into a temp variable, then check if it’s larger than 0. If it is, then we return it. If not, then we return nil
. With this approach there’s no need to allocate an array. Does it make sense?
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 I see, indeed a benchmark shows that an if is slightly faster, but allocating a temporary variable is actually slower:
require 'benchmark'
array = (1..1000000).map { rand(10)-5 }
Benchmark.bmbm do |x|
x.report("array") { array.each {|x| [x,0].max }}
x.report("if") { array.each {|x| x >= 0 ? x : 0 }}
x.report("if temp var") { array.each {|x| y=x; y=0 if y <= 0 }}
end
user system total real
array 0.048741 0.000000 0.048741 (0.048804)
if 0.047802 0.000000 0.047802 (0.047877)
if temp var 0.052751 0.000000 0.052751 (0.052818)
Anyway I don't really think microoptimising this code is really important (especially since it follows an io operation), so I changed my version to your suggestion.
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.
Seems like you are right. Thanks for running this benchmark and sorry for the nitpicky comment.
I am sorry, I got confused and mixed issues together. Unfortunately, I still cannot replicate this error even when I explicitly enable |
Sure no problem! So your review is about another pr right? |
My review is still applicable to this PR, this stands the same :)
Heh, same here. I'll try to upgrade my Ruby to see if it happens but either way the review is still applicable. |
Ok, I replied to your review, but I am not sure what you meant.
What does the following do? i=Pry::Indent.new
i.indent("def foo") #so that i.@indent_level=1
orig="\t'bar'"
indented=i.indent(orig) # => " 'bar'"
overhang=orig.length - indented.length # overhang = -1 This should replicate a case where overhang will be equal to -1. By the way when you paste the snippet, do you paste it line by line or at once? To simulate a full paste, you can use this snippet require 'pry'
class Repl < Pry::REPL
def read_line(_prompt)
<<-EOS
foo do
bar #note the tab here
end
EOS
end
end
Repl.new(Pry.new, {}).send(:read) #=> ArgumentError: negative argument |
e65464d
to
88b8f80
Compare
I can now reproduce the problem this way. I used to paste at once but perhaps it depends on how your terminal handles this. I run iTerm 2 but even Terminal.app doesn't show any errors. Either way, thanks for the patch. Have you considered this comment of mine?
You basically wrote that test in your last comment, we just need to put that to the test file 😄 P.S. Could you also rebase this on |
When copy pasting into pry a code like this ~~~ ruby foo do bar #note the tab here done ~~~ then `@indent.indent(val)` replace the tab by two spaces, and so `overhang = original_val.length - indented_val.length` is negative, which yields an Exception in `whitespace = ' ' * overhang`. Guard against this by taking the max with 0.
88b8f80
to
80decc7
Compare
Ok, so it seems that these terminals split the paste on new lines, interesting...
I used the
Done. |
it "should raise no exception when indented with a tab" do | ||
ReplTester.start(correct_indent: true, auto_indent: true) do | ||
output=@pry.config.output | ||
def output.tty?; true; end |
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.
Can we mock using RSpec?
If I am not mistaken, this test modifies the outside environment, which we'd want to prevent.
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.
Can we mock using RSpec?
We could, but there is no reason to, since we don't want to test tty?
here.
If I am not mistaken, this test modifies the outside environment, which we'd want to prevent.
No, ReplTester.start invokes redirect_pry_io input, output
where output
is (essentially) a StringIO.new. So this is a temp object that only lives during the duration of the ReplTester block, and I am only adding to its singleton class, so this won't change the outside environment.
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 don't necessarily agree with this reasoning but I have to admit it's not a big deal.
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.
This looks good to me. Could you rebase, please? Otherwise, I'll have to hit the "update branch" button, which will create a new junk commit. Happy to merge as soon as this is rebased.
Never mind, it turned out to be a setting in the project which requires PRs to be rebased on latest Thanks for your work 👍 |
Yes I had rebased, but you pushed a new commit in the meantime :) Thanks for merging! |
pkgsr change: * Remove @Prefix@ from ALTERNATIVES file. ### [v0.12.2][v0.12.2] (November 12, 2018) #### Bug fixes * Restore removed deprecations, which were removed by accident due to a bad rebase. ### [v0.12.1][v0.12.1] (November 12, 2018) #### Bug fixes * Stopped creating a new hash each time `Pry::Prompt#[]` is invoked ([#1855](pry/pry#1855)) * Fixed `less` pager not working when it's available ([#1861](pry/pry#1861)) ### [v0.12.0][v0.12.0] (November 5, 2018) #### Major changes * Dropped support for Rubinius ([#1785](pry/pry#1785)) #### Features * Added a new command, `clear-screen`, that clears the content of the screen Pry is running in regardless of platform (Windows or UNIX-like) ([#1723](pry/pry#1723)) * Added a new command, `gem-stat`, that prints gem statistics such as gem dependencies and downloads ([#1707](pry/pry#1707)) * Added support for nested exceptions for the `wtf` command ([#1791](pry/pry#1791)) * Added support for dynamic prompt names ([#1833](pry/pry#1833)) ```rb # pryrc Pry.config.prompt_name = Pry.lazy { rand(100) } # Session [1] 80(main)> [2] 87(main)> [3] 30(main)> ``` * Added support for XDG Base Directory Specification ([#1609](pry/pry#1609), [#1844](pry/pry#1844), ([#1848](pry/pry#1848))) * Removed the `simple-prompt`. Use `change-prompt simple` instead. The `list-prompt` command was removed and embedded as `change-prompt --list` ([#1849](pry/pry#1849)) #### API changes * The following methods started accepting the new optional `config` parameter ([#1809](pry/pry#1809)): * `Pry::Helpers.tablify(things, line_length, config = Pry.config)` * `Pry::Helpers.tablify_or_one_line(heading, things, config = Pry.config)` * `Pry::Helpers.tablify_to_screen_width(things, options, config = Pry.config)` * `Pry::Helpers::Table.new(items, args, config = Pry.config)` You are expected to pass a session-local `_pry_.config` instead of the global one. * Added new method `Pry::Config.assign`, for creating a Config non-recursively ([#1725](pry/pry#1725)) * Added `Pry.lazy`, which is a helper method for values that need to be calculated dynamically. Currently, only `config.prompt_name` supports it ([#1833](pry/pry#1833)) * `Pry::Prompt` responds to `.[]`, `.all` & `.add` now. The `Pry::Prompt.add` method must be used for implementing custom prompts. See the API in the documentation for the class ([#1846](pry/pry#1846)) #### Breaking changes * Deleted the `Pry::Helpers::Text.bright_default` alias for `Pry::Helpers::Text.bold` ([#1795](pry/pry#1795)) * `Pry::Helpers.tablify_to_screen_width(things, options, config = Pry.config)` requires `options` or `nil` in place of them. * `Pry::Helpers::Table.new(items, args, config = Pry.config)` requires `args` or `nil` in place of them. * Completely revamped `Pry::HistoryArray` ([#1818](pry/pry#1818)). * It's been renamed to `Pry::Ring` ([#1817](pry/pry#1817)) * The implementation has changed and as result, the following methods were removed: * `Pry::Ring#length` (use `Pry::Ring#count` instead) * `#empty?`, `#each`, `#inspect`, `#pop!`, `#to_h` * To access old Enumerable methods convert the ring to Array with `#to_a` * Fixed indexing for elements (e.g. `_pry_.input_ring[0]` always return some element and not `nil`) * Renamed `Pry.config.prompt_safe_objects` to `Pry.config.prompt_safe_contexts` * Removed deprecated `Pry::CommandSet#before_command` & `Pry::CommandSet#after_command` ([#1838](pry/pry#1838)) #### Deprecations * Deprecated `_pry_.input_array` & `_pry_.output_array` in favour of `_pry_.input_ring` & `_pry_.output_ring` respectively ([#1814](pry/pry#1814)) * Deprecated `Pry::Command#text`. Please use `#black`, `#white`, etc. directly instead (as you would with helper functions from `BaseHelpers` and `CommandHelpers`) ([#1701](pry/pry#1701)) * Deprecated `_pry_.input_array` & `_pry_.output_array` in favour of `_pry_.input_ring` and `_pry_.output_ring` respectively ([#1817](pry/pry#1817)) * Deprecated `Pry::Platform`. Use `Pry::Helpers::Platform` instead. Note that `Pry::Helpers::BaseHelpers` still includes the `Platform` methods but emits a warning. You must switch to `Pry::Helpers::Platform` in your code ([#1838](pry/pry#1838), ([#1845](pry/pry#1845))) * Deprecated `Pry::Prompt::MAP`. You should use `Pry::Prompt.all` instead to access the same map ([#1846](pry/pry#1846)) #### Bug fixes * Fixed a bug where `cd Hash.new` reported `self` as an instance of Pry::Config in the prompt ([#1725](pry/pry#1725)) * Silenced the `Could not find files for the given pattern(s)` error message coming from `where` on Windows, when `less` or another pager is not installed ([#1767](pry/pry#1767)) * Fixed possible double loading of Pry plugins' `cli.rb` on Ruby (>= 2.4) due to [the `realpath` changes while invoking `require`](https://bugs.ruby-lang.org/issues/10222) ([#1762](pry/pry#1762), [#1774](pry/pry#1762)) * Fixed `NoMethodError` on code objects that have a comment but no source when invoking `show-source` ([#1779](pry/pry#1779)) * Fixed `negative argument (ArgumentError)` upon pasting code with tabs, which used to confuse automatic indentation ([#1771](pry/pry#1771)) * Fixed Pry not being able to load history on Ruby 2.4.4+ when it contains the null character ([#1789](pry/pry#1789)) * Fixed Pry raising errors on `cd`'ing into some objects that redefine `method_missing` and `respond_to?` ([#1811](pry/pry#1811)) * Fixed bug when indentation leaves parts of input after pressing enter when Readline is enabled with mode indicators for vi mode ([#1813](pry/pry#1813), [#1820](pry/pry#1820), [#1825](pry/pry#1825)) * Fixed `edit` not writing to history ([#1749](pry/pry#1749)) #### Other changes * Deprecated the `Data` constant to match Ruby 2.5 in the `ls` command ([#1731](pry/pry#1731))
When copy pasting into pry a code like this
then
@indent.indent(val)
replace the tab by two spaces, and sooverhang = original_val.length - indented_val.length
is negative,which yields an Exception in
whitespace = ' ' * overhang
.Guard against this by taking the max with 0.