Ruby CSV truncating backtrace - ruby

Given the following code:
test.rb
require 'csv'
def meth1
meth2
end
def meth2
begin
iter = CSV.foreach('').each # empty file path, will raise exception.
iter.next
rescue Exception => e
puts e
puts e.backtrace
end
end
meth1
Two questons.
First, why is the backtrace truncated and not showing meth1 or meth2 calls:
No such file or directory # rb_sysopen -
ruby test.rb
/Users/x/.rbenv/versions/2.7.2/lib/ruby/2.7.0/csv.rb:641:in `initialize'
/Users/x/.rbenv/versions/2.7.2/lib/ruby/2.7.0/csv.rb:641:in `open'
/Users/x/.rbenv/versions/2.7.2/lib/ruby/2.7.0/csv.rb:641:in `open'
/Users/x/.rbenv/versions/2.7.2/lib/ruby/2.7.0/csv.rb:510:in `foreach'
test.rb:in `each'
Second, the (truncated) backtrace points to line 641 of CSV (using Ruby v 2.7.2). However line 641 has no initialize() method. Where is this coming from?
.rbenv/versions/2.7.2/lib/ruby/2.7.0/csv.rb
...
begin
f = File.open(filename, mode, **file_opts) ## line 641
rescue ArgumentError => e
raise unless /needs binmode/.match?(e.message) and mode == "r"
mode = "rb"
file_opts = {encoding: Encoding.default_external}.merge(file_opts)
retry
end
If I try another test calling File.open('') directly (instead of through CSV), the resulting backtrace shows everything as expected (including the calls to meth1 and meth2).
Any Ruby gurus out there know what is going on?

I'm not sure I understand; #foreach:510 calls open, #open:641 calls File.open.
In any case, Ruby backtraces have always been a bit wonky, particularly with local top-level files.
The reason you see the initialize is because foreach is a class method of CSV, so there's some behind-the-scenes Ruby shenanigans.
You can use the private method caller_locations, however:
puts e.send(:caller_locations)
which outputs:
test.rb:7:in `meth2'
test.rb:4:in `meth1'
test.rb:20:in `<main>'
This is just the file's top level methods.
You can play games like ruby -d that'll at least get you the line of the script:
Exception `LoadError' at /Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/rubygems.rb:1424 - cannot load such file -- rubygems/defaults/operating_system
Exception `LoadError' at /Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/rubygems.rb:1432 - cannot load such file -- rubygems/defaults/ruby
Exception `SyntaxError' at /Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/forwardable/impl.rb:5 - /Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/forwardable/impl.rb:5: syntax error, unexpected end-of-input
Exception `SyntaxError' at /Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/forwardable/impl.rb:5 - /Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/forwardable/impl.rb:5: syntax error, unexpected end-of-input
Exception `Errno::ENOENT' at /Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/csv.rb:641 - No such file or directory # rb_sysopen -
Exception `Errno::ENOENT' at test.rb:10 - No such file or directory # rb_sysopen -
/Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/csv.rb:641:in `initialize'
/Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/csv.rb:641:in `open'
/Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/csv.rb:641:in `open'
/Users/dave/.asdf/installs/ruby/2.7.2/lib/ruby/2.7.0/csv.rb:510:in `foreach'
test.rb:in `each'
Along with other noise.
If you really want to blow your mind, inside the rescue:
puts Thread.current.backtrace
Which outputs:
test.rb:21:in `backtrace'
test.rb:21:in `rescue in meth2'
test.rb:7:in `meth2'
test.rb:4:in `meth1'
test.rb:35:in `<main>'

Related

knowing if file is YAML or not

