Skip to content

Commit 8a67917

Browse files
committed
Python: Use API graphs instead of points-to for simple built-ins
Removes the use of points-to for accessing various built-ins from three of the queries. In order for this to work I had to extend the lists of known built-ins slightly.
1 parent 07099f1 commit 8a67917

File tree

9 files changed

+42
-48
lines changed

9 files changed

+42
-48
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ module Builtins {
3030
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
3131
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
3232
// Added for compatibility
33-
"exec"
33+
"exec",
34+
// Added by the `site` module (available by default unless `-S` is used)
35+
"copyright", "credits", "exit", "quit"
3436
]
3537
or
3638
// Built-in constants shared between Python 2 and 3
@@ -49,8 +51,8 @@ module Builtins {
4951
or
5052
// Python 2 only
5153
result in [
52-
"basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload", "unichr",
53-
"unicode", "xrange"
54+
"apply", "basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload",
55+
"unichr", "unicode", "xrange"
5456
]
5557
}
5658

python/ql/src/Expressions/UseofApply.ql

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
*/
1111

1212
import python
13-
private import LegacyPointsTo
14-
private import semmle.python.types.Builtins
13+
private import semmle.python.ApiGraphs
1514

16-
from CallNode call, ControlFlowNodeWithPointsTo func
17-
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply"))
15+
from CallNode call
16+
where
17+
major_version() = 2 and
18+
call = API::builtin("apply").getACall().asCfgNode()
1819
select call, "Call to the obsolete builtin function 'apply'."

python/ql/src/Imports/DeprecatedModule.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import python
14-
private import LegacyPointsTo
14+
private import semmle.python.ApiGraphs
1515

1616
/**
1717
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
@@ -80,7 +80,7 @@ where
8080
name = imp.getName() and
8181
deprecated_module(name, instead, _, _) and
8282
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
83-
except.getType().(ExprWithPointsTo).pointsTo(ClassValue::importError()) and
83+
except.getType() = API::builtin("ImportError").getAValueReachableFromSource().asExpr() and
8484
except.containsInScope(imp)
8585
)
8686
select imp, deprecation_message(name) + replacement_message(name)

python/ql/src/Statements/ModificationOfLocals.ql

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
*/
1313

1414
import python
15-
private import LegacyPointsTo
15+
private import semmle.python.ApiGraphs
1616

