Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge JRuby's ruby-debug-base

Conflicts:
	.gitignore
	AUTHORS
	ChangeLog
	README
	Rakefile
	lib/ruby-debug-base.rb
	svn2cl_usermap
  • Loading branch information...
commit 5883da3e2f5811fb6b11f3ec31119c9583cd7750 2 parents 1e9d7f0 + 344b3be
@jfirebaugh jfirebaugh authored
View
2  .gitignore
@@ -16,3 +16,5 @@ pkg
ruby-debug-*.tar.gz
ext/*.o
ext/ruby_debug.bundle
+tmp/*
+ruby_debug.jar
View
5 AUTHORS
@@ -1,7 +1,12 @@
Author and maintainer:
Kent Sibilev
+JRuby Extension Authors:
+Martin Krauskopf
+Peter Brant
+
Contributers:
Markus Barchfeld
R. Bernstein
Anders Lindgren
+Chris Nelson
View
6,064 ChangeLog
0 additions, 6,064 deletions not shown
View
2  INSTALL.SVN
@@ -63,7 +63,7 @@ little more detail as to what happens under the covers when you do the
gem install.
Run (from trunk)
- rake lib
+ rake compile
This creates a Makefile and builds the ruby-debug shared library. (On
Unix the name is ruby_debug.so).
View
21 MIT-LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2007-2008, debug-commons team
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
View
51 README
@@ -112,3 +112,54 @@ your program can be minimized.
== License
See LICENSE for license information.
+
+
+
+= ruby-debug-base for JRuby
+
+== Overview
+
+(j)ruby-debug-base provides the fast debugger extension for JRuby interpreter.
+It is the same as ruby-debug-base native C extension from ruby-debug project
+(http://rubyforge.org/projects/ruby-debug/), but for JRuby.
+
+== Install
+
+(j)ruby-debug-base is available as a RubyGem:
+
+ jruby -S gem install ruby-debug-base
+
+== Installation of trunk version
+
+Handy for developers or users living on the cutting-edge.
+
+$ cd ~/tmp
+$ svn co svn://rubyforge.org/var/svn/debug-commons/jruby-debug/trunk jruby-debug
+$ cd jruby-debug
+$ rake gem
+$ jruby -S gem install -l pkg/ruby-debug-base-0.10.3-java.gem
+$ jruby -S gem install columnize
+$ jruby -S gem install -r ruby-debug -v 0.10.3 --ignore-dependencies
+
+== Usage
+
+The usage is then the same as with native ruby-debugger, but you might need to
+force JRuby which has to run in interpreted mode. Simplest usage is:
+
+ $ jruby --debug -S rdebug <your-script>
+
+Or easier, you might create 'jruby-dm' ('dm' for 'debugger-mode'):
+
+ $ cat ~/bin/jruby-dm
+ #!/bin/bash
+ jruby --debug "$@"
+
+Then you may run just as you used to:
+
+ $ jruby-dm -S rdebug <your-script>
+
+For more information see: http://bashdb.sourceforge.net/ruby-debug.html
+
+== License
+
+See MIT-LICENSE for license information.
View
20 README-DEV
@@ -0,0 +1,20 @@
+== How to setup and run tests
+
+0) checkout the sources (see README, section "Installation of trunk version")
+1) apply patch by running:
+
+ $ rake prepare_tests'
+
+
+You are ready to run all tests, e.g.:
+
+ $ jruby test/test-help.rb
+ $ jruby --debug -S rake test_stable
+
+
+We need to tweak MRI ruby-debug test suite since the debuggers differs in some
+cases:
+
+ - JRuby does not support invalid breakpoint places
+ - MRI stops at some expressions, like 'if', twice, JRuby once
+
View
71 Rakefile
@@ -1,9 +1,10 @@
#!/usr/bin/env rake
# -*- Ruby -*-
require 'rubygems'
-require 'rake/gempackagetask'
-require 'rake/rdoctask'
+require 'rubygems/package_task'
+require 'rdoc/task'
require 'rake/testtask'
+require 'rake/javaextensiontask'
SO_NAME = "ruby_debug.so"
ROOT_DIR = File.dirname(__FILE__)
@@ -73,7 +74,7 @@ BASE_FILES = COMMON_FILES + FileList[
'ext/ruby_debug.c',
'ext/ruby_debug.h',
'ext/win32/*',
- 'lib/**/*',
+ 'lib/ruby-debug-base.rb',
BASE_TEST_FILE_LIST,
]
@@ -90,7 +91,7 @@ task test_and_args do
end
desc "Test ruby-debug-base."
-task :test_base => :lib do
+task :test_base => :compile do
Rake::TestTask.new(:test_base) do |t|
t.libs += ['./ext', './lib']
t.test_files = FileList[BASE_TEST_FILE_LIST]
@@ -102,7 +103,7 @@ desc "Test everything - same as test."
task :check => :test
desc "Create the core ruby-debug shared library extension"
-task :lib do
+task :compile do
Dir.chdir("ext") do
system("#{Gem.ruby} extconf.rb && make")
end
@@ -121,6 +122,7 @@ task :ChangeLog do
system('svn2cl --authors=svn2cl_usermap http://ruby-debug.rubyforge.org/svn/trunk')
system("svn2cl --authors=svn2cl_usermap http://ruby-debug.rubyforge.org/svn/trunk/ext -o ext/ChangeLog")
system("svn2cl --authors=svn2cl_usermap http://ruby-debug.rubyforge.org/svn/trunk/lib -o lib/ChangeLog")
+ system("svn2cl --authors=svn2cl_usermap svn://rubyforge.org/var/svn/debug-commons/jruby-debug/trunk")
end
# Base GEM Specification
@@ -191,10 +193,10 @@ EOF
end
# Rake task to build the default package
-Rake::GemPackageTask.new(base_spec) do |pkg|
+Gem::PackageTask.new(base_spec) do |pkg|
pkg.need_tar = true
end
-Rake::GemPackageTask.new(cli_spec) do |pkg|
+Gem::PackageTask.new(cli_spec) do |pkg|
pkg.need_tar = true
end
@@ -245,11 +247,12 @@ task :clean do
derived_files = Dir.glob(".o") + Dir.glob("*.so")
rm derived_files unless derived_files.empty?
end
+ rm 'lib/ruby_debug.jar' if File.exists?("lib/ruby_debug.jar")
end
# --------- RDoc Documentation ------
desc "Generate rdoc documentation"
-Rake::RDocTask.new("rdoc") do |rdoc|
+RDoc::Task.new("rdoc") do |rdoc|
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "ruby-debug"
# Show source inline with line numbers
@@ -310,3 +313,55 @@ end
task :make_version_file do
make_version_file
end
+
+namespace :jruby do
+ desc "Helps to setup the project to be able to run tests"
+ task :prepare_tests do
+ File.open('test/config.private.yaml', 'w') do |f|
+ f.write <<-EOF
+# either should be on the $PATH or use full path
+ruby: jruby
+
+# possibility to specify interpreter parameters
+ruby_params: --debug
+ EOF
+ end
+
+ # - prepare default customized test/config.private.yaml suitable for JRuby
+ # - tweak test suite to be able to pass for jruby-debug-base which does not
+ # support e.g. TraceLineNumbers yet.
+ sh "patch -p0 < patch-#{ruby_debug_version}.diff"
+ end
+
+ jruby_spec = Gem::Specification.new do |s|
+ s.platform = "java"
+ s.summary = "Java implementation of Fast Ruby Debugger"
+ s.name = 'ruby-debug-base'
+ s.version = ruby_debug_version
+ s.require_path = 'lib'
+ s.files = ['AUTHORS',
+ 'ChangeLog',
+ 'lib/linecache.rb',
+ 'lib/linecache-ruby.rb',
+ 'lib/ruby-debug-base.rb',
+ 'lib/ruby_debug.jar',
+ 'lib/tracelines.rb',
+ 'MIT-LICENSE',
+ 'Rakefile',
+ 'README']
+ s.description = <<-EOF
+Java extension to make fast ruby debugger run on JRuby.
+It is the same what ruby-debug-base is for native Ruby.
+ EOF
+ s.author = 'debug-commons team'
+ s.homepage = 'http://rubyforge.org/projects/debug-commons/'
+ s.has_rdoc = true
+ s.rubyforge_project = 'debug-commons'
+ end
+
+ Gem::PackageTask.new(jruby_spec) {}
+
+ Rake::JavaExtensionTask.new('ruby_debug') do |t|
+ t.ext_dir = "src"
+ end
+end
View
12 lib/linecache-ruby.rb
@@ -0,0 +1,12 @@
+# Provides alternative to native TraceLineNumbers.lnums_for_str implemented
+# currently only for C Ruby in linecache gem, ext/trace_nums.c
+
+module TraceLineNumbers
+
+ # Trivial implementation allowing to stop on every line.
+ def lnums_for_str(code)
+ (1..code.entries.size).to_a
+ end
+ module_function :lnums_for_str
+
+end
View
408 lib/linecache.rb
@@ -0,0 +1,408 @@
+#!/usr/bin/env ruby
+
+# Temporary solution. Copy-pasted from rocky-hacks/linecache project.
+# Will/should be solved generally in the future.
+
+# Copyright (C) 2007, 2008 Rocky Bernstein <rockyb@rubyforge.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA.
+#
+
+# Author:: Rocky Bernstein (mailto:rockyb@rubyforge.net)
+#
+# = linecache
+# Module to read and cache lines of a file
+# == Version
+# :include:VERSION
+
+# == SYNOPSIS
+#
+# The LineCache module allows one to get any line from any file,
+# caching lines of the file on first access to the file. The may be is
+# useful when a small random sets of lines are read from a single
+# file, in particular in a debugger to show source lines.
+#
+# require 'linecache'
+# lines = LineCache::getlines('/tmp/myruby.rb')
+# # The following lines have same effect as the above.
+# $: << '/tmp'
+# Dir.chdir('/tmp') {lines = LineCache::getlines('myruby.rb')
+#
+# line = LineCache::getline('/tmp/myruby.rb', 6)
+# # Note lines[6] == line (if /tmp/myruby.rb has 6 lines)
+#
+# LineCache::clear_file_cache
+# LineCache::clear_file_cache('/tmp/myruby.rb')
+# LineCache::update_cache # Check for modifications of all cached files.
+#
+# Some parts of the interface is derived from the Python module of the
+# same name.
+#
+
+# Defining SCRIPT_LINES__ causes Ruby to cache the lines of files
+# it reads. The key the setting of __FILE__ at the time when Ruby does
+# its read. LineCache keeps a separate copy of the lines elsewhere
+# and never destroys SCRIPT_LINES__
+SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
+
+require 'linecache-ruby'
+
+require 'digest/sha1'
+require 'set'
+
+begin require 'rubygems' rescue LoadError end
+require 'tracelines'
+# require 'ruby-debug' ; Debugger.start
+
+# = module LineCache
+# Module caching lines of a file
+module LineCache
+ LineCacheInfo = Struct.new(:stat, :line_numbers, :lines, :path, :sha1) unless
+ defined?(LineCacheInfo)
+
+ # The file cache. The key is a name as would be given by Ruby for
+ # __FILE__. The value is a LineCacheInfo object.
+ @@file_cache = {}
+
+ # Maps a string filename (a String) to a key in @@file_cache (a
+ # String).
+ #
+ # One important use of @@file2file_remap is mapping the a full path
+ # of a file into the name stored in @@file_cache or given by Ruby's
+ # __FILE__. Applications such as those that get input from users,
+ # may want canonicalize a file name before looking it up. This map
+ # gives a way to do that.
+ #
+ # Another related use is when a template system is used. Here we'll
+ # probably want to remap not only the file name but also line
+ # ranges. Will probably use this for that, but I'm not sure.
+ @@file2file_remap = {}
+ @@file2file_remap_lines = {}
+
+ # Clear the file cache entirely.
+ def clear_file_cache()
+ @@file_cache = {}
+ @@file2file_remap = {}
+ @@file2file_remap_lines = {}
+ end
+ module_function :clear_file_cache
+
+ # Return an array of cached file names
+ def cached_files()
+ @@file_cache.keys
+ end
+ module_function :cached_files
+
+ # Discard cache entries that are out of date. If +filename+ is +nil+
+ # all entries in the file cache +@@file_cache+ are checked.
+ # If we don't have stat information about a file which can happen
+ # if the file was read from __SCRIPT_LINES but no corresponding file
+ # is found, it will be kept. Return a list of invalidated filenames.
+ # nil is returned if a filename was given but not found cached.
+ def checkcache(filename=nil, use_script_lines=false)
+
+ if !filename
+ filenames = @@file_cache.keys()
+ elsif @@file_cache.member?(filename)
+ filenames = [filename]
+ else
+ return nil
+ end
+
+ result = []
+ for filename in filenames
+ next unless @@file_cache.member?(filename)
+ path = @@file_cache[filename].path
+ if File.exist?(path)
+ cache_info = @@file_cache[filename]
+ stat = File.stat(path)
+ if stat &&
+ (cache_info.size != stat.size or cache_info.mtime != stat.mtime)
+ result << filename
+ update_cache(filename, use_script_lines)
+ end
+ end
+ end
+ return result
+ end
+ module_function :checkcache
+
+ # Cache filename if it's not already cached.
+ # Return the expanded filename for it in the cache
+ # or nil if we can't find the file.
+ def cache(filename, reload_on_change=false)
+ if @@file_cache.member?(filename)
+ checkcache(filename) if reload_on_change
+ else
+ update_cache(filename, true)
+ end
+ if @@file_cache.member?(filename)
+ @@file_cache[filename].path
+ else
+ nil
+ end
+ end
+ module_function :cache
+
+ # Return true if filename is cached
+ def cached?(filename)
+ @@file_cache.member?(unmap_file(filename))
+ end
+ module_function :cached?
+
+ def cached_script?(filename)
+ SCRIPT_LINES__.member?(unmap_file(filename))
+ end
+ module_function :cached_script?
+
+ def empty?(filename)
+ filename=unmap_file(filename)
+ @@file_cache[filename].lines.empty?
+ end
+ module_function :empty?
+
+ # Get line +line_number+ from file named +filename+. Return nil if
+ # there was a problem. If a file named filename is not found, the
+ # function will look for it in the $: path array.
+ #
+ # Examples:
+ #
+ # lines = LineCache::getline('/tmp/myfile.rb)
+ # # Same as above
+ # $: << '/tmp'
+ # lines = Dir.chdir('/tmp') do
+ # lines = LineCache::getlines ('myfile.rb')
+ # end
+ #
+ def getline(filename, line_number, reload_on_change=true)
+ filename = unmap_file(filename)
+ filename, line_number = unmap_file_line(filename, line_number)
+ lines = getlines(filename, reload_on_change)
+ if lines and (1..lines.size) === line_number
+ return lines[line_number-1]
+ else
+ return nil
+ end
+ end
+ module_function :getline
+
+ # Read lines of +filename+ and cache the results. However +filename+ was
+ # previously cached use the results from the cache. Return nil
+ # if we can't get lines
+ def getlines(filename, reload_on_change=false)
+ filename = unmap_file(filename)
+ checkcache(filename) if reload_on_change
+ if @@file_cache.member?(filename)
+ return @@file_cache[filename].lines
+ else
+ update_cache(filename, true)
+ return @@file_cache[filename].lines if @@file_cache.member?(filename)
+ end
+ end
+ module_function :getlines
+
+ # Return full filename path for filename
+ def path(filename)
+ filename = unmap_file(filename)
+ return nil unless @@file_cache.member?(filename)
+ @@file_cache[filename].path
+ end
+ module_function :path
+
+ def remap_file(from_file, to_file)
+ @@file2file_remap[to_file] = from_file
+ end
+ module_function :remap_file
+
+ def remap_file_lines(from_file, to_file, range, start)
+ range = (range..range) if range.is_a?(Fixnum)
+ to_file = from_file unless to_file
+ if @@file2file_remap_lines[to_file]
+ # FIXME: need to check for overwriting ranges: whether
+ # they intersect or one encompasses another.
+ @@file2file_remap_lines[to_file] << [from_file, range, start]
+ else
+ @@file2file_remap_lines[to_file] = [[from_file, range, start]]
+ end
+ end
+ module_function :remap_file_lines
+
+ # Return SHA1 of filename.
+ def sha1(filename)
+ filename = unmap_file(filename)
+ return nil unless @@file_cache.member?(filename)
+ return @@file_cache[filename].sha1.hexdigest if
+ @@file_cache[filename].sha1
+ sha1 = Digest::SHA1.new
+ @@file_cache[filename].lines.each do |line|
+ sha1 << line
+ end
+ @@file_cache[filename].sha1 = sha1
+ sha1.hexdigest
+ end
+ module_function :sha1
+
+ # Return the number of lines in filename
+ def size(filename)
+ filename = unmap_file(filename)
+ return nil unless @@file_cache.member?(filename)
+ @@file_cache[filename].lines.length
+ end
+ module_function :size
+
+ # Return File.stat in the cache for filename.
+ def stat(filename)
+ return nil unless @@file_cache.member?(filename)
+ @@file_cache[filename].stat
+ end
+ module_function :stat
+
+ # Return an Array of breakpoints in filename.
+ # The list will contain an entry for each distinct line event call
+ # so it is possible (and possibly useful) for a line number appear more
+ # than once.
+ def trace_line_numbers(filename, reload_on_change=false)
+ fullname = cache(filename, reload_on_change)
+ return nil unless fullname
+ e = @@file_cache[filename]
+ unless e.line_numbers
+ e.line_numbers =
+ TraceLineNumbers.lnums_for_str_array(e.lines)
+ e.line_numbers = false unless e.line_numbers
+ end
+ e.line_numbers
+ end
+ module_function :trace_line_numbers
+
+ def unmap_file(file)
+ @@file2file_remap[file] ? @@file2file_remap[file] : file
+ end
+ module_function :unmap_file
+
+ def unmap_file_line(file, line)
+ if @@file2file_remap_lines[file]
+ @@file2file_remap_lines[file].each do |from_file, range, start|
+ if range === line
+ from_file = from_file || file
+ return [from_file, start+line-range.begin]
+ end
+ end
+ end
+ return [file, line]
+ end
+ module_function :unmap_file_line
+
+ # Update a cache entry. If something's
+ # wrong, return nil. Return true if the cache was updated and false
+ # if not. If use_script_lines is true, use that as the source for the
+ # lines of the file
+ def update_cache(filename, use_script_lines=false)
+
+ return nil unless filename
+
+ @@file_cache.delete(filename)
+ path = File.expand_path(filename)
+
+ if use_script_lines
+ list = [filename]
+ list << @@file2file_remap[path] if @@file2file_remap[path]
+ list.each do |name|
+ if !SCRIPT_LINES__[name].nil? && SCRIPT_LINES__[name] != true
+ begin
+ stat = File.stat(name)
+ rescue
+ stat = nil
+ end
+ lines = SCRIPT_LINES__[name]
+
+ # Temporary workaround for bug in the JRuby: http://jira.codehaus.org/browse/JRUBY-2442
+ # Chop last two redundant empty lines
+ lines = lines[0..-3] if (lines.length >= 2 && lines.last(2).join == '')
+
+ @@file_cache[filename] = LineCacheInfo.new(stat, nil, lines, path, nil)
+ @@file2file_remap[path] = filename
+ return true
+ end
+ end
+ end
+
+ if File.exist?(path)
+ stat = File.stat(path)
+ elsif File.basename(filename) == filename
+ # try looking through the search path.
+ stat = nil
+ for dirname in $:
+ path = File.join(dirname, filename)
+ if File.exist?(path)
+ stat = File.stat(path)
+ break
+ end
+ end
+ return false unless stat
+ end
+ begin
+ fp = File.open(path, 'r')
+ lines = fp.readlines()
+ fp.close()
+ rescue
+ ## print '*** cannot open', path, ':', msg
+ return nil
+ end
+ @@file_cache[filename] = LineCacheInfo.new(File.stat(path), nil, lines,
+ path, nil)
+ @@file2file_remap[path] = filename
+ return true
+ end
+
+ module_function :update_cache
+
+end
+
+# example usage
+if __FILE__ == $0
+ def yes_no(var)
+ return var ? "" : "not "
+ end
+
+ lines = LineCache::getlines(__FILE__)
+ puts "#{__FILE__} has #{LineCache.size(__FILE__)} lines"
+ line = LineCache::getline(__FILE__, 6)
+ puts "The 6th line is\n#{line}"
+ line = LineCache::remap_file(__FILE__, 'another_name')
+ puts LineCache::getline('another_name', 7)
+
+ puts("Files cached: #{LineCache::cached_files.inspect}")
+ LineCache::update_cache(__FILE__)
+ LineCache::checkcache(__FILE__)
+ puts "#{__FILE__} has #{LineCache::size(__FILE__)} lines"
+ puts "#{__FILE__} trace line numbers:\n" +
+ "#{LineCache::trace_line_numbers(__FILE__).to_a.sort.inspect}"
+ puts("#{__FILE__} is %scached." %
+ yes_no(LineCache::cached?(__FILE__)))
+ puts LineCache::stat(__FILE__).inspect
+ puts "Full path: #{LineCache::path(__FILE__)}"
+ LineCache::checkcache # Check all files in the cache
+ LineCache::clear_file_cache
+ puts("#{__FILE__} is now %scached." %
+ yes_no(LineCache::cached?(__FILE__)))
+ digest = SCRIPT_LINES__.select{|k,v| k =~ /digest.rb$/}
+ puts digest.first[0] if digest
+ line = LineCache::getline(__FILE__, 7)
+ puts "The 7th line is\n#{line}"
+ LineCache::remap_file_lines(__FILE__, 'test2', (10..20), 6)
+ puts LineCache::getline('test2', 10)
+ puts "Remapped 10th line of test2 is\n#{line}"
+end
View
45 lib/tracelines.rb
@@ -0,0 +1,45 @@
+#!/usr/bin/env ruby
+
+# Temporary solution. Copy-pasted from rocky-hacks/linecache project.
+# Will/should be solved generally in the future.
+
+begin require 'rubygems' rescue LoadError end
+# require 'ruby-debug' ; Debugger.start
+
+module TraceLineNumbers
+ @@SRC_DIR = File.expand_path(File.dirname(__FILE__))
+# require File.join(@@SRC_DIR, '..', 'ext', 'trace_nums')
+
+ # Return an array of lines numbers that could be
+ # stopped at given a file name of a Ruby program.
+ def lnums_for_file(file)
+ lnums_for_str(File.read(file))
+ end
+ module_function :lnums_for_file
+
+ # Return an array of lines numbers that could be
+ # stopped at given a file name of a Ruby program.
+ # We assume the each line has \n at the end. If not
+ # set the newline parameters to \n.
+ def lnums_for_str_array(string_array, newline='')
+ lnums_for_str(string_array.join(newline))
+ end
+ module_function :lnums_for_str_array
+end
+
+if __FILE__ == $0
+ SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
+ # test_file = '../test/rcov-bug.rb'
+ test_file = '../test/lnum-data/begin1.rb'
+ if File.exists?(test_file)
+ puts TraceLineNumbers.lnums_for_file(test_file).inspect
+ load(test_file, 0) # for later
+ end
+ puts TraceLineNumbers.lnums_for_file(__FILE__).inspect
+ unless SCRIPT_LINES__.empty?
+ key = SCRIPT_LINES__.keys.first
+ puts key
+ puts SCRIPT_LINES__[key]
+ puts TraceLineNumbers.lnums_for_str_array(SCRIPT_LINES__[key]).inspect
+ end
+end
View
297 patch-0.10.3.diff
@@ -0,0 +1,297 @@
+
+Property changes on: test
+___________________________________________________________________
+Name: svn:ignore
+ - config.private.yaml
+
+ + config.private.yaml
+.rdebug_hist
+
+
+Index: test/data/breakpoints.right
+===================================================================
+--- test/data/breakpoints.right (revision 859)
++++ test/data/breakpoints.right (working copy)
+@@ -16,7 +16,7 @@
+ # break 10
+ Breakpoint 2 file ./gcd.rb, line 10
+ # break 11
+-*** Line 11 is not a stopping point in file "gcd.rb".
++Breakpoint 3 file ./gcd.rb, line 11
+ # continue
+ Breakpoint 1 at gcd.rb:6
+ gcd.rb:6
+@@ -25,13 +25,14 @@
+ --> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:6
+ #1 at line gcd.rb:18
+ # break Object.gcd
+-Breakpoint 3 at Object::gcd
++Breakpoint 4 at Object::gcd
+ # info break
+ Num Enb What
+ 1 y at gcd.rb:6
+ breakpoint already hit 1 time
+ 2 y at gcd.rb:10
+- 3 y at Object:gcd
++ 3 y at gcd.rb:11
++ 4 y at Object:gcd
+ # continue
+ Breakpoint 2 at gcd.rb:10
+ gcd.rb:10
+@@ -83,7 +84,7 @@
+ # disable
+ *** "disable" must be followed "display", "breakpoints" or breakpoint numbers.
+ # # We should be able to delete 2
+-# delete 2 3
++# delete 2 3 4
+ # info break
+ No breakpoints.
+ # # Should get a message about having no breakpoints.
+Index: test/data/frame.right
+===================================================================
+--- test/data/frame.right (revision 859)
++++ test/data/frame.right (working copy)
+@@ -7,10 +7,6 @@
+ Currently testing the debugger is on.
+ # set callstyle last
+ Frame call-display style is last.
+-# # Invalid line number in continue command
+-# continue 3
+-*** Line 3 is not a stopping point in file "gcd.rb".
+-# # This time, for sure!
+ # continue 6
+ gcd.rb:6
+ if a > b
+Index: test/data/stepping.cmd
+===================================================================
+--- test/data/stepping.cmd (revision 859)
++++ test/data/stepping.cmd (working copy)
+@@ -7,13 +7,13 @@
+ where
+ step a
+ set forcestep on
+-step- ; step-
++step-
+ set forcestep off
+ where
+-n 2
++n
+ step+
+ where
+-step 3
++step 2
+ step+
+ where
+ next+
+Index: test/data/frame.cmd
+===================================================================
+--- test/data/frame.cmd (revision 859)
++++ test/data/frame.cmd (working copy)
+@@ -3,9 +3,6 @@
+ # ***************************************************
+ set debuggertesting on
+ set callstyle last
+-# Invalid line number in continue command
+-continue 3
+-# This time, for sure!
+ continue 6
+ where
+ up
+Index: test/data/test-init.right
+===================================================================
+--- test/data/test-init.right (revision 859)
++++ test/data/test-init.right (working copy)
+@@ -1,5 +1,7 @@
+ gcd-dbg.rb:18
+ if a > b
+-(rdb:1) "./gcd-dbg.rb"
+-(rdb:1) Argument list to give program being debugged when it is started is "5".
+-(rdb:1)
+\ No newline at end of file
++(rdb:1) p Debugger::PROG_SCRIPT
++"./gcd-dbg.rb"
++(rdb:1) show args
++Argument list to give program being debugged when it is started is "5".
++(rdb:1) quit unconditionally
+Index: test/data/annotate.cmd
+===================================================================
+--- test/data/annotate.cmd (revision 859)
++++ test/data/annotate.cmd (working copy)
+@@ -25,5 +25,4 @@
+ # Test error annotations
+ up 10
+ frame 100
+-break bogus:5
+ quit
+Index: test/data/emacs_basic.cmd
+===================================================================
+--- test/data/emacs_basic.cmd (revision 859)
++++ test/data/emacs_basic.cmd (working copy)
+@@ -16,7 +16,6 @@
+ continue
+ where
+ info program
+-c 10
+ info break
+ break foo
+ info break
+Index: test/data/stepping.right
+===================================================================
+--- test/data/stepping.right (revision 859)
++++ test/data/stepping.right (working copy)
+@@ -16,7 +16,7 @@
+ Step argument 'a' needs to be a number.
+ # set forcestep on
+ force-stepping is on.
+-# step- ; step-
++# step-
+ gcd.rb:6
+ if a > b
+ # set forcestep off
+@@ -24,7 +24,7 @@
+ # where
+ --> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:6
+ #1 at line gcd.rb:18
+-# n 2
++# n
+ gcd.rb:10
+ return nil if a <= 0
+ # step+
+@@ -33,7 +33,7 @@
+ # where
+ --> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:12
+ #1 at line gcd.rb:18
+-# step 3
++# step 2
+ gcd.rb:6
+ if a > b
+ # step+
+Index: test/data/break_bad.right
+===================================================================
+--- test/data/break_bad.right (revision 859)
++++ test/data/break_bad.right (working copy)
+@@ -18,10 +18,10 @@
+ # # Line one isn't a valid stopping point.
+ # # It is a comment.
+ # break gcd.rb:1
+-*** Line 1 is not a stopping point in file "gcd.rb".
++Breakpoint 1 file gcd.rb, line 1
+ # # This line is okay
+ # break gcd.rb:4
+-Breakpoint 1 file gcd.rb, line 4
++Breakpoint 2 file gcd.rb, line 4
+ # # No class Foo.
+ # break Foo.bar
+ *** Unknown class Foo.
+Index: test/data/breakpoints.cmd
+===================================================================
+--- test/data/breakpoints.cmd (revision 859)
++++ test/data/breakpoints.cmd (working copy)
+@@ -30,7 +30,7 @@
+ disable bar
+ disable
+ # We should be able to delete 2
+-delete 2 3
++delete 2 3 4
+ info break
+ # Should get a message about having no breakpoints.
+ disable 1
+Index: test/data/annotate.right
+===================================================================
+--- test/data/annotate.right (revision 859)
++++ test/data/annotate.right (working copy)
+@@ -83,7 +83,7 @@
+ display
+ 
+ stack
+---> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:6
++--> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:10
+ #1 at line gcd.rb:18
+ 
+ variables
+@@ -91,8 +91,8 @@
+ b = 5
+ self = main
+ 
+-source gcd.rb:6
+-if a > b
++source gcd.rb:10
++return nil if a <= 0
+ # # Test error annotations
+ # up 10
+ error-begin
+@@ -101,7 +101,7 @@
+ display
+ 
+ stack
+---> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:6
++--> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:10
+ #1 at line gcd.rb:18
+ 
+ variables
+@@ -116,7 +116,7 @@
+ display
+ 
+ stack
+---> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:6
++--> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:10
+ #1 at line gcd.rb:18
+ 
+ variables
+@@ -124,14 +124,4 @@
+ b = 5
+ self = main
+ 
+-# break bogus:5
+-error-begin
+-No source file named bogus
+-
+-breakpoints
+-Num Enb What
+- 2 y at gcd.rb:12
+-
+-display
+-
+ # quit
+Index: test/data/emacs_basic.right
+===================================================================
+--- test/data/emacs_basic.right (revision 859)
++++ test/data/emacs_basic.right (working copy)
+@@ -41,9 +41,6 @@
+ #1 at line gcd.rb:18
+ # info program
+ Program stopped. It stopped at a breakpoint.
+-# c 10
+-gcd.rb:10
+-return nil if a <= 0
+ # info break
+ Num Enb What
+ 1 y at gcd.rb:6
+Index: test/data/info.right
+===================================================================
+--- test/data/info.right (revision 859)
++++ test/data/info.right (working copy)
+@@ -61,5 +61,5 @@
+ # info file ./gcd.rb break
+ File ./gcd.rb
+ breakpoint line numbers:
+-4 6 6 7 10 10 12 12 13 15 18
++1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
+ # quit
+Index: test/helper.rb
+===================================================================
+--- test/helper.rb (revision 859)
++++ test/helper.rb (working copy)
+@@ -42,7 +42,11 @@
+ end
+
+ def cheap_diff(got_lines, correct_lines)
+- puts got_lines if $DEBUG
++ if $DEBUG
++ got_lines.each_with_index do |line, i|
++ printf "%3d %s\n", i+1, line
++ end
++ end
+ correct_lines.each_with_index do |line, i|
+ correct_lines[i].chomp!
+ if got_lines[i] != correct_lines[i]
View
35 rakelib/test.rake
@@ -0,0 +1,35 @@
+ALL_TEST_FILES = FileList['test/test*.rb']
+
+# TODO: describe why below are excluded
+UNSTABLE_TEST_FILES = %w(
+ test/test-finish.rb
+ test/test-pm.rb
+ test/test-trace.rb
+)
+
+# Does not pass due to:
+# [#JRUBY-2816] Kernel#system call from within debugger session freezes JRuby
+# - http://jira.codehaus.org/browse/JRUBY-2816
+UNSTABLE_TEST_FILES << 'test/test-edit.rb'
+
+# Does not pass, because exception is written to stderr instead of stdout as in
+# MRI. Investigate.
+UNSTABLE_TEST_FILES << 'test/test-raise.rb'
+
+# Depends on:
+# [#JRUBY-2456] Broken tracing for Kernel.load
+# - http://jira.codehaus.org/browse/JRUBY-2456
+UNSTABLE_TEST_FILES << 'test/test-dollar-0.rb'
+
+STABLE_TEST_FILES = ALL_TEST_FILES - UNSTABLE_TEST_FILES
+
+desc "Test passing with jruby-debug-base."
+task :test_stable => :test_base do
+ Rake::TestTask.new(:test_stable) do |t|
+ t.libs << './ext'
+ t.libs << './lib'
+ t.libs << './cli'
+ t.test_files = STABLE_TEST_FILES
+ t.verbose = true
+ end
+end
View
36 src/RubyDebugService.java
@@ -0,0 +1,36 @@
+/*
+ * header & license
+ * Copyright (c) 2007 Christopher Nelson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+import java.io.IOException;
+import org.jruby.Ruby;
+import org.jruby.debug.RubyDebugger;
+import org.jruby.runtime.load.BasicLibraryService;
+
+public final class RubyDebugService implements BasicLibraryService {
+
+ public boolean basicLoad(Ruby runtime) throws IOException {
+ RubyDebugger.createDebuggerModule(runtime);
+ return true;
+ }
+
+}
View
168 src/org/jruby/debug/Breakpoint.java
@@ -0,0 +1,168 @@
+/*
+ * header & license
+ * Copyright (c) 2007-2008 Martin Krauskopf
+ * Copyright (c) 2007 Peter Brant
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.jruby.debug;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyFixnum;
+import org.jruby.RubyNumeric;
+import org.jruby.RubyObject;
+import org.jruby.RubySymbol;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.builtin.IRubyObject;
+
+public class Breakpoint extends RubyObject {
+
+ private static final long serialVersionUID = 1L;
+
+ protected Breakpoint(Ruby runtime, RubyClass type) {
+ super(runtime, type);
+ }
+
+ DebugBreakpoint debuggerBreakpoint() {
+ return (DebugBreakpoint)dataGetStruct();
+ }
+
+ @JRubyMethod(name="enabled=", required=1)
+ public IRubyObject setEnabled(IRubyObject enabled, Block block) {
+ debuggerBreakpoint().setEnabled(enabled.isTrue());
+ return enabled;
+ }
+
+ @JRubyMethod(name="enabled?")
+ public IRubyObject isEnabled(Block block) {
+ return getRuntime().newBoolean(debuggerBreakpoint().isEnabled());
+ }
+
+ @JRubyMethod(name="id")
+ public RubyFixnum id(Block block) {
+ return getRuntime().newFixnum(debuggerBreakpoint().getId());
+ }
+
+ @JRubyMethod(name="source")
+ public IRubyObject source(Block block) {
+ return debuggerBreakpoint().getSource();
+ }
+
+ @JRubyMethod(name="source=", required=1)
+ public IRubyObject source_set(IRubyObject source, Block block) {
+ debuggerBreakpoint().setSource(source.convertToString());
+
+ return source;
+ }
+
+ @JRubyMethod(name="pos")
+ public IRubyObject pos(Block block) {
+ DebugBreakpoint debugBreakpoint = debuggerBreakpoint();
+ if (debugBreakpoint.getType() == DebugBreakpoint.Type.METHOD) {
+ return getRuntime().newString(debuggerBreakpoint().getPos().getMethodName());
+ } else {
+ return getRuntime().newFixnum(debuggerBreakpoint().getPos().getLine());
+ }
+ }
+
+ @JRubyMethod(name="pos=", required=1)
+ public IRubyObject pos_set(IRubyObject pos, Block block) {
+ DebugBreakpoint debugBreakpoint = debuggerBreakpoint();
+ if (debugBreakpoint.getType() == DebugBreakpoint.Type.METHOD) {
+ debugBreakpoint.getPos().setMethodName(pos.convertToString().toString());
+ } else {
+ debugBreakpoint.getPos().setLine(RubyNumeric.fix2int(pos));
+ }
+
+ return pos;
+ }
+
+ @JRubyMethod(name="expr")
+ public IRubyObject expr(Block block) {
+ return debuggerBreakpoint().getExpr();
+ }
+
+ @JRubyMethod(name="expr=", required=1)
+ public IRubyObject expr_set(IRubyObject expr, Block block) {
+ debuggerBreakpoint().setExpr(expr.isNil() ? expr : expr.convertToString());
+ return expr;
+ }
+
+ @JRubyMethod(name="hit_count")
+ public IRubyObject hit_count(Block block) {
+ return getRuntime().newFixnum(debuggerBreakpoint().getHitCount());
+ }
+
+ @JRubyMethod(name="hit_value")
+ public IRubyObject hit_value(Block block) {
+ return getRuntime().newFixnum(debuggerBreakpoint().getHitValue());
+ }
+
+ @JRubyMethod(name="hit_value=", required=1)
+ public IRubyObject hit_value_set(IRubyObject hit_value, Block block) {
+ debuggerBreakpoint().setHitValue(RubyNumeric.fix2int(hit_value));
+
+ return hit_value;
+ }
+
+ @JRubyMethod(name="hit_condition")
+ public IRubyObject hit_condition(Block block) {
+ DebugBreakpoint.HitCondition cond = debuggerBreakpoint().getHitCondition();
+ if (cond == null) {
+ return getRuntime().getNil();
+ } else {
+ switch (cond) {
+ case GE:
+ return getRuntime().newSymbol("greater_or_equal");
+ case EQ:
+ return getRuntime().newSymbol("equal");
+ case MOD:
+ return getRuntime().newSymbol("modulo");
+ case NONE:
+ default:
+ return getRuntime().getNil();
+ }
+ }
+ }
+
+ @JRubyMethod(name="hit_condition=", required=1)
+ public IRubyObject hit_condition_set(IRubyObject hit_condition, Block block) {
+ DebugBreakpoint debugBreakpoint = debuggerBreakpoint();
+
+ if (! getRuntime().getSymbol().isInstance(hit_condition)) {
+ throw getRuntime().newArgumentError("Invalid condition parameter");
+ }
+
+ String symbol = ((RubySymbol)hit_condition).asJavaString();
+ if (symbol.equals("greater_or_equal") || symbol.equals("ge")) {
+ debugBreakpoint.setHitCondition(DebugBreakpoint.HitCondition.GE);
+ } else if (symbol.equals("equal") || symbol.equals("eq")) {
+ debugBreakpoint.setHitCondition(DebugBreakpoint.HitCondition.EQ);
+ } else if (symbol.equals("modulo") || symbol.equals("mod")) {
+ debugBreakpoint.setHitCondition(DebugBreakpoint.HitCondition.MOD);
+ } else {
+ throw getRuntime().newArgumentError("Invalid condition parameter");
+ }
+
+ return hit_condition;
+ }
+}
View
426 src/org/jruby/debug/Context.java
@@ -0,0 +1,426 @@
+/*
+ * header & license
+ * Copyright (c) 2007-2008 Martin Krauskopf
+ * Copyright (c) 2007 Christopher Nelson
+ * Copyright (c) 2007 Peter Brant
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.jruby.debug;
+
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyClass;
+import org.jruby.RubyFixnum;
+import org.jruby.RubyHash;
+import org.jruby.RubyNumeric;
+import org.jruby.RubyObject;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.parser.StaticScope;
+import org.jruby.runtime.Arity;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.DynamicScope;
+import org.jruby.runtime.builtin.IRubyObject;
+
+public class Context extends RubyObject {
+ private static final long serialVersionUID = 1L;
+
+ private final Debugger debugger;
+
+ Context(Ruby runtime, RubyClass type, Debugger debugger) {
+ super(runtime, type);
+ this.debugger = debugger;
+ }
+
+ DebugContext debugContext() {
+ return (DebugContext) dataGetStruct();
+ }
+
+ @JRubyMethod(name={"stop_next=","step"}, required=1, optional=1)
+ public IRubyObject stop_next_set(IRubyObject[] args, Block block) {
+ Ruby rt = getRuntime();
+ checkStarted();
+ IRubyObject force;
+ if (Arity.checkArgumentCount(rt, args, 1, 2) == 2) {
+ force = args[1];
+ } else {
+ force = rt.getNil();
+ }
+ IRubyObject steps = args[0];
+
+ if (RubyFixnum.fix2int(steps) < 0) {
+ rt.newRuntimeError("Steps argument can't be negative.");
+ }
+
+ DebugContext debug_context = debugContext();
+ debug_context.setStopNext(RubyFixnum.fix2int(steps));
+ debug_context.setForceMove(!force.isNil() && force.isTrue());
+ return steps;
+ }
+
+ @JRubyMethod(name="step_over", required=1, optional=2)
+ public IRubyObject step_over(IRubyObject[] rawArgs, Block block) {
+ Ruby rt = getRuntime();
+ checkStarted();
+ DebugContext debugContext = debugContext();
+ if (debugContext.getStackSize() == 0) {
+ rt.newRuntimeError("No frames collected.");
+ }
+
+ IRubyObject[] args = Arity.scanArgs(rt, rawArgs, 1, 2);
+
+ IRubyObject lines = args[0];
+ IRubyObject frame = args[1];
+ IRubyObject force = args[2];
+
+ debugContext.setStopLine(RubyFixnum.fix2int(lines));
+ debugContext.setStepped(false);
+ if (frame.isNil()) {
+ debugContext.setDestFrame(debugContext.getStackSize());
+ } else {
+ int frameInt = checkFrameNumber(frame);
+ debugContext.setDestFrame(debugContext.getStackSize() - frameInt);
+ }
+ debugContext.setForceMove(force.isTrue());
+ return rt.getNil();
+ }
+
+ @JRubyMethod(name="stop_frame=", required=1)
+ public IRubyObject stop_frame_set(IRubyObject rFrameNo, Block block) {
+ checkStarted();
+ DebugContext debugContext = debugContext();
+ int frameNo = RubyNumeric.fix2int(rFrameNo);
+ if (frameNo < 0 || frameNo >= debugContext.getStackSize()) {
+ getRuntime().newRuntimeError("Stop frame is out of range.");
+ }
+ debugContext.setStopFrame(debugContext.getStackSize() - frameNo);
+
+ return rFrameNo;
+ }
+
+ @JRubyMethod(name="thread")
+ public IRubyObject thread(Block block) {
+ checkStarted();
+ return debugContext().getThread();
+ }
+
+ @JRubyMethod(name="thnum")
+ public IRubyObject thnum(Block block) {
+ return RubyFixnum.newFixnum(getRuntime(), debugContext().getThnum());
+ }
+
+ @JRubyMethod(name="stop_reason")
+ public IRubyObject stop_reason(Block block) {
+ Ruby rt = getRuntime();
+ checkStarted();
+ DebugContext debugContext = debugContext();
+
+ String symName;
+ switch(debugContext.getStopReason()) {
+ case STEP:
+ symName = "step";
+ break;
+ case BREAKPOINT:
+ symName = "breakpoint";
+ break;
+ case CATCHPOINT:
+ symName = "catchpoint";
+ break;
+ case NONE:
+ default:
+ symName = "none";
+ }
+
+ if (debugContext.isDead()) {
+ symName = "post-mortem";
+ }
+
+ return rt.newSymbol(symName);
+ }
+
+ @JRubyMethod(name="suspend")
+ public IRubyObject suspend(Block block) {
+ checkStarted();
+ DebugContext debugContext = debugContext();
+ if (debugContext.isSuspended()) {
+ throw getRuntime().newRuntimeError("Already suspended.");
+ }
+
+ suspend0();
+
+ return getRuntime().getNil();
+ }
+
+ protected void suspend0() {
+ DebugContext debugContext = debugContext();
+
+ String status = debugContext.getThread().status().toString();
+ if (status.equals("run") || status.equals("sleep")) {
+ synchronized (this) {
+ debugContext.setWasRunning(true);
+ debugContext.setSuspended(true);
+ }
+ }
+ }
+
+ @JRubyMethod(name="suspended?")
+ public IRubyObject suspended_p(Block block) {
+ checkStarted();
+ return getRuntime().newBoolean(debugContext().isSuspended());
+ }
+
+ @JRubyMethod(name="resume")
+ public IRubyObject resume(Block block) {
+ checkStarted();
+ DebugContext debugContext = debugContext();
+
+ if (!debugContext.isSuspended()) {
+ throw getRuntime().newRuntimeError("Thread is not suspended.");
+ }
+
+ resume0();
+ return getRuntime().getNil();
+ }
+
+ void resume0() {
+ DebugContext debugContext = debugContext();
+
+ synchronized (this) {
+ debugContext.setSuspended(false);
+ }
+
+ if (debugContext.isWasRunning()) {
+ debugContext.getThread().wakeup();
+ }
+ }
+
+ @JRubyMethod(name="tracing")
+ public IRubyObject tracing(Block block) {
+ checkStarted();
+ DebugContext debugContext = debugContext();
+ return getRuntime().newBoolean(debugContext.isTracing());
+ }
+
+ @JRubyMethod(name="tracing=", required=1)
+ public IRubyObject tracing_set(IRubyObject tracing, Block block) {
+ checkStarted();
+ DebugContext debugContext = debugContext();
+ debugContext.setTracing(tracing.isTrue());
+ return tracing;
+ }
+
+ @JRubyMethod(name="ignored?")
+ public IRubyObject ignored_p(Block block) {
+ checkStarted();
+ return getRuntime().newBoolean(debugContext().isIgnored());
+ }
+
+ @JRubyMethod(name="frame_args", required=1)
+ public IRubyObject frame_args(IRubyObject frameNo, Block block) {
+ checkStarted();
+ DebugFrame frame = getFrame(frameNo);
+ if (frame.isDead()) {
+ return frame.getInfo().getCopyArgs();
+ } else {
+ return contextCopyArgs(frame);
+ }
+ }
+
+ @JRubyMethod(name="frame_binding", required=1)
+ public IRubyObject frame_binding(IRubyObject frameNo, Block block) {
+ checkStarted();
+ return getFrame(frameNo).getBinding();
+ }
+
+ @JRubyMethod(name={"frame_id", "frame_method"}, required=1)
+ public IRubyObject frame_method(IRubyObject frameNo, Block block) {
+ checkStarted();
+ String methodName = getFrame(frameNo).getMethodName();
+ return methodName == null ? getRuntime().getNil() : getRuntime().newSymbol(methodName);
+ }
+
+ @JRubyMethod(name="frame_args_info", optional=1)
+ public IRubyObject frame_args_info(IRubyObject[] args, Block block) {
+ checkStarted();
+ return getFrame(args).getArgValues();
+ }
+
+ @JRubyMethod(name="frame_line", optional=1)
+ public IRubyObject frame_line(IRubyObject[] args, Block block) {
+ checkStarted();
+ return getRuntime().newFixnum(getFrame(args).getLine());
+ }
+
+ @JRubyMethod(name="frame_file", optional=1)
+ public IRubyObject frame_file(IRubyObject[] args, Block block) {
+ return getRuntime().newString(getFrame(args).getFile());
+ }
+
+ @JRubyMethod(name="frame_locals", required=1)
+ public IRubyObject frame_locals(IRubyObject frameNo, Block block) {
+ checkStarted();
+ DebugFrame frame = getFrame(frameNo);
+ if (frame.isDead()) {
+ return frame.getInfo().getCopyLocals();
+ } else {
+ return contextCopyLocals(frame);
+ }
+ }
+
+ @JRubyMethod(name="frame_self", required=1)
+ public IRubyObject frame_self(IRubyObject frameNo, Block block) {
+ checkStarted();
+ return getFrame(frameNo).getSelf();
+ }
+
+ @JRubyMethod(name="frame_class", optional=1)
+ public IRubyObject frame_class(IRubyObject[] args, Block block) {
+ checkStarted();
+ DebugFrame frame = getFrame(args);
+ if (frame.isDead()) {
+ return getRuntime().getNil();
+ }
+ // FIXME implement correctly
+ return frame.getInfo().getFrame().getKlazz();
+ }
+
+ @JRubyMethod(name="stack_size")
+ public IRubyObject stack_size(Block block) {
+ checkStarted();
+ return getRuntime().newFixnum(debugContext().getStackSize());
+ }
+
+ @JRubyMethod(name="dead?")
+ public IRubyObject dead_p(Block block) {
+ checkStarted();
+ return Util.toRBoolean(this, debugContext().isDead());
+ }
+
+ @JRubyMethod(name="breakpoint")
+ public IRubyObject breakpoint(Block block) {
+ checkStarted();
+
+ return debugContext().getBreakpoint();
+ }
+
+ /**
+ * <pre>
+ * call-seq:
+ * context.set_breakpoint(source, pos, condition = nil) -> breakpoint
+ * </pre>
+ * <p>
+ * Sets a context-specific temporary breakpoint, which can be used to implement
+ * 'Run to Cursor' debugger function. When this breakpoint is reached, it will be
+ * cleared out.
+ * </p>
+ * <p>
+ * <i>source</i> is a name of a file or a class.<br/>
+ * <i>pos</i> is a line number or a method name if <i>source</i> is a class name.<br/>
+ * <i>condition</i> is a string which is evaluated to <tt>true</tt> when this breakpoint is activated.<br/>
+ * </p>
+ */
+ @JRubyMethod(name="set_breakpoint", required=2, optional=1)
+ public IRubyObject set_breakpoint(IRubyObject[] args, Block block) {
+ checkStarted();
+
+ IRubyObject breakpoint = debugger.createBreakpointFromArgs(this, args, 0);
+ debugContext().setBreakpoint(breakpoint);
+
+ return breakpoint;
+ }
+
+ private IRubyObject getFrameNumber(final IRubyObject[] args) {
+ return args.length == 1 ? args[0] : getRuntime().newFixnum(0);
+ }
+
+ private DebugFrame getFrame(final IRubyObject[] args) {
+ return getFrame(getFrameNumber(args));
+ }
+
+ private DebugFrame getFrame(final IRubyObject frameNo) {
+ DebugContext debugContext = debugContext();
+ int frameNoInt = checkFrameNumber(frameNo);
+ return debugContext.getFrame(frameNoInt);
+ }
+
+ private int checkFrameNumber(IRubyObject rFrameNo) {
+ int frameNo = RubyFixnum.fix2int(rFrameNo);
+
+ if (frameNo < 0 || frameNo >= debugContext().getStackSize()) {
+ throw rFrameNo.getRuntime().newArgumentError(
+ String.format("Invalid frame number %d, stack (0...%d)",
+ frameNo, debugContext().getStackSize()));
+ }
+
+ return frameNo;
+ }
+
+ private void checkStarted() {
+ debugger.checkStarted(getRuntime());
+ }
+
+ /*
+ * call-seq:
+ * context.copy_args(frame) -> list of args
+ *
+ * Returns a array of argument names.
+ */
+ private IRubyObject contextCopyArgs(DebugFrame debugFrame) {
+ RubyArray result = getRuntime().newArray();
+ StaticScope scope = debugFrame.getInfo().getScope();
+
+ int count = scope.getRequiredArgs() + scope.getOptionalArgs();
+ if (scope.getRestArg() >= 0) {
+ count++;
+ }
+
+ String[] names = scope.getVariables();
+ for (int i = 0; i < count; i++) {
+ result.append(getRuntime().newString(names[i]));
+ }
+
+ return result;
+ }
+
+ private IRubyObject contextCopyLocals(final DebugFrame debugFrame) {
+ RubyHash locals = RubyHash.newHash(getRuntime());
+ DynamicScope scope = debugFrame.getInfo().getDynaVars();
+ if (scope != null) {
+ DynamicScope evalScope = scope.getEvalScope();
+ if (evalScope != null) {
+ scope = evalScope;
+ }
+ while (scope != null) {
+ String[] variableNames = scope.getStaticScope().getVariables();
+ if (variableNames != null) {
+ for (int i = 0; i < variableNames.length; i++) {
+ locals.op_aset(getRuntime().getCurrentContext(),
+ RubyString.newString(getRuntime(), variableNames[i]),
+ scope.getValues()[i]);
+ }
+ }
+ scope = scope.getNextCapturedScope();
+ }
+ }
+ return locals;
+ }
+
+}
View
151 src/org/jruby/debug/DebugBreakpoint.java
@@ -0,0 +1,151 @@
+/*
+ * header & license
+ * Copyright (c) 2007-2008 Martin Krauskopf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.jruby.debug;
+
+import org.jruby.runtime.builtin.IRubyObject;
+
+final class DebugBreakpoint {
+
+ enum Type {
+ POS, METHOD
+ }
+
+ enum HitCondition {
+ NONE, GE, EQ, MOD
+ }
+
+ private int id;
+ private Type type;
+ private boolean enabled;
+ private IRubyObject source;
+ private Pos pos;
+ private IRubyObject expr;
+ private int hitCount;
+ private int hitValue;
+ private HitCondition hitCondition;
+
+ DebugBreakpoint() {
+ this.enabled = true;
+ this.pos = new Pos();
+ }
+
+ boolean isEnabled() {
+ return enabled;
+ }
+
+ void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ IRubyObject getExpr() {
+ return expr;
+ }
+
+ void setExpr(IRubyObject expr) {
+ this.expr = expr;
+ }
+
+ HitCondition getHitCondition() {
+ return hitCondition;
+ }
+
+ void setHitCondition(HitCondition hitCondition) {
+ this.hitCondition = hitCondition;
+ }
+
+ int getHitCount() {
+ return hitCount;
+ }
+
+ void setHitCount(int hitCount) {
+ this.hitCount = hitCount;
+ }
+
+ int getHitValue() {
+ return hitValue;
+ }
+
+ void setHitValue(int hitValue) {
+ this.hitValue = hitValue;
+ }
+
+ int getId() {
+ return id;
+ }
+
+ void setId(int id) {
+ this.id = id;
+ }
+
+ Pos getPos() {
+ return pos;
+ }
+
+ void setPos(Pos pos) {
+ this.pos = pos;
+ }
+
+ IRubyObject getSource() {
+ return source;
+ }
+
+ void setSource(IRubyObject source) {
+ this.source = source;
+ }
+
+ Type getType() {
+ return type;
+ }
+
+ void setType(Type type) {
+ this.type = type;
+ }
+
+ static class Pos {
+
+ private int line;
+ private String methodName;
+
+ public int getLine() {
+ return line;
+ }
+
+ public void setLine(int line) {
+ this.line = line;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public void setMethodName(String methodName) {
+ this.methodName = methodName;
+ }
+
+ public @Override String toString() {
+ return "DebugBreakpoint$Pos[line:" + getLine() + ", methodName:" + getMethodName() + ']';
+ }
+
+ }
+}
View
263 src/org/jruby/debug/DebugContext.java
@@ -0,0 +1,263 @@
+/*
+ * header & license
+ * Copyright (c) 2007 Martin Krauskopf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.jruby.debug;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jruby.RubyThread;
+import org.jruby.runtime.builtin.IRubyObject;
+
+final class DebugContext {
+
+ static final String AT_BREAKPOINT = "at_breakpoint";
+ static final String AT_CATCHPOINT = "at_catchpoint";
+ static final String AT_LINE = "at_line";
+ static final String AT_TRACING = "at_tracing";
+ static final String LIST = "list";
+
+ private static int thnumMax = 0;
+
+ enum StopReason {
+ NONE, STEP, BREAKPOINT, CATCHPOINT
+ }
+
+ private final RubyThread thread;
+ private IRubyObject breakpoint;
+ private final List<DebugFrame> frames;
+ private int lastLine;
+ private String lastFile;
+ private int destFrame;
+ private int stopFrame;
+ private int stopNext;
+ private int stopLine;
+ private int stackLen;
+ private StopReason stopReason;
+ private int thnum;
+ private boolean dead;
+
+ // flags
+ private boolean suspended;
+ private boolean wasRunning;
+ private boolean ignored;
+ private boolean skipped;
+ private boolean enableBreakpoint;
+ private boolean stepped;
+ private boolean tracing;
+ private boolean forceMove;
+
+ DebugContext(final RubyThread thread) {
+ thnum = ++thnumMax;
+ lastFile = null;
+ lastLine = 0;
+ stopNext = -1;
+ destFrame = -1;
+ stopLine = -1;
+ stopFrame = -1;
+ stopReason = StopReason.NONE;
+ frames = new LinkedList<DebugFrame>();
+ breakpoint = thread.getRuntime().getNil();
+ this.thread = thread;
+ }
+
+ void addFrame(final DebugFrame debugFrame) {
+ frames.add(debugFrame);
+ }
+
+ RubyThread getThread() {
+ return thread;
+ }
+
+ DebugFrame getTopFrame() {
+ return frames.get(getStackSize() - 1);
+ }
+
+ DebugFrame getFrame(int index) {
+ return frames.get(getStackSize() - index - 1);
+ }
+
+ DebugFrame popFrame() {
+ return frames.remove(getStackSize() - 1);
+ }
+
+ void clearFrames() {
+ frames.clear();
+ }
+
+ IRubyObject getBreakpoint() {
+ return breakpoint;
+ }
+
+ void setBreakpoint(IRubyObject breakpoint) {
+ this.breakpoint = breakpoint;
+ }
+
+ int getDestFrame() {
+ return destFrame;
+ }
+
+ void setDestFrame(int destFrame) {
+ this.destFrame = destFrame;
+ }
+
+ boolean isEnableBreakpoint() {
+ return enableBreakpoint;
+ }
+
+ void setEnableBreakpoint(boolean enableBreakpoint) {
+ this.enableBreakpoint = enableBreakpoint;
+ }
+
+ boolean isForceMove() {
+ return forceMove;
+ }
+
+ void setForceMove(boolean forceMove) {
+ this.forceMove = forceMove;
+ }
+
+ boolean isIgnored() {
+ return ignored;
+ }
+
+ void setIgnored(boolean ignored) {
+ this.ignored = ignored;
+ }
+
+ String getLastFile() {
+ return lastFile;
+ }
+
+ void setLastFile(String lastFile) {
+ this.lastFile = lastFile;
+ }
+
+ int getLastLine() {
+ return lastLine;
+ }
+
+ void setLastLine(int lastLine) {
+ this.lastLine = lastLine;
+ }
+
+ boolean isSkipped() {
+ return skipped;
+ }
+
+ void setSkipped(boolean skipped) {
+ this.skipped = skipped;
+ }
+
+ int getStackLen() {
+ return stackLen;
+ }
+
+ void setStackLen(int stackLen) {
+ this.stackLen = stackLen;
+ }
+
+ int getStackSize() {
+ return frames.size();
+ }
+
+ boolean isStepped() {
+ return stepped;
+ }
+
+ void setStepped(boolean stepped) {
+ this.stepped = stepped;
+ }
+
+ int getStopFrame() {
+ return stopFrame;
+ }
+
+ void setStopFrame(int stopFrame) {
+ this.stopFrame = stopFrame;
+ }
+
+ int getStopLine() {
+ return stopLine;
+ }
+
+ void setStopLine(int stopLine) {
+ this.stopLine = stopLine;
+ }
+
+ int getStopNext() {
+ return stopNext;
+ }
+
+ void setStopNext(int stopNext) {
+ this.stopNext = stopNext;
+ }
+
+ StopReason getStopReason() {
+ return stopReason;
+ }
+
+ void setStopReason(StopReason stopReason) {
+ this.stopReason = stopReason;
+ }
+
+ boolean isSuspended() {
+ return suspended;
+ }
+
+ void setSuspended(boolean suspended) {
+ this.suspended = suspended;
+ }
+
+ int getThnum() {
+ return thnum;
+ }
+
+ void setThnum(int thnum) {
+ this.thnum = thnum;
+ }
+
+ boolean isTracing() {
+ return tracing;
+ }
+
+ void setTracing(boolean tracing) {
+ this.tracing = tracing;
+ }
+
+ boolean isWasRunning() {
+ return wasRunning;
+ }
+
+ void setWasRunning(boolean wasRunning) {
+ this.wasRunning = wasRunning;
+ }
+
+ boolean isDead() {
+ return dead;
+ }
+
+ void setDead(boolean dead) {
+ this.dead = dead;
+ }
+}
View
616 src/org/jruby/debug/DebugEventHook.java
@@ -0,0 +1,616 @@
+/*
+ * header & license
+ * Copyright (c) 2007-2008 Martin Krauskopf
+ * Copyright (c) 2007 Peter Brant
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.jruby.debug;
+
+import java.io.File;
+
+import org.jruby.*;
+import org.jruby.debug.DebugContext.StopReason;
+import org.jruby.debug.DebugFrame.Info;
+import org.jruby.debug.Debugger.DebugContextPair;
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.EventHook;
+import org.jruby.runtime.RubyEvent;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+import static org.jruby.runtime.RubyEvent.*;
+
+final class DebugEventHook extends EventHook {
+
+ private final Debugger debugger;
+ private final Ruby runtime;
+
+ private int hookCount;
+ private int lastDebuggedThnum;
+ private int lastCheck;
+
+ private boolean inDebugger;
+
+ public DebugEventHook(final Debugger debugger, final Ruby runtime) {
+ this.debugger = debugger;
+ lastDebuggedThnum = -1;
+ this.runtime = runtime;
+ }
+
+ @Override
+ public boolean isInterestedInEvent(RubyEvent event) {
+ return true;
+ }
+
+ @Override
+ public void eventHandler(final ThreadContext tCtx, String event, final String file, final int line,
+ final String methodName, final IRubyObject klass) {
+
+ RubyThread currThread = tCtx.getThread();
+ DebugContextPair contexts = debugger.threadContextLookup(currThread, true);
+
+ // return if thread is marked as 'ignored'. debugger's threads are marked this way
+ if (contexts.debugContext.isIgnored()) {
+ return;
+ }
+
+ /* ignore a skipped section of code */
+ if (contexts.debugContext.isSkipped()) {
+ cleanUp(contexts.debugContext);
+ return;
+ }
+
+ /** Ignore JRuby core classes by default. Consider option for enabling it. */
+ if (Util.isJRubyCore(file)) {
+ return;
+ }
+
+ if (contexts.debugContext.isSuspended()) {
+ RubyThread.stop(tCtx, currThread);
+ }
+
+ synchronized (this) {
+ if (isInDebugger()) {
+ return;
+ }
+ setInDebugger(true);
+ try {
+ processEvent(tCtx, Util.typeForEvent(event), Util.relativizeToPWD(file), line, methodName, klass, contexts);
+ } finally {
+ setInDebugger(false);
+ }
+ }
+ }
+
+ @SuppressWarnings("fallthrough")
+ private void processEvent(final ThreadContext tCtx, final RubyEvent event, final String file, final int line,
+ final String methodName, final IRubyObject klass, DebugContextPair contexts) {
+ if (debugger.isDebug()) {
+ Util.logEvent(event, file, line, methodName, klass);
+ }
+ // one-based; jruby by default passes zero-based
+ hookCount++;
+ Ruby _runtime = tCtx.getRuntime();
+ IRubyObject breakpoint = getNil();
+ IRubyObject binding = getNil();
+ IRubyObject context = contexts.context;
+ DebugContext debugContext = contexts.debugContext;
+
+// debug("jrubydebug> %s:%d [%s] %s\n", file, line, EVENT_NAMES[event], methodName);
+
+ boolean moved = false;
+ if (!debugContext.isForceMove() ||
+ debugContext.getLastLine() != line || debugContext.getLastFile() == null ||
+ !Util.areSameFiles(debugContext.getLastFile(), file)) {
+ debugContext.setEnableBreakpoint(true);
+ moved = true;
+ }
+ // else if(event != RUBY_EVENT_RETURN && event != RUBY_EVENT_C_RETURN) {
+ // if(debug == Qtrue)
+ // fprintf(stderr, "nodeless [%s] %s\n", get_event_name(event), rb_id2name(methodName));
+ // goto cleanup;
+ // } else {
+ // if(debug == Qtrue)
+ // fprintf(stderr, "nodeless [%s] %s\n", get_event_name(event), rb_id2name(methodName));
+ // }
+ if (LINE == event) {
+ debugContext.setStepped(true);
+ }
+
+ switch (event) {
+ case LINE:
+ if (debugContext.getStackSize() == 0) {
+ saveCallFrame(event, tCtx, file, line, methodName, debugContext);
+ } else {
+ updateTopFrame(event, debugContext, tCtx, file, line, methodName);
+ }
+ if (debugger.isTracing() || debugContext.isTracing()) {
+ IRubyObject[] args = new IRubyObject[]{
+ _runtime.newString(file),
+ _runtime.newFixnum(line)
+ };
+ context.callMethod(tCtx, DebugContext.AT_TRACING, args);
+ }
+ if (debugContext.getDestFrame() == -1 || debugContext.getStackSize() == debugContext.getDestFrame()) {
+ if (moved || !debugContext.isForceMove()) {
+ debugContext.setStopNext(debugContext.getStopNext() - 1);
+ }
+ if (debugContext.getStopNext() < 0) {
+ debugContext.setStopNext(-1);
+ }
+ if (moved || (debugContext.isStepped() && !debugContext.isForceMove())) {
+ debugContext.setStopLine(debugContext.getStopLine() - 1);
+ debugContext.setStepped(false);
+ }
+ } else if (debugContext.getStackSize() < debugContext.getDestFrame()) {
+ debugContext.setStopNext(0);
+ }
+
+ if (debugContext.getStopNext() == 0 || debugContext.getStopLine() == 0 ||
+ !(breakpoint = checkBreakpointsByPos(debugContext, file, line)).isNil()) {
+ binding = (tCtx != null ? RubyBinding.newBinding(_runtime, tCtx.currentBinding()) : getNil());
+ saveTopBinding(debugContext, binding);
+
+ debugContext.setStopReason(DebugContext.StopReason.STEP);
+
+ /* Check breakpoint expression. */
+ if (!breakpoint.isNil()) {
+ if (!checkBreakpointExpression(tCtx, breakpoint, binding)) {
+ break;
+ }
+ if (!checkBreakpointHitCondition(breakpoint)) {
+ break;
+ }
+ if (breakpoint != debugContext.getBreakpoint()) {
+ debugContext.setStopReason(DebugContext.StopReason.BREAKPOINT);
+ context.callMethod(tCtx, DebugContext.AT_BREAKPOINT, breakpoint);
+ } else {
+ debugContext.setBreakpoint(getNil());
+ }
+ }
+
+ /* reset all pointers */
+ debugContext.setDestFrame(-1);
+ debugContext.setStopLine(-1);
+ debugContext.setStopNext(-1);
+ callAtLine(tCtx, context, debugContext, _runtime, file, line);
+ }
+ break;
+ case CALL:
+ saveCallFrame(event, tCtx, file, line, methodName, debugContext);
+ breakpoint = checkBreakpointsByMethod(debugContext, klass, methodName);
+ if (!breakpoint.isNil()) {
+ DebugFrame debugFrame = getTopFrame(debugContext);
+ if (debugFrame != null) {
+ binding = debugFrame.getBinding();
+ }
+ if (tCtx != null && binding.isNil()) {
+ binding = RubyBinding.newBinding(_runtime, tCtx.currentBinding());
+ }
+ saveTopBinding(debugContext, binding);
+
+ if(!checkBreakpointExpression(tCtx, breakpoint, binding)) {
+ break;
+ }
+ if(!checkBreakpointHitCondition(breakpoint)) {
+ break;
+ }
+ if (breakpoint != debugContext.getBreakpoint()) {
+ debugContext.setStopReason(DebugContext.StopReason.BREAKPOINT);
+ context.callMethod(tCtx, DebugContext.AT_BREAKPOINT, breakpoint);
+ } else {
+ debugContext.setBreakpoint(getNil());
+ }
+ callAtLine(tCtx, context, debugContext, _runtime, file, line);
+ }
+ break;
+ case C_CALL:
+ if(cCallNewFrameP(klass)) {
+ saveCallFrame(event, tCtx, file, line, methodName, debugContext);
+ } else {
+ updateTopFrame(event, debugContext, tCtx, file, line, methodName);
+ }
+ break;
+ case C_RETURN:
+ /* note if a block is given we fall through! */
+ if (!cCallNewFrameP(klass)) {
+ break;
+ }
+ case RETURN:
+ case END:
+ if (debugContext.getStackSize() == debugContext.getStopFrame()) {
+ debugContext.setStopNext(1);
+ debugContext.setStopFrame(0);
+ }
+ while (debugContext.getStackSize() > 0) {
+ DebugFrame topFrame = debugContext.popFrame();
+ String origMethodName = topFrame.getOrigMethodName();
+ if ((origMethodName == null && methodName == null) ||
+ (origMethodName != null && origMethodName.equals(methodName))) {
+ break;
+ }
+ }
+ debugContext.setEnableBreakpoint(true);
+ break;
+ case CLASS:
+ resetTopFrameMethodName(debugContext);
+ saveCallFrame(event, tCtx, file, line, methodName, debugContext);
+ break;
+ case RAISE:
+ updateTopFrame(event, debugContext, tCtx, file, line, methodName);
+
+ // XXX Implement post mortem debugging
+
+ IRubyObject exception = _runtime.getGlobalVariables().get("$!");
+ // Might happen if the current ThreadContext is within 'defined?'
+ if (exception.isNil()) {
+ assert tCtx.isWithinDefined() : "$! should be nil only when within defined?";
+ break;
+ }
+
+ if (_runtime.getClass("SystemExit").isInstance(exception)) {
+ // Can't do this because this unhooks the event hook causing
+ // a ConcurrentModificationException because the runtime
+ // is still iterating over event hooks. Shouldn't really
+ // matter. We're on our way out regardless.
+
+ // debugger.stop(runtime);
+ break;
+ }
+
+ if (debugger.getCatchpoints().isNil() || debugger.getCatchpoints().isEmpty()) {
+ break;
+ }
+
+ RubyArray ancestors = exception.getType().ancestors(tCtx);
+ int l = ancestors.getLength();
+ for (int i = 0; i < l; i++) {
+ RubyModule module = (RubyModule)ancestors.get(i);