JS: add type inference for the return value of captured method calls

This commit is contained in:
Esben Sparre Andreasen
2019-02-12 13:06:24 +01:00
parent c84d898727
commit bdd8691e65
5 changed files with 56 additions and 9 deletions

View File

@@ -7,6 +7,8 @@
import javascript
import AbstractValuesImpl
import semmle.javascript.dataflow.CapturedNodes
/**
* Flow analysis for `this` expressions inside functions.
*/
@@ -230,3 +232,47 @@ private class TypeInferredCalleeWithAnalyzedReturnFlow extends CallWithNonLocalA
override AnalyzedFunction getACallee() { result = fun }
}
/**
* Holds if `call` uses `receiver` as its only receiver value.
*/
pragma[noinline]
private predicate hasDefiniteReceiver(
DataFlow::MethodCallNode call, CapturedSource receiver
) {
call = receiver.getAMethodCall() and
exists (DataFlow::AnalyzedNode receiverNode, AbstractValue abstractCapturedReceiver |
receiverNode = call.getReceiver() and
not receiverNode.getALocalValue().isIndefinite(_) and
abstractCapturedReceiver = receiver.analyze().getALocalValue() and
forall(DataFlow::AbstractValue v |
receiverNode.getALocalValue() = v |
v = abstractCapturedReceiver
)
)
}
/**
* Enables inter-procedural type inference for the return value of a
* method call to a flow-insensitively type-inferred callee.
*/
class TypeInferredMethodWithAnalyzedReturnFlow extends CallWithNonLocalAnalyzedReturnFlow {
DataFlow::FunctionNode fun;
TypeInferredMethodWithAnalyzedReturnFlow() {
exists(CapturedSource s, DataFlow::PropWrite w, string name |
this.(DataFlow::MethodCallNode).getMethodName() = name and
s.hasOwnProperty(name) and
hasDefiniteReceiver(this, s) and
w = s.getAPropertyWrite() and
fun.flowsTo(w.getRhs()) and
(
not exists(w.getPropertyName())
or
w.getPropertyName() = name
)
)
}
override AnalyzedFunction getACallee() { result = fun }
}

View File

@@ -1,10 +1,10 @@
| method-calls.js:8:2:8:8 | o1.m1() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:9:2:9:8 | o1.m2() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:8:2:8:8 | o1.m1() | object |
| method-calls.js:9:2:9:8 | o1.m2() | object |
| method-calls.js:12:2:12:7 | o.m3() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:19:2:19:7 | o2.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:23:11:23:21 | {m: f1}.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:28:11:28:16 | o2.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:32:11:32:23 | ({m: f3}).m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:23:11:23:21 | {m: f1}.m() | object |
| method-calls.js:28:11:28:16 | o2.m() | object |
| method-calls.js:32:11:32:23 | ({m: f3}).m() | object |
| method-calls.js:37:11:37:16 | o4.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:43:12:43:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| method-calls.js:47:12:47:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |

View File

@@ -1,4 +1,4 @@
| method-calls.js:23:11:23:21 | {m: f1}.m() | method-calls.js:24:2:24:3 | v1 | file://:0:0:0:0 | indefinite value (call) |
| method-calls.js:28:11:28:16 | o2.m() | method-calls.js:29:2:29:3 | v2 | file://:0:0:0:0 | indefinite value (call) |
| method-calls.js:32:11:32:23 | ({m: f3}).m() | method-calls.js:33:2:33:3 | v3 | file://:0:0:0:0 | indefinite value (call) |
| method-calls.js:23:11:23:21 | {m: f1}.m() | method-calls.js:24:2:24:3 | v1 | method-calls.js:22:23:22:24 | object literal |
| method-calls.js:28:11:28:16 | o2.m() | method-calls.js:29:2:29:3 | v2 | method-calls.js:26:23:26:24 | object literal |
| method-calls.js:32:11:32:23 | ({m: f3}).m() | method-calls.js:33:2:33:3 | v3 | method-calls.js:31:23:31:24 | object literal |
| method-calls.js:37:11:37:16 | o4.m() | method-calls.js:38:2:38:3 | v4 | file://:0:0:0:0 | indefinite value (call) |

View File

@@ -7,6 +7,7 @@
| tst.js:11:5:11:8 | f1() | file://:0:0:0:0 | undefined |
| tst.js:14:5:14:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:15:5:15:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | undefined |
| tst.js:23:5:23:8 | f4() | tst.js:21:16:21:30 | function f5 |
| tst.js:23:5:23:10 | f4()() | file://:0:0:0:0 | undefined |
| tst.js:30:5:30:8 | f6() | tst.js:26:16:28:9 | function f7 |

View File

@@ -11,7 +11,7 @@
| tst.js:11:5:11:8 | f1() | file://:0:0:0:0 | undefined |
| tst.js:14:5:14:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:15:5:15:8 | f2() | file://:0:0:0:0 | undefined |
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | indefinite value (call) |
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | undefined |
| tst.js:23:5:23:8 | f4() | tst.js:21:16:21:30 | function f5 |
| tst.js:23:5:23:10 | f4()() | file://:0:0:0:0 | undefined |
| tst.js:30:5:30:8 | f6() | tst.js:26:16:28:9 | function f7 |