Merge branch 'main' into js/shared-dataflow-merge-main

This commit is contained in:
Asger F
2024-12-19 10:14:38 +01:00
3417 changed files with 72118 additions and 42902 deletions

View File

@@ -0,0 +1,187 @@
/**
* DEPRECATED, but can be imported with a `deprecated import`.
*
* Will be replaced with standardized inline test expectations in the future.
*/
import javascript
/**
* A configuration for consistency checking.
* Used to specify where the alerts are (the positives)
* And which files should be included in the consistency-check.
*
* If no configuration is specified, then the default is that the all sinks from a `DataFlow::Configuration` are alerts, and all files are consistency-checked.
*/
abstract deprecated class ConsistencyConfiguration extends string {
bindingset[this]
ConsistencyConfiguration() { any() }
/**
* Gets an alert that should be checked for consistency.
* The alert must match with a `NOT OK` comment.
*
* And likewise a `OK` comment must not have a corresponding alert on the same line.
*/
DataFlow::Node getAnAlert() { result = getASink() }
/**
* Gets a file to include in the consistency checking.
*/
File getAFile() { none() }
}
/**
* A string that either equals a `ConsistencyConfiguration`, or the empty string if no such configuration exists.
*
* Is used internally to match a configuration or lack thereof.
*/
deprecated final private class Conf extends string {
Conf() {
this instanceof ConsistencyConfiguration
or
not exists(ConsistencyConfiguration c) and
this = ""
}
}
/**
* A comment that asserts whether a result exists at that line or not.
* Can optionally include `[INCONSISTENCY]` to indicate that a consistency issue is expected at the location
*/
private class AssertionComment extends Comment {
boolean shouldHaveAlert;
AssertionComment() {
if this.getText().regexpMatch("\\s*(NOT OK|BAD).*")
then shouldHaveAlert = true
else (
this.getText().regexpMatch("\\s*(OK|GOOD).*") and shouldHaveAlert = false
)
}
/**
* Holds if there should be an alert at this location
*/
predicate shouldHaveAlert() { shouldHaveAlert = true }
/**
* Holds if a consistency issue is expected at this location.
*/
predicate expectConsistencyError() { this.getText().matches("%[INCONSISTENCY]%") }
}
deprecated private DataFlow::Node getASink() {
exists(DataFlow::Configuration cfg | cfg.hasFlow(_, result))
}
/**
* Gets all the alerts for consistency consistency checking from a configuration `conf`.
*/
deprecated private DataFlow::Node alerts(Conf conf) {
result = conf.(ConsistencyConfiguration).getAnAlert()
or
not exists(ConsistencyConfiguration r) and
result = getASink() and
conf = ""
}
/**
* Gets an alert in `file` at `line` for configuration `conf`.
* The `line` can be either the first or the last line of the alert.
* And if no expression exists at `line`, then an alert on the next line is used.
*/
deprecated private DataFlow::Node getAlert(File file, int line, Conf conf) {
result = alerts(conf) and
result.getFile() = file and
(result.hasLocationInfo(_, _, _, line, _) or result.hasLocationInfo(_, line, _, _, _))
or
// The comment can be right above the result, so an alert also counts for the line above.
not exists(Expr e |
e.getFile() = file and [e.getLocation().getStartLine(), e.getLocation().getEndLine()] = line
) and
result = alerts(conf) and
result.getFile() = file and
result.hasLocationInfo(_, line + 1, _, _, _)
}
/**
* Gets a comment that asserts either the existence or the absence of an alert in `file` at `line`.
*/
private AssertionComment getComment(File file, int line) {
result.getLocation().getEndLine() = line and
result.getFile() = file
}
/**
* Holds if there is a false positive in `file` at `line` for configuration `conf`.
*/
deprecated private predicate falsePositive(File file, int line, AssertionComment comment, Conf conf) {
exists(getAlert(file, line, conf)) and
comment = getComment(file, line) and
not comment.shouldHaveAlert()
}
/**
* Holds if there is a false negative in `file` at `line` for configuration `conf`.
*/
deprecated private predicate falseNegative(File file, int line, AssertionComment comment, Conf conf) {
not exists(getAlert(file, line, conf)) and
comment = getComment(file, line) and
comment.shouldHaveAlert()
}
/**
* Gets a file that should be included for consistency checking for configuration `conf`.
*/
deprecated private File getATestFile(string conf) {
not exists(any(ConsistencyConfiguration res).getAFile()) and
result = any(LineComment comment).getFile() and
(conf = "" or conf instanceof ConsistencyConfiguration)
or
result = conf.(ConsistencyConfiguration).getAFile()
}
/**
* Gets a description of the configuration that has a sink in `file` at `line` for configuration `conf`.
* Or the empty string
*/
bindingset[file, line]
deprecated private string getSinkDescription(File file, int line, Conf conf) {
not exists(DataFlow::Configuration c | c.hasFlow(_, getAlert(file, line, conf))) and
result = ""
or
exists(DataFlow::Configuration c | c.hasFlow(_, getAlert(file, line, conf)) |
result = " for " + c
)
}
/**
* Holds if there is a consistency-issue at `location` with description `msg` for configuration `conf`.
* The consistency issue an unexpected false positive/negative.
* Or that false positive/negative was expected, and none were found.
*/
deprecated query predicate consistencyIssue(
string location, string msg, string commentText, Conf conf
) {
exists(File file, int line |
file = getATestFile(conf) and location = file.getRelativePath() + ":" + line
|
exists(AssertionComment comment |
comment.getText().trim() = commentText and comment = getComment(file, line)
|
falsePositive(file, line, comment, conf) and
not comment.expectConsistencyError() and
msg = "did not expect an alert, but found an alert" + getSinkDescription(file, line, conf)
or
falseNegative(file, line, comment, conf) and
not comment.expectConsistencyError() and
msg = "expected an alert, but found none"
or
not falsePositive(file, line, comment, conf) and
not falseNegative(file, line, comment, conf) and
comment.expectConsistencyError() and
msg = "expected consistency issue, but found no such issue (" + comment.getText().trim() + ")"
)
)
}

