CSV.parse suddenly raises error

Interestingly, I am having troubles with CSV.parse

require "csv"
=> true
string = "foo,0\nbar,1\nbaz,2\n"
=> "foo,0\nbar,1\nbaz,2\n"
CSV.parse(string)
Error: #<TypeError: no implicit conversion of nil into String>
=> nil

No extensions installed except the ones that come with SketchUp by default.
I am running SketchUp 24.0.553. Anyone else that runs into the same issues?

Weirdly enough, it seems to be related to starting SketchUp from CLI.
When I start SU as a regular user, I have no issues with the above written code snippet. However, when I start from command line, like the following:

cd /D "C:\Program Files\SketchUp\SketchUp 2024\"
SketchUp.exe -rdebug "ide port=7000"

I get the above written errorā€¦

hmmā€¦, when I leave out the rbdebug option, I have no issues with parsing a csvā€¦

:thinking:

This old error report shows that CSV can choke on nil values:

CSV.parse(csv, col_sep: nil) throw a TypeError (no implicit conversion of nil into String) in ruby 2.6 Ā· Issue #94 Ā· ruby/csv Ā· GitHub


Related, ā€¦ the more I read about the issues with Rubyā€™s CSV library, the more I want to avoid ever using it.

1 Like

Isnā€™t the current version 24.0.594?

Regardless, I tried to repro, but couldnā€™t. Were you using a debug console? I was not.

That GitHub issue doesnā€™t apply here, it was about the optional col_sep: parameter.

Iā€™m not aware of any significant issues with the csv gem. Iā€™ve got code that updates https://msp-greg.github.io/, and it uses csv. Itā€™s worked for several years.

I updated SketchUp moments after my post and am still experiencing the same issues.
I have no debugger attached.
I only have the issue when I launch SketchUp from CLI with the rdebug option. If I leave out the option, CSV functions as it should.

I have been launching SketchUp this way for the passedā€¦ 9 to 10 years I think and have been using the CSV gem significantly longer in many extensions.

@kengey

I tried again, and I canā€™t repro this, maybe others can. One odd thing, code in csv.rb as follows:

def <<(row)
  writer << row
  self
end
alias_method :add_row, :<<
alias_method :puts,    :<<

I was checking/debugging the code flow thru csv, and the aliasing of puts caused some issues. I had to use $stdout.write.

I may re-install 2024, sometimes I hack versions and forget to backout the changesā€¦

No worries, it is not a real issue anymore now I know it is somehow related to rdebug, however, it would be great to know why :slight_smile:

Iā€™ll try to reproduce on some of our other machines as well.

I donā€™t know if you have time for it, but without any backtrace info, itā€™s impossible to figure out whatā€™s causing the error.

A little diiscussion about this error.

The class CSV::Parser is ā€˜castā€™ to an enumerator in Parser.parse() (parser.rb, line: 400). The error is raised on the first invocation of enummerator.next() in CSV.each() (csv.rb, line: 2693)

With the ruby debugger loaded I can replicate this failue in SU2017 and SU2024 with the following code.

class Bubble
  def list(&block)
    return to_enum(__method__) unless block_given?
    yield 1
    yield 2
  end
end

bubble = Bubble.new

list_enumerator = bubble.list
#p list_enumerator.to_a

p list_enumerator.next
#=> #<TypeError: no implicit conversion of nil into String>

I think this is the fix. Let me know if further research is needed.

SURubyDebugger.dll is calling rb_convert_type() to convert a Qnil to a String using the method to_str(). But NilClass has no method named to_str(). Hereā€™s the fix. Just drop this snippet of code into a ruby file and save that in the plugins directory.

class NilClass
  def to_str(*args)
    inspect(*args)
  end
end

Update: The above Band-Aid fixed the example Iā€™ve posted above but I see that it broke the CSV gem in a new and different way. oh-well.

1 Like

I do not think this should serve as a permanent solution.

But only the String class should have a #to_str method, which is normally inherited as is and not overridden in subclasses. Its purpose is to return a new instance object of the superclass, when called upon subclass objects.
(And yes, the Fiddle::Pointer class violates this convention.)

What happens is that Ruby coders often use this convention as a rule, and do type checking with obj.respond_to?(:to_str) instead of obj.is_a?(String).
So, IMO, it is an issue of poor coding practice.

All other classes would have a #to_s method to do type conversion, which the NilClass already has.


Although your ā€œpatchā€ may work in the short term, if this is fault of the SURubyDebugger code, then they need to fix it going forward.

@bugra @tt_su

This has been reported before

2 Likes

Hereā€™s what Iā€™ve found. Although Iā€™m familiar enough with the ruby tracepoint implementation I donā€™t feel confident in making changes to the debugger source code and will leave that to the pros

In the problem code examples above the Enumerator is an external iterator (read the app notes here: ruby/enumerator.c at master Ā· ruby/ruby Ā· GitHub) thus it executes in a separate Fiber/Stack.

When the method next() is called on the Enumerator a c_call trace event is triggered in the SURubydebugger. This in turn makes a call to retrieve the current file path (rb_tracearg_path() in the code below). This results in the rb_tracearg_path() returning Qnil because weā€™re executing in two different Fibers/Stracks (see: ruby/vm_trace.c at master Ā· ruby/ruby Ā· GitHub). The subsequent call to GetRubyString() is where the error is raised. A check needs to be added to test for nil in the value returned from rb_tracearg_path().

Server.cpp circa line:400

#define EVENT_COMMON_CODE \
  rb_trace_arg_t* trace_arg = rb_tracearg_from_tracepoint(tp_val);\
  Server::Impl* server = reinterpret_cast<Server::Impl*>(data);\
  server->ClearBreakData();\
  std::string file_path = GetRubyString(rb_tracearg_path(trace_arg));\
  int line = GetRubyInt(rb_tracearg_lineno(trace_arg));\
  VALUE event_sym = rb_tracearg_event(trace_arg);\
//   Log(("\n*** Debugger event: " +\
//       GetRubyString(rb_sym_to_s(event_sym)) + ", " + file_path.c_str() + ":" +\
//       boost::lexical_cast<std::string>(line).c_str() + "\n").c_str())
1 Like

Nice job nailing that down! Perhaps add this information to the tracker issue 36?