castwide / readapt

A Ruby debugger for the Debug Adapter Protocol.
MIT License
42 stars 3 forks source link

Support for eval in eclipse #11

Closed pebauer68 closed 2 years ago

pebauer68 commented 3 years ago

ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux] eclipse: Version: 2020-09 (4.17.0) Entering a valid ruby expression in the eclipse console during debugging gives the following error in the eclipse console: [NoMethodError] private method `eval' called for nil:NilClass Where is this error coming from ?

Exception NoMethodError' at /.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/readapt-1.2.0/lib/readapt/frame.rb:13 - private methodeval' called for nil:NilClass

castwide commented 3 years ago

I'm running into other problems with Ruby 3.0.0 that may or may not be related to this one. If the problem persists with gem 1.3.0, I'll work on a new release that specifically targets Ruby 3 in the near future.

castwide commented 3 years ago

I've confirmed that the latest version of Readapt works with Ruby 3. @PyvesB could this be an issue with the Eclipse plugin instead of the gem?

PyvesB commented 3 years ago

I would tend to say it's unlikely given that it's a Ruby exception. The Eclipse plugin is a thin Java wrapper that launches Readapt as an external process.

@castwide have you specifically tried evaluating an expression in VSCode's debug console with Ruby 3?

castwide commented 3 years ago

@PyvesB I went back to make sure. Confirmed working with Ruby 3.0.0 in VS Code on Windows 10 and CentOS 7.6.

readapt-console

PyvesB commented 3 years ago

Will investigate some more in the coming weeks.

PyvesB commented 3 years ago

I've had some time to circle back to this. I can reproduce the same error as described by @pebauer68. Here's what I've observed so far.

Using the following example Ruby script from the ruby-lang.org website and setting a breakpoint on the say['love'] = "*love*" line (I now realise this is a somewhat cheesy example, but it'll do 😄):

# Output "I love Ruby"
say = "I love Ruby"
puts say

# Output "I *LOVE* RUBY"
say['love'] = "*love*"
puts say.upcase

# Output "I *love* Ruby"
# five times
5.times { puts say }

Here's the extract of messages between the debug adapter and the IDE:

Content-Length: 344
{"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"exceptionBreakpointFilters":[{"filter":"raise","label":"Break on raised exceptions","description":"The debugger will break when an exception is raised, regardless of whether it is subsequently rescued.","default":false}]}}

Content-Length: 38
{"type":"event","event":"initialized"}

Content-Length: 79
{"type":"response","request_seq":2,"success":true,"command":"launch","body":{}}

Content-Length: 287
{"type":"response","request_seq":3,"success":true,"command":"setBreakpoints","body":{"breakpoints":[{"verified":true,"source":{"name":"hello-world.rb","path":"C:\\Users\\Pierre-Yves\\Documents\\runtime-EclipseApplication\\hello-world\\hello-world.rb"},"line":6}]}}

Content-Length: 90
{"type":"response","request_seq":4,"success":true,"command":"configurationDone","body":{}}

Content-Length: 158
{"type":"event","event":"process","body":{"name":"C:/Users/Pierre-Yves/Documents/runtime-EclipseApplication/hello-world/hello-world.rb"}}

Content-Length: 74
{"type":"event","event":"thread","body":{"reason":"started","threadId":1}}

Content-Length: 74
{"type":"event","event":"thread","body":{"reason":"started","threadId":1}}

Content-Length: 78
{"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1}}

Content-Length: 87
{"type":"event","event":"output","body":{"output":"I love Ruby\n","category":"stdout"}}

Content-Length: 118
{"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"Thread 1"}]}}

Content-Length: 866
{"type":"response","request_seq":6,"success":true,"command":"stackTrace","body":{"stackFrames":[{"name":"say['love'] = \"*love*\"","source":{"name":"hello-world.rb","path":"C:/Users/Pierre-Yves/Documents/runtime-EclipseApplication/hello-world/hello-world.rb"},"id":340,"line":6,"column":0},{"name":"run { load @file }","source":{"name":"debugger.rb","path":"C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/readapt-1.4.3/lib/readapt/debugger.rb"},"id":360,"line":66,"column":0},{"name":"yield if block_given?","source":{"name":"debugger.rb","path":"C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/readapt-1.4.3/lib/readapt/debugger.rb"},"id":380,"line":80,"column":0},{"name":"run { load @file }","source":{"name":"debugger.rb","path":"C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/readapt-1.4.3/lib/readapt/debugger.rb"},"id":400,"line":66,"column":0}],"totalFrames":4}}

Content-Length: 207
{"type":"response","request_seq":7,"success":true,"command":"scopes","body":{"scopes":[{"name":"Local","variablesReference":340,"expensive":false},{"name":"Global","variablesReference":1,"expensive":true}]}}

Content-Length: 153
{"type":"response","request_seq":8,"success":true,"command":"evaluate","body":{"result":"[NoMethodError] private method `eval' called for nil:NilClass"}}
PyvesB commented 2 years ago

I circled back to this and did some deeper debugging.

According to the protocol messages I previously posted, the Readapt line of code that is throwing the error is the following: https://github.com/castwide/readapt/blob/dbc5ec193bb45c07c6eef4bfb0050116b0339fc2/lib/readapt/frame.rb#L13

Adding some debug statements to the following line of code shows that, unlike VS Code, the request from Eclipse does not contain a frameId parameter: https://github.com/castwide/readapt/blob/dbc5ec193bb45c07c6eef4bfb0050116b0339fc2/lib/readapt/message/evaluate.rb#L9

Indeed, looking at the code that generates the request on the Eclipse side, the frameId is not being set: https://github.com/eclipse/lsp4e/blob/607e86e697e49e4f1cb1969ecdf9009cbfa47df4/org.eclipse.lsp4e.debug/src/org/eclipse/lsp4e/debug/console/DSPStreamsProxy.java#L51

However, according to the protocol specification, Eclipse is technically allowed to do so:

/**
 * Evaluate the expression in the scope of this stack frame. If not specified,
 * the expression is evaluated in the global scope.
 */
frameId?: number;

Readapt is essentially treating an optional parameter as mandatory, which is a bug. I'll be submitting a fix shortly to evaluate in the global scope when no frameId is specified. I'll also submit an improvement to Eclipse so that it sends the frameId when possible.