diff --git a/python/ql/src/Functions/ReturnConsistentTupleSizes.ql b/python/ql/src/Functions/ReturnConsistentTupleSizes.ql index 9046f52cecbd..f0cb83067e0f 100644 --- a/python/ql/src/Functions/ReturnConsistentTupleSizes.ql +++ b/python/ql/src/Functions/ReturnConsistentTupleSizes.ql @@ -4,6 +4,7 @@ * @kind problem * @tags reliability * maintainability + * quality * @problem.severity recommendation * @sub-severity high * @precision high @@ -11,13 +12,15 @@ */ import python +import semmle.python.ApiGraphs -predicate returns_tuple_of_size(Function func, int size, AstNode origin) { - exists(Return return, TupleValue val | +predicate returns_tuple_of_size(Function func, int size, Tuple tuple) { + exists(Return return, DataFlow::Node value | + value.asExpr() = return.getValue() and return.getScope() = func and - return.getValue().pointsTo(val, origin) + any(DataFlow::LocalSourceNode n | n.asExpr() = tuple).flowsTo(value) | - size = val.length() + size = count(int n | exists(tuple.getElt(n))) ) } @@ -25,6 +28,8 @@ from Function func, int s1, int s2, AstNode t1, AstNode t2 where returns_tuple_of_size(func, s1, t1) and returns_tuple_of_size(func, s2, t2) and - s1 < s2 + s1 < s2 and + // Don't report on functions that have a return type annotation + not exists(func.getDefinition().(FunctionExpr).getReturns()) select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2, "tuple of size " + s2 diff --git a/python/ql/src/change-notes/2025-03-27-modernize-mixed-tuple-returns-query.md b/python/ql/src/change-notes/2025-03-27-modernize-mixed-tuple-returns-query.md new file mode 100644 index 000000000000..57cf5c69a139 --- /dev/null +++ b/python/ql/src/change-notes/2025-03-27-modernize-mixed-tuple-returns-query.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- + +- The `py/mixed-tuple-returns` query no longer flags instances where the tuple is passed into the function as an argument, as this led to too many false positives. diff --git a/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.expected b/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.expected index fd4f1ee2dd70..2733ae8c26ac 100644 --- a/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.expected +++ b/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.expected @@ -1,2 +1 @@ | functions_test.py:306:1:306:39 | Function returning_different_tuple_sizes | returning_different_tuple_sizes returns $@ and $@. | functions_test.py:308:16:308:18 | Tuple | tuple of size 2 | functions_test.py:310:16:310:20 | Tuple | tuple of size 3 | -| functions_test.py:324:1:324:50 | Function indirectly_returning_different_tuple_sizes | indirectly_returning_different_tuple_sizes returns $@ and $@. | functions_test.py:319:12:319:14 | Tuple | tuple of size 2 | functions_test.py:322:12:322:16 | Tuple | tuple of size 3 | diff --git a/python/ql/test/query-tests/Functions/return_values/functions_test.py b/python/ql/test/query-tests/Functions/return_values/functions_test.py index 24b1943feeb5..9f72a7fec600 100644 --- a/python/ql/test/query-tests/Functions/return_values/functions_test.py +++ b/python/ql/test/query-tests/Functions/return_values/functions_test.py @@ -321,7 +321,7 @@ def function_returning_2_tuple(): def function_returning_3_tuple(): return 1,2,3 -def indirectly_returning_different_tuple_sizes(x): +def indirectly_returning_different_tuple_sizes(x): # OK, since we only look at local tuple returns if x: return function_returning_2_tuple() else: @@ -347,3 +347,9 @@ def ok_match2(x): # FP return 0 case _: return 1 + +def ok_tuple_returns_captured_in_type(x: bool) -> tuple[int, ...]: # OK because there is a type annotation present + if x: + return 1, 2 + else: + return 1, 2, 3