Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 335 additions & 0 deletions javascript/ql/src/semmle/javascript/dataflow/Nodes.qll
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,338 @@ DataFlow::SourceNode moduleMember(string path, string m) {
result = DataFlow::ssaDefinitionNode(ssa)
)
}

/**
* The string `method`, `getter`, or `setter`, representing the kind of a function member
* in a class.
*
* Can can be used with `ClassNode.getInstanceMember` to obtain members of a specific kind.
*/
class MemberKind extends string {
MemberKind() { this = "method" or this = "getter" or this = "setter" }
}

module MemberKind {
/** The kind of a method, such as `m() {}` */
MemberKind method() { result = "method" }

/** The kind of a getter accessor, such as `get f() {}`. */
MemberKind getter() { result = "getter" }

/** The kind of a setter accessor, such as `set f() {}`. */
MemberKind setter() { result = "setter" }

/** The `getter` and `setter` kinds. */
MemberKind accessor() { result = getter() or result = setter() }

/**
* Gets the member kind of a given syntactic member declaration in a class.
*/
MemberKind of(MemberDeclaration decl) {
decl instanceof GetterMethodDeclaration and result = getter()
or
decl instanceof SetterMethodDeclaration and result = setter()
or
decl instanceof MethodDeclaration and
not decl instanceof AccessorMethodDeclaration and
not decl instanceof ConstructorDeclaration and
result = method()
}
}

/**
* A data flow node corresponding to a class definition or a function definition
* acting as a class.
*
* The following patterns are recognized as classes and methods:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a note here about how to extend the set of recognized patterns?

* ```
* class C {
* method()
* }
*
* function F() {}
*
* F.prototype.method = function() {}
*
* F.prototype = {
* method: function() {}
* }
*
* extend(F.prototype, {
* method: function() {}
* });
* ```
*
* Additional patterns can be recognized as class nodes, by extending `DataFlow::ClassNode::Range`.
*/
class ClassNode extends DataFlow::SourceNode {
ClassNode::Range impl;

ClassNode() { this = impl }

/**
* Gets the name of the class, if it has one.
*/
string getName() { result = impl.getName() }

/**
* Gets a description of the class.
*/
string describe() { result = impl.describe() }

/**
* Gets the constructor function of this class.
*/
FunctionNode getConstructor() { result = impl.getConstructor() }

/**
* Gets an instance method declared in this class, with the given name, if any.
*
* Does not include methods from superclasses.
*/
FunctionNode getInstanceMethod(string name) { result = impl.getInstanceMember(name, MemberKind::method()) }

/**
* Gets an instance method declared in this class.
*
* The constructor is not considered an instance method.
*
* Does not include methods from superclasses.
*/
FunctionNode getAnInstanceMethod() { result = impl.getAnInstanceMember(MemberKind::method()) }

/**
* Gets the instance method, getter, or setter with the given name and kind.
*
* Does not include members from superclasses.
*/
FunctionNode getInstanceMember(string name, MemberKind kind) { result = impl.getInstanceMember(name, kind) }

/**
* Gets an instance method, getter, or setter with the given kind.
*
* Does not include members from superclasses.
*/
FunctionNode getAnInstanceMember(MemberKind kind) { result = impl.getAnInstanceMember(kind) }

/**
* Gets an instance method, getter, or setter declared in this class.
*
* Does not include members from superclasses.
*/
FunctionNode getAnInstanceMember() { result = impl.getAnInstanceMember(_) }

/**
* Gets the static method declared in this class with the given name.
*/
FunctionNode getStaticMethod(string name) { result = impl.getStaticMethod(name) }

/**
* Gets a static method declared in this class.
*
* The constructor is not considered a static method.
*/
FunctionNode getAStaticMethod() { result = impl.getAStaticMethod() }

/**
* Gets a direct super class of this class.
*/
ClassNode getADirectSuperClass() {
result.getConstructor().getAstNode() = impl
.getASuperClassNode()
.analyze()
.getAValue()
.(AbstractCallable)
.getFunction()
}
}

module ClassNode {
/**
* A dataflow node that should be considered a class node.
*
* Subclass this to introduce new kinds of class nodes. If you want to refine
* the definition of existing class nodes, subclass `DataFlow::ClassNode` instead.
*/
abstract class Range extends DataFlow::SourceNode {
/**
* Gets the name of the class, if it has one.
*/
abstract string getName();

/**
* Gets a description of the class.
*/
abstract string describe();

/**
* Gets the constructor function of this class.
*/
abstract FunctionNode getConstructor();

/**
* Gets the instance member with the given name and kind.
*/
abstract FunctionNode getInstanceMember(string name, MemberKind kind);

/**
* Gets an instance member with the given kind.
*/
abstract FunctionNode getAnInstanceMember(MemberKind kind);

/**
* Gets the static method of this class with the given name.
*/
abstract FunctionNode getStaticMethod(string name);

/**
* Gets a static method of this class.
*
* The constructor is not considered a static method.
*/
abstract FunctionNode getAStaticMethod();

/**
* Gets a dataflow node representing a class to be used as the super-class
* of this node.
*/
abstract DataFlow::Node getASuperClassNode();
}

/**
* An ES6 class as a `ClassNode` instance.
*/
private class ES6Class extends Range, DataFlow::ValueNode {
override ClassDefinition astNode;

override string getName() { result = astNode.getName() }

override string describe() { result = astNode.describe() }

override FunctionNode getConstructor() { result = astNode.getConstructor().getBody().flow() }

override FunctionNode getInstanceMember(string name, MemberKind kind) {
exists(MethodDeclaration method |
method = astNode.getMethod(name) and
not method.isStatic() and
kind = MemberKind::of(method) and
result = method.getBody().flow()
)
}

override FunctionNode getAnInstanceMember(MemberKind kind) {
exists(MethodDeclaration method |
method = astNode.getAMethod() and
not method.isStatic() and
kind = MemberKind::of(method) and
result = method.getBody().flow()
)
}

override FunctionNode getStaticMethod(string name) {
exists(MethodDeclaration method |
method = astNode.getMethod(name) and
method.isStatic() and
result = method.getBody().flow()
)
or
result = getAPropertySource(name)
}

override FunctionNode getAStaticMethod() {
exists(MethodDeclaration method |
method = astNode.getAMethod() and
method.isStatic() and
result = method.getBody().flow()
)
}

override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() }
}

