PY-33148 Fix debugger hang when exec or spawn are called with bytes args

GitOrigin-RevId: 4aaa502a6745033c61e74fd1f32b25a18ef55fe7
This commit is contained in:
Andrey Lisin
2019-04-23 14:38:40 +03:00
committed by intellij-monorepo-bot
parent 4b6648679c
commit 3ed0ece29c
4 changed files with 105 additions and 11 deletions

View File

@@ -57,6 +57,15 @@ def _is_already_patched(args):
return False
def _is_py3_and_has_bytes_args(args):
if not isinstance('', type(u'')):
return False
for arg in args:
if isinstance(arg, bytes):
return True
return False
def _on_forked_process():
import pydevd
pydevd.threadingCurrentThread().__pydevd_main_thread = True
@@ -72,7 +81,7 @@ def _on_set_trace_for_new_thread(global_debugger):
# Things related to monkey-patching
#===============================================================================
def is_python_args(args):
return len(args) > 0 and is_python(args[0])
return not _is_py3_and_has_bytes_args(args) and len(args) > 0 and is_python(args[0])
def is_executable(path):
@@ -153,6 +162,11 @@ def get_c_option_index(args):
def patch_args(args):
try:
log_debug("Patching args: %s"% str(args))
if _is_py3_and_has_bytes_args(args):
warn_bytes_args()
return args
args = remove_quotes_from_args(args)
from pydevd import SetupHolder
@@ -363,10 +377,6 @@ def patch_fork_exec_executable_list(args, other_args):
return other_args
def patch_path(path):
return sys.executable if is_python(path) else path
def monkey_patch_module(module, funcname, create_func):
if hasattr(module, funcname):
original_name = 'original_' + funcname
@@ -385,6 +395,11 @@ def warn_multiproc():
"pydev debugger: To debug that process please enable 'Attach to subprocess automatically while debugging?' option in the debugger settings.\n")
def warn_bytes_args():
log_error_once(
"pydev debugger: bytes arguments were passed to a new process creation function. Breakpoints may not work correctly.\n")
def create_warn_multiproc(original_name):
def new_warn_multiproc(*args):
@@ -395,6 +410,7 @@ def create_warn_multiproc(original_name):
return getattr(os, original_name)(*args)
return new_warn_multiproc
def create_execl(original_name):
def new_execl(path, *args):
"""
@@ -406,8 +422,9 @@ def create_execl(original_name):
import os
args = patch_args(args)
if is_python_args(args):
path = args[0]
send_process_will_be_substituted()
return getattr(os, original_name)(patch_path(path), *args)
return getattr(os, original_name)(path, *args)
return new_execl
@@ -418,9 +435,11 @@ def create_execv(original_name):
os.execvp(file, args)
"""
import os
args = patch_args(args)
if is_python_args(args):
path = args[0]
send_process_will_be_substituted()
return getattr(os, original_name)(patch_path(path), patch_args(args))
return getattr(os, original_name)(path, args)
return new_execv
@@ -431,9 +450,11 @@ def create_execve(original_name):
"""
def new_execve(path, args, env):
import os
args = patch_args(args)
if is_python_args(args):
path = args[0]
send_process_will_be_substituted()
return getattr(os, original_name)(patch_path(path), patch_args(args), env)
return getattr(os, original_name)(path, args, env)
return new_execve
@@ -457,8 +478,9 @@ def create_spawnv(original_name):
os.spawnvp(mode, file, args)
"""
import os
args = patch_args(args)
send_process_created_message()
return getattr(os, original_name)(mode, path, patch_args(args))
return getattr(os, original_name)(mode, path, args)
return new_spawnv
@@ -469,8 +491,9 @@ def create_spawnve(original_name):
"""
def new_spawnve(mode, path, args, env):
import os
args = patch_args(args)
send_process_created_message()
return getattr(os, original_name)(mode, path, patch_args(args), env)
return getattr(os, original_name)(mode, path, args, env)
return new_spawnve

View File

@@ -0,0 +1,5 @@
import os
import sys
args = [sys.executable, b'test4.py']
os.execv(args[0], args)

View File

@@ -0,0 +1,5 @@
import os
import sys
args = [sys.executable, b'test4.py']
os.spawnv(os.P_WAIT, args[0], args)

View File

@@ -2,6 +2,7 @@ package com.jetbrains.env.python;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
@@ -35,8 +36,11 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.nio.file.Paths;
import static org.junit.Assert.*;
/**
@@ -1954,7 +1958,7 @@ public class PythonDebuggerTest extends PyEnvTestCase {
@Test
public void testExecutableScriptDebug() {
Assume.assumeFalse("Don't run under Windows", UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows);
runPythonTest(new PyDebuggerTask("/debug", "test_executable_script_debug.py") {
@@ -2006,4 +2010,61 @@ public class PythonDebuggerTest extends PyEnvTestCase {
}
});
}
@Test
public void testExecAndSpawnWithBytesArgs() {
class ExecAndSpawnWithBytesArgsTask extends PyDebuggerTask {
private final static String BYTES_ARGS_WARNING = "pydev debugger: bytes arguments were passed to a new process creation function. " +
"Breakpoints may not work correctly.\n";
private final static String PYTHON2_TAG = "python2";
private Map<String, Set<String>> myEnvTags = Maps.newHashMap();
ExecAndSpawnWithBytesArgsTask(@Nullable String relativeTestDataPath, String scriptName) {
super(relativeTestDataPath, scriptName);
loadEnvTags();
}
private void loadEnvTags() {
List<String> roots = PythonDebuggerTest.getPythonRoots();
roots.forEach((root) -> {
Set<String> tags = Sets.newHashSet();
tags.addAll(PythonDebuggerTest.loadEnvTags(root));
myEnvTags.put(root, tags);
});
}
private boolean hasPython2Tag(){
String env = Paths.get(myRunConfiguration.getSdkHome()).getParent().getParent().toString();
return myEnvTags.get(env).stream().anyMatch((tag) -> tag.startsWith(PYTHON2_TAG));
}
@Override
public void testing() throws Exception {
if (hasPython2Tag()) {
waitForTerminate();
assertFalse(output().contains(BYTES_ARGS_WARNING));
}
else
waitForOutput(BYTES_ARGS_WARNING);
}
}
runPythonTest(new ExecAndSpawnWithBytesArgsTask("/debug", "test_call_exec_with_bytes_args.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
});
runPythonTest(new ExecAndSpawnWithBytesArgsTask("/debug", "test_call_spawn_with_bytes_args.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
});
}
}