From c4ec331e965af920d576ba1976aea32c3c22b362 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 24 Feb 2026 22:24:19 +0000 Subject: [PATCH] Python: Port IllegalRaise.ql Adds a convenient way to get the class name for an immutable literal (to maintain the same output format as was provided by the points-to version). I don't know if people are in the habit of writing `raise 5`, but I guess `raise "NotImplemented"` (wrong on so many levels) is not entirely impossible. No test changes. --- .../new/internal/DataFlowDispatch.qll | 17 ++++++ python/ql/src/Exceptions/IllegalRaise.ql | 53 +++++++++++++++---- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll index 7a1e240572e..90e0ff8ea59 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll @@ -2134,6 +2134,23 @@ module DuckTyping { or f.getADecorator().(Name).getId() = "property" } + + /** Gets the name of the builtin class of the immutable literal `lit`. */ + string getClassName(ImmutableLiteral lit) { + lit instanceof IntegerLiteral and result = "int" + or + lit instanceof FloatLiteral and result = "float" + or + lit instanceof ImaginaryLiteral and result = "complex" + or + lit instanceof NegativeIntegerLiteral and result = "int" + or + lit instanceof StringLiteral and result = "str" + or + lit instanceof BooleanLiteral and result = "bool" + or + lit instanceof None and result = "NoneType" + } } /** diff --git a/python/ql/src/Exceptions/IllegalRaise.ql b/python/ql/src/Exceptions/IllegalRaise.ql index f9f263552b1..3ae99a55f7c 100644 --- a/python/ql/src/Exceptions/IllegalRaise.ql +++ b/python/ql/src/Exceptions/IllegalRaise.ql @@ -12,15 +12,48 @@ */ import python -import Raising -import Exceptions.NotImplemented -private import LegacyPointsTo +import semmle.python.dataflow.new.internal.DataFlowDispatch +import semmle.python.ApiGraphs +private import ExceptionTypes -from Raise r, ClassValue t +/** + * Holds if `r` raises an instance of a builtin non-exception class named `name`. + */ +private predicate raisesNonExceptionBuiltin(Raise r, string name) { + exists(Expr raised | raised = r.getRaised() | + API::builtin(name).getAValueReachableFromSource().asExpr() = raised + or + API::builtin(name).getAValueReachableFromSource().asExpr() = raised.(Call).getFunc() and + // Exclude `type` since `type(x)` returns the class of `x`, not a `type` instance + not name = "type" + ) and + not builtinException(name) +} + +from Raise r, string msg where - type_or_typeof(r, t, _) and - not t.isLegalExceptionType() and - not t.failedInference(_) and - not use_of_not_implemented_in_raise(r, _) -select r, - "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." + not raisesNonExceptionBuiltin(r, "NotImplemented") and + ( + exists(ExceptType t | + t.isRaisedBy(r) and + not t.isLegalExceptionType() and + not t.getName() = "None" and + msg = + "Illegal class '" + t.getName() + + "' raised; will result in a TypeError being raised instead." + ) + or + exists(ImmutableLiteral lit | lit = r.getRaised() | + msg = + "Illegal class '" + DuckTyping::getClassName(lit) + + "' raised; will result in a TypeError being raised instead." + ) + or + exists(string name | + raisesNonExceptionBuiltin(r, name) and + not r.getRaised() instanceof ImmutableLiteral and + not name = "None" and + msg = "Illegal class '" + name + "' raised; will result in a TypeError being raised instead." + ) + ) +select r, msg