From 90298c4e7903513e53b86bc670be9fdfe8754be3 Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Thu, 29 Aug 2019 15:45:30 -0300 Subject: [PATCH] Password no longer echoed in getpass (in Windows). Fixes #1723 --- .../_pydev_bundle/pydev_console_utils.py | 54 ++++++++++++------- .../pydevd/_pydevd_bundle/pydevd_console.py | 21 +++----- src/ptvsd/_vendored/pydevd/pydevd.py | 44 ++++++++++++--- .../pydevd/tests_python/test_pydevd_io.py | 50 +++++++++++++++++ 4 files changed, 129 insertions(+), 40 deletions(-) diff --git a/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py b/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py index 8c246bc6..f706435d 100644 --- a/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py +++ b/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py @@ -6,8 +6,11 @@ from _pydev_bundle._pydev_calltip_util import get_description from _pydev_imps._pydev_saved_modules import thread from _pydevd_bundle import pydevd_vars from _pydevd_bundle import pydevd_xml -from _pydevd_bundle.pydevd_constants import IS_JYTHON, dict_iter_items, NEXT_VALUE_SEPARATOR, Null +from _pydevd_bundle.pydevd_constants import (IS_JYTHON, dict_iter_items, NEXT_VALUE_SEPARATOR, Null, + get_global_debugger) import signal +from contextlib import contextmanager +from _pydev_bundle import pydev_log try: import cStringIO as StringIO # may not always be available @UnusedImport @@ -109,31 +112,44 @@ class DebugConsoleStdIn(BaseStdIn): Object to be added to stdin (to emulate it as non-blocking while the next line arrives) ''' - def __init__(self, dbg, original_stdin): + def __init__(self, py_db, original_stdin): + ''' + :param py_db: + If None, get_global_debugger() is used. + ''' BaseStdIn.__init__(self, original_stdin) - self.debugger = dbg + self._py_db = py_db + self._in_notification = 0 - def __pydev_run_command(self, is_started): + def __send_input_requested_message(self, is_started): try: - cmd = self.debugger.cmd_factory.make_input_requested_message(is_started) - self.debugger.writer.add_command(cmd) + py_db = self._py_db + if py_db is None: + py_db = get_global_debugger() + cmd = py_db.cmd_factory.make_input_requested_message(is_started) + py_db.writer.add_command(cmd) except Exception: - traceback.print_exc() - return '\n' + pydev_log.exception() + + @contextmanager + def notify_input_requested(self): + self._in_notification += 1 + if self._in_notification == 1: + self.__send_input_requested_message(True) + try: + yield + finally: + self._in_notification -= 1 + if self._in_notification == 0: + self.__send_input_requested_message(False) def readline(self, *args, **kwargs): - # Notify Java side about input and call original function - self.__pydev_run_command(True) - result = self.original_stdin.readline(*args, **kwargs) - self.__pydev_run_command(False) - return result + with self.notify_input_requested(): + return self.original_stdin.readline(*args, **kwargs) def read(self, *args, **kwargs): - # Notify Java side about input and call original function - self.__pydev_run_command(True) - result = self.original_stdin.read(*args, **kwargs) - self.__pydev_run_command(False) - return result + with self.notify_input_requested(): + return self.original_stdin.read(*args, **kwargs) class CodeFragment: @@ -220,7 +236,7 @@ class BaseInterpreterInterface: if debugger is None: return StdIn(self, self.host, self.client_port, original_stdin=original_std_in) else: - return DebugConsoleStdIn(dbg=debugger, original_stdin=original_std_in) + return DebugConsoleStdIn(py_db=debugger, original_stdin=original_std_in) def add_exec(self, code_fragment, debugger=None): # In case sys.excepthook called, use original excepthook #PyDev-877: Debug console freezes with Python 3.5+ diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_console.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_console.py index d504b669..cf151b75 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_console.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_console.py @@ -23,6 +23,7 @@ CONSOLE_ERROR = "error" class ConsoleMessage: """Console Messages """ + def __init__(self): self.more = False # List of tuple [('error', 'error_message'), ('message_list', 'output_message')] @@ -62,15 +63,16 @@ class ConsoleMessage: #======================================================================================================================= -# DebugConsoleStdIn +# _DebugConsoleStdIn #======================================================================================================================= -class DebugConsoleStdIn(BaseStdIn): +class _DebugConsoleStdIn(BaseStdIn): @overrides(BaseStdIn.readline) def readline(self, *args, **kwargs): sys.stderr.write('Warning: Reading from stdin is still not supported in this console.\n') return '\n' + #======================================================================================================================= # DebugConsole #======================================================================================================================= @@ -87,8 +89,7 @@ class DebugConsole(InteractiveConsole, BaseInterpreterInterface): except: pass - return DebugConsoleStdIn() #If buffered, raw_input is not supported in this console. - + return _DebugConsoleStdIn() # If buffered, raw_input is not supported in this console. @overrides(InteractiveConsole.push) def push(self, line, frame, buffer_output=True): @@ -121,7 +122,7 @@ class DebugConsole(InteractiveConsole, BaseInterpreterInterface): else: sys.stderr.write("Internal Error: %s\n" % (exc,)) finally: - #Remove frame references. + # Remove frame references. self.frame = None frame = None if buffer_output: @@ -133,12 +134,10 @@ class DebugConsole(InteractiveConsole, BaseInterpreterInterface): else: return more, [], [] - @overrides(BaseInterpreterInterface.do_add_exec) def do_add_exec(self, line): return InteractiveConsole.push(self, line) - @overrides(InteractiveConsole.runcode) def runcode(self, code): """Execute a code object. @@ -183,7 +182,7 @@ class InteractiveConsoleCache: interactive_console_instance = None -#Note: On Jython 2.1 we can't use classmethod or staticmethod, so, just make the functions below free-functions. +# Note: On Jython 2.1 we can't use classmethod or staticmethod, so, just make the functions below free-functions. def get_interactive_console(thread_id, frame_id, frame, console_message): """returns the global interactive console. interactive console should have been initialized by this time @@ -198,7 +197,7 @@ def get_interactive_console(thread_id, frame_id, frame, console_message): console_stacktrace = traceback.extract_stack(frame, limit=1) if console_stacktrace: - current_context = console_stacktrace[0] # top entry from stacktrace + current_context = console_stacktrace[0] # top entry from stacktrace context_message = 'File "%s", line %s, in %s' % (current_context[0], current_context[1], current_context[2]) console_message.add_console_message(CONSOLE_OUTPUT, "[Current context]: %s" % (context_message,)) return InteractiveConsoleCache.interactive_console_instance @@ -247,7 +246,3 @@ def get_completions(frame, act_tok): """ return _pydev_completer.generate_completions_as_xml(frame, act_tok) - - - - diff --git a/src/ptvsd/_vendored/pydevd/pydevd.py b/src/ptvsd/_vendored/pydevd/pydevd.py index 1d5581bf..df7ccf67 100644 --- a/src/ptvsd/_vendored/pydevd/pydevd.py +++ b/src/ptvsd/_vendored/pydevd/pydevd.py @@ -15,6 +15,8 @@ import itertools import os import traceback import weakref +import getpass as getpass_mod +import functools from _pydev_bundle import pydev_imports, pydev_log from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding @@ -27,6 +29,7 @@ from _pydevd_bundle import pydevd_extension_utils from _pydevd_bundle.pydevd_filtering import FilesFiltering from _pydevd_bundle import pydevd_io, pydevd_vm_type from _pydevd_bundle import pydevd_utils +from _pydev_bundle.pydev_console_utils import DebugConsoleStdIn from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info from _pydevd_bundle.pydevd_breakpoints import ExceptionBreakpoint, get_exception_breakpoint from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, CMD_STEP_INTO, CMD_SET_BREAK, @@ -36,7 +39,7 @@ from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, CMD_STEP_I from _pydevd_bundle.pydevd_constants import (IS_JYTH_LESS25, get_thread_id, get_current_thread_id, dict_keys, dict_iter_items, DebugInfoHolder, PYTHON_SUSPEND, STATE_SUSPEND, STATE_RUN, get_frame, clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, IS_PY34_OR_GREATER, IS_PY2, NULL, - NO_FTRACE, IS_IRONPYTHON, JSON_PROTOCOL, IS_CPYTHON) + NO_FTRACE, IS_IRONPYTHON, JSON_PROTOCOL, IS_CPYTHON, call_only_once) from _pydevd_bundle.pydevd_defaults import PydevdCustomization from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE, LIB_FILE @@ -2250,7 +2253,7 @@ def _locked_settrace( if bufferStdErrToServer: init_stderr_redirect() - patch_stdin(debugger) + patch_stdin() t = threadingCurrentThread() additional_info = set_additional_thread_info(t) @@ -2464,12 +2467,37 @@ def apply_debugger_options(setup_options): enable_qt_support(setup_options['qt-support']) -def patch_stdin(debugger): - from _pydev_bundle.pydev_console_utils import DebugConsoleStdIn - orig_stdin = sys.stdin - sys.stdin = DebugConsoleStdIn(debugger, orig_stdin) +@call_only_once +def patch_stdin(): + _internal_patch_stdin(None, sys, getpass_mod) -# Dispatch on_debugger_modules_loaded here, after all primary debugger modules are loaded + +def _internal_patch_stdin(py_db=None, sys=None, getpass_mod=None): + ''' + Note: don't use this function directly, use `patch_stdin()` instead. + (this function is only meant to be used on test-cases to avoid patching the actual globals). + ''' + # Patch stdin so that we notify when readline() is called. + original_sys_stdin = sys.stdin + debug_console_stdin = DebugConsoleStdIn(py_db, original_sys_stdin) + sys.stdin = debug_console_stdin + + _original_getpass = getpass_mod.getpass + + @functools.wraps(_original_getpass) + def getpass(*args, **kwargs): + with DebugConsoleStdIn.notify_input_requested(debug_console_stdin): + try: + curr_stdin = sys.stdin + if curr_stdin is debug_console_stdin: + sys.stdin = original_sys_stdin + return _original_getpass(*args, **kwargs) + finally: + sys.stdin = curr_stdin + + getpass_mod.getpass = getpass + +# Dispatch on_debugger_modules_loaded here, after all primary py_db modules are loaded for handler in pydevd_extension_utils.extensions_of_type(DebuggerEventHandler): @@ -2606,7 +2634,7 @@ def main(): pass is_module = setup['module'] - patch_stdin(debugger) + patch_stdin() if setup['json-dap']: PyDevdAPI().set_protocol(debugger, 0, JSON_PROTOCOL) diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_pydevd_io.py b/src/ptvsd/_vendored/pydevd/tests_python/test_pydevd_io.py index 41a9a121..116e60bf 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_pydevd_io.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_pydevd_io.py @@ -52,6 +52,50 @@ class _DummyPyDb(object): self.writer = _DummyWriter() +def test_patch_stdin(): + from pydevd import _internal_patch_stdin + + py_db = _DummyPyDb() + + class _Stub(object): + pass + + actions = [] + + class OriginalStdin(object): + + def readline(self): + # On a readline we keep the patched version. + assert sys_mod.stdin is not original_stdin + actions.append('readline') + return 'read' + + def getpass_stub(*args, **kwargs): + # On getpass we need to revert to the original version. + actions.append('getpass') + assert sys_mod.stdin is original_stdin + return 'pass' + + sys_mod = _Stub() + original_stdin = sys_mod.stdin = OriginalStdin() + + getpass_mod = _Stub() + getpass_mod.getpass = getpass_stub + + _internal_patch_stdin(py_db, sys_mod, getpass_mod) + + assert sys_mod.stdin.readline() == 'read' + + assert py_db.writer.command_meanings == ['CMD_INPUT_REQUESTED', 'CMD_INPUT_REQUESTED'] + del py_db.writer.command_meanings[:] + assert actions == ['readline'] + del actions[:] + + assert getpass_mod.getpass() == 'pass' + assert py_db.writer.command_meanings == ['CMD_INPUT_REQUESTED', 'CMD_INPUT_REQUESTED'] + del py_db.writer.command_meanings[:] + + def test_debug_console(): from _pydev_bundle.pydev_console_utils import DebugConsoleStdIn @@ -67,4 +111,10 @@ def test_debug_console(): assert debug_console_std_in.readline() == 'read' assert py_db.writer.command_meanings == ['CMD_INPUT_REQUESTED', 'CMD_INPUT_REQUESTED'] + del py_db.writer.command_meanings[:] + + with debug_console_std_in.notify_input_requested(): + with debug_console_std_in.notify_input_requested(): + pass + assert py_db.writer.command_meanings == ['CMD_INPUT_REQUESTED', 'CMD_INPUT_REQUESTED']