rules:
  - id: lock_not_unlocked
    message: |
      Lock $LOCK not unlocked on branch with $COND
    languages: [go]
    severity: WARNING
    patterns:
      - pattern: |
         $LOCK.Lock()
         ...
         if $COND {
           ...
           return ...
         }
      # manual unlock before return
      - pattern-not: |
         $LOCK.Lock()
         ...
         if $COND {
           ...
           $LOCK.Unlock()
           ...
           return ...
         }    
      - pattern-not: |
         $LOCK.Lock()
         ...
         $LOCK.Unlock()
         ...
         if $COND {
           ...
           return ...
         }
      # manual unlock with release function
      - pattern-not: |
         $LOCK.Lock()
         ...
         $UNLOCKFN = $LOCK.Unlock
         ...
         if $COND {
           ...
           $UNLOCKFN()
           ...
           return ...
         }    
      - pattern-not: |
         $LOCK.Lock()
         ...
         $UNLOCKFN := $LOCK.Unlock
         ...
         if $COND {
           ...
           $UNLOCKFN()
           ...
           return ...
         }    
      # defered unlock
      - pattern-not: |
         $LOCK.Lock()
         ...
         defer $LOCK.Unlock()
         ...
         if $COND {
           ...
           return ...
         }
      - pattern-not: |
         $LOCK.Lock()
         ...
         if $COND {
           ...
           defer $LOCK.Unlock()
           ...
           return ...
         }
      - pattern-not: |
         $LOCK.Lock()
         ...
         defer func(){
           ...
           $LOCK.Unlock()
           ...
         }()
         ...
         if $COND {
           ...
           return ...
         }
      # deferred unlock with release function
      - pattern-not: |
         $LOCK.Lock()
         ...
         $UNLOCKFN := $LOCK.Unlock
         ...
         defer func() {
           ...
           $UNLOCKFN()
           ...
         }()
         ...
         if $COND {
           ...
           return ...
         }    
      - pattern-not: |
         $LOCK.Lock()
         ...
         $UNLOCKFN = $LOCK.Unlock
         ...
         defer func() {
           ...
           $UNLOCKFN()
           ...
         }()
         ...
         if $COND {
           ...
           return ...
         }
      # variation where defer is called first,
      # unlock function is changed afterwards
      - pattern-not-inside: |
         defer func() {
           ...
           $UNLOCKFN()
           ...
         }()
         ...
         $LOCK.Lock()
         ...
         $UNLOCKFN = $LOCK.Unlock
         ...
         if $COND {
           ...
           return ...
         }    
      # variation where defer is called previously, lock is reacquired
      # maybe include the Unlock call here?
      - pattern-not-inside: |
         defer $LOCK.Unlock()
         ...
         $LOCK.Lock()
         ...
         if $COND {
           ...
           return ...
         }    
  - id: read_lock_not_unlocked
    message: |
      Lock $LOCK not unlocked on branch with $COND
    languages: [go]
    severity: WARNING
    patterns:
      - pattern: |
         $LOCK.RLock()
         ...
         if $COND {
           ...
           return ...
         }
      # manual unlock before return
      - pattern-not: |
         $LOCK.RLock()
         ...
         if $COND {
           ...
           $LOCK.RUnlock()
           ...
           return ...
         }    
      - pattern-not: |
         $LOCK.RLock()
         ...
         $LOCK.RUnlock()
         ...
         if $COND {
           ...
           return ...
         }
      # manual unlock with release function
      - pattern-not: |
         $LOCK.RLock()
         ...
         $UNLOCKFN = $LOCK.RUnlock
         ...
         if $COND {
           ...
           $UNLOCKFN()
           ...
           return ...
         }    
      - pattern-not: |
         $LOCK.RLock()
         ...
         $UNLOCKFN := $LOCK.RUnlock
         ...
         if $COND {
           ...
           $UNLOCKFN()
           ...
           return ...
         }    
      # defered unlock
      - pattern-not: |
         $LOCK.RLock()
         ...
         defer $LOCK.RUnlock()
         ...
         if $COND {
           ...
           return ...
         }
      - pattern-not: |
         $LOCK.RLock()
         ...
         if $COND {
           ...
           defer $LOCK.RUnlock()
           ...
           return ...
         }
      - pattern-not: |
         $LOCK.RLock()
         ...
         defer func(){
           ...
           $LOCK.RUnlock()
           ...
         }()
         ...
         if $COND {
           ...
           return ...
         }
      # deferred unlock with release function
      - pattern-not: |
         $LOCK.RLock()
         ...
         $UNLOCKFN := $LOCK.RUnlock
         ...
         defer func() {
           ...
           $UNLOCKFN()
           ...
         }()
         ...
         if $COND {
           ...
           return ...
         }    
      - pattern-not: |
         $LOCK.RLock()
         ...
         $UNLOCKFN = $LOCK.RUnlock
         ...
         defer func() {
           ...
           $UNLOCKFN()
           ...
         }()
         ...
         if $COND {
           ...
           return ...
         }
      # variation where defer is called first,
      # unlock function is changed afterwards
      - pattern-not-inside: |
         defer func() {
           ...
           $UNLOCKFN()
           ...
         }()
         ...
         $LOCK.RLock()
         ...
         $UNLOCKFN = $LOCK.RUnlock
         ...
         if $COND {
           ...
           return ...
         }    
      # variation where defer is called previously, lock is reacquired
      # maybe include the Unlock call here?
      - pattern-not-inside: |
         defer $LOCK.RUnlock()
         ...
         $LOCK.RLock()
         ...
         if $COND {
           ...
           return ...
         }    