Skip to content

Commit 408fd3e

Browse files
committed
JS: Augment call graph using type-tracked class instances
1 parent 779d98a commit 408fd3e

File tree

2 files changed

+99
-7
lines changed

2 files changed

+99
-7
lines changed

javascript/ql/src/semmle/javascript/dataflow/Nodes.qll

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -642,16 +642,23 @@ class ClassNode extends DataFlow::SourceNode {
642642
*/
643643
FunctionNode getAStaticMethod() { result = impl.getAStaticMethod() }
644644

645+
/**
646+
* Gets a dataflow node that refers to the superclass of this class.
647+
*/
648+
DataFlow::Node getASuperClassNode() { result = impl.getASuperClassNode() }
649+
645650
/**
646651
* Gets a direct super class of this class.
647652
*/
648653
ClassNode getADirectSuperClass() {
649-
result.getConstructor().getAstNode() = impl
650-
.getASuperClassNode()
651-
.analyze()
652-
.getAValue()
653-
.(AbstractCallable)
654-
.getFunction()
654+
result.getAClassReference().flowsTo(getASuperClassNode())
655+
}
656+
657+
/**
658+
* Gets a direct subclass of this class.
659+
*/
660+
final ClassNode getADirectSubClass() {
661+
this = result.getADirectSuperClass()
655662
}
656663

657664
/**
@@ -662,6 +669,63 @@ class ClassNode extends DataFlow::SourceNode {
662669
or
663670
result = getAnInstanceMember().getReceiver()
664671
}
672+
673+
/**
674+
* Gets the abstract value representing the class itself.
675+
*/
676+
AbstractValue getAbstractClassValue() {
677+
result = this.(AnalyzedNode).getAValue()
678+
}
679+
680+
/**
681+
* Gets the abstract value representing an instance of this class.
682+
*/
683+
AbstractValue getAbstractInstanceValue() {
684+
result = AbstractInstance::of(getAstNode())
685+
}
686+
687+
/**
688+
* Gets a dataflow node that refers to this class object.
689+
*/
690+
private DataFlow::SourceNode getAClassReference(DataFlow::TypeTracker t) {
691+
t.start() and
692+
result.(AnalyzedNode).getAValue() = getAbstractClassValue()
693+
or
694+
exists(DataFlow::TypeTracker t2 |
695+
result = getAClassReference(t2).track(t2, t)
696+
)
697+
}
698+
699+
/**
700+
* Gets a dataflow node that refers to this class object.
701+
*/
702+
DataFlow::SourceNode getAClassReference() {
703+
result = getAClassReference(DataFlow::TypeTracker::end())
704+
}
705+
706+
/**
707+
* Gets a dataflow node that refers to an instance of this class.
708+
*/
709+
private DataFlow::SourceNode getAnInstanceReference(DataFlow::TypeTracker t) {
710+
result = getAClassReference(t.continue()).getAnInstantiation()
711+
or
712+
t.start() and
713+
result.(AnalyzedNode).getAValue() = getAbstractInstanceValue()
714+
or
715+
t.start() and
716+
result = getAReceiverNode()
717+
or
718+
exists(DataFlow::TypeTracker t2 |
719+
result = getAnInstanceReference(t2).track(t2, t)
720+
)
721+
}
722+
723+
/**
724+
* Gets a dataflow node that refers to an instance of this class.
725+
*/
726+
DataFlow::SourceNode getAnInstanceReference() {
727+
result = getAnInstanceReference(DataFlow::TypeTracker::end())
728+
}
665729
}
666730

667731
module ClassNode {
@@ -803,6 +867,9 @@ module ClassNode {
803867
kind = MemberKind::method() and
804868
result = getAPrototypeReference().getAPropertySource(name)
805869
or
870+
kind = MemberKind::method() and
871+
result = getConstructor().getReceiver().getAPropertyWrite(name).getRhs().getALocalSource()
872+
or
806873
exists(PropertyAccessor accessor |
807874
accessor = getAnAccessor(kind) and
808875
accessor.getName() = name and
@@ -814,6 +881,9 @@ module ClassNode {
814881
kind = MemberKind::method() and
815882
result = getAPrototypeReference().getAPropertyWrite().getRhs().getALocalSource()
816883
or
884+
kind = MemberKind::method() and
885+
result = getConstructor().getReceiver().getAPropertyWrite().getRhs().getALocalSource()
886+
or
817887
exists(PropertyAccessor accessor |
818888
accessor = getAnAccessor(kind) and
819889
result = accessor.getInit().flow()

javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,33 @@ private module CachedSteps {
9797
f = cap.getContainer()
9898
}
9999

100+
/**
101+
* Holds if the method invoked by `invoke` resolved to a member named `name` in `cls`
102+
* or one of its super classes.
103+
*/
104+
cached
105+
predicate callResolvesToClass(DataFlow::InvokeNode invoke, DataFlow::ClassNode cls, string name) {
106+
invoke = cls.getAnInstanceReference().getAMethodCall(name)
107+
or
108+
exists(DataFlow::ClassNode subclass |
109+
callResolvesToClass(invoke, subclass, name) and
110+
not exists(subclass.getAnInstanceMember(name)) and
111+
cls = subclass.getADirectSuperClass()
112+
)
113+
}
114+
100115
/**
101116
* Holds if `invk` may invoke `f`.
102117
*/
103118
cached
104-
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
119+
predicate calls(DataFlow::InvokeNode invk, Function f) {
120+
f = invk.getACallee(0)
121+
or
122+
exists(DataFlow::ClassNode cls, string name |
123+
callResolvesToClass(invk, cls, name) and
124+
f = cls.getInstanceMethod(name).getFunction()
125+
)
126+
}
105127

106128
/**
107129
* Holds if `invk` may invoke `f` indirectly through the given `callback` argument.

0 commit comments

Comments
 (0)