mirror of
https://github.com/github/codeql.git
synced 2026-02-23 10:23:41 +01:00
Merge branch 'main' into redsun82/bazel-9
This commit is contained in:
@@ -1726,9 +1726,7 @@ private module Cached {
|
||||
SsaImpl::ssaFlow(n, succ) and
|
||||
bb1 = n.getBasicBlock() and
|
||||
bb2 = succ.getBasicBlock() and
|
||||
bb1 != bb2 and
|
||||
bb2.dominates(bb1) and
|
||||
bb1.getASuccessor+() = bb2
|
||||
bb2.strictlyDominates(bb1)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,5 +44,5 @@ NHibernate,3,,,,,,,,,,,,3,,,,,,,,,,
|
||||
Newtonsoft.Json,,,91,,,,,,,,,,,,,,,,,,,73,18
|
||||
ServiceStack,194,,7,27,,,,,75,,,,92,,,,,,,,,7,
|
||||
SourceGenerators,,,5,,,,,,,,,,,,,,,,,,,,5
|
||||
System,59,47,12491,,6,5,12,,,4,1,,31,2,,6,15,17,4,3,,6378,6113
|
||||
System,59,47,12495,,6,5,12,,,4,1,,31,2,,6,15,17,4,3,,6382,6113
|
||||
Windows.Security.Cryptography.Core,1,,,,,,,1,,,,,,,,,,,,,,,
|
||||
|
||||
|
@@ -8,7 +8,7 @@ C# framework & library support
|
||||
|
||||
Framework / library,Package,Flow sources,Taint & value steps,Sinks (total),`CWE-079` :sub:`Cross-site scripting`
|
||||
`ServiceStack <https://servicestack.net/>`_,"``ServiceStack.*``, ``ServiceStack``",,7,194,
|
||||
System,"``System.*``, ``System``",47,12491,59,5
|
||||
System,"``System.*``, ``System``",47,12495,59,5
|
||||
Others,"``Amazon.Lambda.APIGatewayEvents``, ``Amazon.Lambda.Core``, ``Dapper``, ``ILCompiler``, ``ILLink.RoslynAnalyzer``, ``ILLink.Shared``, ``ILLink.Tasks``, ``Internal.IL``, ``Internal.Pgo``, ``Internal.TypeSystem``, ``Microsoft.ApplicationBlocks.Data``, ``Microsoft.AspNetCore.Components``, ``Microsoft.AspNetCore.Http``, ``Microsoft.AspNetCore.Mvc``, ``Microsoft.AspNetCore.WebUtilities``, ``Microsoft.CSharp``, ``Microsoft.Data.SqlClient``, ``Microsoft.Diagnostics.Tools.Pgo``, ``Microsoft.DotNet.Build.Tasks``, ``Microsoft.DotNet.PlatformAbstractions``, ``Microsoft.EntityFrameworkCore``, ``Microsoft.Extensions.Caching.Distributed``, ``Microsoft.Extensions.Caching.Memory``, ``Microsoft.Extensions.Configuration``, ``Microsoft.Extensions.DependencyInjection``, ``Microsoft.Extensions.DependencyModel``, ``Microsoft.Extensions.Diagnostics.Metrics``, ``Microsoft.Extensions.FileProviders``, ``Microsoft.Extensions.FileSystemGlobbing``, ``Microsoft.Extensions.Hosting``, ``Microsoft.Extensions.Http``, ``Microsoft.Extensions.Logging``, ``Microsoft.Extensions.Options``, ``Microsoft.Extensions.Primitives``, ``Microsoft.Interop``, ``Microsoft.JSInterop``, ``Microsoft.NET.Build.Tasks``, ``Microsoft.VisualBasic``, ``Microsoft.Win32``, ``Mono.Linker``, ``MySql.Data.MySqlClient``, ``NHibernate``, ``Newtonsoft.Json``, ``SourceGenerators``, ``Windows.Security.Cryptography.Core``",60,2406,162,4
|
||||
Totals,,107,14904,415,9
|
||||
Totals,,107,14908,415,9
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package,sink,source,summary,sink:command-injection,sink:credentials-key,sink:jwt,sink:log-injection,sink:nosql-injection,sink:path-injection,sink:regex-use[0],sink:regex-use[1],sink:regex-use[c],sink:request-forgery,sink:request-forgery[TCP Addr + Port],sink:sql-injection,sink:url-redirection,sink:url-redirection[0],sink:url-redirection[receiver],sink:xpath-injection,source:commandargs,source:database,source:environment,source:file,source:remote,source:stdin,summary:taint,summary:value
|
||||
,,,8,,,,,,,,,,,,,,,,,,,,,,,3,5
|
||||
,,,9,,,,,,,,,,,,,,,,,,,,,,,3,6
|
||||
archive/tar,,,5,,,,,,,,,,,,,,,,,,,,,,,5,
|
||||
archive/zip,,,6,,,,,,,,,,,,,,,,,,,,,,,6,
|
||||
bufio,,,17,,,,,,,,,,,,,,,,,,,,,,,17,
|
||||
bytes,,,43,,,,,,,,,,,,,,,,,,,,,,,43,
|
||||
bytes,,,44,,,,,,,,,,,,,,,,,,,,,,,44,
|
||||
clevergo.tech/clevergo,1,,,,,,,,,,,,,,,,,1,,,,,,,,,
|
||||
cloud.google.com/go/bigquery,1,,,,,,,,,,,,,,1,,,,,,,,,,,,
|
||||
compress/bzip2,,,1,,,,,,,,,,,,,,,,,,,,,,,1,
|
||||
@@ -18,7 +18,7 @@ context,,,5,,,,,,,,,,,,,,,,,,,,,,,5,
|
||||
crypto,,,10,,,,,,,,,,,,,,,,,,,,,,,10,
|
||||
database/sql,30,18,12,,,,,,,,,,,,30,,,,,,18,,,,,12,
|
||||
encoding,,,81,,,,,,,,,,,,,,,,,,,,,,,81,
|
||||
errors,,,3,,,,,,,,,,,,,,,,,,,,,,,3,
|
||||
errors,,,4,,,,,,,,,,,,,,,,,,,,,,,4,
|
||||
expvar,,,6,,,,,,,,,,,,,,,,,,,,,,,6,
|
||||
fmt,3,,16,,,,3,,,,,,,,,,,,,,,,,,,16,
|
||||
github.com/ChrisTrenkamp/goxpath,3,,,,,,,,,,,,,,,,,,3,,,,,,,,
|
||||
|
||||
|
@@ -32,7 +32,7 @@ Go framework & library support
|
||||
`Revel <http://revel.github.io/>`_,"``github.com/revel/revel*``, ``github.com/robfig/revel*``",46,20,4
|
||||
`SendGrid <https://github.com/sendgrid/sendgrid-go>`_,``github.com/sendgrid/sendgrid-go*``,,1,
|
||||
`Squirrel <https://github.com/Masterminds/squirrel>`_,"``github.com/Masterminds/squirrel*``, ``github.com/lann/squirrel*``, ``gopkg.in/Masterminds/squirrel``",81,,96
|
||||
`Standard library <https://pkg.go.dev/std>`_,"````, ``archive/*``, ``bufio``, ``bytes``, ``cmp``, ``compress/*``, ``container/*``, ``context``, ``crypto``, ``crypto/*``, ``database/*``, ``debug/*``, ``embed``, ``encoding``, ``encoding/*``, ``errors``, ``expvar``, ``flag``, ``fmt``, ``go/*``, ``hash``, ``hash/*``, ``html``, ``html/*``, ``image``, ``image/*``, ``index/*``, ``io``, ``io/*``, ``log``, ``log/*``, ``maps``, ``math``, ``math/*``, ``mime``, ``mime/*``, ``net``, ``net/*``, ``os``, ``os/*``, ``path``, ``path/*``, ``plugin``, ``reflect``, ``reflect/*``, ``regexp``, ``regexp/*``, ``slices``, ``sort``, ``strconv``, ``strings``, ``sync``, ``sync/*``, ``syscall``, ``syscall/*``, ``testing``, ``testing/*``, ``text/*``, ``time``, ``time/*``, ``unicode``, ``unicode/*``, ``unsafe``, ``weak``",52,609,104
|
||||
`Standard library <https://pkg.go.dev/std>`_,"````, ``archive/*``, ``bufio``, ``bytes``, ``cmp``, ``compress/*``, ``container/*``, ``context``, ``crypto``, ``crypto/*``, ``database/*``, ``debug/*``, ``embed``, ``encoding``, ``encoding/*``, ``errors``, ``expvar``, ``flag``, ``fmt``, ``go/*``, ``hash``, ``hash/*``, ``html``, ``html/*``, ``image``, ``image/*``, ``index/*``, ``io``, ``io/*``, ``log``, ``log/*``, ``maps``, ``math``, ``math/*``, ``mime``, ``mime/*``, ``net``, ``net/*``, ``os``, ``os/*``, ``path``, ``path/*``, ``plugin``, ``reflect``, ``reflect/*``, ``regexp``, ``regexp/*``, ``slices``, ``sort``, ``strconv``, ``strings``, ``sync``, ``sync/*``, ``syscall``, ``syscall/*``, ``testing``, ``testing/*``, ``text/*``, ``time``, ``time/*``, ``unicode``, ``unicode/*``, ``unsafe``, ``weak``",52,612,104
|
||||
`XORM <https://xorm.io>`_,"``github.com/go-xorm/xorm*``, ``xorm.io/xorm*``",,,68
|
||||
`XPath <https://github.com/antchfx/xpath>`_,``github.com/antchfx/xpath*``,,,4
|
||||
`appleboy/gin-jwt <https://github.com/appleboy/gin-jwt>`_,``github.com/appleboy/gin-jwt*``,,,1
|
||||
@@ -74,5 +74,5 @@ Go framework & library support
|
||||
`xpathparser <https://github.com/santhosh-tekuri/xpathparser>`_,``github.com/santhosh-tekuri/xpathparser*``,,,2
|
||||
`yaml <https://gopkg.in/yaml.v3>`_,``gopkg.in/yaml*``,,9,
|
||||
`zap <https://go.uber.org/zap>`_,``go.uber.org/zap*``,,11,33
|
||||
Totals,,688,1069,1557
|
||||
Totals,,688,1072,1557
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `UnreachableBlocks.qll` library has been deprecated.
|
||||
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* DEPRECATED: This module is no longer maintained, and will be removed in a future release.
|
||||
*
|
||||
* Provides classes and predicates for identifying unreachable blocks under a "closed-world" assumption.
|
||||
*/
|
||||
overlay[local?]
|
||||
module;
|
||||
deprecated module;
|
||||
|
||||
import java
|
||||
import semmle.code.java.controlflow.Guards
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/**
|
||||
* DEPRECATED: This module is no longer maintained, and will be removed in a future release.
|
||||
*/
|
||||
overlay[local?]
|
||||
module;
|
||||
deprecated module;
|
||||
|
||||
import java
|
||||
import semmle.code.java.controlflow.UnreachableBlocks
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
| unreachableblocks/Unreachable.java:3:14:3:24 | Exceptional Exit |
|
||||
| unreachableblocks/Unreachable.java:3:14:3:24 | Exceptional Exit |
|
||||
| unreachableblocks/Unreachable.java:5:14:5:19 | Exceptional Exit |
|
||||
| unreachableblocks/Unreachable.java:6:14:8:3 | { ... } |
|
||||
| unreachableblocks/Unreachable.java:9:21:11:3 | { ... } |
|
||||
| unreachableblocks/Unreachable.java:12:22:14:3 | { ... } |
|
||||
| unreachableblocks/Unreachable.java:17:3:17:9 | case ... |
|
||||
| unreachableblocks/Unreachable.java:19:3:19:9 | case ... |
|
||||
| unreachableblocks/Unreachable.java:24:3:24:9 | case ... |
|
||||
| unreachableblocks/Unreachable.java:26:3:26:10 | case ... |
|
||||
| unreachableblocks/Unreachable.java:27:3:27:10 | default |
|
||||
| unreachableblocks/Unreachable.java:32:18:32:28 | Exceptional Exit |
|
||||
@@ -1,5 +0,0 @@
|
||||
import default
|
||||
import semmle.code.java.controlflow.UnreachableBlocks
|
||||
|
||||
from UnreachableBasicBlock unreachableBasicBlock
|
||||
select unreachableBasicBlock
|
||||
@@ -1,35 +0,0 @@
|
||||
package unreachableblocks;
|
||||
|
||||
public class Unreachable {
|
||||
private boolean privateFalse = false;
|
||||
public void method() {
|
||||
if (false) {
|
||||
// unreachable
|
||||
}
|
||||
if (privateFalse) {
|
||||
// unreachable
|
||||
}
|
||||
if (methodFalse()) {
|
||||
// unreachable
|
||||
}
|
||||
|
||||
switch (7) {
|
||||
case 5: // unreachable
|
||||
break;
|
||||
case 6: // unreachable
|
||||
System.out.println("dead"); // unreachable
|
||||
case 7:
|
||||
case 8: // reachable from 7
|
||||
break; // reachable
|
||||
case 9: //unreachable
|
||||
break;
|
||||
case 10: // unreachable
|
||||
default:
|
||||
break; //unreachable
|
||||
}
|
||||
}
|
||||
|
||||
private boolean methodFalse() {
|
||||
return privateFalse;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ private module Input implements InputSig<Location, PythonDataFlow> {
|
||||
or
|
||||
// TODO: Implement post-updates for **kwargs, see tests added in https://github.com/github/codeql/pull/14936
|
||||
exists(ArgumentPosition apos | n.argumentOf(_, apos) and apos.isDictSplat())
|
||||
or
|
||||
missingArgumentCallExclude(n)
|
||||
}
|
||||
|
||||
predicate reverseReadExclude(Node n) {
|
||||
@@ -134,6 +136,18 @@ private module Input implements InputSig<Location, PythonDataFlow> {
|
||||
other.getNode().getScope() = f
|
||||
)
|
||||
}
|
||||
|
||||
predicate missingArgumentCallExclude(ArgumentNode arg) {
|
||||
// We overapproximate the argument nodes in order to not rely on the global `getCallArg`
|
||||
// predicate.
|
||||
// Because of this, we must exclude the cases where we have an approximation but no actual
|
||||
// argument node.
|
||||
arg = getCallArgApproximation() and not getCallArg(_, _, _, arg, _)
|
||||
or
|
||||
// Likewise, capturing closure arguments do not have corresponding argument nodes in some cases.
|
||||
arg instanceof SynthCapturedVariablesArgumentNode and
|
||||
not arg.argumentOf(_, _)
|
||||
}
|
||||
}
|
||||
|
||||
import MakeConsistency<Location, PythonDataFlow, PythonTaintTracking, Input>
|
||||
|
||||
@@ -1714,36 +1714,66 @@ private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNodeImpl
|
||||
* This is also known as the environment part of a closure.
|
||||
*
|
||||
* This is used for tracking flow through captured variables.
|
||||
*
|
||||
* TODO:
|
||||
* We might want a synthetic node here, but currently that incurs problems
|
||||
* with non-monotonic recursion, because of the use of `resolveCall` in the
|
||||
* char pred. This may be solvable by using
|
||||
* `CallGraphConstruction::Make` in stead of
|
||||
* `CallGraphConstruction::Simple::Make` appropriately.
|
||||
*/
|
||||
class CapturedVariablesArgumentNode extends CfgNode, ArgumentNode {
|
||||
CallNode callNode;
|
||||
class SynthCapturedVariablesArgumentNode extends Node, TSynthCapturedVariablesArgumentNode {
|
||||
ControlFlowNode callable;
|
||||
|
||||
CapturedVariablesArgumentNode() {
|
||||
node = callNode.getFunction() and
|
||||
exists(Function target | resolveCall(callNode, target, _) |
|
||||
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
|
||||
)
|
||||
}
|
||||
SynthCapturedVariablesArgumentNode() { this = TSynthCapturedVariablesArgumentNode(callable) }
|
||||
|
||||
/** Gets the `CallNode` corresponding to this captured variables argument node. */
|
||||
CallNode getCallNode() { result.getFunction() = callable }
|
||||
|
||||
/** Gets the `CfgNode` that corresponds to this synthetic node. */
|
||||
CfgNode getUnderlyingNode() { result.asCfgNode() = callable }
|
||||
|
||||
override Scope getScope() { result = callable.getScope() }
|
||||
|
||||
override Location getLocation() { result = callable.getLocation() }
|
||||
|
||||
override string toString() { result = "Capturing closure argument" }
|
||||
}
|
||||
|
||||
/** A captured variables argument node viewed as an argument node. Needed because `argumentOf` is a global predicate. */
|
||||
class CapturedVariablesArgumentNodeAsArgumentNode extends ArgumentNode,
|
||||
SynthCapturedVariablesArgumentNode
|
||||
{
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
callNode = call.getNode() and
|
||||
pos.isLambdaSelf()
|
||||
exists(CallNode callNode | callNode = this.getCallNode() |
|
||||
callNode = call.getNode() and
|
||||
exists(Function target | resolveCall(callNode, target, _) |
|
||||
target = any(VariableCapture::CapturedVariable v).getACapturingScope()
|
||||
) and
|
||||
pos.isLambdaSelf()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A synthetic node representing the values of captured variables after the output has been computed. */
|
||||
class SynthCapturedVariablesArgumentPostUpdateNode extends PostUpdateNodeImpl,
|
||||
TSynthCapturedVariablesArgumentPostUpdateNode
|
||||
{
|
||||
ControlFlowNode callable;
|
||||
|
||||
SynthCapturedVariablesArgumentPostUpdateNode() {
|
||||
this = TSynthCapturedVariablesArgumentPostUpdateNode(callable)
|
||||
}
|
||||
|
||||
/** Gets the `PostUpdateNode` (for a `CfgNode`) that corresponds to this synthetic node. */
|
||||
PostUpdateNode getUnderlyingNode() { result.getPreUpdateNode().asCfgNode() = callable }
|
||||
|
||||
override string toString() { result = "[post] Capturing closure argument" }
|
||||
|
||||
override Scope getScope() { result = callable.getScope() }
|
||||
|
||||
override Location getLocation() { result = callable.getLocation() }
|
||||
|
||||
override SynthCapturedVariablesArgumentNode getPreUpdateNode() {
|
||||
result = TSynthCapturedVariablesArgumentNode(callable)
|
||||
}
|
||||
}
|
||||
|
||||
/** A synthetic node representing the values of variables captured by a comprehension. */
|
||||
class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVariablesArgumentNode,
|
||||
ArgumentNode
|
||||
{
|
||||
class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVariablesArgumentNode {
|
||||
Comp comp;
|
||||
|
||||
SynthCompCapturedVariablesArgumentNode() { this = TSynthCompCapturedVariablesArgumentNode(comp) }
|
||||
@@ -1755,7 +1785,11 @@ class SynthCompCapturedVariablesArgumentNode extends Node, TSynthCompCapturedVar
|
||||
override Location getLocation() { result = comp.getLocation() }
|
||||
|
||||
Comp getComprehension() { result = comp }
|
||||
}
|
||||
|
||||
class SynthCompCapturedVariablesArgumentNodeAsArgumentNode extends SynthCompCapturedVariablesArgumentNode,
|
||||
ArgumentNode
|
||||
{
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
call.(ComprehensionCall).getComprehension() = comp and
|
||||
pos.isLambdaSelf()
|
||||
|
||||
@@ -1128,6 +1128,14 @@ predicate nodeIsHidden(Node n) {
|
||||
n instanceof SynthCaptureNode
|
||||
or
|
||||
n instanceof SynthCapturedVariablesParameterNode
|
||||
or
|
||||
n instanceof SynthCapturedVariablesArgumentNode
|
||||
or
|
||||
n instanceof SynthCapturedVariablesArgumentPostUpdateNode
|
||||
or
|
||||
n instanceof SynthCompCapturedVariablesArgumentNode
|
||||
or
|
||||
n instanceof SynthCompCapturedVariablesArgumentPostUpdateNode
|
||||
}
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
@@ -76,15 +76,7 @@ newtype TNode =
|
||||
node.getNode() = any(Comp c).getIterable()
|
||||
} or
|
||||
/** A node representing a global (module-level) variable in a specific module. */
|
||||
TModuleVariableNode(Module m, GlobalVariable v) {
|
||||
v.getScope() = m and
|
||||
(
|
||||
v.escapes()
|
||||
or
|
||||
isAccessedThroughImportStar(m) and
|
||||
ImportStar::globalNameDefinedInModule(v.getId(), m)
|
||||
)
|
||||
} or
|
||||
TModuleVariableNode(Module m, GlobalVariable v) { v.getScope() = m } or
|
||||
/**
|
||||
* A synthetic node representing that an iterable sequence flows to consumer.
|
||||
*/
|
||||
@@ -129,6 +121,20 @@ newtype TNode =
|
||||
f = any(VariableCapture::CapturedVariable v).getACapturingScope() and
|
||||
exists(TFunction(f))
|
||||
} or
|
||||
/**
|
||||
* A synthetic node representing the values of the variables captured
|
||||
* by the callable being called.
|
||||
*/
|
||||
TSynthCapturedVariablesArgumentNode(ControlFlowNode callable) {
|
||||
callable = any(CallNode c).getFunction()
|
||||
} or
|
||||
/**
|
||||
* A synthetic node representing the values of the variables captured
|
||||
* by the callable being called, after the output has been computed.
|
||||
*/
|
||||
TSynthCapturedVariablesArgumentPostUpdateNode(ControlFlowNode callable) {
|
||||
callable = any(CallNode c).getFunction()
|
||||
} or
|
||||
/** A synthetic node representing the values of variables captured by a comprehension. */
|
||||
TSynthCompCapturedVariablesArgumentNode(Comp comp) {
|
||||
comp.getFunction() = any(VariableCapture::CapturedVariable v).getACapturingScope()
|
||||
@@ -347,27 +353,51 @@ abstract class ArgumentNode extends Node {
|
||||
final ExtractedDataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
/** Gets an overapproximation of the argument nodes that are included in `getCallArg`. */
|
||||
Node getCallArgApproximation() {
|
||||
// pre-update nodes for calls
|
||||
result = any(CallCfgNode c).(PostUpdateNode).getPreUpdateNode()
|
||||
or
|
||||
// self parameters in methods
|
||||
exists(Class c | result.asExpr() = c.getAMethod().getArg(0))
|
||||
or
|
||||
// the object part of an attribute expression (which might be a bound method)
|
||||
result.asCfgNode() = any(AttrNode a).getObject()
|
||||
or
|
||||
// the function part of any call
|
||||
result.asCfgNode() = any(CallNode c).getFunction()
|
||||
}
|
||||
|
||||
/** Gets the extracted argument nodes that do not rely on `getCallArg`. */
|
||||
private Node implicitArgumentNode() {
|
||||
// for potential summaries we allow all normal call arguments
|
||||
normalCallArg(_, result, _)
|
||||
or
|
||||
// and self arguments
|
||||
result.asCfgNode() = any(CallNode c).getFunction().(AttrNode).getObject()
|
||||
or
|
||||
// for comprehensions, we allow the synthetic `iterable` argument
|
||||
result.asExpr() = any(Comp c).getIterable()
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that represents a call argument found in the source code.
|
||||
*/
|
||||
class ExtractedArgumentNode extends ArgumentNode {
|
||||
ExtractedArgumentNode() {
|
||||
// for resolved calls, we need to allow all argument nodes
|
||||
getCallArg(_, _, _, this, _)
|
||||
this = getCallArgApproximation()
|
||||
or
|
||||
// for potential summaries we allow all normal call arguments
|
||||
normalCallArg(_, this, _)
|
||||
or
|
||||
// and self arguments
|
||||
this.asCfgNode() = any(CallNode c).getFunction().(AttrNode).getObject()
|
||||
or
|
||||
// for comprehensions, we allow the synthetic `iterable` argument
|
||||
this.asExpr() = any(Comp c).getIterable()
|
||||
this = implicitArgumentNode()
|
||||
}
|
||||
|
||||
final override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this = call.getArgument(pos) and
|
||||
call instanceof ExtractedDataFlowCall
|
||||
call instanceof ExtractedDataFlowCall and
|
||||
(
|
||||
this = implicitArgumentNode()
|
||||
or
|
||||
this = getCallArgApproximation() and getCallArg(_, _, _, this, _)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,13 +470,17 @@ class ModuleVariableNode extends Node, TModuleVariableNode {
|
||||
|
||||
/** Gets a node that reads this variable. */
|
||||
Node getARead() {
|
||||
result.asCfgNode() = var.getALoad().getAFlowNode() and
|
||||
// Ignore reads that happen when the module is imported. These are only executed once.
|
||||
not result.getScope() = mod
|
||||
result = this.getALocalRead()
|
||||
or
|
||||
this = import_star_read(result)
|
||||
}
|
||||
|
||||
/** Gets a node that reads this variable, excluding reads that happen through `from ... import *`. */
|
||||
Node getALocalRead() {
|
||||
result.asCfgNode() = var.getALoad().getAFlowNode() and
|
||||
not result.getScope() = mod
|
||||
}
|
||||
|
||||
/** Gets an `EssaNode` that corresponds to an assignment of this global variable. */
|
||||
Node getAWrite() {
|
||||
any(EssaNodeDefinition def).definedBy(var, result.asCfgNode().(DefinitionNode))
|
||||
@@ -466,8 +500,6 @@ class ModuleVariableNode extends Node, TModuleVariableNode {
|
||||
override Location getLocation() { result = mod.getLocation() }
|
||||
}
|
||||
|
||||
private predicate isAccessedThroughImportStar(Module m) { m = ImportStar::getStarImported(_) }
|
||||
|
||||
private ModuleVariableNode import_star_read(Node n) {
|
||||
resolved_import_star_module(result.getModule(), result.getVariable().getId(), n)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class LocalSourceNode extends Node {
|
||||
or
|
||||
// We explicitly include any read of a global variable, as some of these may have local flow going
|
||||
// into them.
|
||||
this = any(ModuleVariableNode mvn).getARead()
|
||||
this = any(ModuleVariableNode v).getALocalRead()
|
||||
or
|
||||
// We include all scope entry definitions, as these act as the local source within the scope they
|
||||
// enter.
|
||||
@@ -248,7 +248,7 @@ private module Cached {
|
||||
pragma[nomagic]
|
||||
private predicate localSourceFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo, _) and
|
||||
not nodeTo = any(ModuleVariableNode v).getARead()
|
||||
not nodeTo = any(ModuleVariableNode v).getALocalRead()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,6 +114,12 @@ private Flow::ClosureNode asClosureNode(Node n) {
|
||||
result.(Flow::ExprNode).getExpr().getNode() = comp
|
||||
)
|
||||
or
|
||||
// For captured variable argument nodes (and their post-update variants), we use the closure node
|
||||
// for the underlying node.
|
||||
result = asClosureNode(n.(SynthCapturedVariablesArgumentNode).getUnderlyingNode())
|
||||
or
|
||||
result = asClosureNode(n.(SynthCapturedVariablesArgumentPostUpdateNode).getUnderlyingNode())
|
||||
or
|
||||
// TODO: Should the `Comp`s above be excluded here?
|
||||
result.(Flow::ExprNode).getExpr() = n.(CfgNode).getNode()
|
||||
or
|
||||
|
||||
@@ -8,7 +8,9 @@ module MaximalFlowTest implements FlowTestSig {
|
||||
|
||||
predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
source != sink and
|
||||
MaximalFlows::flow(source, sink)
|
||||
MaximalFlows::flow(source, sink) and
|
||||
// exclude ModuleVariableNodes (which have location 0:0:0:0)
|
||||
not sink instanceof DataFlow::ModuleVariableNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +35,7 @@ module MaximalFlowsConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
exists(node.getLocation().getFile().getRelativePath()) and
|
||||
not any(CallNode c).getArg(_) = node.asCfgNode() and
|
||||
not node instanceof DataFlow::ArgumentNode and
|
||||
not isArgumentNode(node, _, _) and
|
||||
not node.asCfgNode().(NameNode).getId().matches("SINK%") and
|
||||
not DataFlow::localFlowStep(node, _)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ module CallGraphConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) {
|
||||
node instanceof DataFlowPrivate::ReturnNode
|
||||
or
|
||||
node instanceof DataFlow::ArgumentNode
|
||||
DataFlowPrivate::isArgumentNode(node, _, _)
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import semmle.python.dataflow.new.internal.DataFlowPrivate
|
||||
|
||||
predicate initSelfCallOverridden(
|
||||
Function init, DataFlow::Node self, DataFlow::MethodCallNode call, Function target,
|
||||
@@ -39,7 +40,7 @@ predicate readsFromSelf(Function method) {
|
||||
self.getParameter() = method.getArg(0) and
|
||||
DataFlow::localFlow(self, sink)
|
||||
|
|
||||
sink instanceof DataFlow::ArgumentNode
|
||||
isArgumentNode(sink, _, _)
|
||||
or
|
||||
sink = any(DataFlow::AttrRead a).getObject()
|
||||
)
|
||||
|
||||
@@ -13,21 +13,27 @@ attacker being able to influence behavior by modifying unexpected files.
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Validate user input before using it to construct a file path, either using an off-the-shelf library function
|
||||
like <code>werkzeug.utils.secure_filename</code>, or by performing custom validation.
|
||||
Validate paths constructed from untrusted user input before using them to access files.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Ideally, follow these rules:
|
||||
The choice of validation depends on the use case.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Do not allow more than a single "." character.</li>
|
||||
<li>Do not allow directory separators such as "/" or "\" (depending on the file system).</li>
|
||||
<li>Do not rely on simply replacing problematic sequences such as "../". For example, after
|
||||
applying this filter to ".../...//", the resulting string would still be "../".</li>
|
||||
<li>Use an allowlist of known good patterns.</li>
|
||||
</ul>
|
||||
<p>
|
||||
If you want to allow paths spanning multiple folders, a common strategy is to make sure that the constructed
|
||||
file path is contained within a safe root folder. First, normalize the path using <code>os.path.normpath</code> or
|
||||
<code>os.path.realpath</code> (make sure to use the latter if symlinks are a consideration)
|
||||
to remove any internal ".." segments and/or follow links. Then check that the normalized path starts with the
|
||||
root folder. Note that the normalization step is important, since otherwise even a path that starts with the root
|
||||
folder could be used to access files outside the root folder.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
More restrictive options include using a library function like <code>werkzeug.utils.secure_filename</code> to eliminate
|
||||
any special characters from the file path, or restricting the path to a known list of safe paths. These options are
|
||||
safe, but can only be used in particular circumstances.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
| test.py:5:9:5:16 | ControlFlowNode for __init__ | test.py:4:1:4:20 | ControlFlowNode for ClassExpr | __init__ | test.py:5:5:5:28 | ControlFlowNode for FunctionExpr |
|
||||
| test.py:6:9:6:16 | ControlFlowNode for Attribute | test.py:6:9:6:12 | ControlFlowNode for self | foo | test.py:6:20:6:22 | ControlFlowNode for foo |
|
||||
| test.py:9:1:9:9 | ControlFlowNode for Attribute | test.py:0:0:0:0 | ModuleVariableNode in Module test for myobj | foo | test.py:9:13:9:17 | ControlFlowNode for StringLiteral |
|
||||
| test.py:9:1:9:9 | ControlFlowNode for Attribute | test.py:9:1:9:5 | ControlFlowNode for myobj | foo | test.py:9:13:9:17 | ControlFlowNode for StringLiteral |
|
||||
| test.py:12:1:12:25 | ControlFlowNode for setattr() | test.py:12:9:12:13 | ControlFlowNode for myobj | foo | test.py:12:23:12:24 | ControlFlowNode for IntegerLiteral |
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id |
|
||||
| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:3:3:3:3 | ControlFlowNode for z |
|
||||
@@ -8,26 +11,33 @@
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:2:3:2:3 | ControlFlowNode for y | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z |
|
||||
| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y |
|
||||
| test.py:2:3:2:3 | ControlFlowNode for y | test.py:4:10:4:10 | ControlFlowNode for z |
|
||||
| test.py:2:3:2:3 | ControlFlowNode for y | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:2:3:2:3 | ControlFlowNode for y | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:2:7:2:7 | ControlFlowNode for x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y |
|
||||
| test.py:2:7:2:7 | ControlFlowNode for x | test.py:3:3:3:3 | ControlFlowNode for z |
|
||||
| test.py:2:7:2:7 | ControlFlowNode for x | test.py:3:7:3:7 | ControlFlowNode for y |
|
||||
| test.py:2:7:2:7 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z |
|
||||
| test.py:2:7:2:7 | ControlFlowNode for x | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:2:7:2:7 | ControlFlowNode for x | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:3:3:3:3 | ControlFlowNode for z | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z |
|
||||
| test.py:3:3:3:3 | ControlFlowNode for z | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:3:3:3:3 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:3:7:3:7 | ControlFlowNode for y | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z |
|
||||
| test.py:3:7:3:7 | ControlFlowNode for y | test.py:4:10:4:10 | ControlFlowNode for z |
|
||||
| test.py:3:7:3:7 | ControlFlowNode for y | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:3:7:3:7 | ControlFlowNode for y | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:4:10:4:10 | ControlFlowNode for z | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:2:3:2:3 | ControlFlowNode for y |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:2:7:2:7 | ControlFlowNode for x |
|
||||
@@ -37,6 +47,8 @@
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for a |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:1:19:1:19 | ControlFlowNode for x |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:2:3:2:3 | ControlFlowNode for y |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:2:7:2:7 | ControlFlowNode for x |
|
||||
@@ -47,7 +59,10 @@
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:19:7:19 | ControlFlowNode for a |
|
||||
| test.py:7:1:7:1 | ControlFlowNode for b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:7:19:7:19 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:7:19:7:19 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x |
|
||||
| test.py:7:19:7:19 | ControlFlowNode for a | test.py:2:3:2:3 | ControlFlowNode for y |
|
||||
| test.py:7:19:7:19 | ControlFlowNode for a | test.py:2:7:2:7 | ControlFlowNode for x |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id |
|
||||
| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y |
|
||||
@@ -31,10 +32,13 @@
|
||||
| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z |
|
||||
| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a |
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:1:6:1 | ControlFlowNode for a |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:1:6:1 | ControlFlowNode for a |
|
||||
| test.py:7:1:7:1 | ControlFlowNode for b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:7:19:7:19 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x |
|
||||
| test.py:7:19:7:19 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x |
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for __name__ | test.py:0:0:0:0 | ModuleVariableNode in Module test for __name__ |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for __package__ | test.py:0:0:0:0 | ModuleVariableNode in Module test for __package__ |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
@@ -31,7 +36,9 @@
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:19:7:19 | ControlFlowNode for a |
|
||||
| test.py:7:1:7:1 | ControlFlowNode for b | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:7:5:7:17 | Capturing closure argument | test.py:7:5:7:17 | Capturing closure argument |
|
||||
| test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:7:5:7:17 | [post] Capturing closure argument | test.py:7:5:7:17 | [post] Capturing closure argument |
|
||||
| test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z |
|
||||
| test.py:1:19:1:19 | ControlFlowNode for x | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for a |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:4:10:4:10 | ControlFlowNode for z |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:19:7:19 | ControlFlowNode for a |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for __name__ |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for __package__ |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for a |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr |
|
||||
| test.py:1:1:1:21 | SynthDictSplatParameterNode |
|
||||
| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id |
|
||||
@@ -10,7 +15,9 @@
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral |
|
||||
| test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:7:5:7:17 | Capturing closure argument |
|
||||
| test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:7:5:7:17 | [post] Capturing closure argument |
|
||||
| test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:7:5:7:20 | [pre] ControlFlowNode for obfuscated_id() |
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for __name__ |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for __package__ |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for a |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for b |
|
||||
| test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id |
|
||||
| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr |
|
||||
| test.py:1:1:1:21 | SynthDictSplatParameterNode |
|
||||
| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id |
|
||||
@@ -10,7 +15,9 @@
|
||||
| test.py:6:1:6:1 | ControlFlowNode for a |
|
||||
| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral |
|
||||
| test.py:7:1:7:1 | ControlFlowNode for b |
|
||||
| test.py:7:5:7:17 | Capturing closure argument |
|
||||
| test.py:7:5:7:17 | ControlFlowNode for obfuscated_id |
|
||||
| test.py:7:5:7:17 | [post] Capturing closure argument |
|
||||
| test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id |
|
||||
| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() |
|
||||
| test.py:7:5:7:20 | [pre] ControlFlowNode for obfuscated_id() |
|
||||
|
||||
@@ -8,9 +8,9 @@ g = [5] # $writes=g
|
||||
|
||||
g1, g2 = [6], [7] # $writes=g1 writes=g2
|
||||
|
||||
# Assignment that's only referenced in this scope. This one will not give rise to a `ModuleVariableNode`.
|
||||
# Assignment that's only referenced in this scope.
|
||||
|
||||
unreferenced_g = [8]
|
||||
unreferenced_g = [8] # $writes=unreferenced_g
|
||||
print(unreferenced_g)
|
||||
|
||||
# Testing modifications of globals
|
||||
@@ -34,7 +34,7 @@ g_ins.append(75)
|
||||
|
||||
# A global with multiple potential definitions
|
||||
|
||||
import unknown_module
|
||||
import unknown_module # $writes=unknown_module
|
||||
if unknown_module.attr:
|
||||
g_mult = [200] # $writes=g_mult
|
||||
else:
|
||||
@@ -46,7 +46,7 @@ g_redef = [400] # $writes=g_redef
|
||||
if unknown_module.attr:
|
||||
g_redef = [500] # $writes=g_redef
|
||||
|
||||
def global_access():
|
||||
def global_access(): # $writes=global_access
|
||||
l = 5
|
||||
print(g) # $reads=g
|
||||
print(g1) # $reads=g1
|
||||
@@ -59,12 +59,12 @@ def global_access():
|
||||
def print_g_mod(): # $writes=print_g_mod
|
||||
print(g_mod) # $reads=g_mod
|
||||
|
||||
def global_mod():
|
||||
def global_mod(): # $writes=global_mod
|
||||
global g_mod
|
||||
g_mod += [150] # $reads,writes=g_mod
|
||||
print_g_mod() # $reads=print_g_mod
|
||||
|
||||
def global_inside_local_function():
|
||||
def global_inside_local_function(): # $writes=global_inside_local_function
|
||||
def local_function():
|
||||
print(g) # $reads=g
|
||||
local_function()
|
||||
@@ -76,21 +76,21 @@ def global_inside_local_function():
|
||||
|
||||
import foo_module # $writes=foo_module
|
||||
|
||||
def use_foo():
|
||||
def use_foo(): # $writes=use_foo
|
||||
print(foo_module.attr) # $reads=foo_module
|
||||
|
||||
# Partial imports
|
||||
|
||||
from bar import baz_attr, quux_attr # $writes=baz_attr writes=quux_attr
|
||||
|
||||
def use_partial_import():
|
||||
def use_partial_import(): # $writes=use_partial_import
|
||||
print(baz_attr, quux_attr) # $reads=baz_attr reads=quux_attr
|
||||
|
||||
# Aliased imports
|
||||
|
||||
from spam_module import ham_attr as eggs_attr # $writes=eggs_attr
|
||||
|
||||
def use_aliased_import():
|
||||
def use_aliased_import(): # $writes=use_aliased_import
|
||||
print(eggs_attr) # $reads=eggs_attr
|
||||
|
||||
# Import star (unlikely to work unless we happen to extract/model the referenced module)
|
||||
@@ -99,23 +99,23 @@ def use_aliased_import():
|
||||
|
||||
from unknown import *
|
||||
|
||||
def secretly_use_unknown():
|
||||
def secretly_use_unknown(): # $writes=secretly_use_unknown
|
||||
print(unknown_attr) # $reads=unknown_attr
|
||||
|
||||
# Known modules
|
||||
|
||||
from known import *
|
||||
|
||||
def secretly_use_known():
|
||||
def secretly_use_known(): # $writes=secretly_use_known
|
||||
print(known_attr) # $reads=known_attr
|
||||
|
||||
# Local import in function
|
||||
|
||||
def imports_locally():
|
||||
def imports_locally(): # $writes=imports_locally
|
||||
import mod1
|
||||
|
||||
# Global import hidden in function
|
||||
|
||||
def imports_stuff():
|
||||
def imports_stuff(): # $writes=imports_stuff
|
||||
global mod2
|
||||
import mod2 # $writes=mod2
|
||||
|
||||
@@ -2,6 +2,7 @@ module_tracker
|
||||
| import_as_attr.py:1:6:1:11 | ControlFlowNode for ImportExpr |
|
||||
module_attr_tracker
|
||||
| import_as_attr.py:0:0:0:0 | ModuleVariableNode in Module import_as_attr for attr_ref |
|
||||
| import_as_attr.py:0:0:0:0 | ModuleVariableNode in Module import_as_attr for x |
|
||||
| import_as_attr.py:1:20:1:35 | ControlFlowNode for ImportMember |
|
||||
| import_as_attr.py:1:28:1:35 | ControlFlowNode for attr_ref |
|
||||
| import_as_attr.py:3:1:3:1 | ControlFlowNode for x |
|
||||
|
||||
@@ -17,7 +17,9 @@ module InlinePoorMansFunctionResolutionTest implements TestSig {
|
||||
) and
|
||||
// exclude decorator calls (which with our extractor rewrites does reference the
|
||||
// function)
|
||||
not ref.asExpr() = func.getDefinition().(FunctionExpr).getADecoratorCall()
|
||||
not ref.asExpr() = func.getDefinition().(FunctionExpr).getADecoratorCall() and
|
||||
// exclude ModuleVariableNodes (which have location 0:0:0:0)
|
||||
not ref instanceof DataFlow::ModuleVariableNode
|
||||
|
|
||||
value = func.getName() and
|
||||
tag = "resolved" and
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Provides classes and helper predicates for associated types.
|
||||
*/
|
||||
|
||||
private import rust
|
||||
private import codeql.rust.internal.PathResolution
|
||||
private import TypeMention
|
||||
private import Type
|
||||
private import TypeInference
|
||||
|
||||
/** An associated type, that is, a type alias in a trait block. */
|
||||
final class AssocType extends TypeAlias {
|
||||
Trait trait;
|
||||
|
||||
AssocType() { this = trait.getAssocItemList().getAnAssocItem() }
|
||||
|
||||
Trait getTrait() { result = trait }
|
||||
|
||||
string getText() { result = this.getName().getText() }
|
||||
}
|
||||
|
||||
/** Gets an associated type of `trait` or of a supertrait of `trait`. */
|
||||
AssocType getTraitAssocType(Trait trait) { result.getTrait() = trait.getSupertrait*() }
|
||||
|
||||
/** Holds if `path` is of the form `<type as trait>::name` */
|
||||
pragma[nomagic]
|
||||
predicate pathTypeAsTraitAssoc(Path path, TypeRepr typeRepr, Path traitPath, string name) {
|
||||
exists(PathSegment segment |
|
||||
segment = path.getQualifier().getSegment() and
|
||||
typeRepr = segment.getTypeRepr() and
|
||||
traitPath = segment.getTraitTypeRepr().getPath() and
|
||||
name = path.getText()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `assoc` is accessed on `tp` in `path`.
|
||||
*
|
||||
* That is, this is the case when `path` is of the form `<tp as
|
||||
* Trait>::AssocType` or `tp::AssocType`; and `AssocType` resolves to `assoc`.
|
||||
*/
|
||||
predicate tpAssociatedType(TypeParam tp, AssocType assoc, Path path) {
|
||||
resolvePath(path.getQualifier()) = tp and
|
||||
resolvePath(path) = assoc
|
||||
or
|
||||
exists(PathTypeRepr typeRepr, Path traitPath, string name |
|
||||
pathTypeAsTraitAssoc(path, typeRepr, traitPath, name) and
|
||||
tp = resolvePath(typeRepr.getPath()) and
|
||||
assoc = resolvePath(traitPath).(TraitItemNode).getAssocItem(name)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bound` is a type bound for `tp` that gives rise to `assoc` being
|
||||
* present for `tp`.
|
||||
*/
|
||||
predicate tpBoundAssociatedType(
|
||||
TypeParam tp, TypeBound bound, Path path, TraitItemNode trait, AssocType assoc
|
||||
) {
|
||||
bound = tp.getATypeBound() and
|
||||
path = bound.getTypeRepr().(PathTypeRepr).getPath() and
|
||||
trait = resolvePath(path) and
|
||||
assoc = getTraitAssocType(trait)
|
||||
}
|
||||
@@ -8,11 +8,7 @@ private import codeql.rust.elements.internal.generated.Raw
|
||||
private import codeql.rust.elements.internal.generated.Synth
|
||||
private import codeql.rust.frameworks.stdlib.Stdlib
|
||||
private import codeql.rust.frameworks.stdlib.Builtins as Builtins
|
||||
|
||||
/** Gets a type alias of `trait` or of a supertrait of `trait`. */
|
||||
private TypeAlias getTraitTypeAlias(Trait trait) {
|
||||
result = trait.getSupertrait*().getAssocItemList().getAnAssocItem()
|
||||
}
|
||||
private import AssociatedType
|
||||
|
||||
/**
|
||||
* Holds if a dyn trait type for the trait `trait` should have a type parameter
|
||||
@@ -31,7 +27,7 @@ private TypeAlias getTraitTypeAlias(Trait trait) {
|
||||
*/
|
||||
private predicate dynTraitTypeParameter(Trait trait, AstNode n) {
|
||||
trait = any(DynTraitTypeRepr dt).getTrait() and
|
||||
n = [trait.getGenericParamList().getATypeParam().(AstNode), getTraitTypeAlias(trait)]
|
||||
n = [trait.getGenericParamList().getATypeParam().(AstNode), getTraitAssocType(trait)]
|
||||
}
|
||||
|
||||
cached
|
||||
@@ -43,8 +39,11 @@ newtype TType =
|
||||
TNeverType() or
|
||||
TUnknownType() or
|
||||
TTypeParamTypeParameter(TypeParam t) or
|
||||
TAssociatedTypeTypeParameter(Trait trait, TypeAlias typeAlias) {
|
||||
getTraitTypeAlias(trait) = typeAlias
|
||||
TAssociatedTypeTypeParameter(Trait trait, AssocType typeAlias) {
|
||||
getTraitAssocType(trait) = typeAlias
|
||||
} or
|
||||
TTypeParamAssociatedTypeTypeParameter(TypeParam tp, AssocType assoc) {
|
||||
tpAssociatedType(tp, assoc, _)
|
||||
} or
|
||||
TDynTraitTypeParameter(Trait trait, AstNode n) { dynTraitTypeParameter(trait, n) } or
|
||||
TImplTraitTypeParameter(ImplTraitTypeRepr implTrait, TypeParam tp) {
|
||||
@@ -464,6 +463,52 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara
|
||||
override Location getLocation() { result = typeAlias.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A type parameter corresponding to an associated type accessed on a type
|
||||
* parameter, for example `T::AssociatedType` where `T` is a type parameter.
|
||||
*
|
||||
* These type parameters are created when a function signature accesses an
|
||||
* associated type on a type parameter. For example, in
|
||||
* ```rust
|
||||
* fn foo<T: SomeTrait>(arg: T::Assoc) { }
|
||||
* ```
|
||||
* we create a `TypeParamAssociatedTypeTypeParameter` for `Assoc` on `T` and the
|
||||
* mention `T::Assoc` resolves to this type parameter. If denoting the type
|
||||
* parameter by `T_Assoc` then the above function is treated as if it was
|
||||
* ```rust
|
||||
* fn foo<T: SomeTrait<Assoc = T_Assoc>, T_Assoc>(arg: T_Assoc) { }
|
||||
* ```
|
||||
*/
|
||||
class TypeParamAssociatedTypeTypeParameter extends TypeParameter,
|
||||
TTypeParamAssociatedTypeTypeParameter
|
||||
{
|
||||
private TypeParam typeParam;
|
||||
private AssocType assoc;
|
||||
|
||||
TypeParamAssociatedTypeTypeParameter() {
|
||||
this = TTypeParamAssociatedTypeTypeParameter(typeParam, assoc)
|
||||
}
|
||||
|
||||
/** Gets the type parameter that this associated type is accessed on. */
|
||||
TypeParam getTypeParam() { result = typeParam }
|
||||
|
||||
/** Gets the associated type alias. */
|
||||
AssocType getTypeAlias() { result = assoc }
|
||||
|
||||
/** Gets a path that accesses this type parameter. */
|
||||
Path getAPath() { tpAssociatedType(typeParam, assoc, result) }
|
||||
|
||||
override ItemNode getDeclaringItem() { result.getTypeParam(_) = typeParam }
|
||||
|
||||
override string toString() {
|
||||
result =
|
||||
typeParam.toString() + "::" + assoc.getName().getText() + "[" +
|
||||
assoc.getTrait().getName().getText() + "]"
|
||||
}
|
||||
|
||||
override Location getLocation() { result = typeParam.getLocation() }
|
||||
}
|
||||
|
||||
/** Gets the associated type type-parameter corresponding directly to `typeAlias`. */
|
||||
AssociatedTypeTypeParameter getAssociatedTypeTypeParameter(TypeAlias typeAlias) {
|
||||
result.isDirect() and result.getTypeAlias() = typeAlias
|
||||
|
||||
@@ -108,6 +108,10 @@ private module Input implements InputSig1<Location>, InputSig2<PreTypeMention> {
|
||||
id2 = idOfTypeParameterAstNode(tp0.(AssociatedTypeTypeParameter).getTypeAlias())
|
||||
or
|
||||
kind = 4 and
|
||||
id1 = idOfTypeParameterAstNode(tp0.(TypeParamAssociatedTypeTypeParameter).getTypeParam()) and
|
||||
id2 = idOfTypeParameterAstNode(tp0.(TypeParamAssociatedTypeTypeParameter).getTypeAlias())
|
||||
or
|
||||
kind = 5 and
|
||||
id1 = 0 and
|
||||
exists(AstNode node | id2 = idOfTypeParameterAstNode(node) |
|
||||
node = tp0.(TypeParamTypeParameter).getTypeParam() or
|
||||
@@ -270,13 +274,21 @@ private class FunctionDeclaration extends Function {
|
||||
this = i.asSome().getAnAssocItem()
|
||||
}
|
||||
|
||||
TypeParam getTypeParam(ImplOrTraitItemNodeOption i) {
|
||||
i = parent and
|
||||
result = [this.getGenericParamList().getATypeParam(), i.asSome().getTypeParam(_)]
|
||||
}
|
||||
|
||||
TypeParameter getTypeParameter(ImplOrTraitItemNodeOption i, TypeParameterPosition ppos) {
|
||||
typeParamMatchPosition(this.getTypeParam(i), result, ppos)
|
||||
or
|
||||
// For every `TypeParam` of this function, any associated types accessed on
|
||||
// the type parameter are also type parameters.
|
||||
ppos.isImplicit() and
|
||||
result.(TypeParamAssociatedTypeTypeParameter).getTypeParam() = this.getTypeParam(i)
|
||||
or
|
||||
i = parent and
|
||||
(
|
||||
typeParamMatchPosition(this.getGenericParamList().getATypeParam(), result, ppos)
|
||||
or
|
||||
typeParamMatchPosition(i.asSome().getTypeParam(_), result, ppos)
|
||||
or
|
||||
ppos.isImplicit() and result = TSelfTypeParameter(i.asSome())
|
||||
or
|
||||
ppos.isImplicit() and result.(AssociatedTypeTypeParameter).getTrait() = i.asSome()
|
||||
@@ -2477,10 +2489,10 @@ private module MethodCallMatchingInput implements MatchingWithEnvironmentInputSi
|
||||
additional predicate decodeDerefChainBorrow(
|
||||
string derefChainBorrow, DerefChain derefChain, BorrowKind borrow
|
||||
) {
|
||||
exists(string regexp |
|
||||
regexp = "^(.*);(.*)$" and
|
||||
derefChain = derefChainBorrow.regexpCapture(regexp, 1) and
|
||||
borrow.toString() = derefChainBorrow.regexpCapture(regexp, 2)
|
||||
exists(int i |
|
||||
i = derefChainBorrow.indexOf(";") and
|
||||
derefChain = derefChainBorrow.prefix(i) and
|
||||
borrow.toString() = derefChainBorrow.suffix(i + 1)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2604,27 +2616,30 @@ private Type inferMethodCallTypeNonSelf(AstNode n, boolean isReturn, TypePath pa
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of `n` at `path` after applying `derefChain` and `borrow`,
|
||||
* where `n` is the `self` argument of a method call.
|
||||
* Gets the type of `n` at `path` after applying `derefChain`, where `n` is the
|
||||
* `self` argument of a method call.
|
||||
*
|
||||
* The predicate recursively pops the head of `derefChain` until it becomes
|
||||
* empty, at which point the inferred type can be applied back to `n`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private Type inferMethodCallTypeSelf(
|
||||
AstNode n, DerefChain derefChain, BorrowKind borrow, TypePath path
|
||||
) {
|
||||
exists(MethodCallMatchingInput::AccessPosition apos, string derefChainBorrow |
|
||||
result = inferMethodCallType0(_, apos, n, derefChainBorrow, path) and
|
||||
private Type inferMethodCallTypeSelf(AstNode n, DerefChain derefChain, TypePath path) {
|
||||
exists(
|
||||
MethodCallMatchingInput::AccessPosition apos, string derefChainBorrow, BorrowKind borrow,
|
||||
TypePath path0
|
||||
|
|
||||
result = inferMethodCallType0(_, apos, n, derefChainBorrow, path0) and
|
||||
apos.isSelf() and
|
||||
MethodCallMatchingInput::decodeDerefChainBorrow(derefChainBorrow, derefChain, borrow)
|
||||
)
|
||||
or
|
||||
// adjust for implicit borrow
|
||||
exists(TypePath path0, BorrowKind borrow0 |
|
||||
result = inferMethodCallTypeSelf(n, derefChain, borrow0, path0) and
|
||||
path0.isCons(borrow0.getRefType().getPositionalTypeParameter(0), path) and
|
||||
borrow.isNoBorrow()
|
||||
|
|
||||
borrow.isNoBorrow() and
|
||||
path = path0
|
||||
or
|
||||
// adjust for implicit borrow
|
||||
exists(TypePath prefix |
|
||||
prefix = TypePath::singleton(borrow.getRefType().getPositionalTypeParameter(0)) and
|
||||
path0 = prefix.appendInverse(path)
|
||||
)
|
||||
)
|
||||
or
|
||||
// adjust for implicit deref
|
||||
@@ -2632,9 +2647,8 @@ private Type inferMethodCallTypeSelf(
|
||||
DerefChain derefChain0, Type t0, TypePath path0, DerefImplItemNode impl, Type selfParamType,
|
||||
TypePath selfPath
|
||||
|
|
||||
t0 = inferMethodCallTypeSelf(n, derefChain0, borrow, path0) and
|
||||
t0 = inferMethodCallTypeSelf(n, derefChain0, path0) and
|
||||
derefChain0.isCons(impl, derefChain) and
|
||||
borrow.isNoBorrow() and
|
||||
selfParamType = impl.resolveSelfTypeAt(selfPath)
|
||||
|
|
||||
result = selfParamType and
|
||||
@@ -2653,7 +2667,7 @@ private Type inferMethodCallTypeSelf(
|
||||
private Type inferMethodCallTypePreCheck(AstNode n, boolean isReturn, TypePath path) {
|
||||
result = inferMethodCallTypeNonSelf(n, isReturn, path)
|
||||
or
|
||||
result = inferMethodCallTypeSelf(n, DerefChain::nil(), TNoBorrowKind(), path) and
|
||||
result = inferMethodCallTypeSelf(n, DerefChain::nil(), path) and
|
||||
isReturn = false
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ private import codeql.rust.frameworks.stdlib.Stdlib
|
||||
private import Type
|
||||
private import TypeAbstraction
|
||||
private import TypeInference
|
||||
private import AssociatedType
|
||||
|
||||
bindingset[trait, name]
|
||||
pragma[inline_late]
|
||||
@@ -319,6 +320,22 @@ private module MkTypeMention<getAdditionalPathTypeAtSig/2 getAdditionalPathTypeA
|
||||
tp = TAssociatedTypeTypeParameter(resolved, alias) and
|
||||
path.isEmpty()
|
||||
)
|
||||
or
|
||||
// If this path is a type parameter bound, then any associated types
|
||||
// accessed on the type parameter, which originate from this bound, should
|
||||
// be instantiated into the bound, as explained in the comment for
|
||||
// `TypeParamAssociatedTypeTypeParameter`.
|
||||
// ```rust
|
||||
// fn foo<T: SomeTrait<Assoc = T_Assoc>, T_Assoc>(arg: T_Assoc) { }
|
||||
// ^^^^^^^^^ ^^^^^ ^^^^^^^
|
||||
// this path result
|
||||
// ```
|
||||
exists(TypeParam typeParam, Trait trait, AssocType assoc |
|
||||
tpBoundAssociatedType(typeParam, _, this, trait, assoc) and
|
||||
tp = TAssociatedTypeTypeParameter(resolved, assoc) and
|
||||
result = TTypeParamAssociatedTypeTypeParameter(typeParam, assoc) and
|
||||
path.isEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[name]
|
||||
@@ -372,6 +389,8 @@ private module MkTypeMention<getAdditionalPathTypeAtSig/2 getAdditionalPathTypeA
|
||||
or
|
||||
// Handles paths of the form `Self::AssocType` within a trait block
|
||||
result = TAssociatedTypeTypeParameter(resolvePath(this.getQualifier()), resolved)
|
||||
or
|
||||
result.(TypeParamAssociatedTypeTypeParameter).getAPath() = this
|
||||
}
|
||||
|
||||
override Type resolvePathTypeAt(TypePath typePath) {
|
||||
@@ -690,11 +709,10 @@ private predicate pathConcreteTypeAssocType(
|
||||
|
|
||||
// path of the form `<Type as Trait>::AssocType`
|
||||
// ^^^ tm ^^^^^^^^^ name
|
||||
exists(string name |
|
||||
name = path.getText() and
|
||||
trait = resolvePath(qualifier.getSegment().getTraitTypeRepr().getPath()) and
|
||||
getTraitAssocType(trait, name) = alias and
|
||||
tm = qualifier.getSegment().getTypeRepr()
|
||||
exists(string name, Path traitPath |
|
||||
pathTypeAsTraitAssoc(path, tm, traitPath, name) and
|
||||
trait = resolvePath(traitPath) and
|
||||
getTraitAssocType(trait, name) = alias
|
||||
)
|
||||
or
|
||||
// path of the form `Self::AssocType` within an `impl` block
|
||||
|
||||
@@ -8,7 +8,7 @@ private import codeql.rust.internal.typeinference.TypeInference
|
||||
private import utils.test.InlineExpectationsTest
|
||||
|
||||
private module ResolveTest implements TestSig {
|
||||
string getARelevantTag() { result = "item" }
|
||||
string getARelevantTag() { result = ["item", "target", "item_not_target"] }
|
||||
|
||||
private predicate itemAt(ItemNode i, string filepath, int line) {
|
||||
i.getLocation().hasLocationInfo(filepath, _, _, line, _)
|
||||
@@ -36,19 +36,37 @@ private module ResolveTest implements TestSig {
|
||||
)
|
||||
}
|
||||
|
||||
private Item getCallExprTarget(Path p) {
|
||||
exists(CallExpr ce |
|
||||
p = ce.getFunction().(PathExpr).getPath() and
|
||||
result = ce.getResolvedTarget()
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(AstNode n |
|
||||
exists(AstNode n, ItemNode i |
|
||||
not n = any(Path parent).getQualifier() and
|
||||
location = n.getLocation() and
|
||||
n.fromSource() and
|
||||
not location.getFile().getAbsolutePath().matches("%proc_macro.rs") and
|
||||
not n.isFromMacroExpansion() and
|
||||
element = n.toString() and
|
||||
tag = "item"
|
||||
item(i, value)
|
||||
|
|
||||
item(resolvePath(n), value)
|
||||
i = resolvePath(n) and
|
||||
(
|
||||
if exists(getCallExprTarget(n)) and not i = getCallExprTarget(n)
|
||||
then tag = "item_not_target"
|
||||
else tag = "item"
|
||||
)
|
||||
or
|
||||
item(n.(MethodCallExpr).getStaticTarget(), value)
|
||||
tag = "target" and
|
||||
(
|
||||
i = n.(MethodCallExpr).getStaticTarget()
|
||||
or
|
||||
i = getCallExprTarget(n) and
|
||||
not i = resolvePath(n)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,18 +189,18 @@ mod m8 {
|
||||
#[rustfmt::skip]
|
||||
pub fn g() {
|
||||
let x = MyStruct {}; // $ item=I50
|
||||
MyTrait::f(&x); // $ item=I48
|
||||
MyTrait::f(&x); // $ item_not_target=I48 target=I53
|
||||
MyStruct::f(&x); // $ item=I53
|
||||
<MyStruct as // $ item=I50
|
||||
MyTrait // $ item=I47
|
||||
> // $ MISSING: item=52
|
||||
::f(&x); // $ item=I48
|
||||
::f(&x); // $ item_not_target=I48 target=I53
|
||||
let x = MyStruct {}; // $ item=I50
|
||||
x.f(); // $ item=I53
|
||||
x.f(); // $ target=I53
|
||||
let x = MyStruct {}; // $ item=I50
|
||||
x.g(); // $ item=I54
|
||||
x.g(); // $ target=I54
|
||||
MyStruct::h(&x); // $ item=I74
|
||||
x.h(); // $ item=I74
|
||||
x.h(); // $ target=I74
|
||||
} // I55
|
||||
} // I46
|
||||
|
||||
@@ -316,7 +316,7 @@ mod m15 {
|
||||
fn f(&self) {
|
||||
println!("m15::Trait2::f"); // $ item=println
|
||||
Self::g(self); // $ item=I80
|
||||
self.g(); // $ item=I80
|
||||
self.g(); // $ target=I80
|
||||
} // Trait2::f
|
||||
} // I82
|
||||
|
||||
@@ -331,7 +331,7 @@ mod m15 {
|
||||
fn f(&self, tt: TT) { // $ item=ITT
|
||||
Self::g(self); // $ item=I80
|
||||
TT::g(&tt); // $ item=I80
|
||||
self.g(); // $ item=I80
|
||||
self.g(); // $ target=I80
|
||||
}
|
||||
} // ITrait3
|
||||
|
||||
@@ -343,7 +343,7 @@ mod m15 {
|
||||
fn f(&self) {
|
||||
println!("m15::<S as Trait1>::f"); // $ item=println
|
||||
Self::g(self); // $ item=I77
|
||||
self.g(); // $ item=I77
|
||||
self.g(); // $ target=I77
|
||||
} // I76
|
||||
|
||||
fn g(&self) {
|
||||
@@ -365,12 +365,12 @@ mod m15 {
|
||||
let x = S; // $ item=I81
|
||||
<S // $ item=I81
|
||||
as Trait1 // $ item=I79
|
||||
>::f(&x); // $ item=Trait1::f
|
||||
>::f(&x); // $ item_not_target=Trait1::f target=I76
|
||||
<S // $ item=I81
|
||||
as Trait2 // $ item=I82
|
||||
>::f(&x); // $ item=Trait2::f
|
||||
>::f(&x); // $ item_not_target=Trait2::f target=I78
|
||||
S::g(&x); // $ item=I77
|
||||
x.g(); // $ item=I77
|
||||
x.g(); // $ target=I77
|
||||
} // I75
|
||||
}
|
||||
|
||||
@@ -383,12 +383,12 @@ mod m16 {
|
||||
; // Trait1::f
|
||||
|
||||
fn g(&self) -> T {// $ item=I84
|
||||
self.f() // $ item=Trait1::f
|
||||
self.f() // $ target=Trait1::f
|
||||
} // I85
|
||||
|
||||
fn h(&self) -> T { // $ item=I84
|
||||
Self::g(&self); // $ item=I85
|
||||
self.g() // $ item=I85
|
||||
self.g() // $ target=I85
|
||||
} // I96
|
||||
|
||||
const c: T // $ item=I84
|
||||
@@ -405,7 +405,7 @@ mod m16 {
|
||||
fn f(&self) -> T { // $ item=I87
|
||||
println!("m16::Trait2::f"); // $ item=println
|
||||
Self::g(self); // $ item=I85
|
||||
self.g(); // $ item=I85
|
||||
self.g(); // $ target=I85
|
||||
Self::c // $ item=I94
|
||||
} // Trait2::f
|
||||
} // I89
|
||||
@@ -420,7 +420,7 @@ mod m16 {
|
||||
fn f(&self) -> S { // $ item=I90
|
||||
println!("m16::<S as Trait1<S>>::f"); // $ item=println
|
||||
Self::g(self); // $ item=I92
|
||||
self.g() // $ item=I92
|
||||
self.g() // $ target=I92
|
||||
} // I91
|
||||
|
||||
fn g(&self) -> S { // $ item=I90
|
||||
@@ -452,16 +452,16 @@ mod m16 {
|
||||
as Trait1<
|
||||
S // $ item=I90
|
||||
> // $ item=I86
|
||||
>::f(&x); // $ item=Trait1::f
|
||||
>::f(&x); // $ item_not_target=Trait1::f target=I91
|
||||
<S // $ item=I90
|
||||
as Trait2<
|
||||
S // $ item=I90
|
||||
> // $ item=I89
|
||||
>::f(&x); // $ item=Trait2::f
|
||||
>::f(&x); // $ item_not_target=Trait2::f target=I93
|
||||
S::g(&x); // $ item=I92
|
||||
x.g(); // $ item=I92
|
||||
x.g(); // $ target=I92
|
||||
S::h(&x); // $ item=I96
|
||||
x.h(); // $ item=I96
|
||||
x.h(); // $ target=I96
|
||||
S::c; // $ item=I95
|
||||
<S // $ item=I90
|
||||
as Trait1<
|
||||
@@ -564,13 +564,13 @@ mod m16 {
|
||||
#[rustfmt::skip]
|
||||
fn foo() {
|
||||
S3::<i32>:: // $ item=i32
|
||||
Assoc(); // $ item=S3i32AssocFunc $ SPURIOUS: item=S3boolAssocFunc (the spurious target is later filtered away by type inference)
|
||||
Assoc(); // $ item=S3i32AssocFunc item_not_target=S3boolAssocFunc
|
||||
|
||||
S3::<bool>:: // $ item=bool
|
||||
f1(); // $ item=S3boolf1 $ SPURIOUS: item=S3i32f1 (the spurious target is later filtered away by type inference)
|
||||
f1(); // $ item=S3boolf1 item_not_target=S3i32f1
|
||||
|
||||
S3::<i32>:: // $ item=i32
|
||||
f1(); // $ item=S3i32f1 $ SPURIOUS: item=S3boolf1 (the spurious target is later filtered away by type inference)
|
||||
f1(); // $ item=S3i32f1 item_not_target=S3boolf1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,7 +628,7 @@ mod trait_visibility {
|
||||
{
|
||||
// The `Bar` trait is not visible, but we can refer to its method
|
||||
// with a full path.
|
||||
m::Bar::a_method(&x); // $ item=Bar::a_method
|
||||
m::Bar::a_method(&x); // $ item_not_target=Bar::a_method target=X_Bar::a_method
|
||||
}
|
||||
} // trait_visibility::f
|
||||
}
|
||||
@@ -652,7 +652,7 @@ mod m17 {
|
||||
fn g<T: // I5
|
||||
MyTrait // $ item=I2
|
||||
>(x: T) { // $ item=I5
|
||||
x.f(); // $ item=I1
|
||||
x.f(); // $ target=I1
|
||||
T::f(&x); // $ item=I1
|
||||
MyTrait::f(&x); // $ item=I1
|
||||
} // I6
|
||||
@@ -735,7 +735,7 @@ mod m23 {
|
||||
#[rustfmt::skip]
|
||||
pub fn f() {
|
||||
let x = S; // $ item=I4
|
||||
x.f(); // $ item=I5
|
||||
x.f(); // $ target=I5
|
||||
} // I108
|
||||
}
|
||||
|
||||
@@ -760,7 +760,7 @@ mod m24 {
|
||||
T: TraitA // $ item=I111 item=I1151
|
||||
{
|
||||
fn call_trait_a(&self) {
|
||||
self.data.trait_a_method(); // $ item=I110
|
||||
self.data.trait_a_method(); // $ target=I110
|
||||
} // I116
|
||||
}
|
||||
|
||||
@@ -772,8 +772,8 @@ mod m24 {
|
||||
T: TraitA, // $ item=I111 item=I1161
|
||||
{
|
||||
fn call_both(&self) {
|
||||
self.data.trait_a_method(); // $ item=I110
|
||||
self.data.trait_b_method(); // $ item=I112
|
||||
self.data.trait_a_method(); // $ target=I110
|
||||
self.data.trait_b_method(); // $ target=I112
|
||||
} // I117
|
||||
}
|
||||
|
||||
@@ -798,8 +798,8 @@ mod m24 {
|
||||
let impl_obj = Implementor; // $ item=I118
|
||||
let generic = GenericStruct { data: impl_obj }; // $ item=I115
|
||||
|
||||
generic.call_trait_a(); // $ item=I116
|
||||
generic.call_both(); // $ item=I117
|
||||
generic.call_trait_a(); // $ target=I116
|
||||
generic.call_both(); // $ target=I117
|
||||
|
||||
// Access through where clause type parameter constraint
|
||||
GenericStruct::<Implementor>::call_trait_a(&generic); // $ item=I116 item=I118
|
||||
@@ -1132,7 +1132,7 @@ fn main() {
|
||||
zelf::h(); // $ item=I25
|
||||
z_changed(); // $ item=I122
|
||||
AStruct::z_on_type(); // $ item=I124
|
||||
AStruct {}.z_on_instance(); // $ item=I123 item=I125
|
||||
AStruct {}.z_on_instance(); // $ item=I123 target=I125
|
||||
impl_with_attribute_macro::test(); // $ item=impl_with_attribute_macro::test
|
||||
patterns::test(); // $ item=patterns::test
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ fn int_div(
|
||||
) -> Result<i32> // $ item=my::Result $ item=i32
|
||||
{
|
||||
if y == 0 {
|
||||
return Err("Div by zero".to_string()); // $ item=Err item=to_string
|
||||
return Err("Div by zero".to_string()); // $ item=Err target=to_string
|
||||
}
|
||||
Ok(x / y) // $ item=Ok
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ impl<A> Wrapper<A> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
struct S;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -260,13 +260,65 @@ mod type_param_access_associated_type {
|
||||
)
|
||||
}
|
||||
|
||||
// Associated type accessed on a type parameter of an impl block
|
||||
impl<TI> Wrapper<TI>
|
||||
where
|
||||
TI: GetSet,
|
||||
{
|
||||
fn extract(&self) -> TI::Output {
|
||||
self.0.get() // $ fieldof=Wrapper target=GetSet::get
|
||||
}
|
||||
}
|
||||
|
||||
// Associated type accessed on another associated type
|
||||
|
||||
fn tp_nested_assoc_type<T: GetSet>(thing: T) -> <<T as GetSet>::Output as GetSet>::Output
|
||||
where
|
||||
<T as GetSet>::Output: GetSet,
|
||||
{
|
||||
thing.get().get() // $ target=GetSet::get target=GetSet::get
|
||||
}
|
||||
|
||||
pub trait GetSetWrap {
|
||||
type Assoc: GetSet;
|
||||
|
||||
// GetSetWrap::get_wrap
|
||||
fn get_wrap(&self) -> Self::Assoc;
|
||||
}
|
||||
|
||||
impl GetSetWrap for S {
|
||||
type Assoc = S;
|
||||
|
||||
// S::get_wrap
|
||||
fn get_wrap(&self) -> Self::Assoc {
|
||||
S
|
||||
}
|
||||
}
|
||||
|
||||
// Nested associated type accessed on a type parameter of an impl block
|
||||
impl<TI> Wrapper<TI>
|
||||
where
|
||||
TI: GetSetWrap,
|
||||
{
|
||||
fn extract2(&self) -> <<TI as GetSetWrap>::Assoc as GetSet>::Output {
|
||||
self.0.get_wrap().get() // $ fieldof=Wrapper target=GetSetWrap::get_wrap $ MISSING: target=GetSet::get
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test() {
|
||||
let _o1 = tp_with_as(S); // $ target=tp_with_as MISSING: type=_o1:S3
|
||||
let _o2 = tp_without_as(S); // $ target=tp_without_as MISSING: type=_o2:S3
|
||||
let _o1 = tp_with_as(S); // $ target=tp_with_as type=_o1:S3
|
||||
let _o2 = tp_without_as(S); // $ target=tp_without_as type=_o2:S3
|
||||
let (
|
||||
_o3, // $ MISSING: type=_o3:S3
|
||||
_o4, // $ MISSING: type=_o4:bool
|
||||
_o4, // $ type=_o4:bool
|
||||
) = tp_assoc_from_supertrait(S); // $ target=tp_assoc_from_supertrait
|
||||
|
||||
let _o5 = tp_nested_assoc_type(Wrapper(S)); // $ target=tp_nested_assoc_type MISSING: type=_o5:S3
|
||||
|
||||
let w = Wrapper(S);
|
||||
let _extracted = w.extract(); // $ target=extract type=_extracted:S3
|
||||
|
||||
let _extracted2 = w.extract2(); // $ target=extract2 MISSING: type=_extracted2:S3
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1748,7 +1748,7 @@ mod overloadable_operators {
|
||||
let i64_mul = 17i64 * 18i64; // $ type=i64_mul:i64 target=mul
|
||||
let i64_div = 19i64 / 20i64; // $ type=i64_div:i64 target=div
|
||||
let i64_rem = 21i64 % 22i64; // $ type=i64_rem:i64 target=rem
|
||||
let i64_param_add = param_add(1i64, 2i64); // $ target=param_add $ MISSING: type=i64_param_add:i64
|
||||
let i64_param_add = param_add(1i64, 2i64); // $ target=param_add $ type=i64_param_add:i64
|
||||
|
||||
// Arithmetic assignment operators
|
||||
let mut i64_add_assign = 23i64;
|
||||
@@ -2053,7 +2053,7 @@ mod indexers {
|
||||
let xs: [S; 1] = [S];
|
||||
let x = xs[0].foo(); // $ target=foo type=x:S target=index
|
||||
|
||||
let y = param_index(vec, 0); // $ target=param_index $ MISSING: type=y:S
|
||||
let y = param_index(vec, 0); // $ target=param_index $ type=y:S
|
||||
|
||||
analyze_slice(&xs); // $ target=analyze_slice
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -95,7 +95,7 @@ module Make<RegexTreeViewSig TreeImpl> {
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = this.(InfiniteRepetitionQuantifier).toString() }
|
||||
string toString() { result = super.toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,11 +104,11 @@ module Make<RegexTreeViewSig TreeImpl> {
|
||||
private class RegexpCharacterConstant instanceof RegExpConstant {
|
||||
RegexpCharacterConstant() { this.isCharacter() }
|
||||
|
||||
string toString() { result = this.(RegExpConstant).toString() }
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
RegExpTerm getRootTerm() { result = this.(RegExpConstant).getRootTerm() }
|
||||
RegExpTerm getRootTerm() { result = super.getRootTerm() }
|
||||
|
||||
string getValue() { result = this.(RegExpConstant).getValue() }
|
||||
string getValue() { result = super.getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,11 +578,11 @@ module Make<RegexTreeViewSig TreeImpl> {
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = this.(RegExpTerm).toString() }
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
RegExpTerm getAChild() { result = this.(RegExpTerm).getChild(_) }
|
||||
RegExpTerm getAChild() { result = super.getChild(_) }
|
||||
|
||||
RegExpTerm getChild(int i) { result = this.(RegExpTerm).getChild(i) }
|
||||
RegExpTerm getChild(int i) { result = super.getChild(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -601,11 +601,11 @@ module Make<RegexTreeViewSig TreeImpl> {
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = this.(RegExpTerm).toString() }
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
RegExpTerm getAChild() { result = this.(RegExpTerm).getAChild() }
|
||||
RegExpTerm getAChild() { result = super.getAChild() }
|
||||
|
||||
RegExpTerm getChild(int i) { result = this.(RegExpTerm).getChild(i) }
|
||||
RegExpTerm getChild(int i) { result = super.getChild(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -621,11 +621,11 @@ module Make<RegexTreeViewSig TreeImpl> {
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = this.(RegExpTerm).toString() }
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
RegExpTerm getAChild() { result = this.(RegExpTerm).getAChild() }
|
||||
RegExpTerm getAChild() { result = super.getAChild() }
|
||||
|
||||
RegExpTerm getChild(int i) { result = this.(RegExpTerm).getChild(i) }
|
||||
RegExpTerm getChild(int i) { result = super.getChild(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,6 +78,9 @@ module Make<LocationSig Location, InputSig<Location> Input> {
|
||||
/** Holds if this list is empty. */
|
||||
predicate isEmpty() { this = "" }
|
||||
|
||||
bindingset[this]
|
||||
private int stringLength() { result = super.length() }
|
||||
|
||||
/** Gets the length of this list. */
|
||||
bindingset[this]
|
||||
pragma[inline_late]
|
||||
@@ -115,19 +118,23 @@ module Make<LocationSig Location, InputSig<Location> Input> {
|
||||
/** Holds if this list starts with `e`, followed by `suffix`. */
|
||||
bindingset[this]
|
||||
predicate isCons(Element e, UnboundList suffix) {
|
||||
exists(string regexp | regexp = "^([0-9]+)\\.(.*)$" |
|
||||
e = decode(this.regexpCapture(regexp, 1)) and
|
||||
suffix = this.regexpCapture(regexp, 2)
|
||||
exists(string elem |
|
||||
// it is more efficient to not create a capture group for the suffix, since
|
||||
// `regexpCapture` will then always join in both groups, only to afterwards filter
|
||||
// based on the requested group (the group number is not part of the binding set
|
||||
// of `regexpCapture`)
|
||||
elem = this.regexpCapture("^([0-9]+)\\..*$", 1) and
|
||||
e = decode(elem) and
|
||||
suffix = this.suffix(elem.length() + 1)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this list starts with `prefix`, followed by `e`. */
|
||||
bindingset[this]
|
||||
predicate isSnoc(UnboundList prefix, Element e) {
|
||||
exists(string regexp | regexp = "^(|.+\\.)([0-9]+)\\.$" |
|
||||
prefix = this.regexpCapture(regexp, 1) and
|
||||
e = decode(this.regexpCapture(regexp, 2))
|
||||
)
|
||||
// same remark as above about not using multiple capture groups
|
||||
prefix = this.regexpCapture("^(|.+\\.)[0-9]+\\.$", 1) and
|
||||
e = decode(this.substring(prefix.stringLength(), this.stringLength() - 1))
|
||||
}
|
||||
|
||||
/** Gets the head of this list, if any. */
|
||||
|
||||
Reference in New Issue
Block a user