View File

@@ -0,0 +1,8 @@
/**
* Inline expectation tests for JS.
* See `shared/util/codeql/util/test/InlineExpectationsTest.qll`
*/
private import codeql.util.test.InlineExpectationsTest
private import internal.InlineExpectationsTestImpl
import Make<Impl>

View File

@@ -0,0 +1,21 @@
/**
* @kind test-postprocess
*/
private import javascript
private import codeql.util.test.InlineExpectationsTest as T
private import internal.InlineExpectationsTestImpl
import T::TestPostProcessing
import T::TestPostProcessing::Make<Impl, Input>
private module Input implements T::TestPostProcessing::InputSig<Impl> {
string getRelativeUrl(Location location) {
exists(File f, int startline, int startcolumn, int endline, int endcolumn |
location.hasLocationInfo(_, startline, startcolumn, endline, endcolumn) and
f = location.getFile()
|
result =
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}
}

View File

@@ -0,0 +1,25 @@
/**
* Inline flow tests for JavaScript.
* See `shared/util/codeql/dataflow/test/InlineFlowTest.qll`
*/
private import javascript
private import semmle.javascript.Locations
private import codeql.dataflow.test.InlineFlowTest
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowArg
private import semmle.javascript.frameworks.data.internal.ApiGraphModelsExtensions as ApiGraphModelsExtensions
private import internal.InlineExpectationsTestImpl
private module FlowTestImpl implements InputSig<Location, JSDataFlow> {
import testUtilities.InlineFlowTestUtil
bindingset[src, sink]
string getArgString(DataFlow::Node src, DataFlow::Node sink) {
(if exists(getSourceArgString(src)) then result = getSourceArgString(src) else result = "") and
exists(sink)
}
predicate interpretModelForTest = ApiGraphModelsExtensions::interpretModelForTest/2;
}
import InlineFlowTestMake<Location, JSDataFlow, JSTaintFlow, Impl, FlowTestImpl>

View File

@@ -0,0 +1,21 @@
/**
* Defines the default source and sink recognition for `InlineFlowTest.qll`.
*
* We reuse these predicates in some type-tracking tests that don't wish to bring in the
* test configuration from `InlineFlowTest`.
*/
private import javascript
predicate defaultSource(DataFlow::Node src) { src.(DataFlow::CallNode).getCalleeName() = "source" }
predicate defaultSink(DataFlow::Node sink) {
exists(DataFlow::CallNode call | call.getCalleeName() = "sink" | sink = call.getAnArgument())
}
bindingset[src]
string getSourceArgString(DataFlow::Node src) {
src.(DataFlow::CallNode).getAnArgument().getStringValue() = result
or
src.(DataFlow::ParameterNode).getName() = result
}

View File

@@ -0,0 +1,37 @@
import javascript
import semmle.javascript.dataflow.FlowSummary
class MkSummary extends SummarizedCallable {
private CallExpr mkSummary;
MkSummary() {
mkSummary.getCalleeName() = "mkSummary" and
this =
"mkSummary at " + mkSummary.getFile().getRelativePath() + ":" +
mkSummary.getLocation().getStartLine()
}
override DataFlow::InvokeNode getACallSimple() {
result = mkSummary.flow().(DataFlow::CallNode).getAnInvocation()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
(
// mkSummary(input, output)
input = mkSummary.getArgument(0).getStringValue() and
output = mkSummary.getArgument(1).getStringValue()
or
// mkSummary([
// [input1, output1],
// [input2, output2],
// ...
// ])
exists(ArrayExpr pair |
pair = mkSummary.getArgument(0).(ArrayExpr).getAnElement() and
input = pair.getElement(0).getStringValue() and
output = pair.getElement(1).getStringValue()
)
)
}
}

View File

@@ -0,0 +1,19 @@
private import javascript
private signature class LegacyConfigSig {
predicate hasFlow(DataFlow::Node source, DataFlow::Node sink);
}
module DataFlowDiff<DataFlow::GlobalFlowSig NewFlow, LegacyConfigSig LegacyConfig> {
query predicate legacyDataFlowDifference(
DataFlow::Node source, DataFlow::Node sink, string message
) {
NewFlow::flow(source, sink) and
not any(LegacyConfig cfg).hasFlow(source, sink) and
message = "only flow with NEW data flow library"
or
not NewFlow::flow(source, sink) and
any(LegacyConfig cfg).hasFlow(source, sink) and
message = "only flow with OLD data flow library"
}
}

View File

@@ -0,0 +1,17 @@
private import javascript as JS
private import codeql.util.test.InlineExpectationsTest
module Impl implements InlineExpectationsTestSig {
private import javascript
final private class LineCommentFinal = LineComment;
class ExpectationComment extends LineCommentFinal {
string getContents() { result = this.getText() }
/** Gets this element's location. */
Location getLocation() { result = super.getLocation() }
}
class Location = JS::Location;
}