17-
predicate originIsLocals(ControlFlowNodeWithPointsTo n) {
18-
n.pointsTo(_, _, Value::named("locals").getACall())
17+
predicate originIsLocals(ControlFlowNode n) {
18+
API::builtin("locals").getReturn().getAValueReachableFromSource().asCfgNode() = n
1919
}
2020

2121
predicate modification_of_locals(ControlFlowNode f) {
@@ -37,5 +37,5 @@ where
3737
// in module level scope `locals() == globals()`
3838
// see https://docs.python.org/3/library/functions.html#locals
3939
// FP report in https://github.com/github/codeql/issues/6674
40-
not a.getScope() instanceof ModuleScope
40+
not a.getScope() instanceof Module
4141
select a, "Modification of the locals() dictionary will have no effect on the local variables."

python/ql/src/Statements/SideEffectInAssert.ql

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414

1515
import python
16-
private import LegacyPointsTo
16+
private import semmle.python.ApiGraphs
1717

1818
predicate func_with_side_effects(Expr e) {
1919
exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() |
@@ -24,11 +24,11 @@ predicate func_with_side_effects(Expr e) {
2424
}
2525

2626
predicate call_with_side_effect(Call e) {
27-
e.getAFlowNode() = Value::named("subprocess.call").getACall()
28-
or
29-
e.getAFlowNode() = Value::named("subprocess.check_call").getACall()
30-
or
31-
e.getAFlowNode() = Value::named("subprocess.check_output").getACall()
27+
e.getAFlowNode() =
28+
API::moduleImport("subprocess")
29+
.getMember(["call", "check_call", "check_output"])
30+
.getACall()
31+
.asCfgNode()
3232
}
3333

3434
predicate probable_side_effect(Expr e) {

python/ql/src/Statements/UnnecessaryDelete.ql

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414

1515
import python
16-
private import LegacyPointsTo
16+
private import semmle.python.ApiGraphs
1717

1818
predicate isInsideLoop(AstNode node) {
1919
node.getParentNode() instanceof While
@@ -33,9 +33,9 @@ where
3333
not isInsideLoop(del) and
3434
// False positive: calling `sys.exc_info` within a function results in a
3535
// reference cycle, and an explicit call to `del` helps break this cycle.
36-
not exists(FunctionValue ex |
37-
ex = Value::named("sys.exc_info") and
38-
ex.getACall().getScope() = f
36+
not exists(API::CallNode call |
37+
call = API::moduleImport("sys").getMember("exc_info").getACall() and
38+
call.getScope() = f
3939
)
4040
select del, "Unnecessary deletion of local variable $@ in function $@.", e, e.toString(), f,
4141
f.getName()

python/ql/src/Statements/UnreachableCode.ql

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414

1515
import python
16-
private import LegacyPointsTo
16+
private import semmle.python.ApiGraphs
1717

1818
predicate typing_import(ImportingStmt is) {
1919
exists(Module m |
@@ -34,11 +34,7 @@ predicate unique_yield(Stmt s) {
3434
/** Holds if `contextlib.suppress` may be used in the same scope as `s` */
3535
predicate suppression_in_scope(Stmt s) {
3636
exists(With w |
37-
w.getContextExpr()
38-
.(Call)
39-
.getFunc()
40-
.(ExprWithPointsTo)
41-
.pointsTo(Value::named("contextlib.suppress")) and
37+
w.getContextExpr() = API::moduleImport("contextlib").getMember("suppress").getACall().asExpr() and
4238
w.getScope() = s.getScope()
4339
)
4440
}

python/ql/src/Statements/UseOfExit.ql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
*/
1313

1414
import python
15-
private import LegacyPointsTo
15+
private import semmle.python.ApiGraphs
1616

1717
from CallNode call, string name
18-
where call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::siteQuitter(name))
18+
where
19+
name = ["exit", "quit"] and
20+
call = API::builtin(name).getACall().asCfgNode()
1921
select call,
2022
"The '" + name +
2123
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified."

python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
*/
1313

1414
import python
15-
private import LegacyPointsTo
15+
private import semmle.python.ApiGraphs
1616
import Definition
1717

1818
predicate is_increment(Stmt s) {
@@ -41,23 +41,16 @@ predicate one_item_only(For f) {
4141
)
4242
}
4343

44-
predicate points_to_call_to_range(ControlFlowNode f) {
45-
/* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */
46-
exists(Value range |
47-
range = Value::named("range") or
48-
range = Value::named("xrange")
49-
|
50-
f = range.getACall()
51-
)
44+
/** Holds if `node` is a call to `range`, `xrange`, or `list(range(...))`. */
45+
predicate call_to_range(DataFlow::Node node) {
46+
node = API::builtin(["range", "xrange"]).getACall()
5247
or
53-
/* In case points-to fails due to 'from six.moves import range' or similar. */
54-
exists(string range | f.getNode().(Call).getFunc().(Name).getId() = range |
55-
range = "range" or range = "xrange"
56-
)
48+
/* Handle 'from six.moves import range' or similar. */
49+
node = API::moduleImport("six").getMember("moves").getMember(["range", "xrange"]).getACall()
5750
or
5851
/* Handle list(range(...)) and list(list(range(...))) */
59-
f.(CallNode).(ControlFlowNodeWithPointsTo).pointsTo().getClass() = ClassValue::list() and
60-
points_to_call_to_range(f.(CallNode).getArg(0))
52+
node = API::builtin("list").getACall() and
53+
call_to_range(node.(DataFlow::CallCfgNode).getArg(0))
6154
}
6255

6356
/** Whether n is a use of a variable that is a not effectively a constant. */
@@ -102,8 +95,8 @@ from For f, Variable v, string msg
10295
where
10396
f.getTarget() = v.getAnAccess() and
10497
not f.getAStmt().contains(v.getAnAccess()) and
105-
not points_to_call_to_range(f.getIter().getAFlowNode()) and
106-
not points_to_call_to_range(get_comp_iterable(f)) and
98+
not call_to_range(DataFlow::exprNode(f.getIter())) and
99+
not call_to_range(DataFlow::exprNode(get_comp_iterable(f).getNode())) and
107100
not name_acceptable_for_unused_variable(v) and
108101
not f.getScope().getName() = "genexpr" and
109102
not empty_loop(f) and

0 commit comments

Comments
 (0)