From b9a5b3b628b699f7415f8b4738191f613c041f5a Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 26 Nov 2025 13:55:20 +0000 Subject: [PATCH] Python: Remove points-to from `SSA.ql` Happily, this was not as deeply entwined as it looked at first glance. --- python/ql/lib/LegacyPointsTo.qll | 88 +++++++++++++++++++++++++++++ python/ql/lib/semmle/python/SSA.qll | 86 +--------------------------- 2 files changed, 89 insertions(+), 85 deletions(-) diff --git a/python/ql/lib/LegacyPointsTo.qll b/python/ql/lib/LegacyPointsTo.qll index 9e6efdb29cf..99b49a91986 100644 --- a/python/ql/lib/LegacyPointsTo.qll +++ b/python/ql/lib/LegacyPointsTo.qll @@ -340,3 +340,91 @@ Object getLiteralObject(ImmutableLiteral l) { name_consts(l, "None") and result = theNoneObject() } + +private predicate gettext_installed() { + // Good enough (and fast) approximation + exists(Module m | m.getName() = "gettext") +} + +private predicate builtin_constant(string name) { + exists(Object::builtin(name)) + or + name = "WindowsError" + or + name = "_" and gettext_installed() +} + +/** Whether this name is (almost) always defined, ie. it is a builtin or VM defined name */ +predicate globallyDefinedName(string name) { builtin_constant(name) or auto_name(name) } + +private predicate auto_name(string name) { + name = "__file__" or name = "__builtins__" or name = "__name__" +} + +/** An extension of `SsaVariable` that provides points-to related methods. */ +class SsaVariableWithPointsTo extends SsaVariable { + /** Gets an argument of the phi function defining this variable, pruned of unlikely edges. */ + SsaVariable getAPrunedPhiInput() { + result = this.getAPhiInput() and + exists(BasicBlock incoming | incoming = this.getPredecessorBlockForPhiArgument(result) | + not incoming.getLastNode().(RaisingNode).unlikelySuccessor(this.getDefinition()) + ) + } + + /** Gets the incoming edges for a Phi node, pruned of unlikely edges. */ + private BasicBlockWithPointsTo getAPrunedPredecessorBlockForPhi() { + result = this.getAPredecessorBlockForPhi() and + not result.unlikelySuccessor(this.getDefinition().getBasicBlock()) + } + + private predicate implicitlyDefined() { + not exists(this.getDefinition()) and + not py_ssa_phi(this, _) and + exists(GlobalVariable var | this.getVariable() = var | + globallyDefinedName(var.getId()) + or + var.getId() = "__path__" and var.getScope().(Module).isPackageInit() + ) + } + + /** Whether this variable may be undefined */ + predicate maybeUndefined() { + not exists(this.getDefinition()) and not py_ssa_phi(this, _) and not this.implicitlyDefined() + or + this.getDefinition().isDelete() + or + exists(SsaVariableWithPointsTo var | var = this.getAPrunedPhiInput() | var.maybeUndefined()) + or + /* + * For phi-nodes, there must be a corresponding phi-input for each control-flow + * predecessor. Otherwise, the variable will be undefined on that incoming edge. + * WARNING: the same phi-input may cover multiple predecessors, so this check + * cannot be done by counting. + */ + + exists(BasicBlock incoming | + reaches_end(incoming) and + incoming = this.getAPrunedPredecessorBlockForPhi() and + not this.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming) + ) + } + + override string getAQlClass() { none() } +} + +private predicate reaches_end(BasicBlock b) { + not exits_early(b) and + ( + /* Entry point */ + not exists(BasicBlock prev | prev.getASuccessor() = b) + or + exists(BasicBlock prev | prev.getASuccessor() = b | reaches_end(prev)) + ) +} + +private predicate exits_early(BasicBlock b) { + exists(FunctionObject f | + f.neverReturns() and + f.getACall().getBasicBlock() = b + ) +} diff --git a/python/ql/lib/semmle/python/SSA.qll b/python/ql/lib/semmle/python/SSA.qll index fc98e5975fb..b71bd95de79 100644 --- a/python/ql/lib/semmle/python/SSA.qll +++ b/python/ql/lib/semmle/python/SSA.qll @@ -1,7 +1,6 @@ /** SSA library */ import python -private import LegacyPointsTo /** * A single static assignment variable. @@ -62,14 +61,6 @@ class SsaVariable extends @py_ssa_var { ) } - /** Gets an argument of the phi function defining this variable, pruned of unlikely edges. */ - SsaVariable getAPrunedPhiInput() { - result = this.getAPhiInput() and - exists(BasicBlock incoming | incoming = this.getPredecessorBlockForPhiArgument(result) | - not incoming.getLastNode().(RaisingNode).unlikelySuccessor(this.getDefinition()) - ) - } - /** Gets a variable that ultimately defines this variable and is not itself defined by another variable */ SsaVariable getAnUltimateDefinition() { result = this and not exists(this.getAPhiInput()) @@ -86,17 +77,11 @@ class SsaVariable extends @py_ssa_var { string getId() { result = this.getVariable().getId() } /** Gets the incoming edges for a Phi node. */ - private BasicBlock getAPredecessorBlockForPhi() { + BasicBlock getAPredecessorBlockForPhi() { exists(this.getAPhiInput()) and result.getASuccessor() = this.getDefinition().getBasicBlock() } - /** Gets the incoming edges for a Phi node, pruned of unlikely edges. */ - private BasicBlockWithPointsTo getAPrunedPredecessorBlockForPhi() { - result = this.getAPredecessorBlockForPhi() and - not result.unlikelySuccessor(this.getDefinition().getBasicBlock()) - } - /** Whether it is possible to reach a use of this variable without passing a definition */ predicate reachableWithoutDefinition() { not exists(this.getDefinition()) and not py_ssa_phi(this, _) @@ -116,38 +101,6 @@ class SsaVariable extends @py_ssa_var { ) } - /** Whether this variable may be undefined */ - predicate maybeUndefined() { - not exists(this.getDefinition()) and not py_ssa_phi(this, _) and not this.implicitlyDefined() - or - this.getDefinition().isDelete() - or - exists(SsaVariable var | var = this.getAPrunedPhiInput() | var.maybeUndefined()) - or - /* - * For phi-nodes, there must be a corresponding phi-input for each control-flow - * predecessor. Otherwise, the variable will be undefined on that incoming edge. - * WARNING: the same phi-input may cover multiple predecessors, so this check - * cannot be done by counting. - */ - - exists(BasicBlock incoming | - reaches_end(incoming) and - incoming = this.getAPrunedPredecessorBlockForPhi() and - not this.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming) - ) - } - - private predicate implicitlyDefined() { - not exists(this.getDefinition()) and - not py_ssa_phi(this, _) and - exists(GlobalVariable var | this.getVariable() = var | - globallyDefinedName(var.getId()) - or - var.getId() = "__path__" and var.getScope().(Module).isPackageInit() - ) - } - /** * Gets the global variable that is accessed if this local is undefined. * Only applies to local variables in class scopes. @@ -174,43 +127,6 @@ class SsaVariable extends @py_ssa_var { } } -private predicate reaches_end(BasicBlock b) { - not exits_early(b) and - ( - /* Entry point */ - not exists(BasicBlock prev | prev.getASuccessor() = b) - or - exists(BasicBlock prev | prev.getASuccessor() = b | reaches_end(prev)) - ) -} - -private predicate exits_early(BasicBlock b) { - exists(FunctionObject f | - f.neverReturns() and - f.getACall().getBasicBlock() = b - ) -} - -private predicate gettext_installed() { - // Good enough (and fast) approximation - exists(Module m | m.getName() = "gettext") -} - -private predicate builtin_constant(string name) { - exists(Object::builtin(name)) - or - name = "WindowsError" - or - name = "_" and gettext_installed() -} - -private predicate auto_name(string name) { - name = "__file__" or name = "__builtins__" or name = "__name__" -} - -/** Whether this name is (almost) always defined, ie. it is a builtin or VM defined name */ -predicate globallyDefinedName(string name) { builtin_constant(name) or auto_name(name) } - /** An SSA variable that is backed by a global variable */ class GlobalSsaVariable extends EssaVariable { GlobalSsaVariable() { this.getSourceVariable() instanceof GlobalVariable }