Repository: getsentry/sentry

Issue:
Remove more module outliers for Java's sun.reflect
## Important Details

How are you running Sentry?
* Saas (sentry.io)

## Description

In addition to normalising `sun.reflect.GeneratedMethodAccessor` in Java [stack traces](https://github.com/getsentry/sentry/blob/master/src/sentry/interfaces/stacktrace.py#L199-L200) it would also be useful to normalise `sun.reflect.GeneratedSerializationConstructorAccessor` and `sun.reflect.GeneratedConstructorAccessor` in the same way.

Similarly to the supported one, they have an unpredictable number as a name suffix which breaks automatic events grouping.

## Steps to Reproduce

Throw an exception object created, for instance, with `org.apache.commons.lang3.reflect.ConstructorUtils`. The stack trace would be something like this:

```
com.github.detouched.TestException: Test exception
    at sun.reflect.GeneratedConstructorAccessor89.newInstance
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor(ConstructorUtils.java:121)
    at org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor(ConstructorUtils.java:84)
    at com.github.detouched.SentrySandbox.instantiateClass(SentrySandbox.java:27)
```
Note this class name `GeneratedConstructorAccessor89` – the 89 bit may be different in different events with effectively same stack trace.

### What you expected to happen

The counter in the end of generated class name shouldn't be involved in [frame hash computation](https://github.com/getsentry/sentry/blob/master/src/sentry/interfaces/stacktrace.py#L418).

### Possible Solution

Add extra cases into [`remove_module_outliers`](https://github.com/getsentry/sentry/blob/master/src/sentry/interfaces/stacktrace.py#L196-L200) method.



Discussion:

Resolving patch:
diff --git a/src/sentry/interfaces/stacktrace.py b/src/sentry/interfaces/stacktrace.py
--- a/src/sentry/interfaces/stacktrace.py
+++ b/src/sentry/interfaces/stacktrace.py
@@ -36,6 +36,17 @@
 )/""", re.X | re.I
 )
 
+# OpenJDK auto-generated classes for reflection access:
+#   sun.reflect.GeneratedSerializationConstructorAccessor123
+#   sun.reflect.GeneratedConstructorAccessor456
+# Note that this doesn't cover the following pattern for the sake of
+# backward compatibility (to not to change the existing grouping):
+#   sun.reflect.GeneratedMethodAccessor789
+_java_reflect_enhancer_re = re.compile(
+    r'''(sun\.reflect\.Generated(?:Serialization)?ConstructorAccessor)\d+''',
+    re.X
+)
+
 # Java Spring specific anonymous classes.
 # see: http://mydailyjava.blogspot.co.at/2013/11/cglib-missing-manual.html
 _java_cglib_enhancer_re = re.compile(r'''(\$\$[\w_]+?CGLIB\$\$)[a-fA-F0-9]+(_[0-9]+)?''', re.X)
@@ -198,6 +209,7 @@ def remove_module_outliers(module, platform=None):
     if platform == 'java':
         if module[:35] == 'sun.reflect.GeneratedMethodAccessor':
             return 'sun.reflect.GeneratedMethodAccessor'
+        module = _java_reflect_enhancer_re.sub(r'\1<auto>', module)
         module = _java_cglib_enhancer_re.sub(r'\1<auto>', module)
         module = _java_assist_enhancer_re.sub(r'\1<auto>', module)
         module = _clojure_enhancer_re.sub(r'\1<auto>', module)


Test patch:
diff --git a/tests/sentry/interfaces/test_stacktrace.py b/tests/sentry/interfaces/test_stacktrace.py
--- a/tests/sentry/interfaces/test_stacktrace.py
+++ b/tests/sentry/interfaces/test_stacktrace.py
@@ -392,6 +392,31 @@ def test_get_hash_ignores_javassist(self):
             ]
         )
 
+    def test_get_hash_ignores_sun_java_generated_constructors(self):
+        interface = Frame.to_python(
+            {
+                'module': 'sun.reflect.GeneratedSerializationConstructorAccessor1',
+                'function': 'invoke',
+            }
+        )
+        result = interface.get_hash(platform='java')
+        self.assertEquals(result, [
+            'sun.reflect.GeneratedSerializationConstructorAccessor<auto>',
+            'invoke',
+        ])
+
+        interface = Frame.to_python(
+            {
+                'module': 'sun.reflect.GeneratedConstructorAccessor2',
+                'function': 'invoke',
+            }
+        )
+        result = interface.get_hash(platform='java')
+        self.assertEquals(result, [
+            'sun.reflect.GeneratedConstructorAccessor<auto>',
+            'invoke',
+        ])
+
     def test_get_hash_ignores_sun_java_generated_methods(self):
         interface = Frame.to_python(
             {