Repository: microsoft/ptvsd

Issue:
Debugger should not stop on GeneratorExit and StopIteration exceptions
<!-- Please search existing issues to avoid creating duplicates. -->

## Environment data

- VS Code version: 1.40.1
- Extension version (available under the Extensions sidebar): 2019.11.50794
- OS and version: Win7 x64
- Python version (& distribution if applicable, e.g. Anaconda): 3.7.4 64bit
- Jedi or Language Server? (i.e. what is `"python.jediEnabled"` set to; more info microsoft/vscode-python#3977): Language server

## Expected behaviour

No exception

## Actual behaviour

It shows 

![image](https://user-images.githubusercontent.com/4130991/69904087-f4edb600-1367-11ea-976b-14b78445e2dc.png)


## Steps to reproduce:

1. Debug the following code in a file:

```python
a = [1, 2, 3, 4]
b = [2, 7]
print(any(x in a for x in b))
```

It has been reported by other people at SO too: https://stackoverflow.com/questions/58261287/generatorexit-exception-when-debugging-any-line



Discussion:
I don't think the problem here is with `any()` as such - it just causes the generator to execute immediately. The exception itself is normal - it gets raised inside the generator whenever the outside code (in this case, `any()`, closes it). The bug here is that we're reporting it, even though the generator machinery will catch and handle it.

@fabioz, it looks like we should just be ignoring `GeneratorExit` in all circumstances? The docs straight up say that "it is technically not an error".
Makes sense, thanks for investigating. Feel free to change the title to reflect that better.
> @fabioz, it looks like we should just be ignoring `GeneratorExit` in all circumstances? The docs straight up say that "it is technically not an error".

Agreed... I'll take a look at it.

Resolving patch:
diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py
--- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py
+++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py
@@ -140,6 +140,11 @@ def should_stop_on_exception(self, frame, event, arg):
                         if exception is SystemExit and main_debugger.ignore_system_exit_code(value):
                             return False, frame
 
+                        if exception in (GeneratorExit, StopIteration):
+                            # These exceptions are control-flow related (they work as a generator
+                            # pause), so, we shouldn't stop on them.
+                            return False, frame
+
                         if exception_breakpoint.condition is not None:
                             eval_result = main_debugger.handle_breakpoint_condition(info, exception_breakpoint, frame)
                             if not eval_result:
diff --git a/src/ptvsd/_vendored/pydevd/build_tools/build.py b/src/ptvsd/_vendored/pydevd/build_tools/build.py
--- a/src/ptvsd/_vendored/pydevd/build_tools/build.py
+++ b/src/ptvsd/_vendored/pydevd/build_tools/build.py
@@ -33,6 +33,7 @@ def consume(it):
     except StopIteration:
         pass
 
+
 def get_environment_from_batch_command(env_cmd, initial=None):
     """
     Take a command (either a single command or list of arguments)
@@ -95,22 +96,21 @@ def build():
 
     os.chdir(root_dir)
 
-    env=None
+    env = None
     if sys.platform == 'win32':
         # "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat"
         # set MSSdk=1
         # set DISTUTILS_USE_SDK=1
         # set VS100COMNTOOLS=C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools
 
-
         env = os.environ.copy()
-        if sys.version_info[:2] in ((2, 6), (2, 7), (3, 5), (3, 6), (3, 7)):
-            import setuptools # We have to import it first for the compiler to be found
+        if sys.version_info[:2] in ((2, 6), (2, 7), (3, 5), (3, 6), (3, 7), (3, 8)):
+            import setuptools  # We have to import it first for the compiler to be found
             from distutils import msvc9compiler
 
             if sys.version_info[:2] in ((2, 6), (2, 7)):
                 vcvarsall = msvc9compiler.find_vcvarsall(9.0)
-            elif sys.version_info[:2] in ((3, 5), (3, 6), (3, 7)):
+            elif sys.version_info[:2] in ((3, 5), (3, 6), (3, 7), (3, 8)):
                 vcvarsall = msvc9compiler.find_vcvarsall(14.0)
             if vcvarsall is None or not os.path.exists(vcvarsall):
                 raise RuntimeError('Error finding vcvarsall.')
@@ -124,7 +124,7 @@ def build():
                     [vcvarsall, 'x86'],
                     initial=os.environ.copy()))
 
-        elif sys.version_info[:2] in ((3,3), (3,4)):
+        elif sys.version_info[:2] in ((3, 3), (3, 4)):
             if is_python_64bit():
                 env.update(get_environment_from_batch_command(
                     [r"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd", '/x64'],
@@ -152,7 +152,7 @@ def build():
 
     args = [
         sys.executable, os.path.join(os.path.dirname(__file__), '..', 'setup_cython.py'), 'build_ext', '--inplace',
-    ]+additional_args
+    ] + additional_args
     print('Calling args: %s' % (args,))
     subprocess.check_call(args, env=env,)
 
diff --git a/src/ptvsd/_vendored/pydevd/build_tools/check_no_git_modifications.py b/src/ptvsd/_vendored/pydevd/build_tools/check_no_git_modifications.py
new file mode 100644
--- /dev/null
+++ b/src/ptvsd/_vendored/pydevd/build_tools/check_no_git_modifications.py
@@ -0,0 +1,32 @@
+import sys
+
+
+def main():
+    import subprocess
+    process = subprocess.Popen(
+        'git status --porcelain'.split(), stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+    output, _ = process.communicate()
+    if output:
+        if sys.version_info[0] > 2:
+            output = output.decode('utf-8')
+
+        files = set()
+        for line in output.splitlines():
+            filename = line[3:]
+            files.add(filename.strip())
+
+        files.discard('.travis_install_python_deps.sh')
+        files.discard('miniconda.sh')
+        if files:
+            # If there are modifications, show a diff of the modifications and fail the script.
+            # (we're mostly interested in modifications to the .c generated files by cython).
+            print('Found modifications in git:\n%s ' % (output,))
+            print('Files: %s' % (files,))
+            print('----------- diff -------------')
+            subprocess.call('git diff'.split())
+            print('----------- end diff -------------')
+            sys.exit(1)
+
+
+if __name__ == '__main__':
+    main()


Test patch:
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_ignore_exceptions.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_ignore_exceptions.py
new file mode 100644
--- /dev/null
+++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_ignore_exceptions.py
@@ -0,0 +1,25 @@
+import sys
+if __name__ == '__main__':
+
+    a = [1, 2, 3, 4]
+    b = [2, 7]
+
+    assert any(x in a for x in b)
+
+    def gen():
+        yield 1
+
+    for i in gen():
+        pass
+
+    def gen2():
+        yield 2
+        if sys.version_info[:2] < (3, 7):
+            # On Python 3.7 onwards this will generate an unhandled exception, which
+            # is not what we want.
+            raise StopIteration()
+
+    for i in gen2():
+        pass
+
+    print('TEST SUCEEDED!')
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
@@ -508,6 +508,17 @@ def test_case_json_change_breaks(case_setup):
         writer.finished_ok = True
 
 
+def test_case_handled_exception_no_break_on_generator(case_setup):
+    with case_setup.test_file('_debugger_case_ignore_exceptions.py') as writer:
+        json_facade = JsonFacade(writer)
+
+        json_facade.write_launch()
+        json_facade.write_set_exception_breakpoints(['raised'])
+        json_facade.write_make_initial_run()
+
+        writer.finished_ok = True
+
+
 def test_case_handled_exception_breaks(case_setup):
     with case_setup.test_file('_debugger_case_exceptions.py') as writer:
         json_facade = JsonFacade(writer)