Repository: microsoft/debugpy

Issue:
Debugger Does Not Enumerate ctypes Arrays
**Describe the bug**
The debugger in Visual Studio 2022 does not treat ctypes arrays as containers, and hovering over them or looking at them in the variables window does not show length or allow the items to be enumerated like a list or dict. This is a new problem in VS2022, and did not occur in VS2017 or VS2019, which is why I've only recently started using VS2022 and this problem is making it very unpleasant as I use ctypes extensively in my work.

Ordinarily I would post in a forum to ask if there is some setting that's set incorrectly (the settings are default for Python), but as there's no forum here I'm making a bug report.

**Steps to Reproduce**
For me this occurs any and all times I use ctypes arrays in any context, so I don't really have any specific instructions to reproduce. The only thing worth noting is that this occurs regardless of whether "Enable debugging of the Python standard library" is enabled (I mostly work with it enabled). Maybe read a random binary file into a bytearray and do something like

x = (ctypes.c_uint8 * len(file_bytes)).from_buffer(file_bytes)

Then hover over x (or look at it in the variables window). It will only show "special variables" and "protected variables".

**Expected behavior**
In past VS versions the debugger would display as if it was a list, showing the length and list of all elements.

**Configuration information**
VS Version: Community 2022 (64-bit) 17.3.0
PTVS version: 17.0.22179.3
Python version: 3.9.7 64-bit (x64)
Debugger Type: I don't think this is relevant for VS2022?

Discussion:

Patch:
diff --git a/cgmanifest.json b/cgmanifest.json
index aca38bd3..d23f3aa2 100644
--- a/cgmanifest.json
+++ b/cgmanifest.json
@@ -1,4 +1,5 @@
 {
+  "$schema": "https://json.schemastore.org/component-detection-manifest.json",
   "Registrations": [
     {
       "Component": {
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py
index dcd1cdce..b523ffaf 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py
@@ -91,6 +91,12 @@ def _create_default_type_map():
     except:
         pass
 
+    try:
+        from ctypes import Array
+        default_type_map.append((Array, pydevd_resolver.tupleResolver))
+    except:
+        pass
+
     if frame_type is not None:
         default_type_map.append((frame_type, pydevd_resolver.frameResolver))
 
diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py
index e2be5378..2ba94612 100644
--- a/src/debugpy/server/api.py
+++ b/src/debugpy/server/api.py
@@ -23,6 +23,7 @@ _config = {
     "qt": "none",
     "subProcess": True,
     "python": sys.executable,
+    "pythonEnv": {},
 }
 
 _config_valid_values = {
@@ -191,6 +192,12 @@ def listen(address, settrace_kwargs):
             creationflags |= 0x08000000  # CREATE_NO_WINDOW
             creationflags |= 0x00000200  # CREATE_NEW_PROCESS_GROUP
 
+        # On embedded applications, environment variables might not contain
+        # Python environment settings.
+        python_env = _config.get("pythonEnv")
+        if not bool(python_env):
+            python_env = None
+
         # Adapter will outlive this process, so we shouldn't wait for it. However, we
         # need to ensure that the Popen instance for it doesn't get garbage-collected
         # by holding a reference to it in a non-local variable, to avoid triggering
@@ -198,7 +205,7 @@ def listen(address, settrace_kwargs):
         try:
             global _adapter_process
             _adapter_process = subprocess.Popen(
-                adapter_args, close_fds=True, creationflags=creationflags
+                adapter_args, close_fds=True, creationflags=creationflags, env=python_env
             )
             if os.name == "posix":
                 # It's going to fork again to daemonize, so we need to wait on it to


Test patch:
diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py b/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py
index 8e8cc496..e7710c8a 100644
--- a/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py
+++ b/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py
@@ -321,3 +321,42 @@ def test_tuple_resolver_mixed():
     check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 2))
     assert contents_debug_adapter_protocol == [
         ('some_value', 10, '.some_value'), ('0', 1, '[0]'), ('1', 2, '[1]'), ]
+
+
+def test_tuple_resolver_ctypes():
+    import ctypes
+    from _pydevd_bundle.pydevd_xml import get_type
+
+    array_type = ctypes.c_int32 * 3
+    array_value = array_type(1, 2, 3)
+    _, _, resolver = get_type(array_value)
+
+    contents_dap = resolver.get_contents_debug_adapter_protocol(array_value)
+    # We only care about the array elements and len() here, so remove all preceding entries.
+    while True:
+        _, _, path = contents_dap[0]
+        if path.startswith("["):
+            break
+        contents_dap.pop(0)
+
+    len_entry = contents_dap.pop(-1)
+    assert contents_dap == [
+        ('0', 1, '[0]'),
+        ('1', 2, '[1]'),
+        ('2', 3, '[2]'),
+    ]
+    check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 3))
+
+    dict_dap = resolver.get_dictionary(array_value)
+    # We only care about the array elements and len() here, so remove all other entries.
+    dict_dap = {
+        k: v
+        for k, v in dict_dap.items()
+        if k.isdigit() or k == GENERATED_LEN_ATTR_NAME
+    }
+    assert dict_dap == {
+        '0': 1,
+        '1': 2,
+        '2': 3,
+        GENERATED_LEN_ATTR_NAME: 3
+    }