JS: Add flow summaries for core methods

This commit is contained in:
Asger F
2023-10-03 13:14:14 +02:00
parent 46fec8ea7e
commit a31e251529
4 changed files with 168 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ private import semmle.javascript.dataflow.internal.AdditionalFlowInternal
private import semmle.javascript.dataflow.internal.Contents::Private
private import semmle.javascript.dataflow.internal.VariableCapture
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
private import semmle.javascript.internal.flow_summaries.AllFlowSummaries
private import sharedlib.FlowSummaryImpl as FlowSummaryImpl
private class Node = DataFlow::Node;

View File

@@ -0,0 +1 @@
private import AmbiguousCoreMethods

View File

@@ -0,0 +1,151 @@
/**
* Contains flow summaries for methods with a name that can found on more than one of the core types: Array, String, Map, Set, Promise.
*
* This is an overview of the ambiguous methods and the classes that contain them (not all of these require a flow summary):
* ```
* at: String, Array
* concat: String, Array
* includes: String, Array
* indexOf: String, Array
* lastIndexOf: String, Array
* slice: String, Array
* entries: Array, Map, Set
* forEach: Array, Map, Set
* keys: Array, Map, Set
* values: Array, Map, Set
* clear: Map, Set
* delete: Map, Set
* has: Map, Set
* ```
*
* (Promise is absent in the table above as there currently are no name clashes with Promise methods)
*/
private import javascript
private import semmle.javascript.dataflow.internal.DataFlowNode
private import semmle.javascript.dataflow.FlowSummary
private import FlowSummaryUtil
class At extends SummarizedCallable {
At() { this = "Array#at / String#at" }
override InstanceCall getACallSimple() { result.getMethodName() = "at" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this].ArrayElement" and
output = "ReturnValue"
//
// There is no flow for String#at since we currently consider single-character extraction to be too restrictive
}
}
class Concat extends SummarizedCallable {
Concat() { this = "Array#concat / String#concat" }
override InstanceCall getACallSimple() { result.getMethodName() = "concat" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this,0..].ArrayElement" and
output = "ReturnValue.ArrayElement"
or
preservesValue = false and
input = "Argument[this,0..]" and
output = "ReturnValue"
}
}
class Slice extends SummarizedCallable {
Slice() { this = "Array#slice / String#slice" }
override InstanceCall getACallSimple() { result.getMethodName() = "slice" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this].ArrayElement" and
output = "ReturnValue.ArrayElement"
or
preservesValue = false and
input = "Argument[this]" and
output = "ReturnValue"
}
}
class Entries extends SummarizedCallable {
Entries() { this = "Array#entries / Map#entries / Set#entries" }
override InstanceCall getACall() {
result.getMethodName() = "entries" and
result.getNumArgument() = 0
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
(
input = "Argument[this]." + ["MapKey", "SetElement"] and
output = "ReturnValue.IteratorElement.Member[0]"
or
input = "Argument[this]." + ["ArrayElement", "SetElement", "MapValue"] and
output = "ReturnValue.IteratorElement.Member[1]"
)
}
}
class ForEach extends SummarizedCallable {
ForEach() { this = "Array#forEach / Map#forEach / Set#forEach" }
override InstanceCall getACallSimple() { result.getMethodName() = "forEach" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
/*
* array.forEach(callbackfn, thisArg)
* callbackfn(value, index, array)
*/
(
input = "Argument[this]." + ["ArrayElement", "SetElement", "MapValue"] and
output = "Argument[0].Parameter[0]"
or
input = "Argument[this]." + ["MapKey", "SetElement"] and
output = "Argument[0].Parameter[1]"
or
input = "Argument[this]" and
output = "Argument[0].Parameter[2]" // object being iterated over
or
input = "Argument[1]" and // thisArg
output = "Argument[0].Parameter[this]"
)
}
}
class Keys extends SummarizedCallable {
Keys() { this = "Array#keys / Map#keys / Set#keys" }
override InstanceCall getACallSimple() {
result.getMethodName() = "keys" and
result.getNumArgument() = 0
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this]." + ["MapKey", "SetElement"] and
output = "ReturnValue.IteratorElement"
}
}
class Values extends SummarizedCallable {
Values() { this = "Array#values / Map#values / Set#values" }
override InstanceCall getACallSimple() {
result.getMethodName() = "values" and
result.getNumArgument() = 0
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this]." + ["ArrayElement", "SetElement", "MapValue"] and
output = "ReturnValue.IteratorElement"
}
}

View File

@@ -0,0 +1,15 @@
private import javascript
private import semmle.javascript.dataflow.FlowSummary
private import semmle.javascript.dataflow.internal.Contents::Private
/**
* A method call or a reflective invocation (`call` or `apply`) that takes a receiver.
*
* Note that `DataFlow::MethodCallNode` does not include reflective invocation.
*/
class InstanceCall extends DataFlow::CallNode {
InstanceCall() { exists(this.getReceiver()) }
/** Gets the name of method being invoked */
string getMethodName() { result = this.getCalleeName() }
}