Repository: semgrep/semgrep-rules

Issue:
False positive for return-in-init when return in internal function
**Describe the bug**

[`return-in-init`](https://github.com/returntocorp/semgrep-rules/blob/master/python/lang/correctness/return-in-init.yaml) warns about a return statement in `__init__`. However, this may be valid if another function is defined within `__init__` and return is used there.

**To Reproduce**

```
class Odd:
    def __init__(self, numbers):
        def is_odd(n):
            return n % 2 == 1
        self.numbers = filter(is_odd, numbers)
```

```
$ semgrep --config=p/ci
test1.py
severity:error rule:python.lang.correctness.return-in-init.return-in-init: `return` should never appear inside a class __init__ function. This will cause a runtime error.
4:            return n % 2 == 1
```

**Expected behavior**

I expect no error from `return-in-init` in this case.

**Priority**
How important is this to you?
- P2: annoying but not blocking me



Discussion:
[Here is another false positive](https://github.com/html5lib/html5lib-python/blob/master/html5lib/treebuilders/etree_lxml.py#L193-L228), where a whole class is defined in the `__init__`.
I have created some test cases:

```
class Odd:
    def __init__(self, numbers):
        def is_odd(n):
            # ok:return-in-init
            return n % 2 == 1
        self.numbers = filter(is_odd, numbers)
        
        # ruleid:return-in-init
        return self.numbers

class Even:
    def __init__(self):
        class EvenNumber:
            def __init__(self, n):
                self.n = n
                # ruleid:return-in-init
                return n
            
            def is_even(self):
                # ok:return-in-init
                return self.n % 2 == 0
        
        self.number = EvenNumber()
    
    def not_init(self):
        class EvenNumber:
            def __init__(self, n):
                self.n = n
                # ruleid:return-in-init
                return n
            
            def is_even(self):
                # ok:return-in-init
                return self.n % 2 == 0
        
        # ok:return-in-init
        return EvenNumber()
```

I have attempted to solve this, but I can't think of a pattern that works perfectly. I tried variations on the following, but this no longer matches anything.

```
  - pattern-not-inside: |
      def $METHOD(...):
          ...
  - metavariable-pattern:
      metavariable: $METHOD
      patterns:
        - pattern-not: __init__
```

I tried this, but this is not valid:

```
  - patterns-either:
    - pattern-inside: |
        def __init__(self):
          ...
          def __init__(...):
              ...
    - pattern-not-inside: |
        def __init__(self):
            ...
            def $METHOD(...):
                ...
```


@inkz Do you have any idea to express this in a semgrep rule? Is this even possible?
One problem with metavariables in `pattern-not` and `pattern-not-inside` is that those operators may not match, in which case the metavariable is not defined, and then `metavariable-regex` and `metavariable-pattern` will evaluate to `false`.  Perhaps we should have a `defaults` field so that you can choose how to evaluate it in case the metavariable doesn't exist.
```
      - pattern-inside: |
          def __init__(...):
              ...
              def $F(...):
                  ...
```
appears to be matching the entire method (https://semgrep.dev/s/eK3E). Is this how it should be @aryx ?
@Sjord if you're into bug finding and want a low number of FPs then my suggestion would be to use this rule for now:
```
    patterns:
      - pattern-inside: |
          class $A(...):
              ...
      - pattern-inside: |
          def __init__(...):
              ...
      - pattern-not-inside: |
          def __init__(...):
              ...
              def $F(...):
                  ...
      - patterns:
          - pattern: return ...
          - pattern-not: return
          - pattern-not: return None
```
If there is any nested function then it won't find anything, but hopefully those are rare cases and you still get value from running it. I still think we should be able to write the rule you want. Maybe it's possible but I didn't succeed either.

Also note that many rules in semgrep-rules follow the "secure defaults" philosophy that R2C promotes. I am not sure if this is the case for this rule or not. But in those cases the rules define a best practice, and perhaps (I don't know) one of those best practices is not defining nested functions inside `__init__`.

@Sjord 
I tried to investigate this issue and was not able to come up with anything better than Iago's example: https://github.com/returntocorp/semgrep-rules/issues/1390#issuecomment-913520276.
What do you think? If it works for you I'll create a PR

Resolving patch:
diff --git a/python/lang/correctness/return-in-init.py b/python/lang/correctness/return-in-init.py
--- a/python/lang/correctness/return-in-init.py
+++ b/python/lang/correctness/return-in-init.py
@@ -75,3 +75,41 @@ class H:
     def __init__(self, x):
         # ok:return-in-init
         return None
+
+class Odd:
+    def __init__(self, numbers):
+        def is_odd(n):
+            # ok:return-in-init
+            return n % 2 == 1
+        self.numbers = filter(is_odd, numbers)
+
+        # todoruleid:return-in-init
+        return self.numbers
+
+class Even:
+    def __init__(self):
+        class EvenNumber:
+            def __init__(self, n):
+                self.n = n
+                # todoruleid:return-in-init
+                return n
+
+            def is_even(self):
+                # ok:return-in-init
+                return self.n % 2 == 0
+
+        self.number = EvenNumber()
+
+    def not_init(self):
+        class EvenNumber:
+            def __init__(self, n):
+                self.n = n
+                # ruleid:return-in-init
+                return n
+
+            def is_even(self):
+                # ok:return-in-init
+                return self.n % 2 == 0
+
+        # ok:return-in-init
+        return EvenNumber()


Test patch: