Merge pull request #1577 from asger-semmle/infername

Approved by xiemaisi
This commit is contained in:
semmle-qlci
2019-07-22 21:01:48 +01:00
committed by GitHub
11 changed files with 121 additions and 37 deletions

View File

@@ -27,3 +27,5 @@
## Changes to QL libraries
- The `getName()` predicate on functions and classes now gets a name
inferred from the context if the function or class was not declared with a name.

View File

@@ -32,8 +32,15 @@ class ClassOrInterface extends @classorinterface, TypeParameterized {
/** Gets the identifier naming the declared type, if any. */
Identifier getIdentifier() { none() } // Overridden in subtypes.
/** Gets the name of the defined class or interface, if any. */
string getName() { result = getIdentifier().getName() }
/**
* Gets the name of the defined class or interface, possibly inferred
* from the context if this is an anonymous class expression.
*
* Has no result if no name could be determined.
*/
string getName() {
result = getIdentifier().getName() // Overridden in ClassExpr
}
/** Gets the nearest enclosing function or toplevel in which this class or interface occurs. */
StmtContainer getContainer() { result = this.(ExprOrStmt).getContainer() }
@@ -203,8 +210,8 @@ class ClassDefinition extends @classdefinition, ClassOrInterface, AST::ValueNode
*/
private string inferNameFromVarDef() {
// in ambiguous cases like `let C = class D {}`, prefer `D` to `C`
if exists(getName())
then result = "class " + getName()
if exists(getIdentifier())
then result = "class " + getIdentifier().getName()
else
exists(VarDef vd | this = vd.getSource() |
result = "class " + vd.getTarget().(VarRef).getName()
@@ -272,6 +279,26 @@ class ClassDeclStmt extends @classdeclstmt, ClassDefinition, Stmt {
* ```
*/
class ClassExpr extends @classexpr, ClassDefinition, Expr {
override string getName() {
result = ClassDefinition.super.getName()
or
not exists(getIdentifier()) and
(
exists(VarDef vd | this = vd.getSource() | result = vd.getTarget().(VarRef).getName())
or
exists(ValueProperty p |
this = p.getInit() and
result = p.getName()
)
or
exists(AssignExpr assign, PropAccess prop |
this = assign.getRhs().getUnderlyingValue() and
prop = assign.getLhs() and
result = prop.getPropertyName()
)
)
}
override predicate isImpure() { none() }
/** Gets the nearest enclosing function or toplevel in which this class expression occurs. */

View File

@@ -751,9 +751,6 @@ class SpreadProperty extends Property {
* ```
*/
class FunctionExpr extends @functionexpr, Expr, Function {
/** Gets the name of this function expression, if any. */
override string getName() { result = getId().getName() }
/** Holds if this function expression is a property setter. */
predicate isSetter() { exists(PropertySetter s | s.getInit() = this) }

View File

@@ -80,8 +80,35 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
/** Gets the identifier specifying the name of this function, if any. */
VarDecl getId() { result = getChildExpr(-1) }
/** Gets the name of this function, if any. */
string getName() { result = getId().getName() }
/**
* Gets the name of this function if it has one, or a name inferred from its context.
*
* For named functions such as `function f() { ... }`, this is just the declared
* name. For functions assigned to variables or properties (including class
* members), this is the name of the variable or property. If no meaningful name
* can be inferred, there is no result.
*/
string getName() {
result = getId().getName()
or
not exists(getId()) and
(
exists(VarDef vd | this = vd.getSource() | result = vd.getTarget().(VarRef).getName())
or
exists(Property p |
this = p.getInit() and
result = p.getName()
)
or
exists(AssignExpr assign, PropAccess prop |
this = assign.getRhs().getUnderlyingValue() and
prop = assign.getLhs() and
result = prop.getPropertyName()
)
or
exists(ClassOrInterface c | this = c.getMember(result).getInit())
)
}
/** Gets the variable holding this function. */
Variable getVariable() { result = getId().getVariable() }
@@ -274,8 +301,8 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
*/
private string inferNameFromVarDef() {
// in ambiguous cases like `var f = function g() {}`, prefer `g` to `f`
if exists(getName())
then result = "function " + getName()
if exists(getId())
then result = "function " + getId().getName()
else
exists(VarDef vd | this = vd.getSource() |
result = "function " + vd.getTarget().(VarRef).getName()

View File

@@ -340,12 +340,12 @@ class FunctionNode extends DataFlow::ValueNode, DataFlow::SourceNode {
int getNumParameter() { result = count(astNode.getAParameter()) }
/** Gets the last parameter of this function. */
ParameterNode getLastParameter() { result = getParameter(getNumParameter()-1) }
ParameterNode getLastParameter() { result = getParameter(getNumParameter() - 1) }
/** Holds if the last parameter of this function is a rest parameter. */
predicate hasRestParameter() { astNode.hasRestParameter() }
/** Gets the name of this function, if it has one. */
/** Gets the unqualified name of this function, if it has one or one can be determined from the context. */
string getName() { result = astNode.getName() }
/** Gets a data flow node corresponding to a return value of this function. */
@@ -576,7 +576,7 @@ class ClassNode extends DataFlow::SourceNode {
ClassNode() { this = impl }
/**
* Gets the name of the class, if it has one.
* Gets the unqualified name of the class, if it has one or one can be determined from the context.
*/
string getName() { result = impl.getName() }
@@ -651,16 +651,12 @@ class ClassNode extends DataFlow::SourceNode {
/**
* Gets a direct super class of this class.
*/
ClassNode getADirectSuperClass() {
result.getAClassReference().flowsTo(getASuperClassNode())
}
ClassNode getADirectSuperClass() { result.getAClassReference().flowsTo(getASuperClassNode()) }
/**
* Gets a direct subclass of this class.
*/
final ClassNode getADirectSubClass() {
this = result.getADirectSuperClass()
}
final ClassNode getADirectSubClass() { this = result.getADirectSuperClass() }
/**
* Gets the receiver of an instance member or constructor of this class.
@@ -674,16 +670,12 @@ class ClassNode extends DataFlow::SourceNode {
/**
* Gets the abstract value representing the class itself.
*/
AbstractValue getAbstractClassValue() {
result = this.(AnalyzedNode).getAValue()
}
AbstractValue getAbstractClassValue() { result = this.(AnalyzedNode).getAValue() }
/**
* Gets the abstract value representing an instance of this class.
*/
AbstractValue getAbstractInstanceValue() {
result = AbstractInstance::of(getAstNode())
}
AbstractValue getAbstractInstanceValue() { result = AbstractInstance::of(getAstNode()) }
/**
* Gets a dataflow node that refers to this class object.
@@ -692,9 +684,7 @@ class ClassNode extends DataFlow::SourceNode {
t.start() and
result.(AnalyzedNode).getAValue() = getAbstractClassValue()
or
exists(DataFlow::TypeTracker t2 |
result = getAClassReference(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = getAClassReference(t2).track(t2, t))
}
/**
@@ -725,9 +715,7 @@ class ClassNode extends DataFlow::SourceNode {
pragma[noinline]
private DataFlow::SourceNode getAnInstanceReferenceAux(DataFlow::TypeTracker t) {
exists(DataFlow::TypeTracker t2 |
result = getAnInstanceReference(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = getAnInstanceReference(t2).track(t2, t))
}
/**
@@ -846,11 +834,12 @@ module ClassNode {
*/
class FunctionStyleClass extends Range, DataFlow::ValueNode {
override Function astNode;
AbstractFunction function;
FunctionStyleClass() {
function.getFunction() = astNode and
exists (DataFlow::PropRef read |
exists(DataFlow::PropRef read |
read.getPropertyName() = "prototype" and
read.getBase().analyze().getAValue() = function
)
@@ -902,15 +891,13 @@ module ClassNode {
override FunctionNode getStaticMethod(string name) { result = getAPropertySource(name) }
override FunctionNode getAStaticMethod() {
result = getAPropertySource()
}
override FunctionNode getAStaticMethod() { result = getAPropertySource() }
/**
* Gets a reference to the prototype of this class.
*/
DataFlow::SourceNode getAPrototypeReference() {
exists (DataFlow::SourceNode base | base.analyze().getAValue() = function |
exists(DataFlow::SourceNode base | base.analyze().getAValue() = function |
result = base.getAPropertyRead("prototype")
or
result = base.getAPropertySource("prototype")

View File

@@ -32,6 +32,7 @@ test_ClassDefinition_getName
| points.js:1:1:18:1 | class P ... ;\\n }\\n} | Point |
| points.js:20:1:33:1 | class C ... ;\\n }\\n} | ColouredPoint |
| staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | MyClass |
| tst.js:1:9:4:1 | class { ... */ }\\n} | A |
| tst.js:6:1:8:1 | class B ... t); }\\n} | B |
| tst.js:11:1:14:1 | class C ... () {}\\n} | C |
test_MethodDefinitions

View File

@@ -1,5 +1,13 @@
getName
| tst.js:16:1:19:1 | class C ... ss C"\\n} | C |
| tst.js:21:2:23:1 | class D ... lass"\\n} | D |
| tst.js:25:11:25:18 | class {} | E |
| tst.js:26:11:29:1 | class G ... ss G"\\n} | G |
| tst.js:34:9:34:16 | class {} | Foo |
#select
| tst.js:16:1:19:1 | class C ... ss C"\\n} | class C |
| tst.js:21:2:23:1 | class D ... lass"\\n} | class D |
| tst.js:22:12:22:18 | class{} | anonymous class |
| tst.js:25:11:25:18 | class {} | class E |
| tst.js:26:11:29:1 | class G ... ss G"\\n} | class G |
| tst.js:34:9:34:16 | class {} | anonymous class |

View File

@@ -1,4 +1,6 @@
import javascript
query string getName(ClassDefinition c) { result = c.getName() }
from ClassDefinition c
select c, c.describe()

View File

@@ -1,3 +1,26 @@
getName
| tst.js:1:1:1:15 | function f() {} | f |
| tst.js:2:2:2:16 | function g() {} | g |
| tst.js:4:9:4:22 | function () {} | h |
| tst.js:5:9:5:14 | x => x | k |
| tst.js:6:9:6:23 | function n() {} | n |
| tst.js:9:6:9:18 | function() {} | p |
| tst.js:10:6:10:20 | function f() {} | f |
| tst.js:11:8:11:12 | () {} | x |
| tst.js:12:8:12:13 | (v) {} | x |
| tst.js:13:4:13:8 | () {} | m |
| tst.js:17:14:17:18 | () {} | constructor |
| tst.js:18:4:18:8 | () {} | n |
| tst.js:22:17:22:16 | () {} | constructor |
| tst.js:22:21:22:20 | (...arg ... rgs); } | constructor |
| tst.js:25:17:25:16 | () {} | constructor |
| tst.js:26:19:26:18 | () {} | constructor |
| tst.js:27:8:27:12 | () {} | y |
| tst.js:28:8:28:13 | (v) {} | y |
| tst.js:31:9:31:21 | function() {} | foo |
| tst.js:32:12:32:24 | function() {} | Hey |
| tst.js:34:15:34:14 | () {} | constructor |
#select
| tst.js:1:1:1:15 | function f() {} | function f |
| tst.js:2:2:2:16 | function g() {} | function g |
| tst.js:3:2:3:14 | function() {} | anonymous function |
@@ -17,3 +40,6 @@
| tst.js:26:19:26:18 | () {} | default constructor of class G |
| tst.js:27:8:27:12 | () {} | getter method for property y of class G |
| tst.js:28:8:28:13 | (v) {} | setter method for property y of class G |
| tst.js:31:9:31:21 | function() {} | anonymous function |
| tst.js:32:12:32:24 | function() {} | anonymous function |
| tst.js:34:15:34:14 | () {} | default constructor of anonymous class |

View File

@@ -1,4 +1,6 @@
import javascript
query string getName(Function f) { result = f.getName() }
from Function f
select f, f.describe()

View File

@@ -27,3 +27,8 @@ const E = class {}, // "class E", "default constructor of class E"
get y() {} // "getter method for property y of class G"
set y(v) {} // "setter method for property y of class G"
};
o.foo = function() {}; // "method foo"
o["Hey"] = function() {}; // "anonymous function"
o.Foo = class {}; // "class Foo"