-
Notifications
You must be signed in to change notification settings - Fork 1.9k
JS: More data flow through classes #760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -462,3 +462,138 @@ DataFlow::SourceNode moduleMember(string path, string m) { | |
| result = DataFlow::ssaDefinitionNode(ssa) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * 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() {} | ||
| * }); | ||
| * ``` | ||
| */ | ||
| class ClassNode extends DataFlow::SourceNode, DataFlow::ValueNode { | ||
| ClassNode() { | ||
| astNode instanceof ClassDefinition | ||
| or | ||
| astNode instanceof Function and | ||
| exists(getAPropertyReference("prototype")) | ||
| } | ||
|
|
||
| /** | ||
| * Gets the name of the class, if it has one. | ||
| */ | ||
| string getName() { | ||
| result = astNode.(ClassDefinition).getName() | ||
| or | ||
| result = astNode.(Function).getName() | ||
| } | ||
|
|
||
| /** | ||
| * Gets a description of the class. | ||
| */ | ||
| string describe() { | ||
| result = astNode.(ClassDefinition).describe() | ||
| or | ||
| result = astNode.(Function).describe() | ||
| } | ||
|
|
||
| /** | ||
| * Gets the constructor function of this class. | ||
| */ | ||
| FunctionNode getConstructor() { | ||
| result = astNode.(ClassDefinition).getConstructor().getBody().flow() | ||
| or | ||
| result = this | ||
| } | ||
|
|
||
| /** | ||
| * Gets an instance method with the given name, if any. | ||
| */ | ||
| FunctionNode getInstanceMethod(string name) { | ||
| exists (MethodDeclaration method | | ||
| method = astNode.(ClassDefinition).getMethod(name) and | ||
| not method.isStatic() and | ||
| not method.isAmbient() and | ||
| not method instanceof ConstructorDeclaration and | ||
| result = method.getBody().flow() | ||
| ) | ||
| or | ||
| result = getAPrototypeReference().getAPropertyWrite(name).getRhs().getALocalSource() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the possibility of multiple prototype definitions and multiple method definitions motivates a name change to |
||
| } | ||
|
|
||
| /** | ||
| * Gets an instance method of this class. | ||
| * | ||
| * The constructor is not considered an instance method. | ||
| */ | ||
| FunctionNode getAnInstanceMethod() { | ||
| exists (MethodDeclaration method | | ||
| method = astNode.(ClassDefinition).getAMethod() and | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This duplicates most of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some duplication here can't be avoided due to methods with computed property names. |
||
| not method.isStatic() and | ||
| not method.isAmbient() and | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really want the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is the dataflow library, I figured we should ignore things that don't have any runtime behaviour. |
||
| not method instanceof ConstructorDeclaration and | ||
| result = method.getBody().flow() | ||
| ) | ||
| or | ||
| result = getAPrototypeReference().getAPropertyWrite().getRhs().getALocalSource() | ||
| } | ||
|
|
||
| /** | ||
| * Gets the static method of this class with the given name. | ||
| */ | ||
| FunctionNode getStaticMethod(string name) { | ||
| exists (MethodDeclaration method | | ||
| method = astNode.(ClassDefinition).getMethod(name) and | ||
| method.isStatic() and | ||
| not method.isAmbient() and | ||
| result = method.getBody().flow() | ||
| ) | ||
| or | ||
| result = getAPropertyWrite(name).getRhs().getALocalSource() | ||
| } | ||
|
|
||
| /** | ||
| * Gets a static method of this class. | ||
| * | ||
| * The constructor is not considered a static method. | ||
| */ | ||
| FunctionNode getAStaticMethod() { | ||
| exists (MethodDeclaration method | | ||
| method = astNode.(ClassDefinition).getAMethod() and | ||
| method.isStatic() and | ||
| not method.isAmbient() and | ||
| result = method.getBody().flow() | ||
| ) | ||
| or | ||
| 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() | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | () {} | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import javascript | ||
|
|
||
| from DataFlow::ClassNode class_ | ||
| select class_, class_.getConstructor() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| | points.js:1:1:18:1 | class P ... ;\\n }\\n} | dist | points.js:7:11:9:3 | () {\\n ... y);\\n } | | ||
| | 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 | () {} | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import javascript | ||
|
|
||
| from DataFlow::ClassNode class_, string name | ||
| select class_, name, class_.getInstanceMethod(name) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"; } | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import javascript | ||
|
|
||
| from DataFlow::ClassNode class_, string name | ||
| select class_, name, class_.getStaticMethod(name) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import * as dummy from 'dummy'; // treat as module | ||
|
|
||
| class EcmaClass { | ||
| constructor(param) { | ||
| this.param = param; | ||
| this.taint = source(); | ||
| } | ||
|
|
||
| fieldSinks() { | ||
| sink(this.param); // NOT OK | ||
| sink(this.taint); // NOT OK | ||
| } | ||
|
|
||
| getParam() { | ||
| return this.param; | ||
| } | ||
|
|
||
| getTaint() { | ||
| return this.taint; | ||
| } | ||
| } | ||
|
|
||
| function testEcmaClass() { | ||
| let c = new EcmaClass(source()); | ||
| sink(c.getParam()); // NOT OK | ||
| sink(c.getTaint()); // NOT OK | ||
| } | ||
|
|
||
|
|
||
| function ProtoStyleClass(param) { | ||
| this.param = param; | ||
| this.taint = source(); | ||
| } | ||
|
|
||
| ProtoStyleClass.prototype.fieldSinks = function() { | ||
| sink(this.param); // NOT OK | ||
| sink(this.taint); // NOT OK | ||
| }; | ||
|
|
||
| ProtoStyleClass.prototype.getParam = function() { | ||
| return this.param; | ||
| }; | ||
|
|
||
| ProtoStyleClass.prototype.getTaint = function() { | ||
| return this.taint; | ||
| }; | ||
|
|
||
| function testProtoStyleClass() { | ||
| let c = new ProtoStyleClass(source()); | ||
| sink(c.getParam()); // NOT OK | ||
| sink(c.getTaint()); // NOT OK | ||
| } | ||
|
|
||
|
|
||
| function ProtoAssignmentClass(param) { | ||
| this.param = param; | ||
| this.taint = source(); | ||
| } | ||
|
|
||
| ProtoAssignmentClass.prototype = { | ||
| fieldSinks() { | ||
| sink(this.param); // NOT OK | ||
| sink(this.taint); // NOT OK | ||
| }, | ||
|
|
||
| getParam() { | ||
| return this.param; | ||
| }, | ||
|
|
||
| getTaint() { | ||
| return this.taint; | ||
| } | ||
| }; | ||
|
|
||
| function testProtoAssignmentClass() { | ||
| let c = new ProtoAssignmentClass(source()); | ||
| sink(c.getParam()); // NOT OK | ||
| sink(c.getTaint()); // NOT OK | ||
| } | ||
|
|
||
|
|
||
| function ProtoExtensionClass(param) { | ||
| this.param = param; | ||
| this.taint = source(); | ||
| } | ||
|
|
||
| let extend = require('extend'); | ||
| extend(ProtoExtensionClass.prototype, { | ||
| fieldSinks() { | ||
| sink(this.param); // NOT OK | ||
| sink(this.taint); // NOT OK | ||
| }, | ||
|
|
||
| getParam() { | ||
| return this.param; | ||
| }, | ||
|
|
||
| getTaint() { | ||
| return this.taint; | ||
| } | ||
| }); | ||
|
|
||
| function testProtoExtensionClass() { | ||
| let c = new ProtoExtensionClass(source()); | ||
| sink(c.getParam()); // NOT OK | ||
| sink(c.getTaint()); // NOT OK | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make this class extensible? I think it would be useful to be able to support ES5 libraries for creating classes, like https://www.npmjs.com/package/create-react-class.