Merge branch 'main' into kaspersv/prevent-python-join-order-regression

This commit is contained in:
Kasper Svendsen
2023-04-24 13:35:07 +02:00
committed by GitHub
43 changed files with 2204 additions and 482 deletions

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -18,12 +18,10 @@ private import semmle.code.csharp.dataflow.TaintTracking2
*/
predicate xssFlow(XssNode source, XssNode sink, string message) {
// standard taint-tracking
exists(
TaintTrackingConfiguration c, DataFlow2::PathNode sourceNode, DataFlow2::PathNode sinkNode
|
exists(XssTracking::PathNode sourceNode, XssTracking::PathNode sinkNode |
sourceNode = source.asDataFlowNode() and
sinkNode = sink.asDataFlowNode() and
c.hasFlowPath(sourceNode, sinkNode) and
XssTracking::flowPath(sourceNode, sinkNode) and
message =
"is written to HTML or JavaScript" +
any(string explanation |
@@ -45,7 +43,7 @@ predicate xssFlow(XssNode source, XssNode sink, string message) {
module PathGraph {
/** Holds if `(pred,succ)` is an edge in the graph of data flow path explanations. */
query predicate edges(XssNode pred, XssNode succ) {
exists(DataFlow2::PathNode a, DataFlow2::PathNode b | DataFlow2::PathGraph::edges(a, b) |
exists(XssTracking::PathNode a, XssTracking::PathNode b | XssTracking::PathGraph::edges(a, b) |
pred.asDataFlowNode() = a and
succ.asDataFlowNode() = b
)
@@ -56,7 +54,7 @@ module PathGraph {
/** Holds if `n` is a node in the graph of data flow path explanations. */
query predicate nodes(XssNode n, string key, string val) {
DataFlow2::PathGraph::nodes(n.asDataFlowNode(), key, val)
XssTracking::PathGraph::nodes(n.asDataFlowNode(), key, val)
or
xssFlow(n, n, _) and
key = "semmle.label" and
@@ -69,13 +67,13 @@ module PathGraph {
* `ret -> out` is summarized as the edge `arg -> out`.
*/
query predicate subpaths(XssNode arg, XssNode par, XssNode ret, XssNode out) {
DataFlow2::PathGraph::subpaths(arg.asDataFlowNode(), par.asDataFlowNode(), ret.asDataFlowNode(),
out.asDataFlowNode())
XssTracking::PathGraph::subpaths(arg.asDataFlowNode(), par.asDataFlowNode(),
ret.asDataFlowNode(), out.asDataFlowNode())
}
}
private newtype TXssNode =
TXssDataFlowNode(DataFlow2::PathNode node) or
TXssDataFlowNode(XssTracking::PathNode node) or
TXssAspNode(AspInlineMember m)
/**
@@ -90,21 +88,25 @@ class XssNode extends TXssNode {
/** Gets the location of this node. */
Location getLocation() { none() }
/** Gets the data flow node corresponding to this node, if any. */
DataFlow2::PathNode asDataFlowNode() { result = this.(XssDataFlowNode).getDataFlowNode() }
/**
* Gets the data flow node corresponding to this node, if any.
*/
XssTracking::PathNode asDataFlowNode() { result = this.(XssDataFlowNode).getDataFlowNode() }
/** Gets the ASP inline code element corresponding to this node, if any. */
AspInlineMember asAspInlineMember() { result = this.(XssAspNode).getAspInlineMember() }
}
/** A data flow node, viewed as an XSS flow node. */
/**
* A data flow node, viewed as an XSS flow node.
*/
class XssDataFlowNode extends TXssDataFlowNode, XssNode {
DataFlow2::PathNode node;
XssTracking::PathNode node;
XssDataFlowNode() { this = TXssDataFlowNode(node) }
/** Gets the data flow node corresponding to this node. */
DataFlow2::PathNode getDataFlowNode() { result = node }
XssTracking::PathNode getDataFlowNode() { result = node }
override string toString() { result = node.toString() }
@@ -136,9 +138,11 @@ abstract class Source extends DataFlow::Node { }
abstract class Sanitizer extends DataFlow::ExprNode { }
/**
* DEPRECATED: Use `XssTracking` instead.
*
* A taint-tracking configuration for cross-site scripting (XSS) vulnerabilities.
*/
class TaintTrackingConfiguration extends TaintTracking2::Configuration {
deprecated class TaintTrackingConfiguration extends TaintTracking2::Configuration {
TaintTrackingConfiguration() { this = "XSSDataFlowConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
@@ -148,6 +152,29 @@ class TaintTrackingConfiguration extends TaintTracking2::Configuration {
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}
/**
* A taint-tracking configuration for cross-site scripting (XSS) vulnerabilities.
*/
module XssTrackingConfig implements DataFlow::ConfigSig {
/**
* Holds if `source` is a relevant data flow source.
*/
predicate isSource(DataFlow::Node source) { source instanceof Source }
/**
* Holds if `sink` is a relevant data flow sink.
*/
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
/**
* Holds if data flow through `node` is prohibited. This completely removes
* `node` from the data flow graph.
*/
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
}
module XssTracking = TaintTracking::Global<XssTrackingConfig>;
/** A source of remote user input. */
private class RemoteSource extends Source instanceof RemoteFlowSource { }

View File

@@ -12,38 +12,36 @@
import csharp
import semmle.code.csharp.frameworks.Format
import DataFlow::PathGraph
import FormatInvalid::PathGraph
private class FormatConfiguration extends DataFlow::Configuration {
FormatConfiguration() { this = "format" }
module FormatInvalidConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
override predicate isSink(DataFlow::Node n) {
exists(FormatCall c | n.asExpr() = c.getFormatExpr())
}
predicate isSink(DataFlow::Node n) { exists(FormatCall c | n.asExpr() = c.getFormatExpr()) }
}
module FormatInvalid = DataFlow::Global<FormatInvalidConfig>;
private predicate invalidFormatString(
InvalidFormatString src, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
InvalidFormatString src, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
FormatCall call, string callString
) {
source.getNode().asExpr() = src and
sink.getNode().asExpr() = call.getFormatExpr() and
any(FormatConfiguration conf).hasFlowPath(source, sink) and
FormatInvalid::flowPath(source, sink) and
call.hasInsertions() and
msg = "Invalid format string used in $@ formatting call." and
callString = "this"
}
private predicate unusedArgument(
FormatCall call, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
FormatCall call, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
ValidFormatString src, string srcString, Expr unusedExpr, string unusedString
) {
exists(int unused |
source.getNode().asExpr() = src and
sink.getNode().asExpr() = call.getFormatExpr() and
any(FormatConfiguration conf).hasFlowPath(source, sink) and
FormatInvalid::flowPath(source, sink) and
unused = call.getASuppliedArgument() and
not unused = src.getAnInsert() and
not src.getValue() = "" and
@@ -55,13 +53,13 @@ private predicate unusedArgument(
}
private predicate missingArgument(
FormatCall call, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
FormatCall call, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
ValidFormatString src, string srcString
) {
exists(int used, int supplied |
source.getNode().asExpr() = src and
sink.getNode().asExpr() = call.getFormatExpr() and
any(FormatConfiguration conf).hasFlowPath(source, sink) and
FormatInvalid::flowPath(source, sink) and
used = src.getAnInsert() and
supplied = call.getSuppliedArguments() and
used >= supplied and
@@ -71,8 +69,8 @@ private predicate missingArgument(
}
from
Element alert, DataFlow::PathNode source, DataFlow::PathNode sink, string msg, Element extra1,
string extra1String, Element extra2, string extra2String
Element alert, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
Element extra1, string extra1String, Element extra2, string extra2String
where
invalidFormatString(alert, source, sink, msg, extra1, extra1String) and
extra2 = extra1 and

View File

@@ -24,10 +24,8 @@ private class ReturnNode extends DataFlow::ExprNode {
}
}
private class Conf extends DataFlow::Configuration {
Conf() { this = "NoDisposeCallOnLocalIDisposable" }
override predicate isSource(DataFlow::Node node) {
module DisposeCallOnLocalIDisposableConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
node.asExpr() =
any(LocalScopeDisposableCreation disposable |
// Only care about library types - user types often have spurious IDisposable declarations
@@ -37,7 +35,7 @@ private class Conf extends DataFlow::Configuration {
)
}
override predicate isSink(DataFlow::Node node) {
predicate isSink(DataFlow::Node node) {
// Things that return may be disposed elsewhere
node instanceof ReturnNode
or
@@ -80,23 +78,27 @@ private class Conf extends DataFlow::Configuration {
)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node2.asExpr() =
any(LocalScopeDisposableCreation other | other.getAnArgument() = node1.asExpr())
}
override predicate isBarrierOut(DataFlow::Node node) {
this.isSink(node) and
predicate isBarrierOut(DataFlow::Node node) {
isSink(node) and
not node instanceof ReturnNode
}
}
module DisposeCallOnLocalIDisposable = DataFlow::Global<DisposeCallOnLocalIDisposableConfig>;
/** Holds if `disposable` may not be disposed. */
predicate mayNotBeDisposed(LocalScopeDisposableCreation disposable) {
exists(Conf conf, DataFlow::ExprNode e |
exists(DataFlow::ExprNode e |
e.getExpr() = disposable and
conf.isSource(e) and
not exists(DataFlow::Node sink | conf.hasFlow(DataFlow::exprNode(disposable), sink) |
DisposeCallOnLocalIDisposableConfig::isSource(e) and
not exists(DataFlow::Node sink |
DisposeCallOnLocalIDisposable::flow(DataFlow::exprNode(disposable), sink)
|
sink instanceof ReturnNode
implies
sink.getEnclosingCallable() = disposable.getEnclosingCallable()

View File

@@ -16,18 +16,21 @@ import csharp
import semmle.code.csharp.security.dataflow.flowsources.Stored
import semmle.code.csharp.security.dataflow.XSSQuery
import semmle.code.csharp.security.dataflow.XSSSinks
import semmle.code.csharp.dataflow.DataFlow2
import DataFlow2::PathGraph
import StoredXss::PathGraph
class StoredTaintTrackingConfiguration extends TaintTrackingConfiguration {
override predicate isSource(DataFlow2::Node source) { source instanceof StoredFlowSource }
module StoredXssTrackingConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof StoredFlowSource }
predicate isSink = XssTrackingConfig::isSink/1;
predicate isBarrier = XssTrackingConfig::isBarrier/1;
}
from
StoredTaintTrackingConfiguration c, DataFlow2::PathNode source, DataFlow2::PathNode sink,
string explanation
module StoredXss = TaintTracking::Global<StoredXssTrackingConfig>;
from StoredXss::PathNode source, StoredXss::PathNode sink, string explanation
where
c.hasFlowPath(source, sink) and
StoredXss::flowPath(source, sink) and
if exists(sink.getNode().(Sink).explanation())
then explanation = " (" + sink.getNode().(Sink).explanation() + ")"
else explanation = ""

View File

@@ -14,7 +14,7 @@
import csharp
import semmle.code.csharp.frameworks.Test
import semmle.code.csharp.dataflow.DataFlow::DataFlow::PathGraph
import Random::InsecureRandomness::PathGraph
module Random {
import semmle.code.csharp.security.dataflow.flowsources.Remote
@@ -38,21 +38,24 @@ module Random {
/**
* A taint-tracking configuration for insecure randomness in security sensitive context.
*/
class TaintTrackingConfiguration extends TaintTracking::Configuration {
TaintTrackingConfiguration() { this = "RandomDataFlowConfiguration" }
module InsecureRandomnessConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
// succ = array_or_indexer[pred] - use of random numbers in an index
succ.asExpr().(ElementAccess).getAnIndex() = pred.asExpr()
}
}
/**
* A taint-tracking module for insecure randomness in security sensitive context.
*/
module InsecureRandomness = TaintTracking::Global<InsecureRandomnessConfig>;
/** A source of cryptographically insecure random numbers. */
class RandomSource extends Source {
RandomSource() {
@@ -112,10 +115,8 @@ module Random {
}
}
from
Random::TaintTrackingConfiguration randomTracking, DataFlow::PathNode source,
DataFlow::PathNode sink
where randomTracking.hasFlowPath(source, sink)
from Random::InsecureRandomness::PathNode source, Random::InsecureRandomness::PathNode sink
where Random::InsecureRandomness::flowPath(source, sink)
select sink.getNode(), source, sink,
"This uses a cryptographically insecure random number generated at $@ in a security context.",
source.getNode(), source.getNode().toString()

View File

@@ -12,9 +12,9 @@
import csharp
import RequestForgery::RequestForgery
import semmle.code.csharp.dataflow.DataFlow::DataFlow::PathGraph
import RequestForgeryFlow::PathGraph
from RequestForgeryConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
where c.hasFlowPath(source, sink)
from RequestForgeryFlow::PathNode source, RequestForgeryFlow::PathNode sink
where RequestForgeryFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "The URL of this request depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -24,9 +24,11 @@ module RequestForgery {
abstract private class Barrier extends DataFlow::Node { }
/**
* DEPRECATED: Use `RequestForgeryFlow` instead.
*
* A data flow configuration for detecting server side request forgery vulnerabilities.
*/
class RequestForgeryConfiguration extends DataFlow::Configuration {
deprecated class RequestForgeryConfiguration extends DataFlow::Configuration {
RequestForgeryConfiguration() { this = "Server Side Request forgery" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
@@ -54,6 +56,40 @@ module RequestForgery {
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
}
/**
* A data flow configuration for detecting server side request forgery vulnerabilities.
*/
private module RequestForgeryFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isAdditionalFlowStep(DataFlow::Node prev, DataFlow::Node succ) {
interpolatedStringFlowStep(prev, succ)
or
stringReplaceStep(prev, succ)
or
uriCreationStep(prev, succ)
or
formatConvertStep(prev, succ)
or
toStringStep(prev, succ)
or
stringConcatStep(prev, succ)
or
stringFormatStep(prev, succ)
or
pathCombineStep(prev, succ)
}
predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
}
/**
* A data flow module for detecting server side request forgery vulnerabilities.
*/
module RequestForgeryFlow = DataFlow::Global<RequestForgeryFlowConfig>;
/**
* A remote data flow source taken as a source
* for Server Side Request Forgery(SSRF) Vulnerabilities.

View File

@@ -36,7 +36,7 @@ where
iResponse.getAppendMethod() = mc.getTarget() and
isCookieWithSensitiveName(mc.getArgument(0)) and
// there is no callback `OnAppendCookie` that sets `HttpOnly` to true
not exists(OnAppendCookieHttpOnlyTrackingConfig config | config.hasFlowTo(_)) and
not OnAppendCookieHttpOnlyTracking::flowTo(_) and
// Passed as third argument to `IResponseCookies.Append`
exists(DataFlow::Node creation, DataFlow::Node append |
CookieOptionsTracking::flow(creation, append) and
@@ -67,7 +67,7 @@ where
// default is not configured or is not set to `Always`
not getAValueForCookiePolicyProp("HttpOnly").getValue() = "1" and
// there is no callback `OnAppendCookie` that sets `HttpOnly` to true
not exists(OnAppendCookieHttpOnlyTrackingConfig config | config.hasFlowTo(_)) and
not OnAppendCookieHttpOnlyTracking::flowTo(_) and
iResponse.getAppendMethod() = mc.getTarget() and
isCookieWithSensitiveName(mc.getArgument(0)) and
(

View File

@@ -30,7 +30,7 @@ where
getAValueForCookiePolicyProp("Secure").getValue() = "1"
) and
// there is no callback `OnAppendCookie` that sets `Secure` to true
not exists(OnAppendCookieSecureTrackingConfig config | config.hasFlowTo(_)) and
not OnAppendCookieSecureTracking::flowTo(_) and
(
// `Secure` property in `CookieOptions` passed to IResponseCookies.Append(...) wasn't set
exists(ObjectCreation oc |
@@ -80,7 +80,7 @@ where
or
oc.getType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
// there is no callback `OnAppendCookie` that sets `Secure` to true
not exists(OnAppendCookieSecureTrackingConfig config | config.hasFlowTo(_)) and
not OnAppendCookieSecureTracking::flowTo(_) and
// the cookie option is passed to `Append`
exists(DataFlow::Node creation |
CookieOptionsTracking::flow(creation, _) and

View File

@@ -102,21 +102,6 @@ private class TokenValidationResultIsValidCall extends PropertyRead {
}
}
/**
* Dataflow from the output of `Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateToken` call to access the `IsValid` or `Exception` property
*/
private class FlowsToTokenValidationResultIsValidCall extends DataFlow::Configuration {
FlowsToTokenValidationResultIsValidCall() { this = "FlowsToTokenValidationResultIsValidCall" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof JsonWebTokenHandlerValidateTokenCall
}
override predicate isSink(DataFlow::Node sink) {
exists(TokenValidationResultIsValidCall call | sink.asExpr() = call.getQualifier())
}
}
/**
* A security-sensitive property for `Microsoft.IdentityModel.Tokens.TokenValidationParameters`
*/

View File

@@ -9,18 +9,16 @@ import semmle.code.csharp.frameworks.microsoft.AspNetCore
* Holds if the expression is a variable with a sensitive name.
*/
predicate isCookieWithSensitiveName(Expr cookieExpr) {
exists(AuthCookieNameConfiguration dataflow, DataFlow::Node sink |
dataflow.hasFlowTo(sink) and
exists(DataFlow::Node sink |
AuthCookieName::flowTo(sink) and
sink.asExpr() = cookieExpr
)
}
/**
* Tracks if a variable with a sensitive name is used as an argument.
* Configuration for tracking if a variable with a sensitive name is used as an argument.
*/
private class AuthCookieNameConfiguration extends DataFlow::Configuration {
AuthCookieNameConfiguration() { this = "AuthCookieNameConfiguration" }
private module AuthCookieNameConfig implements DataFlow::ConfigSig {
private predicate isAuthVariable(Expr expr) {
exists(string val |
(
@@ -32,13 +30,16 @@ private class AuthCookieNameConfiguration extends DataFlow::Configuration {
)
}
override predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) }
predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) }
override predicate isSink(DataFlow::Node sink) {
exists(Call c | sink.asExpr() = c.getAnArgument())
}
predicate isSink(DataFlow::Node sink) { exists(Call c | sink.asExpr() = c.getAnArgument()) }
}
/**
* Tracks if a variable with a sensitive name is used as an argument.
*/
private module AuthCookieName = DataFlow::Global<AuthCookieNameConfig>;
/**
* DEPRECATED: Use `CookieOptionsTracking` instead.
*
@@ -134,18 +135,22 @@ Expr getAValueForProp(ObjectCreation create, Assignment a, string prop) {
predicate isPropertySet(ObjectCreation oc, string prop) { exists(getAValueForProp(oc, _, prop)) }
/**
* DEPRECATED: Use `OnAppendCookieSecureTracking` instead.
*
* Tracks if a callback used in `OnAppendCookie` sets `Secure` to `true`.
*/
class OnAppendCookieSecureTrackingConfig extends OnAppendCookieTrackingConfig {
deprecated class OnAppendCookieSecureTrackingConfig extends OnAppendCookieTrackingConfig {
OnAppendCookieSecureTrackingConfig() { this = "OnAppendCookieSecureTrackingConfig" }
override string propertyName() { result = "Secure" }
}
/**
* DEPRECATED: Use `OnAppendCookieHttpOnlyTracking` instead.
*
* Tracks if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
*/
class OnAppendCookieHttpOnlyTrackingConfig extends OnAppendCookieTrackingConfig {
deprecated class OnAppendCookieHttpOnlyTrackingConfig extends OnAppendCookieTrackingConfig {
OnAppendCookieHttpOnlyTrackingConfig() { this = "OnAppendCookieHttpOnlyTrackingConfig" }
override string propertyName() { result = "HttpOnly" }
@@ -205,3 +210,83 @@ abstract private class OnAppendCookieTrackingConfig extends DataFlow::Configurat
)
}
}
private signature string propertyName();
/**
* Configuration for tracking if a callback used in `OnAppendCookie` sets a cookie property to `true`.
*/
private module OnAppendCookieTrackingConfig<propertyName/0 getPropertyName> implements
DataFlow::ConfigSig
{
/**
* Specifies the cookie property name to track.
*/
predicate isSource(DataFlow::Node source) {
exists(PropertyWrite pw, Assignment delegateAssign, Callable c |
pw.getProperty().getName() = "OnAppendCookie" and
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreBuilderCookiePolicyOptions and
delegateAssign.getLValue() = pw and
(
exists(LambdaExpr lambda |
delegateAssign.getRValue() = lambda and
lambda = c
)
or
exists(DelegateCreation delegate |
delegateAssign.getRValue() = delegate and
delegate.getArgument().(CallableAccess).getTarget() = c
)
) and
c.getParameter(0) = source.asParameter()
)
}
predicate isSink(DataFlow::Node sink) {
exists(PropertyWrite pw, Assignment a |
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
pw.getProperty().getName() = getPropertyName() and
a.getLValue() = pw and
exists(Expr val |
DataFlow::localExprFlow(val, a.getRValue()) and
val.getValue() = "true"
) and
sink.asExpr() = pw.getQualifier()
)
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node2.asExpr() =
any(PropertyRead pr |
pr.getQualifier() = node1.asExpr() and
pr.getProperty().getDeclaringType() instanceof
MicrosoftAspNetCoreCookiePolicyAppendCookieContext
)
}
}
private string getPropertyNameSecure() { result = "Secure" }
/**
* Configuration module for tracking if a callback used in `OnAppendCookie` sets `Secure` to `true`.
*/
private module OnAppendCookieSecureTrackingConfig =
OnAppendCookieTrackingConfig<getPropertyNameSecure/0>;
/**
* Tracks if a callback used in `OnAppendCookie` sets `Secure` to `true`.
*/
module OnAppendCookieSecureTracking = DataFlow::Global<OnAppendCookieSecureTrackingConfig>;
private string getPropertyNameHttpOnly() { result = "HttpOnly" }
/**
* Configuration module for tracking if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
*/
private module OnAppendCookieHttpOnlyTrackingConfig =
OnAppendCookieTrackingConfig<getPropertyNameHttpOnly/0>;
/**
* Tracks if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
*/
module OnAppendCookieHttpOnlyTracking = DataFlow::Global<OnAppendCookieHttpOnlyTrackingConfig>;

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -3,22 +3,22 @@ import semmle.code.java.dataflow.TaintTracking
import TestUtilities.InlineExpectationsTest
private import semmle.code.java.dataflow.ExternalFlow
class Config extends TaintTracking::Configuration {
Config() { this = "Config" }
override predicate isSource(DataFlow::Node n) {
module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) {
n.asExpr().(MethodAccess).getCallee().getName() = "source"
or
sourceNode(n, "kotlinMadFlowTest")
}
override predicate isSink(DataFlow::Node n) {
predicate isSink(DataFlow::Node n) {
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
or
sinkNode(n, "kotlinMadFlowTest")
}
}
module Flow = TaintTracking::Global<Config>;
class InlineFlowTest extends InlineExpectationsTest {
InlineFlowTest() { this = "HasFlowTest" }
@@ -26,7 +26,7 @@ class InlineFlowTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "flow" and
exists(DataFlow::Node sink, Config c | c.hasFlowTo(sink) |
exists(DataFlow::Node sink | Flow::flowTo(sink) |
sink.getLocation() = location and
element = sink.toString() and
value = ""

View File

@@ -19,18 +19,18 @@ query predicate superAccesses(
enclosingType = enclosingCallable.getDeclaringType()
}
class Config extends DataFlow::Configuration {
Config() { this = "testconfig" }
override predicate isSource(DataFlow::Node x) {
module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node x) {
x.asExpr() instanceof IntegerLiteral and x.getEnclosingCallable().fromSource()
}
override predicate isSink(DataFlow::Node x) {
predicate isSink(DataFlow::Node x) {
x.asExpr().(Argument).getCall().getCallee().getName() = "sink"
}
}
from Config c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
module Flow = DataFlow::Global<Config>;
from DataFlow::Node source, DataFlow::Node sink
where Flow::flow(source, sink)
select source, sink

View File

@@ -1,17 +1,17 @@
import java
import semmle.code.java.dataflow.DataFlow
import DataFlow::PathGraph
import Flow::PathGraph
class Config extends DataFlow::Configuration {
Config() { this = "Config" }
module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) { n.asExpr().(StringLiteral).getValue() = "taint" }
override predicate isSource(DataFlow::Node n) { n.asExpr().(StringLiteral).getValue() = "taint" }
override predicate isSink(DataFlow::Node n) {
predicate isSink(DataFlow::Node n) {
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, Config c
where c.hasFlowPath(source, sink)
module Flow = DataFlow::Global<Config>;
from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select source, source, sink, "flow path"

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -27,9 +27,8 @@ private J::Method superImpl(J::Method m) {
}
private predicate isInTestFile(J::File file) {
file.getAbsolutePath().matches("%src/test/%") or
file.getAbsolutePath().matches("%/guava-tests/%") or
file.getAbsolutePath().matches("%/guava-testlib/%")
file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and
not file.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work
}
private predicate isJdkInternal(J::CompilationUnit cu) {

View File

@@ -1,284 +1,28 @@
package com.semmle.js.extractor;
import com.semmle.util.data.StringUtil;
import com.semmle.util.exception.CatastrophicError;
import com.semmle.util.exception.UserError;
import com.semmle.util.locations.LineTable;
import com.semmle.util.trap.TrapWriter;
import com.semmle.util.trap.TrapWriter.Label;
import com.semmle.util.trap.TrapWriter.Table;
import java.util.Collections;
import org.yaml.snakeyaml.composer.Composer;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.MarkedYAMLException;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.MappingStartEvent;
import org.yaml.snakeyaml.events.NodeEvent;
import org.yaml.snakeyaml.events.ScalarEvent;
import org.yaml.snakeyaml.events.SequenceStartEvent;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.parser.Parser;
import org.yaml.snakeyaml.parser.ParserImpl;
import org.yaml.snakeyaml.reader.ReaderException;
import org.yaml.snakeyaml.reader.StreamReader;
import org.yaml.snakeyaml.resolver.Resolver;
import com.semmle.extractor.yaml.YamlPopulator;
/**
* Extractor for populating YAML files.
*
* <p>The extractor uses <a href="http://www.snakeyaml.org/">SnakeYAML</a> to parse YAML.
* <p>
* The extractor uses <a href="http://www.snakeyaml.org/">SnakeYAML</a> to parse
* YAML.
*/
public class YAMLExtractor implements IExtractor {
/** The tables constituting the YAML dbscheme. */
private static enum YAMLTables implements Table {
YAML(6), // yaml (id: @yaml_node, kind: int ref, parent: @yaml_node_parent ref,
// idx: int ref, tag: string ref, tostring: string ref)
YAML_ANCHORS(2), // yaml_anchors (node: @yaml_node ref, anchor: string ref)
YAML_ALIASES(2), // yaml_aliases (alias: @yaml_alias_node ref, target: string ref)
YAML_SCALARS(
3), // yaml_scalars (scalar: @yaml_scalar_node ref, style: int ref, value: string ref)
YAML_ERRORS(2); // yaml_errors (id: @yaml_error, message: string ref)
private final int arity;
private YAMLTables(int arity) {
this.arity = arity;
}
@Override
public String getName() {
return StringUtil.lc(name());
}
@Override
public int getArity() {
return arity;
}
@Override
public boolean validate(Object... values) {
return true;
}
}
/*
* case @yaml_node.kind of
* 0 = @yaml_scalar_node
* | 1 = @yaml_mapping_node
* | 2 = @yaml_sequence_node
* | 3 = @yaml_alias_node
*/
private static enum NodeKind {
SCALAR,
MAPPING,
SEQUENCE,
ALIAS
};
private final boolean tolerateParseErrors;
private TextualExtractor textualExtractor;
private LocationManager locationManager;
private TrapWriter trapWriter;
private LineTable lineTable;
/**
* The underlying SnakeYAML parser; we use the relatively low-level {@linkplain Parser} instead of
* the more high-level {@linkplain Composer}, since our dbscheme represents YAML documents in AST
* form, with aliases left unresolved.
*/
private Parser parser;
/** The resolver used for resolving type tags. */
private Resolver resolver;
public YAMLExtractor(ExtractorConfig config) {
this.tolerateParseErrors = config.isTolerateParseErrors();
}
private LineTable getLineTable() {
if (lineTable == null) {
lineTable = new LineTable(this.textualExtractor.getSource());
}
return lineTable;
}
@Override
public ParseResultInfo extract(TextualExtractor textualExtractor) {
this.textualExtractor = textualExtractor;
locationManager = textualExtractor.getLocationManager();
trapWriter = textualExtractor.getTrapwriter();
Label fileLabel = locationManager.getFileLabel();
locationManager.setHasLocationTable("yaml_locations");
try {
parser = new ParserImpl(new StreamReader(textualExtractor.getSource()));
resolver = new Resolver();
int idx = 0;
while (!atStreamEnd())
extractDocument(fileLabel, idx++, textualExtractor.getSource().codePoints().toArray());
} catch (MarkedYAMLException e) {
int line = e.getProblemMark().getLine() + 1;
int column = e.getProblemMark().getColumn() + 1;
if (!this.tolerateParseErrors)
throw new UserError(e.getProblem() + ": " + line + ":" + column);
Label lbl = trapWriter.freshLabel();
trapWriter.addTuple(YAMLTables.YAML_ERRORS, lbl, e.getProblem());
locationManager.emitSnippetLocation(lbl, line, column, line, column);
} catch (ReaderException e) {
if (!this.tolerateParseErrors) throw new UserError(e.toString());
int c = e.getCodePoint();
String s = String.valueOf(Character.toChars(c));
trapWriter.addTuple(
YAMLTables.YAML_ERRORS,
trapWriter.freshLabel(),
"Unexpected character " + s + "(" + c + ")");
// unfortunately, SnakeYAML does not provide structured location information for
// ReaderExceptions
}
new YamlPopulator(textualExtractor.getExtractedFile(), textualExtractor.getSource(),
textualExtractor.getTrapwriter(),
this.tolerateParseErrors).extract();
return new ParseResultInfo(0, 0, Collections.emptyList());
}
/** Check whether the parser has encountered the end of the YAML input stream. */
private boolean atStreamEnd() {
if (parser.checkEvent(Event.ID.StreamStart)) parser.getEvent();
return parser.checkEvent(Event.ID.StreamEnd);
}
/** Extract a complete YAML document; cf. {@link Composer#getNode}. */
private void extractDocument(Label parent, int idx, int[] codepoints) {
// Drop the DOCUMENT-START event
parser.getEvent();
extractNode(parent, idx, codepoints);
// Drop the DOCUMENT-END event
parser.getEvent();
}
/** Extract a single YAML node; cf. {@link Composer#composeNode}. */
private void extractNode(Label parent, int idx, int[] codepoints) {
Label label = trapWriter.freshLabel();
NodeKind kind;
String tag = "";
Event start = parser.getEvent(), end = start;
if (start.is(Event.ID.Alias)) {
kind = NodeKind.ALIAS;
trapWriter.addTuple(YAMLTables.YAML_ALIASES, label, ((AliasEvent) start).getAnchor());
} else {
String anchor = start instanceof NodeEvent ? ((NodeEvent) start).getAnchor() : null;
if (anchor != null) trapWriter.addTuple(YAMLTables.YAML_ANCHORS, label, anchor);
if (start.is(Event.ID.Scalar)) {
kind = NodeKind.SCALAR;
ScalarEvent scalar = (ScalarEvent) start;
tag =
getTag(
scalar.getTag(),
NodeId.scalar,
scalar.getValue(),
scalar.getImplicit().canOmitTagInPlainScalar());
Character style = scalar.getStyle();
int styleCode = style == null ? 0 : (int) style;
trapWriter.addTuple(YAMLTables.YAML_SCALARS, label, styleCode, scalar.getValue());
} else if (start.is(Event.ID.SequenceStart)) {
kind = NodeKind.SEQUENCE;
SequenceStartEvent sequenceStart = (SequenceStartEvent) start;
tag = getTag(sequenceStart.getTag(), NodeId.sequence, null, sequenceStart.getImplicit());
int childIdx = 0;
while (!parser.checkEvent(Event.ID.SequenceEnd)) extractNode(label, childIdx++, codepoints);
end = parser.getEvent();
} else if (start.is(Event.ID.MappingStart)) {
kind = NodeKind.MAPPING;
MappingStartEvent mappingStart = (MappingStartEvent) start;
tag = getTag(mappingStart.getTag(), NodeId.mapping, null, mappingStart.getImplicit());
int childIdx = 1;
while (!parser.checkEvent(Event.ID.MappingEnd)) {
extractNode(label, childIdx, codepoints);
extractNode(label, -childIdx, codepoints);
++childIdx;
}
end = parser.getEvent();
} else {
throw new CatastrophicError("Unexpected YAML parser event: " + start);
}
}
trapWriter.addTuple(
YAMLTables.YAML,
label,
kind.ordinal(),
parent,
idx,
tag,
mkToString(start.getStartMark(), end.getEndMark(), codepoints));
extractLocation(label, start.getStartMark(), end.getEndMark());
}
/** Determine the type tag of a node. */
private String getTag(String explicitTag, NodeId kind, String value, boolean implicit) {
if (explicitTag == null || "!".equals(explicitTag))
return resolver.resolve(kind, value, implicit).getValue();
return explicitTag;
}
private static boolean isNewLine(int codePoint) {
switch (codePoint) {
case '\n':
case '\r':
case '\u0085':
case '\u2028':
case '\u2029':
return true;
default:
return false;
}
}
/**
* SnakeYAML doesn't directly expose the source text of nodes, but we also take the file contents
* as an array of Unicode code points. The start and end marks each contain an index into the code
* point stream (the end is exclusive), so we can reconstruct the snippet. For readability, we
* stop at the first encountered newline.
*/
private static String mkToString(Mark startMark, Mark endMark, int[] codepoints) {
StringBuilder b = new StringBuilder();
for (int i = startMark.getIndex(); i < endMark.getIndex() && !isNewLine(codepoints[i]); i++)
b.appendCodePoint(codepoints[i]);
return TextualExtractor.sanitiseToString(b.toString());
}
/** Emit a source location for a YAML node. */
private void extractLocation(Label label, Mark startMark, Mark endMark) {
int startLine, startColumn, endLine, endColumn;
// SnakeYAML uses 0-based indexing for both lines and columns, so need to +1
startLine = startMark.getLine() + 1;
startColumn = startMark.getColumn() + 1;
// SnakeYAML's end positions are exclusive, so only need to +1 for the line
endLine = endMark.getLine() + 1;
endColumn = endMark.getColumn();
// Avoid emitting column zero for non-empty locations
if (endColumn == 0 && !(startLine == endLine && startColumn == endColumn)) {
String source = textualExtractor.getSource();
int offset = getLineTable().getOffsetFromPoint(endMark.getLine(), endMark.getColumn()) - 1;
while (offset > 0 && isNewLine((int)source.charAt(offset))) {
--offset;
}
com.semmle.util.locations.Position adjustedEndPos = getLineTable().getEndPositionFromOffset(offset);
endLine = adjustedEndPos.getLine();
endColumn = adjustedEndPos.getColumn();
}
locationManager.emitSnippetLocation(label, startLine, startColumn, endLine, endColumn);
}
}

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -1,6 +1,6 @@
/**
* @name SimpleXMLRPCServer DoS vulnerability
* @description SimpleXMLRPCServer is vulnerable to DoS attacks from untrusted user input
* @name SimpleXMLRPCServer denial of service
* @description SimpleXMLRPCServer is vulnerable to denial of service attacks from untrusted user input
* @kind problem
* @problem.severity warning
* @precision high

BIN
ql/Cargo.lock generated

Binary file not shown.

View File

@@ -9,4 +9,4 @@ edition = "2018"
lazy_static = "1.4.0"
chrono = "0.4.24"
rayon = "1.7.0"
regex = "1.7.3"
regex = "1.8.1"

View File

@@ -15,7 +15,7 @@ tree-sitter-blame = {path = "../buramu/tree-sitter-blame"}
tree-sitter-json = {git = "https://github.com/tausbn/tree-sitter-json.git", rev = "745663ee997f1576fe1e7187e6347e0db36ec7a9"}
clap = { version = "4.2", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
rayon = "1.7.0"
regex = "1.7.3"
regex = "1.8.1"
codeql-extractor = { path = "../../shared/tree-sitter-extractor" }

View File

@@ -359,6 +359,11 @@ module API {
Location getLocation() {
result = this.getInducingNode().getLocation()
or
exists(DataFlow::ModuleNode mod |
this = Impl::MkModuleObject(mod) and
result = mod.getLocation()
)
or
// For nodes that do not have a meaningful location, `path` is the empty string and all other
// parameters are zero.
not exists(this.getInducingNode()) and
@@ -601,7 +606,9 @@ module API {
/** A use of an API member at the node `nd`. */
MkUse(DataFlow::Node nd) { isUse(nd) } or
/** A value that escapes into an external library at the node `nd` */
MkDef(DataFlow::Node nd) { isDef(nd) }
MkDef(DataFlow::Node nd) { isDef(nd) } or
/** A module object seen as a use node. */
MkModuleObject(DataFlow::ModuleNode mod)
private string resolveTopLevel(ConstantReadAccess read) {
result = read.getModule().getQualifiedName() and
@@ -684,7 +691,14 @@ module API {
* Holds if `ref` is a use of node `nd`.
*/
cached
predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) }
predicate use(TApiNode nd, DataFlow::Node ref) {
nd = MkUse(ref)
or
exists(DataFlow::ModuleNode mod |
nd = MkModuleObject(mod) and
ref = mod.getAnImmediateReference()
)
}
/**
* Holds if `rhs` is a RHS of node `nd`.
@@ -802,6 +816,14 @@ module API {
trackUseNode(use).flowsTo(call.getReceiver())
}
/**
* Holds if `superclass` is the superclass of `mod`.
*/
pragma[nomagic]
private predicate superclassNode(DataFlow::ModuleNode mod, DataFlow::Node superclass) {
superclass.asExpr().getExpr() = mod.getADeclaration().(ClassDeclaration).getSuperclassExpr()
}
/**
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
*/
@@ -813,38 +835,35 @@ module API {
useRoot(lbl, ref)
or
exists(DataFlow::Node node, DataFlow::Node src |
pred = MkUse(src) and
use(pred, src) and
trackUseNode(src).flowsTo(node) and
useStep(lbl, node, ref)
)
or
exists(DataFlow::Node callback |
pred = MkDef(callback) and
def(pred, callback) and
parameterStep(lbl, trackDefNode(callback), ref)
)
)
or
exists(DataFlow::Node predNode, DataFlow::Node succNode |
def(pred, predNode) and
def(succ, succNode) and
succ = MkDef(succNode) and
defStep(lbl, trackDefNode(predNode), succNode)
)
or
// `pred` is a use of class A
// `succ` is a use of class B
// there exists a class declaration B < A
exists(ClassDeclaration c, DataFlow::Node a, DataFlow::Node b |
use(pred, a) and
use(succ, b) and
b.asExpr().getExpr().(ConstantReadAccess).getAQualifiedName() = c.getAQualifiedName() and
pragma[only_bind_into](c).getSuperclassExpr() = a.asExpr().getExpr() and
exists(DataFlow::Node predNode, DataFlow::Node superclassNode, DataFlow::ModuleNode mod |
use(pred, predNode) and
trackUseNode(predNode).flowsTo(superclassNode) and
superclassNode(mod, superclassNode) and
succ = MkModuleObject(mod) and
lbl = Label::subclass()
)
or
exists(DataFlow::CallNode call |
// from receiver to method call node
exists(DataFlow::Node receiver |
pred = MkUse(receiver) and
use(pred, receiver) and
useNodeReachesReceiver(receiver, call) and
lbl = Label::method(call.getMethodName()) and
succ = MkMethodAccessNode(call)

View File

@@ -1169,13 +1169,12 @@ private module ArrayLiteralDesugar {
child = SynthChild(MethodCallKind("[]", false, al.getNumberOfElements()))
or
parent = TMethodCallSynth(al, -1, _, _, _) and
(
i = 0 and
child = SynthChild(ConstantReadAccessKind("::Array"))
or
parent = TMethodCallSynth(al, -1, _, _, _) and
child = childRef(al.getElement(i - 1))
)
)
}
/**
@@ -1212,13 +1211,12 @@ private module HashLiteralDesugar {
child = SynthChild(MethodCallKind("[]", false, hl.getNumberOfElements()))
or
parent = TMethodCallSynth(hl, -1, _, _, _) and
(
i = 0 and
child = SynthChild(ConstantReadAccessKind("::Hash"))
or
parent = TMethodCallSynth(hl, -1, _, _, _) and
child = childRef(hl.getElement(i - 1))
)
)
}
/**

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -890,6 +890,9 @@ class ModuleNode instanceof Module {
/** Gets a constant or `self` variable that refers to this module. */
LocalSourceNode getAnImmediateReference() {
result.asExpr().getExpr() = super.getAnImmediateReference()
or
// Include 'self' parameters; these are not expressions and so not found by the case above
result = this.getAnOwnModuleSelf()
}
/**

View File

@@ -127,11 +127,28 @@ getModuleLevelSelf
getAnImmediateReference
| file://:0:0:0:0 | Array | tst.rb:59:15:59:21 | Array |
| file://:0:0:0:0 | Hash | tst.rb:60:14:60:45 | Hash |
| tst.rb:1:1:6:3 | C1 | tst.rb:1:1:6:3 | self (C1) |
| tst.rb:1:1:6:3 | C1 | tst.rb:8:12:8:13 | C1 |
| tst.rb:8:1:11:3 | C2 | tst.rb:8:1:11:3 | self (C2) |
| tst.rb:8:1:11:3 | C2 | tst.rb:27:12:27:13 | C2 |
| tst.rb:13:1:18:3 | Mixin | tst.rb:13:1:18:3 | self (Mixin) |
| tst.rb:13:1:18:3 | Mixin | tst.rb:16:5:17:7 | self in m1s |
| tst.rb:13:1:18:3 | Mixin | tst.rb:28:13:28:17 | Mixin |
| tst.rb:20:1:25:3 | Mixin2 | tst.rb:20:1:25:3 | self (Mixin2) |
| tst.rb:20:1:25:3 | Mixin2 | tst.rb:23:5:24:7 | self in m2s |
| tst.rb:20:1:25:3 | Mixin2 | tst.rb:29:13:29:18 | Mixin2 |
| tst.rb:27:1:35:3 | C3 | tst.rb:27:1:35:3 | self (C3) |
| tst.rb:27:1:35:3 | C3 | tst.rb:32:9:33:11 | self in c3_self1 |
| tst.rb:27:1:35:3 | C3 | tst.rb:37:1:38:3 | self in c3_self2 |
| tst.rb:27:1:35:3 | C3 | tst.rb:37:5:37:6 | C3 |
| tst.rb:40:1:47:3 | N1 | tst.rb:40:1:47:3 | self (N1) |
| tst.rb:41:5:42:7 | N1::XY1 | tst.rb:41:5:42:7 | self (XY1) |
| tst.rb:43:5:46:7 | N1::N2 | tst.rb:43:5:46:7 | self (N2) |
| tst.rb:44:9:45:11 | N1::N2::XY2 | tst.rb:44:9:45:11 | self (XY2) |
| tst.rb:49:1:51:3 | N2 | tst.rb:49:1:51:3 | self (N2) |
| tst.rb:49:1:51:3 | N2 | tst.rb:52:1:55:3 | self (N2) |
| tst.rb:53:5:54:7 | N2::XY3 | tst.rb:53:5:54:7 | self (XY3) |
| tst.rb:57:1:62:3 | Nodes | tst.rb:57:1:62:3 | self (Nodes) |
getOwnInstanceMethod
| tst.rb:1:1:6:3 | C1 | c1 | tst.rb:2:5:5:7 | c1 |
| tst.rb:8:1:11:3 | C2 | c2 | tst.rb:9:5:10:7 | c2 |

View File

@@ -94,7 +94,8 @@ void Log::configure() {
// as we are configuring logging right now, we collect problems and log them at the end
auto problems = collectSeverityRulesAndReturnProblems("CODEQL_EXTRACTOR_SWIFT_LOG_LEVELS");
if (text || binary) {
std::filesystem::path logFile = getEnvOr("CODEQL_EXTRACTOR_SWIFT_LOG_DIR", ".");
std::filesystem::path logFile = getEnvOr("CODEQL_EXTRACTOR_SWIFT_LOG_DIR", "extractor-out/log");
logFile /= "swift";
logFile /= logRootName;
logFile /= std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
std::error_code ec;

View File

@@ -177,9 +177,11 @@ codeql::TrapDomain invocationTrapDomain(codeql::SwiftExtractorState& state) {
codeql::SwiftExtractorConfiguration configure(int argc, char** argv) {
codeql::SwiftExtractorConfiguration configuration{};
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", ".");
configuration.sourceArchiveDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", ".");
configuration.scratchDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", ".");
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", "extractor-out/trap/swift");
configuration.sourceArchiveDir =
getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", "extractor-out/src");
configuration.scratchDir =
getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", "extractor-out/working");
configuration.frontendOptions.assign(argv + 1, argv + argc);
return configuration;
}

View File

@@ -60,7 +60,6 @@ ql/lib/codeql/swift/elements/decl/SubscriptDeclConstructor.qll 3a88617b41f96827c
ql/lib/codeql/swift/elements/decl/TopLevelCodeDeclConstructor.qll 6920a4e7aec45ae2a561cef95b9082b861f81c16c259698541f317481645e194 4bd65820b93a5ec7332dd1bbf59326fc19b77e94c122ad65d41393c84e6ac581
ql/lib/codeql/swift/elements/decl/TypeAliasDecl.qll 984c5802c35e595388f7652cef1a50fb963b32342ab4f9d813b7200a0e6a37ca 630dc9cbf20603855c599a9f86037ba0d889ad3d2c2b6f9ac17508d398bff9e3
ql/lib/codeql/swift/elements/decl/TypeAliasDeclConstructor.qll ba70bb69b3a14283def254cc1859c29963838f624b3f1062a200a8df38f1edd5 96ac51d1b3156d4139e583f7f803e9eb95fe25cc61c12986e1b2972a781f9c8b
ql/lib/codeql/swift/elements/decl/ValueDecl.qll 1b7d8eeb17be4bdbabc156cb0689641ed4f9e697e334d2d14f423ed3d1a419f6 e3056cf6a883da2737cb220a89499a9e3977eb1c56b9e1d2f41a56b71a0c29f9
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExpr.qll 39b856c89b8aff769b75051fd9e319f2d064c602733eaa6fed90d8f626516306 a87738539276438cef63145461adf25309d1938cfac367f53f53d33db9b12844
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExprConstructor.qll 7d70e7c47a9919efcb1ebcbf70e69cab1be30dd006297b75f6d72b25ae75502a e7a741c42401963f0c1da414b3ae779adeba091e9b8f56c9abf2a686e3a04d52
ql/lib/codeql/swift/elements/expr/AbstractClosureExpr.qll 4027b51a171387332f96cb7b78ca87a6906aec76419938157ac24a60cff16519 400790fe643585ad39f40c433eff8934bbe542d140b81341bca3b6dfc5b22861

View File

@@ -361,3 +361,52 @@ module MergePathGraph<
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -1,6 +1,8 @@
private import codeql.swift.generated.AstNode
private import codeql.swift.elements.decl.AbstractFunctionDecl
private import codeql.swift.elements.decl.Decl
private import codeql.swift.elements.expr.AbstractClosureExpr
private import codeql.swift.elements.Callable
private import codeql.swift.generated.ParentChild
private module Cached {
@@ -21,10 +23,59 @@ private module Cached {
AbstractFunctionDecl getEnclosingFunction(AstNode ast) {
result = getEnclosingFunctionStep*(getEnclosingDecl(ast))
}
private Element getEnclosingClosureStep(Element e) {
not e instanceof Callable and
result = getImmediateParent(e)
}
cached
AbstractClosureExpr getEnclosingClosure(AstNode ast) {
result = getEnclosingClosureStep*(getImmediateParent(ast))
}
}
/**
* A node in the abstract syntax tree.
*/
class AstNode extends Generated::AstNode {
/**
* Gets the nearest function definition that contains this AST node, if any.
* This includes functions, methods, (de)initializers, and accessors, but not closures.
*
* For example, in the following code, the AST node for `n + 1` has `foo` as its
* enclosing function (via `getEnclosingFunction`), whereas its enclosing callable is
* the closure `{(n : Int) in n + 1 }` (via `getEnclosingCallable`):
*
* ```swift
* func foo() {
* var f = { (n : Int) in n + 1 }
* }
* ```
*/
final AbstractFunctionDecl getEnclosingFunction() { result = Cached::getEnclosingFunction(this) }
/**
* Gets the nearest declaration that contains this AST node, if any.
*/
final Decl getEnclosingDecl() { result = Cached::getEnclosingDecl(this) }
/**
* Gets the nearest `Callable` that contains this AST node, if any.
* This includes (auto)closures, functions, methods, (de)initializers, and accessors.
*
* For example, in the following code, the AST node for `n + 1` has the closure
* `{(n : Int) in n + 1 }` as its enclosing callable.
*
* ```swift
* func foo() {
* var f = { (n : Int) in n + 1 }
* }
* ```
*/
final Callable getEnclosingCallable() {
if exists(Cached::getEnclosingClosure(this))
then result = Cached::getEnclosingClosure(this)
else result = Cached::getEnclosingFunction(this)
}
}

View File

@@ -1,5 +1,23 @@
private import codeql.swift.generated.decl.CapturedDecl
private import codeql.swift.elements.Callable
private import codeql.swift.elements.expr.DeclRefExpr
/**
* A captured variable or function parameter in the scope of a closure.
*/
class CapturedDecl extends Generated::CapturedDecl {
override string toString() { result = this.getDecl().toString() }
/**
* Gets the closure or function that captures this variable.
*/
Callable getScope() { result.getACapture() = this }
/**
* Get an access to this capture within the scope of its closure.
*/
DeclRefExpr getAnAccess() {
result.getEnclosingCallable() = this.getScope() and
result.getDecl() = this.getDecl()
}
}

View File

@@ -1,4 +1,23 @@
// generated by codegen/codegen.py, remove this comment if you wish to edit this file
private import codeql.swift.generated.decl.ValueDecl
private import codeql.swift.elements.decl.CapturedDecl
private import codeql.swift.elements.expr.DeclRefExpr
class ValueDecl extends Generated::ValueDecl { }
/**
* A declaration that introduces a value with a type.
*/
class ValueDecl extends Generated::ValueDecl {
/**
* Gets a capture of this declaration in the scope of a closure.
*/
CapturedDecl asCapturedDecl() { result.getDecl() = this }
/**
* Holds if this declaration is captured by a closure.
*/
predicate isCaptured() { exists(this.asCapturedDecl()) }
/**
* Gets an expression that references this declaration.
*/
DeclRefExpr getAnAccess() { result.getDecl() = this }
}

View File

@@ -1,13 +1,16 @@
private import codeql.swift.generated.decl.VarDecl
private import codeql.swift.elements.expr.DeclRefExpr
private import codeql.swift.elements.decl.Decl
/**
* A variable declaration.
*/
class VarDecl extends Generated::VarDecl {
override string toString() { result = this.getName() }
DeclRefExpr getAnAccess() { result.getDecl() = this }
}
/**
* A field declaration.
*/
class FieldDecl extends VarDecl {
FieldDecl() { this = any(Decl ctx).getAMember() }
}

View File

@@ -1,9 +1,23 @@
private import codeql.swift.generated.expr.DeclRefExpr
private import codeql.swift.elements.decl.CapturedDecl
/**
* An expression that references or accesses a declaration.
*/
class DeclRefExpr extends Generated::DeclRefExpr {
override string toString() {
if exists(this.getDecl().toString())
then result = this.getDecl().toString()
else result = "(unknown declaration)"
}
/**
* Gets the closure capture referenced by this expression, if any.
*/
CapturedDecl getCapturedDecl() { result.getAnAccess() = this }
/**
* Holds if this expression references a closure capture.
*/
predicate hasCapturedDecl() { exists(this.getCapturedDecl()) }
}

View File

@@ -1,5 +1,41 @@
| closures.swift:8:12:8:12 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:7:6:7:6 | x | isDirect: | yes | isEscaping: | no |
| closures.swift:9:12:9:12 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:6:7:6:7 | y | isDirect: | yes | isEscaping: | no |
| closures.swift:16:3:16:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:17:5:17:5 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:15:7:15:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:20:3:20:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:25:3:25:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:24:3:24:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:31:11:31:11 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:29:7:29:7 | x | isDirect: | no | isEscaping: | no |
| closures.swift:32:14:32:14 | f | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:28:7:28:7 | f | isDirect: | no | isEscaping: | no |
| closures.swift:32:14:32:14 | f | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:28:7:28:7 | f | isDirect: | yes | isEscaping: | no |
| closures.swift:32:17:32:17 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:29:7:29:7 | x | isDirect: | yes | isEscaping: | no |
| closures.swift:39:20:39:20 | callback | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:36:21:36:58 | callback | isDirect: | yes | isEscaping: | yes |
| closures.swift:42:35:42:35 | wrapper(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:38:5:40:5 | wrapper(_:) | isDirect: | no | isEscaping: | yes |
| closures.swift:52:18:52:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:51:7:51:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:54:13:54:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:51:7:51:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:61:18:61:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:60:7:60:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:63:13:63:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:60:7:60:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:71:3:71:3 | g | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:68:5:68:5 | g | isDirect: | yes | isEscaping: | yes |
| closures.swift:71:14:71:14 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:70:7:70:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:73:13:73:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:70:7:70:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:85:7:85:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | no | isEscaping: | yes |
| closures.swift:85:7:85:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | yes | isEscaping: | yes |
| closures.swift:85:18:85:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:82:9:82:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:88:9:88:9 | b() | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:92:5:98:5 | b() | isDirect: | no | isEscaping: | yes |
| closures.swift:93:7:93:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | yes | isEscaping: | yes |
| closures.swift:93:20:93:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:82:9:82:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:96:9:96:9 | a() | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:84:5:90:5 | a() | isDirect: | no | isEscaping: | yes |
| closures.swift:111:15:111:15 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:110:9:110:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:111:27:111:27 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:110:9:110:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:115:5:115:5 | incrX | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:109:8:109:8 | incrX | isDirect: | yes | isEscaping: | yes |
| closures.swift:130:25:130:25 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:133:20:133:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | no | isEscaping: | yes |
| closures.swift:133:20:133:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:133:24:133:24 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:132:22:132:22 | y | isDirect: | yes | isEscaping: | yes |
| closures.swift:151:21:151:21 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:10:149:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:154:16:154:16 | g(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:3:165:3 | g(_:) | isDirect: | no | isEscaping: | yes |
| closures.swift:155:21:155:21 | next | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:154:9:154:9 | next | isDirect: | yes | isEscaping: | yes |
| closures.swift:155:34:155:34 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:10:149:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:160:21:160:21 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:10:158:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:163:16:163:16 | f(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:3:156:3 | f(_:) | isDirect: | no | isEscaping: | yes |
| closures.swift:164:21:164:21 | next | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:163:9:163:9 | next | isDirect: | yes | isEscaping: | yes |
| closures.swift:164:36:164:36 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:10:158:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:195:3:195:3 | g | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:68:5:68:5 | g | isDirect: | yes | isEscaping: | yes |

View File

@@ -1,26 +1,211 @@
func bar() -> String {
func hello() -> String {
return "Hello world!"
}
func foo() {
func captureList() {
let y = 123
{ [x = bar()] () in
{ [x = hello()] () in
print(x)
print(y) }()
}
var escape: (() -> ())? = nil
func baz() {
func setEscape() {
var x = 0
func quux() {
escape = {
x += 1
print(x)
}
escape = quux
}
func callEscape() {
baz()
setEscape()
escape?()
}
func logical() -> Bool {
let f: ((Int) -> Int)? = { x in x + 1 }
let x: Int? = 42
return f != nil
&& (x != nil
&& f!(x!) == 43)
}
func asyncTest() {
func withCallback(_ callback: @escaping (Int) async -> Int) {
@Sendable
func wrapper(_ x: Int) async -> Int {
return await callback(x + 1)
}
Task {
print("asyncTest():", await wrapper(40))
}
}
withCallback { x in
x + 1
}
}
func foo() -> Int {
var x = 1 // x is a non-escaping capture of f and r
let f = { y in x += y }
x += 40
let r = { x }
f(1)
return r() // 42
}
func bar() -> () -> Int {
var x = 1 // x is a non-escaping capture of f, escaping capture of r
let f = { y in x += y }
x += 40
let r = { x }
f(1)
return r // constantly 42
}
var g: ((Int) -> Void)? = nil
func baz() -> () -> Int {
var x = 1 // x is an escaping capture of g and r
g = { y in x += y } // closure escapes!
x += 40
let r = { x }
g!(1)
return r
}
func quux() -> Int {
var y = 0
func f() -> () -> Void {
var x = 5
func a() {
y = 10*y + x
x -= 1
if x > 0 {
b()
}
}
func b() {
y = 10*y + 2*x
x -= 1
if x > 0 {
a()
}
}
return a
}
let a = f()
a()
return y // 58341
}
func sharedCapture() -> Int {
let (incrX, getX) = {
var x = 0
return ({ x += 1 }, { x })
}()
let doubleIncrX = {
incrX()
incrX()
}
doubleIncrX()
doubleIncrX()
return getX() // 4
}
func sink(_ x: Int) { print("sink:", x) }
func source() -> Int { -1 }
func sharedCaptureMultipleWriters() {
var x = 123
let callSink = { sink(x) }
let makeSetter = { y in
let setter = { x = y }
return setter
}
let goodSetter = makeSetter(42)
let badSetter = makeSetter(source())
goodSetter()
callSink()
badSetter()
callSink()
}
func reentrant() -> Int {
func f(_ x: Int) -> (Int) -> Int {
if x == 0 {
return { _ in x }
}
let next = g(x - 1)
return { k in k*next(10*k) + x }
}
func g(_ x: Int) -> (Int) -> Int {
if x == 0 {
return { _ in x }
}
let next = f(x - 1)
return { k in k*next(10*k) + 2*x }
}
let h = f(5)
let y = h(10)
return y // 10004003085
}
func main() {
print("captureList():")
captureList() // Hello world! 123
print("callEscape():")
callEscape() // 1
print("logical():", logical()) // true
print("asyncTest():")
asyncTest() // 42
print("foo():", foo()) // 42
let a = bar()
let b = baz()
print("bar():", a(), a()) // 42 42
print("baz():", b(), b()) // 42 42
g!(1)
print("g!(1):", b(), b()) // 43 43
g!(1)
print("g!(1):", b(), b()) // 44 44
print("quux():", quux()) // 58341
print("sharedCapture():", sharedCapture()) // 4
print("sharedCaptureMultipleWriters():")
sharedCaptureMultipleWriters() // 42, -1
print("reentrant():", reentrant()) // 10004003085
}
main()