Skip to content

Commit 87960b6

Browse files
authored
Python: Fix bad join in scope entry transfer
How it started: ``` Tuple counts for Base::BaseFlow::scope_entry_value_transfer_from_earlier#f76ef5bb#ffff/4@f2af49f5 after 18s: 1526390 ~0% {3} r1 = JOIN Base::BaseFlow::scope_entry_value_transfer_from_earlier#f76ef5bb#ffff#shared WITH Essa::EssaVariable::getScope#dispred#f0820431#ff ON FIRST 1 OUTPUT Rhs.1 'pred_scope', Lhs.0 'pred_var', Lhs.1 7798319 ~0% {4} r2 = JOIN r1 WITH Scope::Scope::precedes#dispred#f0820431#ff ON FIRST 1 OUTPUT Rhs.1 'succ_scope', Lhs.1 'pred_var', Lhs.2, Lhs.0 'pred_scope' 5427334 ~0% {4} r3 = JOIN Base::BaseFlow::scope_entry_value_transfer_from_earlier#f76ef5bb#ffff#shared#1 WITH Scope::Scope::precedes#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1 'pred_var', Lhs.2, Lhs.0 'pred_scope', Rhs.1 'succ_scope' 5426883 ~0% {4} r4 = r3 AND NOT Base::BaseFlow::scope_entry_value_transfer_from_earlier#f76ef5bb#ffff#antijoin_rhs(Lhs.0 'pred_var', Lhs.1, Lhs.2 'pred_scope', Lhs.3) 5426883 ~0% {5} r5 = SCAN r4 OUTPUT In.3, "__init__", In.0 'pred_var', In.1, In.2 'pred_scope' 2002084 ~0% {4} r6 = JOIN r5 WITH Scope::Scope::getName#dispred#f0820431#fb ON FIRST 2 OUTPUT Lhs.0, Lhs.2 'pred_var', Lhs.3, Lhs.4 'pred_scope' 39293988 ~2% {4} r7 = JOIN r6 WITH Scope::Scope::precedes#dispred#f0820431#ff ON FIRST 1 OUTPUT Rhs.1 'succ_scope', Lhs.1 'pred_var', Lhs.2, Lhs.3 'pred_scope' 47092307 ~0% {4} r8 = r2 UNION r7 94173236 ~7% {5} r9 = JOIN r8 WITH Essa::ScopeEntryDefinition::getScope#dispred#f0820431#ff_10#join_rhs ON FIRST 1 OUTPUT Lhs.2, Rhs.1 'succ_def', Lhs.1 'pred_var', Lhs.3 'pred_scope', Lhs.0 'succ_scope' 599441 ~1% {4} r10 = JOIN r9 WITH Essa::TEssaNodeDefinition#24e22a14#ffff_03#join_rhs ON FIRST 2 OUTPUT Lhs.2 'pred_var', Lhs.3 'pred_scope', Lhs.1 'succ_def', Lhs.4 'succ_scope' return r10 ``` How it ended: ``` Tuple counts for Base::essa_var_scope#f76ef5bb#fff/3@20fd243c after 153ms: 1526390 ~0% {2} r1 = JOIN Essa::EssaDefinition::getSourceVariable#dispred#f0820431#ff WITH Base::BaseFlow::reaches_exit#f76ef5bb#f ON FIRST 1 OUTPUT Lhs.0 'pred_var', Lhs.1 'var' 1526390 ~5% {3} r2 = JOIN r1 WITH Essa::EssaVariable::getScope#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1 'var', Rhs.1 'pred_scope', Lhs.0 'pred_var' return r2 ``` ``` Tuple counts for Base::scope_entry_def_scope#f76ef5bb#fff/3@34224fid after 40ms: 581249 ~1% {3} r1 = JOIN Essa::TEssaNodeDefinition#24e22a14#ffff_30#join_rhs WITH Essa::ScopeEntryDefinition::getScope#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1 'var', Rhs.1 'succ_scope', Lhs.0 'succ_def' return r1 ``` ``` Tuple counts for Base::scope_entry_value_transfer_through_init#f76ef5bb#ffff#shared/5@cb3c45lu after 76ms: 471230 ~0% {3} r1 = JOIN Variables::GlobalVariable#class#3aa06bbf#f WITH Base::scope_entry_def_scope#f76ef5bb#fff ON FIRST 1 OUTPUT Rhs.1 'arg1', Lhs.0 'arg0', Rhs.2 'arg2' 313791 ~2% {5} r2 = JOIN r1 WITH Base::step_through_init#f76ef5bb#fff ON FIRST 1 OUTPUT Lhs.1 'arg0', Lhs.0 'arg1', Lhs.2 'arg2', Rhs.1 'arg3', Rhs.2 'arg4' return r2 ``` ``` Tuple counts for Base::scope_entry_value_transfer_through_init#f76ef5bb#ffff#antijoin_rhs/5@886d8bvr after 67ms: 508926 ~0% {6} r1 = JOIN Base::scope_entry_value_transfer_through_init#f76ef5bb#ffff#shared WITH Exprs::Name::defines#dispred#f0820431#ff_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.4 'arg4', Lhs.0 'arg0', Lhs.1 'arg1', Lhs.2 'arg2', Lhs.3 'arg3' 25 ~46% {5} r2 = JOIN r1 WITH Exprs::Expr::getScope#dispred#f0820431#ff ON FIRST 2 OUTPUT Lhs.2 'arg0', Lhs.3 'arg1', Lhs.4 'arg2', Lhs.5 'arg3', Lhs.1 'arg4' return r2 ``` ``` Tuple counts for Base::scope_entry_value_transfer_through_init#f76ef5bb#ffff/4@87ec703f after 80ms: 313774 ~2% {5} r1 = Base::scope_entry_value_transfer_through_init#f76ef5bb#ffff#shared AND NOT Base::scope_entry_value_transfer_through_init#f76ef5bb#ffff#antijoin_rhs(Lhs.0, Lhs.1 'succ_scope', Lhs.2 'succ_def', Lhs.3 'pred_scope', Lhs.4) 313774 ~0% {4} r2 = SCAN r1 OUTPUT In.3 'pred_scope', In.0, In.1 'succ_scope', In.2 'succ_def' 313774 ~4% {4} r3 = JOIN r2 WITH @py_scope#f ON FIRST 1 OUTPUT Lhs.1, Lhs.0 'pred_scope', Lhs.2 'succ_scope', Lhs.3 'succ_def' 313778 ~0% {4} r4 = JOIN r3 WITH Base::essa_var_scope#f76ef5bb#fff ON FIRST 2 OUTPUT Rhs.2 'pred_var', Lhs.1 'pred_scope', Lhs.3 'succ_def', Lhs.2 'succ_scope' return r4 ``` ``` Tuple counts for Base::step_through_init#f76ef5bb#fff/3@7ba1ee1c after 17ms: 11763 ~0% {1} r1 = JOIN Scope::Scope::precedes#dispred#f0820431#ff#join_rhs WITH Scope::Scope::getName#dispred#f0820431#fb_10#join_rhs ON FIRST 1 OUTPUT Rhs.1 'init' 196671 ~4% {2} r2 = JOIN r1 WITH Scope::Scope::precedes#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.0 'init', Rhs.1 'succ_scope' 196671 ~6% {3} r3 = JOIN r2 WITH Scope::Scope::precedes#dispred#f0820431#ff_10#join_rhs ON FIRST 1 OUTPUT Lhs.1 'succ_scope', Rhs.1 'pred_scope', Lhs.0 'init' return r3 ``` ``` Tuple counts for Base::BaseFlow::scope_entry_value_transfer_from_earlier#f76ef5bb#ffff/4@4892f93f after 426ms: 1526390 ~0% {3} r1 = SCAN Base::essa_var_scope#f76ef5bb#fff OUTPUT In.1, In.0, In.2 'pred_var' 7798319 ~0% {4} r2 = JOIN r1 WITH Scope::Scope::precedes#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1, Rhs.1 'succ_scope', Rhs.0, Lhs.2 'pred_var' 285663 ~3% {4} r3 = JOIN r2 WITH Base::scope_entry_def_scope#f76ef5bb#fff ON FIRST 2 OUTPUT Lhs.3 'pred_var', Lhs.2 'pred_scope', Rhs.2 'succ_def', Lhs.1 'succ_scope' 599441 ~1% {4} r4 = Base::scope_entry_value_transfer_through_init#f76ef5bb#ffff UNION r3 return r4 ``` It's possible this could be improved even further, but I think this is good enough. (I'm not entirely happy with how many helper predicates I ended up needing, but it was the only way I could get the joins to happen in a semi-sensible order.)
1 parent 4101676 commit 87960b6

