Skip to content

Commit 0432b01

Browse files
authored
Merge pull request #764 from asger-semmle/dataflow-classnode
Approved by esben-semmle, xiemaisi
2 parents dd84b60 + 4b4daa6 commit 0432b01

14 files changed

+418
-0
lines changed

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

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,338 @@ DataFlow::SourceNode moduleMember(string path, string m) {
465465
result = DataFlow::ssaDefinitionNode(ssa)
466466
)
467467
}
468+
469+
/**
470+
* The string `method`, `getter`, or `setter`, representing the kind of a function member
471+
* in a class.
472+
*
473+
* Can can be used with `ClassNode.getInstanceMember` to obtain members of a specific kind.
474+
*/
475+
class MemberKind extends string {
476+
MemberKind() { this = "method" or this = "getter" or this = "setter" }
477+
}
478+
479+
module MemberKind {
480+
/** The kind of a method, such as `m() {}` */
481+
MemberKind method() { result = "method" }
482+
483+
/** The kind of a getter accessor, such as `get f() {}`. */
484+
MemberKind getter() { result = "getter" }
485+
486+
/** The kind of a setter accessor, such as `set f() {}`. */
487+
MemberKind setter() { result = "setter" }
488+
489+
/** The `getter` and `setter` kinds. */
490+
MemberKind accessor() { result = getter() or result = setter() }
491+
492+
/**
493+
* Gets the member kind of a given syntactic member declaration in a class.
494+
*/
495+
MemberKind of(MemberDeclaration decl) {
496+
decl instanceof GetterMethodDeclaration and result = getter()
497+
or
498+
decl instanceof SetterMethodDeclaration and result = setter()
499+
or
500+
decl instanceof MethodDeclaration and
501+
not decl instanceof AccessorMethodDeclaration and
502+
not decl instanceof ConstructorDeclaration and
503+
result = method()
504+
}
505+
}
506+
507+
/**
508+
* A data flow node corresponding to a class definition or a function definition
509+
* acting as a class.
510+
*
511+
* The following patterns are recognized as classes and methods:
512+
* ```
513+
* class C {
514+
* method()
515+
* }
516+
*
517+
* function F() {}
518+
*
519+
* F.prototype.method = function() {}
520+
*
521+
* F.prototype = {
522+
* method: function() {}
523+
* }
524+
*
525+
* extend(F.prototype, {
526+
* method: function() {}
527+
* });
528+
* ```
529+
*
530+
* Additional patterns can be recognized as class nodes, by extending `DataFlow::ClassNode::Range`.
531+
*/
532+
class ClassNode extends DataFlow::SourceNode {
533+
ClassNode::Range impl;
534+
535+
ClassNode() { this = impl }
536+
537+
/**
538+
* Gets the name of the class, if it has one.
539+
*/
540+
string getName() { result = impl.getName() }
541+
542+
/**
543+
* Gets a description of the class.
544+
*/
545+
string describe() { result = impl.describe() }
546+
547+
/**
548+
* Gets the constructor function of this class.
549+
*/
550+
FunctionNode getConstructor() { result = impl.getConstructor() }
551+
552+
/**
553+
* Gets an instance method declared in this class, with the given name, if any.
554+
*
555+
* Does not include methods from superclasses.
556+
*/
557+
FunctionNode getInstanceMethod(string name) { result = impl.getInstanceMember(name, MemberKind::method()) }
558+
559+
/**
560+
* Gets an instance method declared in this class.
561+
*
562+
* The constructor is not considered an instance method.
563+
*
564+
* Does not include methods from superclasses.
565+
*/
566+
FunctionNode getAnInstanceMethod() { result = impl.getAnInstanceMember(MemberKind::method()) }
567+
568+
/**
569+
* Gets the instance method, getter, or setter with the given name and kind.
570+
*
571+
* Does not include members from superclasses.
572+
*/
573+
FunctionNode getInstanceMember(string name, MemberKind kind) { result = impl.getInstanceMember(name, kind) }
574+
575+
/**
576+
* Gets an instance method, getter, or setter with the given kind.
577+
*
578+
* Does not include members from superclasses.
579+
*/
580+
FunctionNode getAnInstanceMember(MemberKind kind) { result = impl.getAnInstanceMember(kind) }
581+
582+
/**
583+
* Gets an instance method, getter, or setter declared in this class.
584+
*
585+
* Does not include members from superclasses.
586+
*/
587+
FunctionNode getAnInstanceMember() { result = impl.getAnInstanceMember(_) }
588+
589+
/**
590+
* Gets the static method declared in this class with the given name.
591+
*/
592+
FunctionNode getStaticMethod(string name) { result = impl.getStaticMethod(name) }
593+
594+
/**
595+
* Gets a static method declared in this class.
596+
*
597+
* The constructor is not considered a static method.
598+
*/
599+
FunctionNode getAStaticMethod() { result = impl.getAStaticMethod() }
600+
601+
/**
602+
* Gets a direct super class of this class.
603+
*/
604+
ClassNode getADirectSuperClass() {
605+
result.getConstructor().getAstNode() = impl
606+
.getASuperClassNode()
607+
.analyze()
608+
.getAValue()
609+
.(AbstractCallable)
610+
.getFunction()
611+
}
612+
}
613+
614+
module ClassNode {
615+
/**
616+
* A dataflow node that should be considered a class node.
617+
*
618+
* Subclass this to introduce new kinds of class nodes. If you want to refine
619+
* the definition of existing class nodes, subclass `DataFlow::ClassNode` instead.
620+
*/
621+
abstract class Range extends DataFlow::SourceNode {
622+
/**
623+
* Gets the name of the class, if it has one.
624+
*/
625+
abstract string getName();
626+
627+
/**
628+
* Gets a description of the class.
629+
*/
630+
abstract string describe();
631+
632+
/**
633+
* Gets the constructor function of this class.
634+
*/
635+
abstract FunctionNode getConstructor();
636+
637+
/**
638+
* Gets the instance member with the given name and kind.
639+
*/
640+
abstract FunctionNode getInstanceMember(string name, MemberKind kind);
641+
642+
/**
643+
* Gets an instance member with the given kind.
644+
*/
645+
abstract FunctionNode getAnInstanceMember(MemberKind kind);
646+
647+
/**
648+
* Gets the static method of this class with the given name.
649+
*/
650+
abstract FunctionNode getStaticMethod(string name);
651+
652+
/**
653+
* Gets a static method of this class.
654+
*
655+
* The constructor is not considered a static method.
656+
*/
657+
abstract FunctionNode getAStaticMethod();
658+
659+
/**
660+
* Gets a dataflow node representing a class to be used as the super-class
661+
* of this node.
662+
*/
663+
abstract DataFlow::Node getASuperClassNode();
664+
}
665+
666+
/**
667+
* An ES6 class as a `ClassNode` instance.
668+
*/
669+
private class ES6Class extends Range, DataFlow::ValueNode {
670+
override ClassDefinition astNode;
671+
672+
override string getName() { result = astNode.getName() }
673+
674+
override string describe() { result = astNode.describe() }
675+
676+
override FunctionNode getConstructor() { result = astNode.getConstructor().getBody().flow() }
677+
678+
override FunctionNode getInstanceMember(string name, MemberKind kind) {
679+
exists(MethodDeclaration method |
680+
method = astNode.getMethod(name) and
681+
not method.isStatic() and
682+
kind = MemberKind::of(method) and
683+
result = method.getBody().flow()
684+
)
685+
}
686+
687+
override FunctionNode getAnInstanceMember(MemberKind kind) {
688+
exists(MethodDeclaration method |
689+
method = astNode.getAMethod() and
690+
not method.isStatic() and
691+
kind = MemberKind::of(method) and
692+
result = method.getBody().flow()
693+
)
694+
}
695+
696+
override FunctionNode getStaticMethod(string name) {
697+
exists(MethodDeclaration method |
698+
method = astNode.getMethod(name) and
699+
method.isStatic() and
700+
result = method.getBody().flow()
701+
)
702+
or
703+
result = getAPropertySource(name)
704+
}
705+
706+
override FunctionNode getAStaticMethod() {
707+
exists(MethodDeclaration method |
708+
method = astNode.getAMethod() and
709+
method.isStatic() and
710+
result = method.getBody().flow()
711+
)
712+
}
713+
714+
override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() }
715+
}
716+
717+
/**
718+
* A function definition with prototype manipulation as a `ClassNode` instance.
719+
*/
720+
class FunctionStyleClass extends Range, DataFlow::ValueNode {
721+
override Function astNode;
722+
723+
FunctionStyleClass() { exists(getAPropertyReference("prototype")) }
724+
725+
override string getName() { result = astNode.getName() }
726+
727+
override string describe() { result = astNode.describe() }
728+
729+
override FunctionNode getConstructor() { result = this }
730+
731+
private PropertyAccessor getAnAccessor(MemberKind kind) {
732+
result.getObjectExpr() = getAPrototypeReference().asExpr() and
733+
(
734+
kind = MemberKind::getter() and
735+
result instanceof PropertyGetter
736+
or
737+
kind = MemberKind::setter() and
738+
result instanceof PropertySetter
739+
)
740+
}
741+
742+
override FunctionNode getInstanceMember(string name, MemberKind kind) {
743+
kind = MemberKind::method() and
744+
result = getAPrototypeReference().getAPropertySource(name)
745+
or
746+
exists (PropertyAccessor accessor |
747+
accessor = getAnAccessor(kind) and
748+
accessor.getName() = name and
749+
result = accessor.getInit().flow()
750+
)
751+
}
752+
753+
override FunctionNode getAnInstanceMember(MemberKind kind) {
754+
kind = MemberKind::method() and
755+
result = getAPrototypeReference().getAPropertyWrite().getRhs().getALocalSource()
756+
or
757+
exists (PropertyAccessor accessor |
758+
accessor = getAnAccessor(kind) and
759+
result = accessor.getInit().flow()
760+
)
761+
}
762+
763+
override FunctionNode getStaticMethod(string name) {
764+
result = getAPropertySource(name)
765+
}
766+
767+
override FunctionNode getAStaticMethod() {
768+
result = getAPropertyWrite().getRhs().getALocalSource()
769+
}
770+
771+
/**
772+
* Gets a reference to the prototype of this class.
773+
*/
774+
private DataFlow::SourceNode getAPrototypeReference() {
775+
result = getAPropertyRead("prototype")
776+
or
777+
result = getAPropertySource("prototype")
778+
or
779+
exists(ExtendCall call |
780+
call.getDestinationOperand() = getAPropertyRead("prototype") and
781+
result = call.getASourceOperand()
782+
)
783+
}
784+
785+
override DataFlow::Node getASuperClassNode() {
786+
// C.prototype = Object.create(D.prototype)
787+
exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto |
788+
getAPropertySource("prototype") = objectCreate and
789+
objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and
790+
superProto.flowsTo(objectCreate.getArgument(0)) and
791+
superProto.getPropertyName() = "prototype" and
792+
result = superProto.getBase()
793+
)
794+
or
795+
// C.prototype = new D()
796+
exists(DataFlow::NewNode newCall |
797+
getAPropertySource("prototype") = newCall and
798+
result = newCall.getCalleeNode()
799+
)
800+
}
801+
}
802+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
| tst.js:4:17:4:21 | () {} | A.instanceMethod | method |
2+
| tst.js:7:6:7:10 | () {} | A.bar | method |
3+
| tst.js:9:10:9:14 | () {} | A.baz | getter |
4+
| tst.js:17:19:17:31 | function() {} | B.foo | method |
5+
| tst.js:21:19:21:31 | function() {} | C.bar | method |
6+
| tst.js:25:13:25:17 | () {} | D.getter | getter |
7+
| tst.js:26:13:26:18 | (x) {} | D.setter | setter |
8+
| tst.js:27:4:27:8 | () {} | D.m | method |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import javascript
2+
3+
from DataFlow::ClassNode cls, string name, string kind
4+
select cls.getInstanceMember(name, kind), cls.getName() + "." + name, kind
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| tst.js:4:17:4:21 | () {} | A.instanceMethod |
2+
| tst.js:7:6:7:10 | () {} | A.bar |
3+
| tst.js:17:19:17:31 | function() {} | B.foo |
4+
| tst.js:21:19:21:31 | function() {} | C.bar |
5+
| tst.js:27:4:27:8 | () {} | D.m |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import javascript
2+
3+
from DataFlow::ClassNode cls, string name
4+
select cls.getInstanceMethod(name), cls.getName() + "." + name
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| tst.js:13:1:13:21 | class A ... ds A {} | A2 | tst.js:3:1:10:1 | class A ... () {}\\n} | A |
2+
| tst.js:15:1:15:15 | function B() {} | B | tst.js:3:1:10:1 | class A ... () {}\\n} | A |
3+
| tst.js:19:1:19:15 | function C() {} | C | tst.js:15:1:15:15 | function B() {} | B |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
from DataFlow::ClassNode cls, DataFlow::ClassNode sup
4+
where sup = cls.getADirectSuperClass()
5+
select cls, cls.getName(), sup, sup.getName()

0 commit comments

Comments
 (0)