JS: Add flow summaries for maps and sets

This commit is contained in:
Asger F
2023-10-03 13:18:15 +02:00
parent 5054c43b18
commit f0c2afe39e
4 changed files with 176 additions and 8 deletions

View File

@@ -16,7 +16,7 @@ private module CollectionDataFlow {
/**
* A step for `Set.add()` method, which adds an element to a Set.
*/
private class SetAdd extends PreCallGraphStep {
private class SetAdd extends LegacyPreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("add") and
@@ -29,7 +29,7 @@ private module CollectionDataFlow {
/**
* A step for the `Set` constructor, which copies any elements from the first argument into the resulting set.
*/
private class SetConstructor extends PreCallGraphStep {
private class SetConstructor extends LegacyPreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
@@ -49,7 +49,7 @@ private module CollectionDataFlow {
* For sets and iterators the l-value are the elements of the set/iterator.
* For maps the l-value is a tuple containing a key and a value.
*/
private class ForOfStep extends PreCallGraphStep {
private class ForOfStep extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
exists(ForOfStmt forOf |
obj = forOf.getIterationDomain().flow() and
@@ -73,7 +73,7 @@ private module CollectionDataFlow {
/**
* A step for a call to `forEach` on a Set or Map.
*/
private class SetMapForEach extends PreCallGraphStep {
private class SetMapForEach extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "forEach" and
@@ -88,7 +88,7 @@ private module CollectionDataFlow {
* A call to the `get` method on a Map.
* If the key of the call to `get` has a known string value, then only the value corresponding to that key will be retrieved. (The known string value is encoded as part of the pseudo-property)
*/
private class MapGet extends PreCallGraphStep {
private class MapGet extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "get" and
@@ -108,7 +108,7 @@ private module CollectionDataFlow {
* Otherwise the value will be stored into a pseudo-property corresponding to values with unknown keys.
* The value will additionally be stored into a pseudo-property corresponding to all values.
*/
class MapSet extends PreCallGraphStep {
class MapSet extends LegacyPreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("set") and
@@ -121,7 +121,7 @@ private module CollectionDataFlow {
/**
* A step for a call to `values` on a Map or a Set.
*/
private class MapAndSetValues extends PreCallGraphStep {
private class MapAndSetValues extends LegacyPreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
@@ -138,7 +138,7 @@ private module CollectionDataFlow {
/**
* A step for a call to `keys` on a Set.
*/
private class SetKeys extends PreCallGraphStep {
private class SetKeys extends LegacyPreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {

View File

@@ -1,4 +1,6 @@
private import AmbiguousCoreMethods
private import Arrays2
private import AsyncAwait
private import Maps2
private import Promises2
private import Sets2

View File

@@ -0,0 +1,120 @@
/**
* Contains flow summaries and steps modelling flow through `Map` objects.
*/
private import javascript
private import semmle.javascript.dataflow.FlowSummary
private import FlowSummaryUtil
private DataFlow::SourceNode mapConstructorRef() { result = DataFlow::globalVarRef("Map") }
class MapConstructor extends SummarizedCallable {
MapConstructor() { this = "Map constructor" }
override DataFlow::InvokeNode getACallSimple() {
result = mapConstructorRef().getAnInstantiation()
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
(
input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement"] + ".Member[0]" and
output = "ReturnValue.MapKey"
or
input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement"] + ".Member[1]" and
output = "ReturnValue.MapValue"
or
input = ["Argument[0].WithMapKey", "Argument[0].WithMapValue"] and
output = "ReturnValue"
)
}
}
/**
* A read step for `Map#get`.
*
* This is implemented as a step instead of a flow summary, as we currently do not expose a MaD syntax
* for map values with a known key.
*/
class MapGetStep extends DataFlow::AdditionalFlowStep {
override predicate readStep(
DataFlow::Node pred, DataFlow::ContentSet contents, DataFlow::Node succ
) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "get" and
call.getNumArgument() = 1 and
pred = call.getReceiver() and
succ = call
|
contents = DataFlow::ContentSet::mapValueFromKey(call.getArgument(0).getStringValue())
or
not exists(call.getArgument(0).getStringValue()) and
contents = DataFlow::ContentSet::mapValueAll()
)
}
}
/**
* A read step for `Map#set`.
*
* This is implemented as a step instead of a flow summary, as we currently do not expose a MaD syntax
* for map values with a known key.
*/
class MapSetStep extends DataFlow::AdditionalFlowStep {
override predicate storeStep(
DataFlow::Node pred, DataFlow::ContentSet contents, DataFlow::Node succ
) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "set" and
call.getNumArgument() = 2 and
pred = call.getArgument(1) and
succ.(DataFlow::ExprPostUpdateNode).getPreUpdateNode() = call.getReceiver()
|
contents = DataFlow::ContentSet::mapValueFromKey(call.getArgument(0).getStringValue())
or
not exists(call.getArgument(0).getStringValue()) and
contents = DataFlow::ContentSet::mapValueWithUnknownKey()
)
}
}
class MapGet extends SummarizedCallable {
MapGet() { this = "Map#get" }
override DataFlow::MethodCallNode getACallSimple() {
none() and // Disabled for now - need MaD syntax for known map values
result.getMethodName() = "get" and
result.getNumArgument() = 1
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this].MapValue" and
output = "ReturnValue"
}
}
class MapSet extends SummarizedCallable {
MapSet() { this = "Map#set" }
override DataFlow::MethodCallNode getACallSimple() {
result.getMethodName() = "set" and
result.getNumArgument() = 2
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = ["Argument[this].WithMapKey", "Argument[this].WithMapValue"] and
output = "ReturnValue"
or
preservesValue = true and
none() and // Disabled for now - need MaD syntax for known map values
(
input = "Argument[0]" and
output = "Argument[this].MapKey"
or
input = "Argument[1]" and
output = "Argument[this].MapValue"
)
}
}

View File

@@ -0,0 +1,46 @@
/**
* Contains flow summaries and steps modelling flow through `Set` objects.
*/
private import javascript
private import semmle.javascript.dataflow.FlowSummary
private import FlowSummaryUtil
private DataFlow::SourceNode setConstructorRef() { result = DataFlow::globalVarRef("Set") }
class SetConstructor extends SummarizedCallable {
SetConstructor() { this = "Set constructor" }
override DataFlow::InvokeNode getACallSimple() {
result = setConstructorRef().getAnInstantiation()
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
(
input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement"] and
output = "ReturnValue.SetElement"
or
input = "Argument[0].MapKey" and
output = "ReturnValue.SetElement.Member[0]"
or
input = "Argument[0].MapValue" and
output = "ReturnValue.SetElement.Member[1]"
)
}
}
class SetAdd extends SummarizedCallable {
SetAdd() { this = "Set#add" }
override DataFlow::MethodCallNode getACallSimple() {
result.getMethodName() = "add" and
result.getNumArgument() = 1
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[0]" and
output = "Argument[this].SetElement"
}
}