I would like to expect that YAML.load_file(foo) of ruby YAML module returns null if foo is not a YAML file. But I get exception:
did not find expected alphabetic or numeric character while scanning an alias at line 3 column 3 (Psych::SyntaxError)
from /usr/lib/ruby/2.4.0/psych.rb:377:in `parse_stream'
from /usr/lib/ruby/2.4.0/psych.rb:325:in `parse'
from /usr/lib/ruby/2.4.0/psych.rb:252:in `load'
from /usr/lib/ruby/2.4.0/psych.rb:473:in `block in load_file'
from /usr/lib/ruby/2.4.0/psych.rb:472:in `open'
from /usr/lib/ruby/2.4.0/psych.rb:472:in `load_file'
from ./select.rb:27:in `block in selecting'
from ./select.rb:26:in `each'
from ./select.rb:26:in `selecting'
from ./select.rb:47:in `block (2 levels) in <main>'
from ./select.rb:46:in `each'
from ./select.rb:46:in `block in <main>'
from ./select.rb:44:in `each'
from ./select.rb:44:in `<main>'
How can I triage if a file is a YAML file or not without a exception? In my case, I navigate to a directory and process markdown files: I add to a list markdown files with a key output: word and I return that list
mylist = Array.new
mylist = []
for d in (directory - excludinglist)
begin
info = YAML.load_file(d)
if info
if info.has_key?('output')
if info['output'].has_key?(word)
mylist.push(d)
end
end
end
rescue Psych::SyntaxError => error
return []
end
end
return mylist
When I catch exceptions, the bucle does not continue to push elements on my list.
The short answer: you can't.
Because YAML is just a text file, the only way to know whether a given text file is YAML or not is to parse it. The parser will try to parse the file, and if it is not valid YAML, it will raise an error.
Errors and exceptions are a common part of Ruby, especially in the world of IO. There's no reason to be afraid of them. You can easily rescue from them and continue on your way:
begin
yaml = YAML.load_file(foo)
rescue Psych::SyntaxError => e
# handle the bad YAML here
end
You mentioned that the following code will not work because you need to handle multiple files in a directory:
def foo
mylist = []
for d in (directory - excludinglist)
begin
info = YAML.load_file(d)
if info
if info.has_key?('output')
if info['output'].has_key?(word)
mylist.push(d)
end
end
end
rescue Psych::SyntaxError => error
return []
end
return mylist
end
The only issue here is that when you hit an error, you respond by returning from the function early. If you don't return, the for-loop will continue and you will get your desired functionality:
def foo
mylist = []
for d in (directory - excludinglist)
begin
info = YAML.load_file(d)
if info
if info.has_key?('output')
if info['output'].has_key?(word)
mylist.push(d)
end
end
end
rescue Psych::SyntaxError => error
# do nothing!
# puts "or your could display an error message!"
end
end
return mylist
end
Psych::SyntaxError gets raised by Psych::Parser#parse, the source for which is written in C. So unless you want to work with C, you can't write a patch for the method in Ruby to prevent the exception from getting raised.
Still, you could certainly rescue the exception, like so:
begin
foo = YAML.load_file("not_yaml.txt")
rescue Psych::SyntaxError => error
puts "bad yaml"
end

How to correctly rescue this NameError

I encountered this NameError
>> v
NameError: undefined local variable or method `v' for #<Rex::Post::Meterpreter::Ui::Console::CommandDispatcher::Core:0x00000002871728>
from (irb):1:in `cmd_irb'
from /home/txjoe/git/metasploit-framework/lib/rex/ui/text/irb_shell.rb:54:in `block in run'
from /home/txjoe/git/metasploit-framework/lib/rex/ui/text/irb_shell.rb:53:in `catch'
from /home/txjoe/git/metasploit-framework/lib/rex/ui/text/irb_shell.rb:53:in `run'
from /home/txjoe/git/metasploit-framework/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb:390:in `cmd_irb'
from /home/txjoe/git/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:430:in `run_command'
from /home/txjoe/git/metasploit-framework/lib/rex/post/meterpreter/ui/console.rb:105:in `run_command'
I opened the file metasploit-framework/lib/rex/ui/text/irb_shell.rb at line 54, which supposedly causes the error:
catch(:IRB_EXIT) do
irb.eval_input
end
Given that it was a name error, I added the statements
catch(:IRB_EXIT) do
begin
irb.eval_input
rescue NameError => e
puts(e)
end
end
but I still get the error. Am I using the rescue at a wrong place?
This happens while using the irb shell from a meterpreter session in metasploit.

Error handling for Ruby Kernel method not working

