Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions languages/python/custom/src/BareExcept/BareExcept.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>

<overview>
<p>
Using a bare <code>except:</code> clause without specifying an exception type is considered bad practice
because it catches all exceptions, including system exits (<code>SystemExit</code>), keyboard interrupts
(<code>KeyboardInterrupt</code>), and other critical exceptions that should typically propagate.
</p>

<p>
This makes debugging difficult, can hide real bugs, and may lead to incorrect program behavior by
catching exceptions that were not intended to be handled.
</p>
</overview>

<recommendation>
<p>
Always specify the exception types you expect to handle. If you need to catch multiple exception types,
use a tuple of exception types. If you must catch most exceptions, catch <code>Exception</code> explicitly,
which still allows system exits and keyboard interrupts to propagate.
</p>
</recommendation>

<example>
<p>
The following example shows a bare <code>except:</code> clause that catches all exceptions:
</p>

<sample src="BareExceptBad.py" />

<p>
The following example shows the corrected code that catches only the specific exception type expected:
</p>

<sample src="BareExceptGood.py" />
</example>

<references>
<li>Python Documentation: <a href="https://docs.python.org/3/tutorial/errors.html#handling-exceptions">Handling Exceptions</a></li>
<li>PEP 8: <a href="https://peps.python.org/pep-0008/#programming-recommendations">Programming Recommendations</a></li>
<li>CWE-396: <a href="https://cwe.mitre.org/data/definitions/396.html">Declaration of Catch for Generic Exception</a></li>
</references>

</qhelp>
18 changes: 18 additions & 0 deletions languages/python/custom/src/BareExcept/BareExcept.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @name Bare except clause
* @description Using a bare 'except:' clause catches all exceptions including system exits and keyboard interrupts, making debugging difficult and potentially hiding real bugs.
* @kind problem
* @problem.severity warning
* @precision high
* @id py/bare-except
* @tags maintainability
* code-quality
* external/cwe/cwe-396
*/

import python

from ExceptStmt except
where not exists(except.getType())
select except,
"Avoid using bare 'except:' clauses. Specify exception types to catch only expected errors."
12 changes: 12 additions & 0 deletions languages/python/custom/src/BareExcept/BareExceptBad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def parse_int(s: str) -> int:
try:
return int(s)
except: # BAD: Catches all exceptions including system exits
return 0

def read_config(filename):
try:
with open(filename) as f:
return f.read()
except: # BAD: Catches all exceptions, even KeyboardInterrupt
return None
21 changes: 21 additions & 0 deletions languages/python/custom/src/BareExcept/BareExceptGood.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
def parse_int(s: str) -> int:
try:
return int(s)
except ValueError: # GOOD: Catches only ValueError
return 0

def read_config(filename):
try:
with open(filename) as f:
return f.read()
except (IOError, OSError) as e: # GOOD: Catches specific file-related exceptions
print(f"Error reading file: {e}")
return None

# If you really need to catch most exceptions, use Exception explicitly
def process_with_logging(data):
try:
return process(data)
except Exception as e: # ACCEPTABLE: Explicitly catches Exception, not bare except
log_error(e)
return None
6 changes: 6 additions & 0 deletions languages/python/custom/test/BareExcept/BareExcept.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
| test.py:10:5:10:11 | ExceptStmt | Avoid using bare 'except:' clauses. Specify exception types to catch only expected errors. |
| test.py:26:5:26:11 | ExceptStmt | Avoid using bare 'except:' clauses. Specify exception types to catch only expected errors. |
| test.py:43:5:43:11 | ExceptStmt | Avoid using bare 'except:' clauses. Specify exception types to catch only expected errors. |
| test.py:72:9:72:15 | ExceptStmt | Avoid using bare 'except:' clauses. Specify exception types to catch only expected errors. |
| test.py:95:9:95:15 | ExceptStmt | Avoid using bare 'except:' clauses. Specify exception types to catch only expected errors. |
| test.py:115:5:115:11 | ExceptStmt | Avoid using bare 'except:' clauses. Specify exception types to catch only expected errors. |
1 change: 1 addition & 0 deletions languages/python/custom/test/BareExcept/BareExcept.qlref
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BareExcept/BareExcept.ql
151 changes: 151 additions & 0 deletions languages/python/custom/test/BareExcept/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""
Test cases for BareExcept query.
This file contains both compliant and non-compliant examples.
"""

# NON_COMPLIANT: Bare except clause
def parse_int_bare_except(s: str) -> int:
try:
return int(s)
except: # DETECT: Bare except clause
return 0


# COMPLIANT: Specific exception type
def parse_int_specific(s: str) -> int:
try:
return int(s)
except ValueError:
return 0


# NON_COMPLIANT: Bare except with pass
def process_data_bare_except():
try:
risky_operation()
except: # DETECT: Bare except clause
pass


# COMPLIANT: Specific exception with pass
def process_data_specific():
try:
risky_operation()
except IOError:
pass


# NON_COMPLIANT: Bare except that logs
def read_file_bare_except(filename):
try:
with open(filename) as f:
return f.read()
except: # DETECT: Bare except clause
print("Error reading file")
return None


# COMPLIANT: Multiple specific exceptions
def read_file_specific(filename):
try:
with open(filename) as f:
return f.read()
except (IOError, OSError) as e:
print(f"Error reading file: {e}")
return None


# COMPLIANT: Catch Exception (not bare)
def process_with_exception_base(data):
try:
return process(data)
except Exception as e:
log_error(e)
return None


# NON_COMPLIANT: Nested bare except
def nested_bare_except():
try:
try:
dangerous_operation()
except: # DETECT: Bare except clause
cleanup()
except ValueError:
pass


# COMPLIANT: Nested with specific exceptions
def nested_specific():
try:
try:
dangerous_operation()
except KeyError:
cleanup()
except ValueError:
pass


# NON_COMPLIANT: Bare except in loop
def process_items_bare_except(items):
results = []
for item in items:
try:
results.append(process(item))
except: # DETECT: Bare except clause
continue
return results


# COMPLIANT: Specific exception in loop
def process_items_specific(items):
results = []
for item in items:
try:
results.append(process(item))
except (ValueError, TypeError):
continue
return results


# NON_COMPLIANT: Bare except with finally
def operation_with_finally_bare():
try:
perform_operation()
except: # DETECT: Bare except clause
handle_error()
finally:
cleanup()


# COMPLIANT: Specific exception with finally
def operation_with_finally_specific():
try:
perform_operation()
except RuntimeError:
handle_error()
finally:
cleanup()


# Helper functions (not real implementations, just for test structure)
def risky_operation():
pass

def process(data):
return data

def log_error(e):
pass

def dangerous_operation():
pass

def cleanup():
pass

def perform_operation():
pass

def handle_error():
pass