/**
* A function definition with prototype manipulation as a `ClassNode` instance.
*/
class FunctionStyleClass extends Range, DataFlow::ValueNode {
override Function astNode;

FunctionStyleClass() { exists(getAPropertyReference("prototype")) }

override string getName() { result = astNode.getName() }

override string describe() { result = astNode.describe() }

override FunctionNode getConstructor() { result = this }

private PropertyAccessor getAnAccessor(MemberKind kind) {
result.getObjectExpr() = getAPrototypeReference().asExpr() and
(
kind = MemberKind::getter() and
result instanceof PropertyGetter
or
kind = MemberKind::setter() and
result instanceof PropertySetter
)
}

override FunctionNode getInstanceMember(string name, MemberKind kind) {
kind = MemberKind::method() and
result = getAPrototypeReference().getAPropertySource(name)
or
exists (PropertyAccessor accessor |
accessor = getAnAccessor(kind) and
accessor.getName() = name and
result = accessor.getInit().flow()
)
}

override FunctionNode getAnInstanceMember(MemberKind kind) {
kind = MemberKind::method() and
result = getAPrototypeReference().getAPropertyWrite().getRhs().getALocalSource()
or
exists (PropertyAccessor accessor |
accessor = getAnAccessor(kind) and
result = accessor.getInit().flow()
)
}

override FunctionNode getStaticMethod(string name) {
result = getAPropertySource(name)
}

override FunctionNode getAStaticMethod() {
result = getAPropertyWrite().getRhs().getALocalSource()
}

/**
* Gets a reference to the prototype of this class.
*/
private DataFlow::SourceNode getAPrototypeReference() {
result = getAPropertyRead("prototype")
or
result = getAPropertySource("prototype")
or
exists(ExtendCall call |
call.getDestinationOperand() = getAPropertyRead("prototype") and
result = call.getASourceOperand()
)
}

override DataFlow::Node getASuperClassNode() {
// C.prototype = Object.create(D.prototype)
exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto |
getAPropertySource("prototype") = objectCreate and
objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and
superProto.flowsTo(objectCreate.getArgument(0)) and
superProto.getPropertyName() = "prototype" and
result = superProto.getBase()
)
or
// C.prototype = new D()
exists(DataFlow::NewNode newCall |
getAPropertySource("prototype") = newCall and
result = newCall.getCalleeNode()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
| tst.js:4:17:4:21 | () {} | A.instanceMethod | method |
| tst.js:7:6:7:10 | () {} | A.bar | method |
| tst.js:9:10:9:14 | () {} | A.baz | getter |
| tst.js:17:19:17:31 | function() {} | B.foo | method |
| tst.js:21:19:21:31 | function() {} | C.bar | method |
| tst.js:25:13:25:17 | () {} | D.getter | getter |
| tst.js:26:13:26:18 | (x) {} | D.setter | setter |
| tst.js:27:4:27:8 | () {} | D.m | method |
4 changes: 4 additions & 0 deletions javascript/ql/test/library-tests/ClassNode/InstanceMember.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from DataFlow::ClassNode cls, string name, string kind
select cls.getInstanceMember(name, kind), cls.getName() + "." + name, kind
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
| tst.js:4:17:4:21 | () {} | A.instanceMethod |
| tst.js:7:6:7:10 | () {} | A.bar |
| tst.js:17:19:17:31 | function() {} | B.foo |
| tst.js:21:19:21:31 | function() {} | C.bar |
| tst.js:27:4:27:8 | () {} | D.m |
4 changes: 4 additions & 0 deletions javascript/ql/test/library-tests/ClassNode/InstanceMethod.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from DataFlow::ClassNode cls, string name
select cls.getInstanceMethod(name), cls.getName() + "." + name
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| tst.js:13:1:13:21 | class A ... ds A {} | A2 | tst.js:3:1:10:1 | class A ... () {}\\n} | A |
| tst.js:15:1:15:15 | function B() {} | B | tst.js:3:1:10:1 | class A ... () {}\\n} | A |
| tst.js:19:1:19:15 | function C() {} | C | tst.js:15:1:15:15 | function B() {} | B |
5 changes: 5 additions & 0 deletions javascript/ql/test/library-tests/ClassNode/SuperClass.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import javascript

from DataFlow::ClassNode cls, DataFlow::ClassNode sup
where sup = cls.getADirectSuperClass()
select cls, cls.getName(), sup, sup.getName()
Loading