diff --git a/languages/python/custom/src/BareExcept/BareExcept.qhelp b/languages/python/custom/src/BareExcept/BareExcept.qhelp new file mode 100644 index 0000000..f2fa7e0 --- /dev/null +++ b/languages/python/custom/src/BareExcept/BareExcept.qhelp @@ -0,0 +1,47 @@ + + + + +

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

+ +

+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. +

+
+ + +

+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 Exception explicitly, +which still allows system exits and keyboard interrupts to propagate. +

+
+ + +

+The following example shows a bare except: clause that catches all exceptions: +

+ + + +

+The following example shows the corrected code that catches only the specific exception type expected: +

+ + +
+ + +
  • Python Documentation: Handling Exceptions
  • +
  • PEP 8: Programming Recommendations
  • +
  • CWE-396: Declaration of Catch for Generic Exception
  • +
    + +
    diff --git a/languages/python/custom/src/BareExcept/BareExcept.ql b/languages/python/custom/src/BareExcept/BareExcept.ql new file mode 100644 index 0000000..4e2f176 --- /dev/null +++ b/languages/python/custom/src/BareExcept/BareExcept.ql @@ -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." diff --git a/languages/python/custom/src/BareExcept/BareExceptBad.py b/languages/python/custom/src/BareExcept/BareExceptBad.py new file mode 100644 index 0000000..129a4ad --- /dev/null +++ b/languages/python/custom/src/BareExcept/BareExceptBad.py @@ -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 diff --git a/languages/python/custom/src/BareExcept/BareExceptGood.py b/languages/python/custom/src/BareExcept/BareExceptGood.py new file mode 100644 index 0000000..3f48313 --- /dev/null +++ b/languages/python/custom/src/BareExcept/BareExceptGood.py @@ -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 diff --git a/languages/python/custom/test/BareExcept/BareExcept.expected b/languages/python/custom/test/BareExcept/BareExcept.expected new file mode 100644 index 0000000..e3298ec --- /dev/null +++ b/languages/python/custom/test/BareExcept/BareExcept.expected @@ -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. | diff --git a/languages/python/custom/test/BareExcept/BareExcept.qlref b/languages/python/custom/test/BareExcept/BareExcept.qlref new file mode 100644 index 0000000..9046482 --- /dev/null +++ b/languages/python/custom/test/BareExcept/BareExcept.qlref @@ -0,0 +1 @@ +BareExcept/BareExcept.ql diff --git a/languages/python/custom/test/BareExcept/test.py b/languages/python/custom/test/BareExcept/test.py new file mode 100644 index 0000000..54f9e8d --- /dev/null +++ b/languages/python/custom/test/BareExcept/test.py @@ -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