File tree

1 file changed

+41
-18
lines changed
  • python/ql/lib/semmle/python/pointsto

1 file changed

+41
-18
lines changed

python/ql/lib/semmle/python/pointsto/Base.qll

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,41 @@ predicate builtin_name_points_to(string name, Object value, ClassObject cls) {
273273
value = Object::builtin(name) and cls.asBuiltin() = value.asBuiltin().getClass()
274274
}
275275

276+
pragma[nomagic]
277+
private predicate essa_var_scope(SsaSourceVariable var, Scope pred_scope, EssaVariable pred_var) {
278+
BaseFlow::reaches_exit(pred_var) and
279+
pred_var.getScope() = pred_scope and
280+
var = pred_var.getSourceVariable()
281+
}
282+
283+
pragma[nomagic]
284+
private predicate scope_entry_def_scope(
285+
SsaSourceVariable var, Scope succ_scope, ScopeEntryDefinition succ_def
286+
) {
287+
var = succ_def.getSourceVariable() and
288+
succ_def.getScope() = succ_scope
289+
}
290+
291+
pragma[nomagic]
292+
private predicate step_through_init(Scope succ_scope, Scope pred_scope, Scope init) {
293+
init.getName() = "__init__" and
294+
init.precedes(succ_scope) and
295+
pred_scope.precedes(init)
296+
}
297+
298+
pragma[nomagic]
299+
private predicate scope_entry_value_transfer_through_init(
300+
EssaVariable pred_var, Scope pred_scope, ScopeEntryDefinition succ_def, Scope succ_scope
301+
) {
302+
exists(SsaSourceVariable var, Scope init |
303+
var instanceof GlobalVariable and
304+
essa_var_scope(var, pragma[only_bind_into](pred_scope), pred_var) and
305+
scope_entry_def_scope(var, succ_scope, succ_def) and
306+
step_through_init(succ_scope, pragma[only_bind_into](pred_scope), init) and
307+
not var.(Variable).getAStore().getScope() = init
308+
)
309+
}
310+
276311
module BaseFlow {
277312
predicate reaches_exit(EssaVariable var) { var.getAUse() = var.getScope().getANormalExit() }
278313

@@ -283,27 +318,15 @@ module BaseFlow {
283318
) {
284319
Stages::DataFlow::ref() and
285320
exists(SsaSourceVariable var |
286-
reaches_exit(pred_var) and
287-
pred_var.getScope() = pred_scope and
288-
var = pred_var.getSourceVariable() and
289-
var = succ_def.getSourceVariable() and
290-
succ_def.getScope() = succ_scope
321+
essa_var_scope(var, pred_scope, pred_var) and
322+
scope_entry_def_scope(var, succ_scope, succ_def)
291323
|
292324
pred_scope.precedes(succ_scope)
293-
or
294-
/*
295-
* If an `__init__` method does not modify the global variable, then
296-
* we can skip it and take the value directly from the module.
297-
*/
298-
299-
exists(Scope init |
300-
init.getName() = "__init__" and
301-
init.precedes(succ_scope) and
302-
pred_scope.precedes(init) and
303-
not var.(Variable).getAStore().getScope() = init and
304-
var instanceof GlobalVariable
305-
)
306325
)
326+
or
327+
// If an `__init__` method does not modify the global variable, then
328+
// we can skip it and take the value directly from the module.
329+
scope_entry_value_transfer_through_init(pred_var, pred_scope, succ_def, succ_scope)
307330
}
308331
}
309332

0 commit comments

Comments
 (0)