diff --git a/hbase-shell/src/main/ruby/irb/hirb.rb b/hbase-shell/src/main/ruby/irb/hirb.rb index 73b0ee91a11d..34638f228a9e 100644 --- a/hbase-shell/src/main/ruby/irb/hirb.rb +++ b/hbase-shell/src/main/ruby/irb/hirb.rb @@ -23,6 +23,10 @@ module IRB # Subclass of IRB so can intercept methods class HIRB < Irb + def self.command_names + @command_names ||= ::Shell.commands.keys.map(&:to_sym).freeze + end + def initialize(workspace = nil, interactive = true, input_method = nil) # This is ugly. Our 'help' method above provokes the following message # on irb construction: 'irb: warn: can't alias help from irb_help.' @@ -53,6 +57,14 @@ def initialize(workspace = nil, interactive = true, input_method = nil) $stdout = STDOUT end + def set_context_workspace(workspace) + if @context.respond_to?(:workspace=) + @context.workspace = workspace + else + @context.instance_variable_set(:@workspace, workspace) + end + end + def output_value(omit = false) # Suppress output if last_value is 'nil' # Otherwise, when user types help, get ugly 'nil' @@ -163,6 +175,23 @@ def eval_input else exc = nil next + ensure + # HBASE-28660: Prevent command shadowing by incorrectly parsed local variables + cmd_names = self.class.command_names + workspace_binding = @context.workspace.binding + shadowing_vars = workspace_binding.local_variables & cmd_names + + if shadowing_vars.any? + shadowing_vars.each do |var| + warn "WARN: '#{var}' is a reserved HBase command. Local variable assignment ignored." + end + + new_binding = @context.workspace.main.get_binding + (workspace_binding.local_variables - shadowing_vars).each do |var| + new_binding.local_variable_set(var, workspace_binding.local_variable_get(var)) + end + set_context_workspace(::IRB::WorkSpace.new(new_binding)) + end end handle_exception(exc) @context.workspace.local_variable_set(:_, exc) diff --git a/hbase-shell/src/test/ruby/shell/general_test_cluster.rb b/hbase-shell/src/test/ruby/shell/general_test_cluster.rb index e1461fd935bd..3c99fe8e17e8 100644 --- a/hbase-shell/src/test/ruby/shell/general_test_cluster.rb +++ b/hbase-shell/src/test/ruby/shell/general_test_cluster.rb @@ -19,6 +19,8 @@ require 'hbase_constants' require 'hbase_shell' +require 'irb/hirb' +require 'stringio' class ShellTest < Test::Unit::TestCase include Hbase::TestHelpers @@ -149,4 +151,70 @@ class TestException < RuntimeError; end # create a table that exists @shell.command('create', 'nothrow_table', 'family_1') end + + #----------------------------------------------------------------------------- + + class MockInputMethod < IRB::InputMethod + def initialize(lines) + super() + @lines = lines + end + def gets + @lines.shift + end + def eof? + @lines.empty? + end + def encoding + Encoding::UTF_8 + end + def readable_after_eof? + false + end + end + + define_test 'Shell::Shell should prevent HBase commands from being shadowed by local variables (HBASE-28660)' do + workspace = @shell.get_workspace + IRB.setup(__FILE__) unless IRB.conf[:IRB_NAME] + + lines = [ + "list = 10\n", + "list_namespace, 'ns.*'\n", + "my_var = 5\n" + ] + + input_method = MockInputMethod.new(lines) + hirb = IRB::HIRB.new(workspace, true, input_method) + + hirb.context.prompt_i = "" + hirb.context.prompt_s = "" + hirb.context.prompt_c = "" + hirb.context.prompt_n = "" + hirb.context.return_format = "" + hirb.context.echo = false + + old_stderr = $stderr + $stderr = StringIO.new + err_output = "" + begin + capture_stdout do + hirb.eval_input + end + ensure + err_output = $stderr.string + $stderr = old_stderr + end + + final_workspace = hirb.context.workspace + final_vars = final_workspace.binding.local_variables + + assert(final_vars.include?(:my_var), "Valid variables should be preserved") + assert_equal(5, final_workspace.binding.local_variable_get(:my_var)) + + assert(!final_vars.include?(:list), "Command 'list' should not be shadowed") + assert(!final_vars.include?(:list_namespace), "Command 'list_namespace' should not be shadowed") + + assert_match(/WARN: 'list' is a reserved HBase command/, err_output) + assert_match(/WARN: 'list_namespace' is a reserved HBase command/, err_output) + end end