Password no longer echoed in getpass (in Windows). Fixes #1723

This commit is contained in:
Fabio Zadrozny 2019-08-29 15:45:30 -03:00
parent a08708dc8b
commit 90298c4e79
4 changed files with 129 additions and 40 deletions

View file

@ -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+

View file

@ -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)

View file

@ -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)

View file

@ -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']