I have found myself in need to execute a string. Current method is to use the Kernel#eval() method. Everything is working fine, but my error handling isn't working. For example, a missing closing quotation mark will completely kill and exit the program.
Here's an excerpt. Any idea why I can't catch the error?
def process(str)
print "\n"
eval(str)
rescue => e
puts e
end
>> process('"')
console.rb:90:in `eval': (eval):1: unterminated string meets end of file (SyntaxError)
from console.rb:90:in `process'
from console.rb:81:in `bouncer'
from console.rb:14:in `block in prompt'
from console.rb:11:in `loop'
from console.rb:11:in `prompt'
from console.rb:97:in `<main>'
According to the documentation:
A rescue clause without an explicit Exception class will rescue all StandardErrors (and only those).
SyntaxError is not a StandardError. To catch it, you have to be explicit, e.g.:
def process(str)
print "\n"
eval(str)
rescue Exception => e
puts e
end
process('"')
Output:
(eval):1: unterminated string meets end of file

Ruby script raising unexpected backtrace

I have a method that should raise a custom error with a message. When I catch the error and raise my own custom error, it is still raising and printing the backtrace of the original error. I just want the custom error and message. Code below.
Method:
def load(configs)
begin
opts = {access_token: configs['token'],
api_endpoint: configs['endpoint'],
web_endpoint: configs['site'],
auto_paginate: configs['pagination']}
client = Octokit::Client.new(opts)
repos = client.org_repos(configs['org'])
repos.each do |r|
Project.create(name: r.name)
end
rescue Octokit::Unauthorized
raise GitConfigError, "boom"
end
#rescue Octokit::Unauthorized
end
class GitConfigError < StandardError
end
My test (which is failling):
context 'with incorrect git configs' do
before do
allow(loader).to receive(:load).and_raise Octokit::Unauthorized
end
it { expect{loader.load(configs)}.to raise_error(GitConfigError, "boom" ) }
end
Test Output:
GitProjectLoader#load with incorrect git configs should raise GitConfigError with "boom"
Failure/Error: it { expect{loader.load(configs)}.to raise_error(GitConfigError, "boom" ) }
expected GitConfigError with "boom", got #<Octokit::Unauthorized: Octokit::Unauthorized> with backtrace:
# ./spec/lib/git_project_loader_spec.rb:24:in `block (5 levels) in <top (required)>'
# ./spec/lib/git_project_loader_spec.rb:24:in `block (4 levels) in <top (required)>'
# ./spec/lib/git_project_loader_spec.rb:24:in `block (4 levels) in <top (required)>'
If you intend to test the handling of the Octokit::Unauthorized error, then raise the error anywhere before the rescue kicks in. Preferably, someplace where it would actually be raised.
Something like this, for example:
before do
allow(Octokit::Client).to receive(:new).and_raise(Octokit::Unauthorized)
end
And then:
expect{ loader.load(configs) }.to raise_error(GitConfigError, "boom" )
As a side note, I would discourage enclosing all lines of your method in a begin;rescue;end structure; you should enclose only the lines from which you are expecting errors.
You are not testing your code as you think. You have mocked it out.
The line
allow(loader).to receive(:load).and_raise Octokit::Unauthorized
replaces the load method on loader with a stub which just raises the named error.
Remove your before block, and it should test your code as intended. Note as written it will make a real request via Octokit, unless you mock that out instead.

Overwriting 'require' leads to "no such file to load

I deleted my original post, because I can now simplify my question with a very short program:
## a.rb
require 'tmpdir'
Executing a.rb is fine, no error.
But if I overwrite require as:
## b.rb
module Kernel
alias_method :original_require, :require
def require name
i = 1
begin
original_require name
rescue LoadError => e
puts "Failure #{i}: #{e}"
i = i + 1
retry if i < 3
end
end
end
require 'tmpdir'
Executing b.rb will get the following error:
Failure 1: no such file to load -- Win32API
Failure 2: no such file to load -- Win32API
/Users/chaol/.rvm/rubies/ruby-1.9.1-p376/lib/ruby/1.9.1/tmpdir.rb:19:in `<class:Dir>': uninitialized constant Dir::Win32API (NameError)
from /Users/chaol/.rvm/rubies/ruby-1.9.1-p376/lib/ruby/1.9.1/tmpdir.rb:9:in `<top (required)>'
from /Users/chaol/Documents/ruby-workspace/Test/root/aaa.rb:9:in `require'
from /Users/chaol/Documents/ruby-workspace/Test/root/aaa.rb:9:in `require'
from /Users/chaol/Documents/ruby-workspace/Test/root/aaa.rb:20:in `<main>'

Resources