Skip to content

Commit 16774ba

Browse files
committed
add support for named parameters in API graphs
1 parent 095c73f commit 16774ba

File tree

2 files changed

+64
-30
lines changed

2 files changed

+64
-30
lines changed

python/ql/lib/semmle/python/ApiGraphs.qll

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ module API {
124124
*/
125125
Node getParameter(int i) { result = this.getASuccessor(Label::parameter(i)) }
126126

127+
/**
128+
* Gets the node representing the parameter named `name` of the function represented by this node.
129+
*
130+
* This predicate may have multiple results when there are multiple invocations of this API component.
131+
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
132+
*/
133+
Node getNamedParameter(string name) { result = this.getASuccessor(Label::namedParameter(name)) }
134+
127135
/**
128136
* Gets the number of parameters of the function represented by this node.
129137
*/
@@ -491,7 +499,6 @@ module API {
491499
* rhs = m.getAnExportedValue(prop)
492500
* )
493501
* or
494-
* // TODO:
495502
*/
496503

497504
/*
@@ -503,10 +510,7 @@ module API {
503510
* )
504511
*/
505512

506-
exists(int i |
507-
lbl = Label::parameter(i) and
508-
argumentPassing(base, i, rhs)
509-
)
513+
argumentPassing(base, lbl, rhs)
510514
or
511515
exists(DataFlow::LocalSourceNode src, DataFlow::AttrWrite pw |
512516
use(base, src) and pw = trackUseNode(src).getAnAttributeWrite() and rhs = pw.getValue()
@@ -553,23 +557,20 @@ module API {
553557
)
554558
)
555559
or
556-
exists(DataFlow::Node def, CallableExpr fn, int i |
560+
exists(DataFlow::Node def, CallableExpr fn |
557561
rhs(base, def) and fn = trackDefNode(def).asExpr()
558562
|
559-
lbl = Label::parameter(i) and
560-
ref.asExpr() = fn.getInnerScope().getArg(i)
563+
exists(int i |
564+
lbl = Label::parameter(i) and
565+
ref.asExpr() = fn.getInnerScope().getArg(i)
566+
)
567+
or
568+
exists(string name |
569+
lbl = Label::namedParameter(name) and
570+
ref.asExpr() = fn.getInnerScope().getArgByName(name)
571+
)
561572
)
562573
or
563-
/*
564-
* or // TODO: Figure out classes.
565-
* exists(DataFlow::Node def, DataFlow::ClassNode cls, int i |
566-
* rhs(base, def) and cls = trackDefNode(def)
567-
* |
568-
* lbl = Label::parameter(i) and
569-
* ref = cls.getConstructor().getParameter(i)
570-
* )
571-
*/
572-
573574
// Built-ins, treated as members of the module `builtins`
574575
base = MkModuleImport("builtins") and
575576
lbl = Label::member(any(string name | ref = Builtins::likelyBuiltin(name)))
@@ -618,23 +619,25 @@ module API {
618619
}
619620

620621
/**
621-
* Holds if `arg` is passed as the `i`th argument to a use of `base`, either by means of a
622-
* full invocation, or in a partial function application.
622+
* Holds if `arg` is passed as an argument to a use of `base`.
623+
*
624+
* `lbl` is represents which parameter of the function was passed. Either a numbered parameter, or a named parameter.
623625
*
624626
* The receiver is considered to be argument -1.
625627
*/
626-
private predicate argumentPassing(TApiNode base, int i, DataFlow::Node arg) {
628+
private predicate argumentPassing(TApiNode base, Label::ApiLabel lbl, DataFlow::Node arg) {
627629
exists(DataFlow::Node use, DataFlow::LocalSourceNode pred |
628630
use(base, use) and pred = trackUseNode(use, _)
629631
|
630-
arg = pred.getACall().getArg(i)
631-
/*
632-
* or // TODO: Figure out self in argument.
633-
* arg = pred.getACall().getReceiver() and
634-
* i = -1
635-
*/
636-
632+
exists(int i |
633+
lbl = Label::parameter(i) and
634+
arg = pred.getACall().getArg(i)
635+
)
636+
or
637+
exists(string name | lbl = Label::namedParameter(name) |
638+
arg = pred.getACall().getArgByName(name)
637639
)
640+
)
638641
}
639642

640643
/**
@@ -778,6 +781,11 @@ module API {
778781
or
779782
exists(any(Function f).getArg(i))
780783
} or
784+
MkLabelNamedParameter(string name) {
785+
exists(any(DataFlow::CallCfgNode c).getArgByName(name))
786+
or
787+
exists(any(Function f).getArgByName(name))
788+
} or
781789
MkLabelReturn() or
782790
MkLabelSubclass() or
783791
MkLabelAwait()
@@ -819,13 +827,24 @@ module API {
819827

820828
LabelParameter() { this = MkLabelParameter(i) }
821829

822-
// TODO: Named parameters, spread arguments.
823830
override string toString() { result = "getParameter(" + i + ")" }
824831

825832
/** Gets the index of the parameter for this label. */
826833
int getIndex() { result = i }
827834
}
828835

836+
/** A label for a named parameter `name`. */
837+
class LabelNamedParameter extends ApiLabel {
838+
string name;
839+
840+
LabelNamedParameter() { this = MkLabelNamedParameter(name) }
841+
842+
override string toString() { result = "getNamedParameter(\"" + name + "\")" }
843+
844+
/** Gets the name of the parameter for this label. */
845+
string getName() { result = name }
846+
}
847+
829848
/** A label that gets the return value of a function. */
830849
class LabelReturn extends ApiLabel {
831850
LabelReturn() { this = MkLabelReturn() }
@@ -868,6 +887,9 @@ module API {
868887
/** Gets the `parameter` edge label for parameter `i`. */
869888
LabelParameter parameter(int i) { result.getIndex() = i }
870889

890+
/** Gets the `parameter` edge label for the named parameter `name`. */
891+
LabelNamedParameter namedParameter(string name) { result.getName() = name }
892+
871893
/** Gets the `return` edge label. */
872894
LabelReturn return() { any() }
873895

python/ql/test/library-tests/ApiGraphs/py3/deftest1.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,16 @@ def callback4(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("quack
3232
}
3333
otherDict.fourth = callback4
3434

35-
foo.quack(otherDict.fourth) #$ def=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("quack").getReturn()
35+
foo.quack(otherDict.fourth) #$ def=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("quack").getReturn()
36+
37+
def namedCallback(myName, otherName):
38+
# Using named parameters:
39+
myName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getNamedParameter("myName").getReturn()
40+
otherName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getNamedParameter("otherName").getReturn()
41+
# Using numbered parameters:
42+
myName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getParameter(0).getReturn()
43+
otherName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getParameter(1).getReturn()
44+
45+
foo.blob(namedCallback) #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getReturn()
46+
47+
foo.named(myName = 2) #$ def=moduleImport("mypkg").getMember("foo").getMember("named").getNamedParameter("myName")

0 commit comments

Comments
 (0)