зеркало из https://github.com/github/codeql.git
Merge pull request #764 from asger-semmle/dataflow-classnode
Approved by esben-semmle, xiemaisi
This commit is contained in:
Коммит
0432b01e3b
|
@ -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:
|
||||
* ```
|
||||
* 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 |
|
|
@ -0,0 +1,4 @@
|
|||
import javascript
|
||||
|
||||
from DataFlow::ClassNode cls, string name, string kind
|
||||
select cls.getInstanceMember(name, kind), cls.getName() + "." + name, kind
|
|
@ -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 |
|
|
@ -0,0 +1,4 @@
|
|||
import javascript
|
||||
|
||||
from DataFlow::ClassNode cls, string name
|
||||
select cls.getInstanceMethod(name), cls.getName() + "." + name
|
|
@ -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 |
|
|
@ -0,0 +1,5 @@
|
|||
import javascript
|
||||
|
||||
from DataFlow::ClassNode cls, DataFlow::ClassNode sup
|
||||
where sup = cls.getADirectSuperClass()
|
||||
select cls, cls.getName(), sup, sup.getName()
|
|
@ -0,0 +1,28 @@
|
|||
import * as dummy from 'dummy'; // treat as module
|
||||
|
||||
class A {
|
||||
instanceMethod() {}
|
||||
static staticMethod() {}
|
||||
|
||||
bar() {}
|
||||
|
||||
get baz() {}
|
||||
}
|
||||
|
||||
|
||||
class A2 extends A {}
|
||||
|
||||
function B() {}
|
||||
B.prototype = Object.create(A.prototype);
|
||||
B.prototype.foo = function() {}
|
||||
|
||||
function C() {}
|
||||
C.prototype = new B();
|
||||
C.prototype.bar = function() {}
|
||||
|
||||
function D() {}
|
||||
D.prototype = {
|
||||
get getter() {},
|
||||
set setter(x) {},
|
||||
m() {}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
| fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | () {} |
|
||||
| points.js:1:1:18:1 | class P ... ;\\n }\\n} | points.js:2:14:5:3 | (x, y) ... y;\\n } |
|
||||
| points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:14:24:3 | (x, y, ... c;\\n } |
|
||||
| staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:1:15:1:14 | () {} |
|
||||
| tst.js:1:9:4:1 | class { ... */ }\\n} | tst.js:2:16:2:50 | () { /* ... r. */ } |
|
||||
| tst.js:6:1:8:1 | class B ... t); }\\n} | tst.js:7:14:7:38 | () { su ... get); } |
|
||||
| tst.js:11:1:14:1 | class C ... () {}\\n} | tst.js:11:9:11:8 | () {} |
|
|
@ -0,0 +1,4 @@
|
|||
import javascript
|
||||
|
||||
from DataFlow::ClassNode class_
|
||||
select class_, class_.getConstructor()
|
|
@ -0,0 +1,4 @@
|
|||
| points.js:1:1:18:1 | class P ... ;\\n }\\n} | toString | points.js:11:11:13:3 | () {\\n ... )";\\n } |
|
||||
| points.js:20:1:33:1 | class C ... ;\\n }\\n} | toString | points.js:26:11:28:3 | () {\\n ... ur;\\n } |
|
||||
| tst.js:1:9:4:1 | class { ... */ }\\n} | constructor | tst.js:3:18:3:56 | () { /* ... r. */ } |
|
||||
| tst.js:11:1:14:1 | class C ... () {}\\n} | m | tst.js:12:4:12:8 | () {} |
|
|
@ -0,0 +1,4 @@
|
|||
import javascript
|
||||
|
||||
from DataFlow::ClassNode class_, string name
|
||||
select class_, name, class_.getInstanceMethod(name)
|
|
@ -0,0 +1,3 @@
|
|||
| points.js:1:1:18:1 | class P ... ;\\n }\\n} | className | points.js:15:19:17:3 | () {\\n ... t";\\n } |
|
||||
| points.js:20:1:33:1 | class C ... ;\\n }\\n} | className | points.js:30:19:32:3 | () {\\n ... t";\\n } |
|
||||
| staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | constructor | staticConstructor.js:2:21:2:59 | () { re ... tor"; } |
|
|
@ -0,0 +1,4 @@
|
|||
import javascript
|
||||
|
||||
from DataFlow::ClassNode class_, string name
|
||||
select class_, name, class_.getStaticMethod(name)
|
Загрузка…
Ссылка в новой задаче