diff --git a/config/identical-files.json b/config/identical-files.json index 84b50a0c502..dc3d0647153 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -22,7 +22,6 @@ "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll", "go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll", "go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll", - "go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll", @@ -562,4 +561,4 @@ "python/ql/lib/semmle/python/security/internal/EncryptionKeySizes.qll", "java/ql/lib/semmle/code/java/security/internal/EncryptionKeySizes.qll" ] -} \ No newline at end of file +} diff --git a/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs b/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs index 06057b971b5..a3e7987394d 100644 --- a/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs +++ b/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs @@ -327,7 +327,7 @@ namespace Semmle.Autobuild.Cpp.Tests { Actions.RunProcess[@"cmd.exe /C nuget restore C:\Project\test.sln -DisableParallelProcessing"] = 1; Actions.RunProcess[@"cmd.exe /C C:\Project\.nuget\nuget.exe restore C:\Project\test.sln -DisableParallelProcessing"] = 0; - Actions.RunProcess[@"cmd.exe /C CALL ^""C:\Program Files ^(x86^)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat^"" && set Platform=&& type NUL && msbuild C:\Project\test.sln /t:rebuild /p:Platform=""x86"" /p:Configuration=""Release"""] = 0; + Actions.RunProcess[@"cmd.exe /C CALL ^""C:\Program^ Files^ ^(x86^)\Microsoft^ Visual^ Studio^ 14.0\VC\vcvarsall.bat^"" && set Platform=&& type NUL && msbuild C:\Project\test.sln /t:rebuild /p:Platform=""x86"" /p:Configuration=""Release"""] = 0; Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = ""; Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = 1; Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = 0; diff --git a/cpp/ql/lib/CHANGELOG.md b/cpp/ql/lib/CHANGELOG.md index 162ce6d354b..e80332de277 100644 --- a/cpp/ql/lib/CHANGELOG.md +++ b/cpp/ql/lib/CHANGELOG.md @@ -1,3 +1,18 @@ +## 0.9.0 + +### Breaking Changes + +* The `shouldPrintFunction` predicate from `PrintAstConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed. +* The `shouldPrintFunction` predicate from `PrintIRConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed. + +### Major Analysis Improvements + +* The `PrintAST` library now also prints global and namespace variables and their initializers. + +### Minor Analysis Improvements + +* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types. + ## 0.8.1 ### Deprecated APIs diff --git a/cpp/ql/lib/change-notes/2023-07-20-print-global-variables.md b/cpp/ql/lib/change-notes/2023-07-20-print-global-variables.md deleted file mode 100644 index 95a2a396cbb..00000000000 --- a/cpp/ql/lib/change-notes/2023-07-20-print-global-variables.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: majorAnalysis ---- -* The `PrintAST` library now also prints global and namespace variables and their initializers. diff --git a/cpp/ql/lib/change-notes/2023-08-07-removal-of-float128x.md b/cpp/ql/lib/change-notes/2023-08-07-removal-of-float128x.md deleted file mode 100644 index 1b91bb6ff89..00000000000 --- a/cpp/ql/lib/change-notes/2023-08-07-removal-of-float128x.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: minorAnalysis ---- -* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types. diff --git a/cpp/ql/lib/change-notes/2023-07-19-rename-should-print-function.md b/cpp/ql/lib/change-notes/released/0.9.0.md similarity index 54% rename from cpp/ql/lib/change-notes/2023-07-19-rename-should-print-function.md rename to cpp/ql/lib/change-notes/released/0.9.0.md index 61ebd0aab34..eaace2d6e2e 100644 --- a/cpp/ql/lib/change-notes/2023-07-19-rename-should-print-function.md +++ b/cpp/ql/lib/change-notes/released/0.9.0.md @@ -1,5 +1,14 @@ ---- -category: breaking ---- +## 0.9.0 + +### Breaking Changes + * The `shouldPrintFunction` predicate from `PrintAstConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed. * The `shouldPrintFunction` predicate from `PrintIRConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed. + +### Major Analysis Improvements + +* The `PrintAST` library now also prints global and namespace variables and their initializers. + +### Minor Analysis Improvements + +* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types. diff --git a/cpp/ql/lib/codeql-pack.release.yml b/cpp/ql/lib/codeql-pack.release.yml index 2f693f95ba6..8b9fc185202 100644 --- a/cpp/ql/lib/codeql-pack.release.yml +++ b/cpp/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.8.1 +lastReleaseVersion: 0.9.0 diff --git a/cpp/ql/lib/qlpack.yml b/cpp/ql/lib/qlpack.yml index 39c03fb590b..182170c144c 100644 --- a/cpp/ql/lib/qlpack.yml +++ b/cpp/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/cpp-all -version: 0.8.2-dev +version: 0.9.1-dev groups: cpp dbscheme: semmlecode.cpp.dbscheme extractor: cpp diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index 6b3f745a641..329164e0fd0 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -1078,7 +1078,7 @@ private IRVariable getIRVariableForParameterNode(ParameterNode p) { /** Holds if `v` is the source variable corresponding to the parameter represented by `p`. */ pragma[nomagic] -private predicate parameterNodeHasSourceVariable(ParameterNode p, Ssa::SourceIRVariable v) { +private predicate parameterNodeHasSourceVariable(ParameterNode p, Ssa::SourceVariable v) { v.getIRVariable() = getIRVariableForParameterNode(p) and exists(Position pos | p.isParameterOf(_, pos) | pos instanceof DirectPosition and diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 209d0246832..6470741d541 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -781,26 +781,12 @@ class IndirectArgumentOutNode extends Node, TIndirectArgumentOutNode, PartialDef override Expr getDefinedExpr() { result = operand.getDef().getUnconvertedResultExpression() } } -pragma[nomagic] -predicate indirectReturnOutNodeOperand0(CallInstruction call, Operand operand, int indirectionIndex) { - Ssa::hasRawIndirectInstruction(call, indirectionIndex) and - operandForFullyConvertedCall(operand, call) -} - -pragma[nomagic] -predicate indirectReturnOutNodeInstruction0( - CallInstruction call, Instruction instr, int indirectionIndex -) { - Ssa::hasRawIndirectInstruction(call, indirectionIndex) and - instructionForFullyConvertedCall(instr, call) -} - /** * Holds if `node` is an indirect operand with columns `(operand, indirectionIndex)`, and * `operand` represents a use of the fully converted value of `call`. */ private predicate hasOperand(Node node, CallInstruction call, int indirectionIndex, Operand operand) { - indirectReturnOutNodeOperand0(call, operand, indirectionIndex) and + operandForFullyConvertedCall(operand, call) and hasOperandAndIndex(node, operand, indirectionIndex) } @@ -813,7 +799,7 @@ private predicate hasOperand(Node node, CallInstruction call, int indirectionInd private predicate hasInstruction( Node node, CallInstruction call, int indirectionIndex, Instruction instr ) { - indirectReturnOutNodeInstruction0(call, instr, indirectionIndex) and + instructionForFullyConvertedCall(instr, call) and hasInstructionAndIndex(node, instr, indirectionIndex) } @@ -1534,6 +1520,25 @@ private module Cached { ) } + /** + * Holds if `operand.getDef() = instr`, but there exists a `StoreInstruction` that + * writes to an address that is equivalent to the value computed by `instr` in + * between `instr` and `operand`, and therefore there should not be flow from `*instr` + * to `*operand`. + */ + pragma[nomagic] + private predicate isStoredToBetween(Instruction instr, Operand operand) { + simpleOperandLocalFlowStep(pragma[only_bind_into](instr), pragma[only_bind_into](operand)) and + exists(StoreInstruction store, IRBlock block, int storeIndex, int instrIndex, int operandIndex | + store.getDestinationAddress() = instr and + block.getInstruction(storeIndex) = store and + block.getInstruction(instrIndex) = instr and + block.getInstruction(operandIndex) = operand.getUse() and + instrIndex < storeIndex and + storeIndex < operandIndex + ) + } + private predicate indirectionInstructionFlow( RawIndirectInstruction nodeFrom, IndirectOperand nodeTo ) { @@ -1543,7 +1548,8 @@ private module Cached { simpleOperandLocalFlowStep(pragma[only_bind_into](instr), pragma[only_bind_into](operand)) | hasOperandAndIndex(nodeTo, operand, pragma[only_bind_into](indirectionIndex)) and - hasInstructionAndIndex(nodeFrom, instr, pragma[only_bind_into](indirectionIndex)) + hasInstructionAndIndex(nodeFrom, instr, pragma[only_bind_into](indirectionIndex)) and + not isStoredToBetween(instr, operand) ) } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll index 0cd152e2473..4e9a90dc0a1 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll @@ -10,32 +10,35 @@ private import ssa0.SsaInternals as SsaInternals0 import SsaInternalsCommon private module SourceVariables { - int getMaxIndirectionForIRVariable(IRVariable var) { - exists(Type type, boolean isGLValue | - var.getLanguageType().hasType(type, isGLValue) and - if isGLValue = true - then result = 1 + getMaxIndirectionsForType(type) - else result = getMaxIndirectionsForType(type) - ) - } - cached private newtype TSourceVariable = - TSourceIRVariable(BaseIRVariable baseVar, int ind) { - ind = [0 .. getMaxIndirectionForIRVariable(baseVar.getIRVariable())] - } or - TCallVariable(AllocationInstruction call, int ind) { - ind = [0 .. countIndirectionsForCppType(getResultLanguageType(call))] + TMkSourceVariable(SsaInternals0::SourceVariable base, int ind) { + ind = [0 .. countIndirectionsForCppType(base.getLanguageType()) + 1] } - abstract class SourceVariable extends TSourceVariable { + class SourceVariable extends TSourceVariable { + SsaInternals0::SourceVariable base; int ind; - bindingset[ind] - SourceVariable() { any() } + SourceVariable() { this = TMkSourceVariable(base, ind) } + + /** Gets the IR variable associated with this `SourceVariable`, if any. */ + IRVariable getIRVariable() { result = base.(BaseIRVariable).getIRVariable() } + + /** + * Gets the base source variable (i.e., the variable without any + * indirections) of this source variable. + */ + SsaInternals0::SourceVariable getBaseVariable() { result = base } /** Gets a textual representation of this element. */ - abstract string toString(); + string toString() { + ind = 0 and + result = this.getBaseVariable().toString() + or + ind > 0 and + result = this.getBaseVariable().toString() + " indirection" + } /** * Gets the number of loads performed on the base source variable @@ -43,65 +46,19 @@ private module SourceVariables { */ int getIndirection() { result = ind } - /** - * Gets the base source variable (i.e., the variable without any - * indirections) of this source variable. - */ - abstract BaseSourceVariable getBaseVariable(); - /** Holds if this variable is a glvalue. */ - predicate isGLValue() { none() } + predicate isGLValue() { ind = 0 } /** * Gets the type of this source variable. If `isGLValue()` holds, then * the type of this source variable should be thought of as "pointer * to `getType()`". */ - abstract DataFlowType getType(); - } - - class SourceIRVariable extends SourceVariable, TSourceIRVariable { - BaseIRVariable var; - - SourceIRVariable() { this = TSourceIRVariable(var, ind) } - - IRVariable getIRVariable() { result = var.getIRVariable() } - - override BaseIRVariable getBaseVariable() { result.getIRVariable() = this.getIRVariable() } - - override string toString() { - ind = 0 and - result = this.getIRVariable().toString() - or - ind > 0 and - result = this.getIRVariable().toString() + " indirection" + DataFlowType getType() { + if this.isGLValue() + then result = base.getType() + else result = getTypeImpl(base.getType(), ind - 1) } - - override predicate isGLValue() { ind = 0 } - - override DataFlowType getType() { - if ind = 0 then result = var.getType() else result = getTypeImpl(var.getType(), ind - 1) - } - } - - class CallVariable extends SourceVariable, TCallVariable { - AllocationInstruction call; - - CallVariable() { this = TCallVariable(call, ind) } - - AllocationInstruction getCall() { result = call } - - override BaseCallVariable getBaseVariable() { result.getCallInstruction() = call } - - override string toString() { - ind = 0 and - result = "Call" - or - ind > 0 and - result = "Call indirection" - } - - override DataFlowType getType() { result = getTypeImpl(call.getResultType(), ind) } } } @@ -137,8 +94,9 @@ predicate hasRawIndirectInstruction(Instruction instr, int indirectionIndex) { cached private newtype TDefOrUseImpl = - TDefImpl(Operand address, int indirectionIndex) { - exists(Instruction base | isDef(_, _, address, base, _, indirectionIndex) | + TDefImpl(BaseSourceVariableInstruction base, Operand address, int indirectionIndex) { + isDef(_, _, address, base, _, indirectionIndex) and + ( // We only include the definition if the SSA pruning stage // concluded that the definition is live after the write. any(SsaInternals0::Def def).getAddressOperand() = address @@ -148,8 +106,8 @@ private newtype TDefOrUseImpl = base.(VariableAddressInstruction).getAstVariable() instanceof GlobalLikeVariable ) } or - TUseImpl(Operand operand, int indirectionIndex) { - isUse(_, operand, _, _, indirectionIndex) and + TUseImpl(BaseSourceVariableInstruction base, Operand operand, int indirectionIndex) { + isUse(_, operand, base, _, indirectionIndex) and not isDef(_, _, operand, _, _, _) } or TGlobalUse(GlobalLikeVariable v, IRFunction f, int indirectionIndex) { @@ -236,7 +194,7 @@ abstract private class DefOrUseImpl extends TDefOrUseImpl { /** * Gets the instruction that computes the base of this definition or use. - * This is always a `VariableAddressInstruction` or an `AllocationInstruction`. + * This is always a `VariableAddressInstruction` or an `CallInstruction`. */ abstract BaseSourceVariableInstruction getBase(); @@ -308,15 +266,17 @@ abstract class DefImpl extends DefOrUseImpl { } private class DirectDef extends DefImpl, TDefImpl { - DirectDef() { this = TDefImpl(address, ind) } + BaseSourceVariableInstruction base; - override BaseSourceVariableInstruction getBase() { isDef(_, _, address, result, _, _) } + DirectDef() { this = TDefImpl(base, address, ind) } - override int getIndirection() { isDef(_, _, address, _, result, ind) } + override BaseSourceVariableInstruction getBase() { result = base } - override Node0Impl getValue() { isDef(_, result, address, _, _, _) } + override int getIndirection() { isDef(_, _, address, base, result, ind) } - override predicate isCertain() { isDef(true, _, address, _, _, ind) } + override Node0Impl getValue() { isDef(_, result, address, base, _, _) } + + override predicate isCertain() { isDef(true, _, address, base, _, ind) } } private class IteratorDef extends DefImpl, TIteratorDef { @@ -359,6 +319,7 @@ abstract class UseImpl extends DefOrUseImpl { abstract private class OperandBasedUse extends UseImpl { Operand operand; + BaseSourceVariableInstruction base; bindingset[ind] OperandBasedUse() { any() } @@ -366,50 +327,44 @@ abstract private class OperandBasedUse extends UseImpl { final override predicate hasIndexInBlock(IRBlock block, int index) { // See the comment in `ssa0`'s `OperandBasedUse` for an explanation of this // predicate's implementation. - exists(BaseSourceVariableInstruction base | base = this.getBase() | - if base.getAst() = any(Cpp::PostfixCrementOperation c).getOperand() - then - exists(Operand op, int indirectionIndex, int indirection | - indirectionIndex = this.getIndirectionIndex() and - indirection = this.getIndirection() and - op = - min(Operand cand, int i | - isUse(_, cand, base, indirection, indirectionIndex) and - block.getInstruction(i) = cand.getUse() - | - cand order by i - ) and - block.getInstruction(index) = op.getUse() - ) - else operand.getUse() = block.getInstruction(index) - ) + if base.getAst() = any(Cpp::PostfixCrementOperation c).getOperand() + then + exists(Operand op, int indirectionIndex, int indirection | + indirectionIndex = this.getIndirectionIndex() and + indirection = this.getIndirection() and + op = + min(Operand cand, int i | + isUse(_, cand, base, indirection, indirectionIndex) and + block.getInstruction(i) = cand.getUse() + | + cand order by i + ) and + block.getInstruction(index) = op.getUse() + ) + else operand.getUse() = block.getInstruction(index) } + final override BaseSourceVariableInstruction getBase() { result = base } + final Operand getOperand() { result = operand } final override Cpp::Location getLocation() { result = operand.getLocation() } } private class DirectUse extends OperandBasedUse, TUseImpl { - DirectUse() { this = TUseImpl(operand, ind) } + DirectUse() { this = TUseImpl(base, operand, ind) } - override int getIndirection() { isUse(_, operand, _, result, ind) } + override int getIndirection() { isUse(_, operand, base, result, ind) } - override BaseSourceVariableInstruction getBase() { isUse(_, operand, result, _, ind) } - - override predicate isCertain() { isUse(true, operand, _, _, ind) } + override predicate isCertain() { isUse(true, operand, base, _, ind) } override Node getNode() { nodeHasOperand(result, operand, ind) } } private class IteratorUse extends OperandBasedUse, TIteratorUse { - BaseSourceVariableInstruction container; + IteratorUse() { this = TIteratorUse(operand, base, ind) } - IteratorUse() { this = TIteratorUse(operand, container, ind) } - - override int getIndirection() { isIteratorUse(container, operand, result, ind) } - - override BaseSourceVariableInstruction getBase() { result = container } + override int getIndirection() { isIteratorUse(base, operand, result, ind) } override predicate isCertain() { none() } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternalsCommon.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternalsCommon.qll index 33b33113d43..4410e2e9e69 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternalsCommon.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternalsCommon.qll @@ -6,6 +6,7 @@ private import DataFlowImplCommon as DataFlowImplCommon private import DataFlowUtil private import semmle.code.cpp.models.interfaces.PointerWrapper private import DataFlowPrivate +private import semmle.code.cpp.ir.ValueNumbering /** * Holds if `operand` is an operand that is not used by the dataflow library. @@ -146,14 +147,6 @@ int countIndirectionsForCppType(LanguageType langType) { ) } -/** - * A `CallInstruction` that calls an allocation function such - * as `malloc` or `operator new`. - */ -class AllocationInstruction extends CallInstruction { - AllocationInstruction() { this.getStaticCallTarget() instanceof Cpp::AllocationFunction } -} - private predicate isIndirectionType(Type t) { t instanceof Indirection } private predicate hasUnspecifiedBaseType(Indirection t, Type base) { @@ -368,17 +361,22 @@ newtype TBaseSourceVariable = // Each IR variable gets its own source variable TBaseIRVariable(IRVariable var) or // Each allocation gets its own source variable - TBaseCallVariable(AllocationInstruction call) + TBaseCallVariable(CallInstruction call) { not call.getResultIRType() instanceof IRVoidType } -abstract class BaseSourceVariable extends TBaseSourceVariable { +abstract private class AbstractBaseSourceVariable extends TBaseSourceVariable { /** Gets a textual representation of this element. */ abstract string toString(); /** Gets the type of this base source variable. */ - abstract DataFlowType getType(); + final DataFlowType getType() { this.getLanguageType().hasUnspecifiedType(result, _) } + + /** Gets the `CppType` of this base source variable. */ + abstract CppType getLanguageType(); } -class BaseIRVariable extends BaseSourceVariable, TBaseIRVariable { +final class BaseSourceVariable = AbstractBaseSourceVariable; + +class BaseIRVariable extends AbstractBaseSourceVariable, TBaseIRVariable { IRVariable var; IRVariable getIRVariable() { result = var } @@ -387,19 +385,19 @@ class BaseIRVariable extends BaseSourceVariable, TBaseIRVariable { override string toString() { result = var.toString() } - override DataFlowType getType() { result = var.getType() } + override CppType getLanguageType() { result = var.getLanguageType() } } -class BaseCallVariable extends BaseSourceVariable, TBaseCallVariable { - AllocationInstruction call; +class BaseCallVariable extends AbstractBaseSourceVariable, TBaseCallVariable { + CallInstruction call; BaseCallVariable() { this = TBaseCallVariable(call) } - AllocationInstruction getCallInstruction() { result = call } + CallInstruction getCallInstruction() { result = call } override string toString() { result = call.toString() } - override DataFlowType getType() { result = call.getResultType() } + override CppType getLanguageType() { result = getResultLanguageType(call) } } /** @@ -499,8 +497,7 @@ private class BaseIRVariableInstruction extends BaseSourceVariableInstruction, override BaseIRVariable getBaseSourceVariable() { result.getIRVariable() = this.getIRVariable() } } -private class BaseAllocationInstruction extends BaseSourceVariableInstruction, AllocationInstruction -{ +private class BaseCallInstruction extends BaseSourceVariableInstruction, CallInstruction { override BaseCallVariable getBaseSourceVariable() { result.getCallInstruction() = this } } @@ -868,7 +865,7 @@ private module Cached { * to a specific address. */ private predicate isCertainAddress(Operand operand) { - operand.getDef() instanceof VariableAddressInstruction + valueNumberOfOperand(operand).getAnInstruction() instanceof VariableAddressInstruction or operand.getType() instanceof Cpp::ReferenceType } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ssa0/SsaInternals.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ssa0/SsaInternals.qll index 38f9bbeec8e..fd27a4e354f 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ssa0/SsaInternals.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ssa0/SsaInternals.qll @@ -15,15 +15,12 @@ private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil private import semmle.code.cpp.ir.dataflow.internal.SsaInternalsCommon private module SourceVariables { - class SourceVariable instanceof BaseSourceVariable { - string toString() { result = BaseSourceVariable.super.toString() } - + class SourceVariable extends BaseSourceVariable { + /** + * Gets the base source variable of this `SourceVariable`. + */ BaseSourceVariable getBaseVariable() { result = this } } - - class SourceIRVariable = BaseIRVariable; - - class CallVariable = BaseCallVariable; } import SourceVariables diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisConstantSpecific.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisConstantSpecific.qll index d1e53530ef5..42edb904c3f 100644 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisConstantSpecific.qll +++ b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisConstantSpecific.qll @@ -3,24 +3,10 @@ */ private import semmle.code.cpp.rangeanalysis.new.internal.semantic.Semantic -private import codeql.util.Unit -private import Reason as Reason private import RangeAnalysisStage private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.FloatDelta module CppLangImplConstant implements LangSig { - private module Param implements Reason::ParamSig { - class TypeReasonImpl = Unit; - } - - class SemReason = Reason::Make::SemReason; - - class SemNoReason = Reason::Make::SemNoReason; - - class SemCondReason = Reason::Make::SemCondReason; - - class SemTypeReason = Reason::Make::SemTypeReason; - /** * Holds if the specified expression should be excluded from the result of `ssaRead()`. * diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisImpl.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisImpl.qll index 08d246072fb..938857c0c2d 100644 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisImpl.qll @@ -61,23 +61,18 @@ private newtype TSemReason = guard = any(ConstantStage::SemCondReason reason).getCond() or guard = any(RelativeStage::SemCondReason reason).getCond() - } or - TSemTypeReason() + } -private ConstantStage::SemReason constantReason(SemReason reason) { +ConstantStage::SemReason constantReason(SemReason reason) { result instanceof ConstantStage::SemNoReason and reason instanceof SemNoReason or result.(ConstantStage::SemCondReason).getCond() = reason.(SemCondReason).getCond() - or - result instanceof ConstantStage::SemTypeReason and reason instanceof SemTypeReason } -private RelativeStage::SemReason relativeReason(SemReason reason) { +RelativeStage::SemReason relativeReason(SemReason reason) { result instanceof RelativeStage::SemNoReason and reason instanceof SemNoReason or result.(RelativeStage::SemCondReason).getCond() = reason.(SemCondReason).getCond() - or - result instanceof RelativeStage::SemTypeReason and reason instanceof SemTypeReason } import Public @@ -116,12 +111,4 @@ module Public { override string toString() { result = this.getCond().toString() } } - - /** - * A reason for an inferred bound that indicates that the bound is inferred - * based on type-information. - */ - class SemTypeReason extends SemReason, TSemTypeReason { - override string toString() { result = "TypeReason" } - } } diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisRelativeSpecific.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisRelativeSpecific.qll index 341fab17376..7643c4aadb9 100644 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisRelativeSpecific.qll +++ b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisRelativeSpecific.qll @@ -7,25 +7,9 @@ private import RangeAnalysisStage private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.FloatDelta private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.IntDelta private import RangeAnalysisImpl -private import codeql.util.Unit -private import Reason as Reason private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils module CppLangImplRelative implements LangSig { - private module Param implements Reason::ParamSig { - class TypeReasonImpl extends Unit { - TypeReasonImpl() { none() } - } - } - - class SemReason = Reason::Make::SemReason; - - class SemNoReason = Reason::Make::SemNoReason; - - class SemCondReason = Reason::Make::SemCondReason; - - class SemTypeReason = Reason::Make::SemTypeReason; - /** * Holds if the specified expression should be excluded from the result of `ssaRead()`. * diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisStage.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisStage.qll index a6cacd8b4a7..0bd665ed10c 100644 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisStage.qll +++ b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/RangeAnalysisStage.qll @@ -113,37 +113,6 @@ signature module DeltaSig { } signature module LangSig { - /** A reason for an inferred bound. */ - class SemReason { - /** - * Returns `this` if `reason` is not a `SemTypeReason`. Otherwise, - * this predicate returns `SemTypeReason`. - * - * This predicate ensures that we propagate `SemTypeReason` all the way - * to the top-level of a call to `semBounded` if the inferred bound is - * based on type-information. - */ - bindingset[this, reason] - SemReason combineWith(SemReason reason); - } - - /** - * A reason for an inferred bound that indicates that the bound is inferred - * without going through a bounding condition. - */ - class SemNoReason extends SemReason; - - /** A reason for an inferred bound pointing to a condition. */ - class SemCondReason extends SemReason { - SemGuard getCond(); - } - - /** - * A reason for an inferred bound that indicates that the bound is inferred - * based on type-information. - */ - class SemTypeReason extends SemReason; - /** * Holds if the specified expression should be excluded from the result of `ssaRead()`. * @@ -280,14 +249,6 @@ module RangeStage< DeltaSig D, BoundSig Bounds, OverflowSig OverflowParam, LangSig LangParam, UtilSig UtilParam> { - class SemReason = LangParam::SemReason; - - class SemCondReason = LangParam::SemCondReason; - - class SemNoReason = LangParam::SemNoReason; - - class SemTypeReason = LangParam::SemTypeReason; - private import Bounds private import LangParam private import UtilParam @@ -548,6 +509,36 @@ module RangeStage< ) } + private newtype TSemReason = + TSemNoReason() or + TSemCondReason(SemGuard guard) { possibleReason(guard) } + + /** + * A reason for an inferred bound. This can either be `CondReason` if the bound + * is due to a specific condition, or `NoReason` if the bound is inferred + * without going through a bounding condition. + */ + abstract class SemReason extends TSemReason { + /** Gets a textual representation of this reason. */ + abstract string toString(); + } + + /** + * A reason for an inferred bound that indicates that the bound is inferred + * without going through a bounding condition. + */ + class SemNoReason extends SemReason, TSemNoReason { + override string toString() { result = "NoReason" } + } + + /** A reason for an inferred bound pointing to a condition. */ + class SemCondReason extends SemReason, TSemCondReason { + /** Gets the condition that is the reason for the bound. */ + SemGuard getCond() { this = TSemCondReason(result) } + + override string toString() { result = this.getCond().toString() } + } + /** * Holds if `e + delta` is a valid bound for `v` at `pos`. * - `upper = true` : `v <= e + delta` @@ -560,13 +551,13 @@ module RangeStage< semSsaUpdateStep(v, e, delta) and pos.hasReadOfVar(v) and (upper = true or upper = false) and - reason instanceof SemNoReason + reason = TSemNoReason() or exists(SemGuard guard, boolean testIsTrue | pos.hasReadOfVar(v) and guard = boundFlowCond(v, e, delta, upper, testIsTrue) and semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue) and - reason.(SemCondReason).getCond() = guard + reason = TSemCondReason(guard) ) } @@ -579,20 +570,10 @@ module RangeStage< pos.hasReadOfVar(v) and guard = semEqFlowCond(v, e, delta, false, testIsTrue) and semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue) and - reason.(SemCondReason).getCond() = guard + reason = TSemCondReason(guard) ) } - /** Holds if `e >= 1` as determined by sign analysis. */ - private predicate strictlyPositiveIntegralExpr(SemExpr e) { - semStrictlyPositive(e) and getTrackedType(e) instanceof SemIntegerType - } - - /** Holds if `e <= -1` as determined by sign analysis. */ - private predicate strictlyNegativeIntegralExpr(SemExpr e) { - semStrictlyNegative(e) and getTrackedType(e) instanceof SemIntegerType - } - /** * Holds if `e1 + delta` is a valid bound for `e2`. * - `upper = true` : `e2 <= e1 + delta` @@ -606,27 +587,6 @@ module RangeStage< delta = D::fromInt(0) and (upper = true or upper = false) or - exists(SemExpr x, SemSubExpr sub | - e2 = sub and - sub.getLeftOperand() = e1 and - sub.getRightOperand() = x - | - // `x instanceof ConstantIntegerExpr` is covered by valueFlowStep - not x instanceof SemConstantIntegerExpr and - if strictlyPositiveIntegralExpr(x) - then upper = true and delta = D::fromInt(-1) - else - if semPositive(x) - then upper = true and delta = D::fromInt(0) - else - if strictlyNegativeIntegralExpr(x) - then upper = false and delta = D::fromInt(1) - else - if semNegative(x) - then upper = false and delta = D::fromInt(0) - else none() - ) - or e2.(SemRemExpr).getRightOperand() = e1 and semPositive(e1) and delta = D::fromInt(-1) and @@ -709,7 +669,7 @@ module RangeStage< // upper = true: v <= mid + d1 <= b + d1 + d2 = b + delta // upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta delta = D::fromFloat(D::toFloat(d1) + D::toFloat(d2)) and - (if r1 instanceof SemNoReason then reason = r2 else reason = r1.combineWith(r2)) + (if r1 instanceof SemNoReason then reason = r2 else reason = r1) ) or exists(D::Delta d, SemReason r1, SemReason r2 | @@ -723,9 +683,9 @@ module RangeStage< upper = false and delta = D::fromFloat(D::toFloat(d) + 1) ) and ( - reason = r1.combineWith(r2) + reason = r1 or - reason = r2.combineWith(r1) and not r2 instanceof SemNoReason + reason = r2 and not r2 instanceof SemNoReason ) ) } @@ -795,7 +755,7 @@ module RangeStage< (upper = true or upper = false) and fromBackEdge0 = false and origdelta = D::fromFloat(0) and - reason instanceof SemNoReason + reason = TSemNoReason() | if semBackEdge(phi, inp, edge) then @@ -1053,13 +1013,13 @@ module RangeStage< (upper = true or upper = false) and fromBackEdge = false and origdelta = delta and - reason instanceof SemNoReason + reason = TSemNoReason() or baseBound(e, delta, upper) and b instanceof SemZeroBound and fromBackEdge = false and origdelta = delta and - reason instanceof SemNoReason + reason = TSemNoReason() or exists(SemSsaVariable v, SemSsaReadPositionBlock bb | boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and @@ -1113,9 +1073,9 @@ module RangeStage< boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and ( - delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1.combineWith(r2) + delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 or - delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2.combineWith(r1) + delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2 ) | upper = true and delta = D::fromFloat(D::toFloat(d1).maximum(D::toFloat(d2))) @@ -1141,15 +1101,26 @@ module RangeStage< delta = D::fromFloat(D::toFloat(dLeft) + D::toFloat(dRight)) and fromBackEdge = fbeLeft.booleanOr(fbeRight) | - b = bLeft and - origdelta = odLeft and - reason = rLeft.combineWith(rRight) and - bRight instanceof SemZeroBound + b = bLeft and origdelta = odLeft and reason = rLeft and bRight instanceof SemZeroBound or - b = bRight and - origdelta = odRight and - reason = rRight.combineWith(rLeft) and - bLeft instanceof SemZeroBound + b = bRight and origdelta = odRight and reason = rRight and bLeft instanceof SemZeroBound + ) + or + exists(D::Delta dLeft, D::Delta dRight, boolean fbeLeft, boolean fbeRight | + boundedSubOperandLeft(e, upper, b, dLeft, fbeLeft, origdelta, reason) and + boundedSubOperandRight(e, upper, dRight, fbeRight) and + // when `upper` is `true` we have: + // left <= b + dLeft + // right >= 0 + dRight + // left - right <= b + dLeft - (0 + dRight) + // = b + (dLeft - dRight) + // and when `upper` is `false` we have: + // left >= b + dLeft + // right <= 0 + dRight + // left - right >= b + dLeft - (0 + dRight) + // = b + (dLeft - dRight) + delta = D::fromFloat(D::toFloat(dLeft) - D::toFloat(dRight)) and + fromBackEdge = fbeLeft.booleanOr(fbeRight) ) or exists( @@ -1165,9 +1136,9 @@ module RangeStage< ( if D::toFloat(d1).abs() > D::toFloat(d2).abs() then ( - d_max = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1.combineWith(r2) + d_max = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 ) else ( - d_max = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2.combineWith(r1) + d_max = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2 ) ) | @@ -1183,14 +1154,11 @@ module RangeStage< boundedMulOperand(e, upper, true, dLeft, fbeLeft, odLeft, rLeft) and boundedMulOperand(e, upper, false, dRight, fbeRight, odRight, rRight) and delta = D::fromFloat(D::toFloat(dLeft) * D::toFloat(dRight)) and - fromBackEdge = fbeLeft.booleanOr(fbeRight) and - b instanceof SemZeroBound + fromBackEdge = fbeLeft.booleanOr(fbeRight) | - origdelta = odLeft and - reason = rLeft.combineWith(rRight) + b instanceof SemZeroBound and origdelta = odLeft and reason = rLeft or - origdelta = odRight and - reason = rRight.combineWith(rLeft) + b instanceof SemZeroBound and origdelta = odRight and reason = rRight ) ) } @@ -1219,6 +1187,37 @@ module RangeStage< ) } + /** + * Holds if `sub = left - right` and `left <= b + delta` if `upper` is `true` + * and `left >= b + delta` is `upper` is `false`. + */ + pragma[nomagic] + private predicate boundedSubOperandLeft( + SemSubExpr sub, boolean upper, SemBound b, D::Delta delta, boolean fromBackEdge, + D::Delta origdelta, SemReason reason + ) { + // `semValueFlowStep` already handles the case where one of the operands is a constant. + not semValueFlowStep(sub, _, _) and + bounded(sub.getLeftOperand(), b, delta, upper, fromBackEdge, origdelta, reason) + } + + /** + * Holds if `sub = left - right` and `right <= 0 + delta` if `upper` is `false` + * and `right >= 0 + delta` is `upper` is `true`. + * + * Note that the boolean value of `upper` is flipped compared to many other predicates in + * this file. This ensures a clean join at the call-site. + */ + pragma[nomagic] + private predicate boundedSubOperandRight( + SemSubExpr sub, boolean upper, D::Delta delta, boolean fromBackEdge + ) { + // `semValueFlowStep` already handles the case where one of the operands is a constant. + not semValueFlowStep(sub, _, _) and + bounded(sub.getRightOperand(), any(SemZeroBound zb), delta, upper.booleanNot(), fromBackEdge, _, + _) + } + pragma[nomagic] private predicate boundedRemExpr( SemRemExpr rem, boolean upper, D::Delta delta, boolean fromBackEdge, D::Delta origdelta, diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/Reason.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/Reason.qll deleted file mode 100644 index 3911e835a1c..00000000000 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/analysis/Reason.qll +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Provides a `Make` parameterized module for constructing a `Reason` type that is used - * when implementing the `LangSig` module. - */ - -private import semmle.code.cpp.rangeanalysis.new.internal.semantic.Semantic - -/** The necessary parameters that must be implemented to instantiate `Make`. */ -signature module ParamSig { - class TypeReasonImpl; -} - -/** - * The module that constructs a `Reason` type when provided with an implementation - * of `ParamSig`. - */ -module Make { - private import Param - - private newtype TSemReason = - TSemNoReason() or - TSemCondReason(SemGuard guard) or - TSemTypeReason(TypeReasonImpl trc) - - /** - * A reason for an inferred bound. This can either be `CondReason` if the bound - * is due to a specific condition, or `NoReason` if the bound is inferred - * without going through a bounding condition. - */ - abstract class SemReason extends TSemReason { - /** Gets a textual representation of this reason. */ - abstract string toString(); - - bindingset[this, reason] - abstract SemReason combineWith(SemReason reason); - } - - /** - * A reason for an inferred bound that indicates that the bound is inferred - * without going through a bounding condition. - */ - class SemNoReason extends SemReason, TSemNoReason { - override string toString() { result = "NoReason" } - - override SemReason combineWith(SemReason reason) { result = reason } - } - - /** A reason for an inferred bound pointing to a condition. */ - class SemCondReason extends SemReason, TSemCondReason { - /** Gets the condition that is the reason for the bound. */ - SemGuard getCond() { this = TSemCondReason(result) } - - override string toString() { result = this.getCond().toString() } - - bindingset[this, reason] - override SemReason combineWith(SemReason reason) { - // Since we end up reporting a `SemReason` for the inferred bound we often pick somewhat - // arbitrarily between two `SemReason`s during the analysis. This isn't an issue for most reasons - // since they're mainly used for constructing alert messages. However, the `SemTypeReason` is - // supposed to be used in query logic to filter out bounds inferred by type-based analysis if - // the query author chooses to do so. So we need to ensure that if _any_ of the bounds that - // contribute to the final bound depends on type information then the `SemReason` we report must - // be a `SemTypeReason`. So when we need to combine this `SemCondReason` with a `SemTypeReason` - // the result should always be a `SemTypeReason`. - if reason instanceof SemTypeReason then result instanceof SemTypeReason else result = this - } - } - - /** - * A reason for an inferred bound that indicates that the bound is inferred - * based on type-information. - */ - class SemTypeReason extends SemReason, TSemTypeReason { - TypeReasonImpl impl; - - SemTypeReason() { this = TSemTypeReason(impl) } - - override string toString() { result = "TypeReason" } - - bindingset[this, reason] - override SemReason combineWith(SemReason reason) { result = this and exists(reason) } - } -} diff --git a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/AllocationToInvalidPointer.qll b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/AllocationToInvalidPointer.qll index 43120e1c7f0..a49f7322e6a 100644 --- a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/AllocationToInvalidPointer.qll +++ b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/AllocationToInvalidPointer.qll @@ -96,7 +96,7 @@ predicate hasSize(HeuristicAllocationExpr alloc, DataFlow::Node n, int state) { * but because there's a strict comparison that compares `n` against the size of the allocation this * snippet is fine. */ -module SizeBarrier { +private module SizeBarrier { private module SizeBarrierConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { // The sources is the same as in the sources for the second @@ -104,35 +104,60 @@ module SizeBarrier { hasSize(_, source, _) } + /** + * Holds if `small <= large + k` holds if `g` evaluates to `testIsTrue`. + */ additional predicate isSink( - DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int k, boolean testIsTrue + DataFlow::Node small, DataFlow::Node large, IRGuardCondition g, int k, boolean testIsTrue ) { - // The sink is any "large" side of a relational comparison. i.e., the `right` expression - // in a guard such as `left < right + k`. - g.comparesLt(left.asOperand(), right.asOperand(), k, true, testIsTrue) + // The sink is any "large" side of a relational comparison. i.e., the `large` expression + // in a guard such as `small <= large + k`. + g.comparesLt(small.asOperand(), large.asOperand(), k + 1, true, testIsTrue) } predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) } } - private import DataFlow::Global + module SizeBarrierFlow = DataFlow::Global; - private int getAFlowStateForNode(DataFlow::Node node) { + private int getASizeAddend(DataFlow::Node node) { exists(DataFlow::Node source | - flow(source, node) and + SizeBarrierFlow::flow(source, node) and hasSize(_, source, result) ) } + /** + * Holds if `small <= large + k` holds if `g` evaluates to `edge`. + */ private predicate operandGuardChecks( - IRGuardCondition g, Operand left, Operand right, int state, boolean edge + IRGuardCondition g, Operand small, DataFlow::Node large, int k, boolean edge ) { - exists(DataFlow::Node nLeft, DataFlow::Node nRight, int k | - nRight.asOperand() = right and - nLeft.asOperand() = left and - SizeBarrierConfig::isSink(nLeft, nRight, g, k, edge) and - state = getAFlowStateForNode(nRight) and - k <= state + SizeBarrierFlow::flowTo(large) and + SizeBarrierConfig::isSink(DataFlow::operandNode(small), large, g, k, edge) + } + + /** + * Gets an instruction `instr` that is guarded by a check such as `instr <= small + delta` where + * `small <= _ + k` and `small` is the "small side" of of a relational comparison that checks + * whether `small <= size` where `size` is the size of an allocation. + */ + Instruction getABarrierInstruction0(int delta, int k) { + exists( + IRGuardCondition g, ValueNumber value, Operand small, boolean edge, DataFlow::Node large + | + // We know: + // 1. result <= value + delta (by `bounded`) + // 2. value <= large + k (by `operandGuardChecks`). + // So: + // result <= value + delta (by 1.) + // <= large + k + delta (by 2.) + small = value.getAUse() and + operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](small), large, + pragma[only_bind_into](k), pragma[only_bind_into](edge)) and + bounded(result, value.getAnInstruction(), delta) and + g.controls(result.getBlock(), edge) and + k < getASizeAddend(large) ) } @@ -140,13 +165,14 @@ module SizeBarrier { * Gets an instruction that is guarded by a guard condition which ensures that * the value of the instruction is upper-bounded by size of some allocation. */ + bindingset[state] + pragma[inline_late] Instruction getABarrierInstruction(int state) { - exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge | - use = value.getAUse() and - operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, - pragma[only_bind_into](state), pragma[only_bind_into](edge)) and - result = value.getAnInstruction() and - g.controls(result.getBlock(), edge) + exists(int delta, int k | + state > k + delta and + // result <= "size of allocation" + delta + k + // < "size of allocation" + state + result = getABarrierInstruction0(delta, k) ) } @@ -155,14 +181,16 @@ module SizeBarrier { * the value of the node is upper-bounded by size of some allocation. */ DataFlow::Node getABarrierNode(int state) { - result.asOperand() = getABarrierInstruction(state).getAUse() + exists(DataFlow::Node source, int delta, int k | + SizeBarrierFlow::flow(source, result) and + hasSize(_, source, state) and + result.asInstruction() = SizeBarrier::getABarrierInstruction0(delta, k) and + state > k + delta + // so now we have: + // result <= "size of allocation" + delta + k + // < "size of allocation" + state + ) } - - /** - * Gets the block of a node that is guarded (see `getABarrierInstruction` or - * `getABarrierNode` for the definition of what it means to be guarded). - */ - IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) } } private module InterestingPointerAddInstruction { diff --git a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/InvalidPointerToDereference.qll b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/InvalidPointerToDereference.qll index d2c67289e4c..4a2175809e6 100644 --- a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/InvalidPointerToDereference.qll +++ b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/InvalidPointerToDereference.qll @@ -66,11 +66,14 @@ * module. Since the node we are tracking is not necessarily _equal_ to the pointer-arithmetic instruction, but rather satisfies * `node.asInstruction() <= pai + deltaDerefSourceAndPai`, we need to account for the delta when checking if a guard is sufficiently * strong to infer that a future dereference is safe. To do this, we check that the guard guarantees that a node `n` satisfies - * `n < node + k` where `node` is a node we know is equal to the value of the dereference source (i.e., it satisfies - * `node.asInstruction() <= pai + deltaDerefSourceAndPai`) and `k <= deltaDerefSourceAndPai`. Combining this we have - * `n < node + k <= node + deltaDerefSourceAndPai <= pai + 2*deltaDerefSourceAndPai` (TODO: Oops. This math doesn't quite work out. - * I think this is because we need to redefine the `BarrierConfig` to start flow at the pointer-arithmetic instruction instead of - * at the dereference source. When combined with TODO above it's easy to show that this guard ensures that the dereference is safe). + * `n < node + k` where `node` is a node such that `node <= pai`. Thus, we know that any node `m` such that `m <= n + delta` where + * `delta + k <= 0` will be safe because: + * ``` + * m <= n + delta + * < node + k + delta + * <= pai + k + delta + * <= pai + * ``` */ private import cpp @@ -82,16 +85,19 @@ private import RangeAnalysisUtil private module InvalidPointerToDerefBarrier { private module BarrierConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - // The sources is the same as in the sources for `InvalidPointerToDerefConfig`. - invalidPointerToDerefSource(_, _, source, _) + additional predicate isSource(DataFlow::Node source, PointerArithmeticInstruction pai) { + invalidPointerToDerefSource(_, pai, _, _) and + // source <= pai + bounded2(source.asInstruction(), pai, any(int d | d <= 0)) } + predicate isSource(DataFlow::Node source) { isSource(source, _) } + additional predicate isSink( - DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int k, boolean testIsTrue + DataFlow::Node small, DataFlow::Node large, IRGuardCondition g, int k, boolean testIsTrue ) { // The sink is any "large" side of a relational comparison. - g.comparesLt(left.asOperand(), right.asOperand(), k, true, testIsTrue) + g.comparesLt(small.asOperand(), large.asOperand(), k, true, testIsTrue) } predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) } @@ -99,59 +105,82 @@ private module InvalidPointerToDerefBarrier { private module BarrierFlow = DataFlow::Global; - private int getInvalidPointerToDerefSourceDelta(DataFlow::Node node) { - exists(DataFlow::Node source | - BarrierFlow::flow(source, node) and - invalidPointerToDerefSource(_, _, source, result) - ) - } - + /** + * Holds if `g` ensures that `small < large + k` if `g` evaluates to `edge`. + * + * Additionally, it also holds that `large <= pai`. Thus, when `g` evaluates to `edge` + * it holds that `small < pai + k`. + */ private predicate operandGuardChecks( - IRGuardCondition g, Operand left, Operand right, int state, boolean edge + PointerArithmeticInstruction pai, IRGuardCondition g, Operand small, int k, boolean edge ) { - exists(DataFlow::Node nLeft, DataFlow::Node nRight, int k | - nRight.asOperand() = right and - nLeft.asOperand() = left and - BarrierConfig::isSink(nLeft, nRight, g, k, edge) and - state = getInvalidPointerToDerefSourceDelta(nRight) and - k <= state + exists(DataFlow::Node source, DataFlow::Node nSmall, DataFlow::Node nLarge | + nSmall.asOperand() = small and + BarrierConfig::isSource(source, pai) and + BarrierFlow::flow(source, nLarge) and + BarrierConfig::isSink(nSmall, nLarge, g, k, edge) ) } - Instruction getABarrierInstruction(int state) { - exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge | + /** + * Gets an instruction `instr` such that `instr < pai`. + */ + Instruction getABarrierInstruction(PointerArithmeticInstruction pai) { + exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge, int delta, int k | use = value.getAUse() and - operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, state, - pragma[only_bind_into](edge)) and - result = value.getAnInstruction() and - g.controls(result.getBlock(), edge) + // value < pai + k + operandGuardChecks(pai, pragma[only_bind_into](g), pragma[only_bind_into](use), + pragma[only_bind_into](k), pragma[only_bind_into](edge)) and + // result <= value + delta + bounded(result, value.getAnInstruction(), delta) and + g.controls(result.getBlock(), edge) and + delta + k <= 0 + // combining the above we have: result < pai + k + delta <= pai ) } - DataFlow::Node getABarrierNode() { result.asOperand() = getABarrierInstruction(_).getAUse() } + DataFlow::Node getABarrierNode(PointerArithmeticInstruction pai) { + result.asOperand() = getABarrierInstruction(pai).getAUse() + } - pragma[nomagic] - IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) } + /** + * Gets an address operand whose definition `instr` satisfies `instr < pai`. + */ + AddressOperand getABarrierAddressOperand(PointerArithmeticInstruction pai) { + result.getDef() = getABarrierInstruction(pai) + } } /** * A configuration to track flow from a pointer-arithmetic operation found * by `AllocToInvalidPointerConfig` to a dereference of the pointer. */ -private module InvalidPointerToDerefConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { invalidPointerToDerefSource(_, _, source, _) } +private module InvalidPointerToDerefConfig implements DataFlow::StateConfigSig { + class FlowState extends PointerArithmeticInstruction { + FlowState() { invalidPointerToDerefSource(_, this, _, _) } + } + + predicate isSource(DataFlow::Node source, FlowState pai) { + invalidPointerToDerefSource(_, pai, source, _) + } pragma[inline] - predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _) } + predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _, _) } + + predicate isSink(DataFlow::Node sink, FlowState pai) { none() } predicate isBarrier(DataFlow::Node node) { node = any(DataFlow::SsaPhiNode phi | not phi.isPhiRead()).getAnInput(true) - or - node = InvalidPointerToDerefBarrier::getABarrierNode() + } + + predicate isBarrier(DataFlow::Node node, FlowState pai) { + // `node = getABarrierNode(pai)` ensures that node < pai, so this node is safe to dereference. + // Note that this is the only place where the `FlowState` is used in this configuration. + node = InvalidPointerToDerefBarrier::getABarrierNode(pai) } } -private import DataFlow::Global +private import DataFlow::GlobalWithState /** * Holds if `allocSource` is dataflow node that represents an allocation that flows to the @@ -165,19 +194,14 @@ private predicate invalidPointerToDerefSource( DataFlow::Node allocSource, PointerArithmeticInstruction pai, DataFlow::Node derefSource, int deltaDerefSourceAndPai ) { - exists(int rhsSizeDelta | - // Note that `deltaDerefSourceAndPai` is not necessarily equal to `rhsSizeDelta`: - // `rhsSizeDelta` is the constant offset added to the size of the allocation, and - // `deltaDerefSourceAndPai` is the constant difference between the pointer-arithmetic instruction - // and the instruction computing the address for which we will search for a dereference. - AllocToInvalidPointer::pointerAddInstructionHasBounds(allocSource, pai, _, rhsSizeDelta) and - bounded2(derefSource.asInstruction(), pai, deltaDerefSourceAndPai) and - deltaDerefSourceAndPai >= 0 and - // TODO: This condition will go away once #13725 is merged, and then we can make `SizeBarrier` - // private to `AllocationToInvalidPointer.qll`. - not derefSource.getBasicBlock() = - AllocToInvalidPointer::SizeBarrier::getABarrierBlock(rhsSizeDelta) - ) + // Note that `deltaDerefSourceAndPai` is not necessarily equal to `rhsSizeDelta`: + // `rhsSizeDelta` is the constant offset added to the size of the allocation, and + // `deltaDerefSourceAndPai` is the constant difference between the pointer-arithmetic instruction + // and the instruction computing the address for which we will search for a dereference. + AllocToInvalidPointer::pointerAddInstructionHasBounds(allocSource, pai, _, _) and + // derefSource <= pai + deltaDerefSourceAndPai + bounded2(derefSource.asInstruction(), pai, deltaDerefSourceAndPai) and + deltaDerefSourceAndPai >= 0 } /** @@ -187,15 +211,14 @@ private predicate invalidPointerToDerefSource( */ pragma[inline] private predicate isInvalidPointerDerefSink( - DataFlow::Node sink, Instruction i, string operation, int deltaDerefSinkAndDerefAddress + DataFlow::Node sink, AddressOperand addr, Instruction i, string operation, + int deltaDerefSinkAndDerefAddress ) { - exists(AddressOperand addr, Instruction s, IRBlock b | + exists(Instruction s | s = sink.asInstruction() and bounded(addr.getDef(), s, deltaDerefSinkAndDerefAddress) and deltaDerefSinkAndDerefAddress >= 0 and - i.getAnOperand() = addr and - b = i.getBlock() and - not b = InvalidPointerToDerefBarrier::getABarrierBlock(deltaDerefSinkAndDerefAddress) + i.getAnOperand() = addr | i instanceof StoreInstruction and operation = "write" @@ -221,9 +244,11 @@ private Instruction getASuccessor(Instruction instr) { instr.getBlock().getASuccessor+() = result.getBlock() } -private predicate paiForDereferenceSink(PointerArithmeticInstruction pai, DataFlow::Node derefSink) { +private predicate paiForDereferenceSink( + PointerArithmeticInstruction pai, DataFlow::Node derefSink, int deltaDerefSourceAndPai +) { exists(DataFlow::Node derefSource | - invalidPointerToDerefSource(_, pai, derefSource, _) and + invalidPointerToDerefSource(_, pai, derefSource, deltaDerefSourceAndPai) and flow(derefSource, derefSink) ) } @@ -235,13 +260,15 @@ private predicate paiForDereferenceSink(PointerArithmeticInstruction pai, DataFl */ private predicate derefSinkToOperation( DataFlow::Node derefSink, PointerArithmeticInstruction pai, DataFlow::Node operation, - string description, int deltaDerefSinkAndDerefAddress + string description, int deltaDerefSourceAndPai, int deltaDerefSinkAndDerefAddress ) { - exists(Instruction operationInstr | - paiForDereferenceSink(pai, pragma[only_bind_into](derefSink)) and - isInvalidPointerDerefSink(derefSink, operationInstr, description, deltaDerefSinkAndDerefAddress) and + exists(Instruction operationInstr, AddressOperand addr | + paiForDereferenceSink(pai, pragma[only_bind_into](derefSink), deltaDerefSourceAndPai) and + isInvalidPointerDerefSink(derefSink, addr, operationInstr, description, + deltaDerefSinkAndDerefAddress) and operationInstr = getASuccessor(derefSink.asInstruction()) and - operation.asInstruction() = operationInstr + operation.asInstruction() = operationInstr and + not addr = InvalidPointerToDerefBarrier::getABarrierAddressOperand(pai) ) } @@ -260,7 +287,8 @@ predicate operationIsOffBy( exists(int deltaDerefSourceAndPai, int deltaDerefSinkAndDerefAddress | invalidPointerToDerefSource(allocation, pai, derefSource, deltaDerefSourceAndPai) and flow(derefSource, derefSink) and - derefSinkToOperation(derefSink, pai, operation, description, deltaDerefSinkAndDerefAddress) and + derefSinkToOperation(derefSink, pai, operation, description, deltaDerefSourceAndPai, + deltaDerefSinkAndDerefAddress) and delta = deltaDerefSourceAndPai + deltaDerefSinkAndDerefAddress ) } diff --git a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/RangeAnalysisUtil.qll b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/RangeAnalysisUtil.qll index 12bb50321fa..0e86b68418a 100644 --- a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/RangeAnalysisUtil.qll +++ b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/RangeAnalysisUtil.qll @@ -18,7 +18,7 @@ private Instruction getABoundIn(SemBound b, IRFunction func) { * Holds if `i <= b + delta`. */ pragma[inline] -private predicate boundedImpl(Instruction i, Instruction b, int delta) { +private predicate boundedImplCand(Instruction i, Instruction b, int delta) { exists(SemBound bound, IRFunction func | semBounded(getSemanticExpr(i), bound, delta, true, _) and b = getABoundIn(bound, func) and @@ -26,6 +26,15 @@ private predicate boundedImpl(Instruction i, Instruction b, int delta) { ) } +/** + * Holds if `i <= b + delta` and `delta` is the smallest integer that satisfies + * this condition. + */ +pragma[inline] +private predicate boundedImpl(Instruction i, Instruction b, int delta) { + delta = min(int cand | boundedImplCand(i, b, cand)) +} + /** * Holds if `i <= b + delta`. * diff --git a/cpp/ql/src/CHANGELOG.md b/cpp/ql/src/CHANGELOG.md index 3527a0fc497..dd21d08e8c7 100644 --- a/cpp/ql/src/CHANGELOG.md +++ b/cpp/ql/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.2 + +No user-facing changes. + ## 0.7.1 ### Minor Analysis Improvements diff --git a/cpp/ql/src/Security/CWE/CWE-120/BadlyBoundedWrite.ql b/cpp/ql/src/Security/CWE/CWE-120/BadlyBoundedWrite.ql index 247606c683d..e7dd6a5d8e3 100644 --- a/cpp/ql/src/Security/CWE/CWE-120/BadlyBoundedWrite.ql +++ b/cpp/ql/src/Security/CWE/CWE-120/BadlyBoundedWrite.ql @@ -24,7 +24,7 @@ import semmle.code.cpp.security.BufferWrite from BufferWrite bw, int destSize where bw.hasExplicitLimit() and // has an explicit size limit - destSize = getBufferSize(bw.getDest(), _) and + destSize = max(getBufferSize(bw.getDest(), _)) and bw.getExplicitLimit() > destSize // but it's larger than the destination select bw, "This '" + bw.getBWDesc() + "' operation is limited to " + bw.getExplicitLimit() + diff --git a/cpp/ql/src/change-notes/2023-08-09-badly-bounded-write.md b/cpp/ql/src/change-notes/2023-08-09-badly-bounded-write.md new file mode 100644 index 00000000000..1dd4705754b --- /dev/null +++ b/cpp/ql/src/change-notes/2023-08-09-badly-bounded-write.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `cpp/badly-bounded-write` query could report false positives when a pointer was first initialized with a literal and later assigned a dynamically allocated array. These false positives now no longer occur. diff --git a/cpp/ql/src/change-notes/released/0.7.2.md b/cpp/ql/src/change-notes/released/0.7.2.md new file mode 100644 index 00000000000..8693d609ec7 --- /dev/null +++ b/cpp/ql/src/change-notes/released/0.7.2.md @@ -0,0 +1,3 @@ +## 0.7.2 + +No user-facing changes. diff --git a/cpp/ql/src/codeql-pack.release.yml b/cpp/ql/src/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/cpp/ql/src/codeql-pack.release.yml +++ b/cpp/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/cpp/ql/src/qlpack.yml b/cpp/ql/src/qlpack.yml index b2f91a3ac62..9c8eb64b9f5 100644 --- a/cpp/ql/src/qlpack.yml +++ b/cpp/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/cpp-queries -version: 0.7.2-dev +version: 0.7.3-dev groups: - cpp - queries diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected index f8b8bab0e4f..b5bbc68dbd4 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected @@ -69,12 +69,6 @@ edges | test.cpp:322:19:322:27 | ... + ... | test.cpp:325:24:325:26 | end | | test.cpp:324:23:324:26 | temp | test.cpp:324:23:324:32 | ... + ... | | test.cpp:324:23:324:32 | ... + ... | test.cpp:325:15:325:19 | temp2 | -| test.cpp:351:9:351:11 | arr | test.cpp:351:9:351:14 | access to array | -| test.cpp:351:9:351:11 | arr | test.cpp:351:18:351:25 | access to array | -| test.cpp:351:18:351:20 | arr | test.cpp:351:9:351:14 | access to array | -| test.cpp:351:18:351:20 | arr | test.cpp:351:18:351:25 | access to array | -| test.cpp:351:29:351:31 | arr | test.cpp:351:9:351:14 | access to array | -| test.cpp:351:29:351:31 | arr | test.cpp:351:18:351:25 | access to array | nodes | test.cpp:34:5:34:24 | access to array | semmle.label | access to array | | test.cpp:34:10:34:12 | buf | semmle.label | buf | @@ -167,11 +161,6 @@ nodes | test.cpp:325:15:325:19 | temp2 | semmle.label | temp2 | | test.cpp:325:24:325:26 | end | semmle.label | end | | test.cpp:325:24:325:26 | end | semmle.label | end | -| test.cpp:351:9:351:11 | arr | semmle.label | arr | -| test.cpp:351:9:351:14 | access to array | semmle.label | access to array | -| test.cpp:351:18:351:20 | arr | semmle.label | arr | -| test.cpp:351:18:351:25 | access to array | semmle.label | access to array | -| test.cpp:351:29:351:31 | arr | semmle.label | arr | subpaths #select | test.cpp:35:5:35:22 | PointerAdd: access to array | test.cpp:35:10:35:12 | buf | test.cpp:35:5:35:22 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:35:5:35:26 | Store: ... = ... | write | @@ -194,6 +183,3 @@ subpaths | test.cpp:322:19:322:27 | PointerAdd: ... + ... | test.cpp:322:19:322:22 | temp | test.cpp:325:24:325:26 | end | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:314:10:314:13 | temp | temp | test.cpp:330:13:330:24 | Store: ... = ... | write | | test.cpp:322:19:322:27 | PointerAdd: ... + ... | test.cpp:322:19:322:22 | temp | test.cpp:325:24:325:26 | end | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:314:10:314:13 | temp | temp | test.cpp:331:13:331:24 | Store: ... = ... | write | | test.cpp:322:19:322:27 | PointerAdd: ... + ... | test.cpp:322:19:322:22 | temp | test.cpp:325:24:325:26 | end | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:314:10:314:13 | temp | temp | test.cpp:333:13:333:24 | Store: ... = ... | write | -| test.cpp:351:18:351:25 | PointerAdd: access to array | test.cpp:351:9:351:11 | arr | test.cpp:351:18:351:25 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:348:9:348:11 | arr | arr | test.cpp:351:18:351:25 | Load: access to array | read | -| test.cpp:351:18:351:25 | PointerAdd: access to array | test.cpp:351:18:351:20 | arr | test.cpp:351:18:351:25 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:348:9:348:11 | arr | arr | test.cpp:351:18:351:25 | Load: access to array | read | -| test.cpp:351:18:351:25 | PointerAdd: access to array | test.cpp:351:29:351:31 | arr | test.cpp:351:18:351:25 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:348:9:348:11 | arr | arr | test.cpp:351:18:351:25 | Load: access to array | read | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp index 2d3945e48db..03de927073a 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp @@ -348,7 +348,7 @@ int positiveRange(int x) { int arr[128]; for(int i=127-offset; i>= 0; i--) { - arr[i] = arr[i+1] + arr[i+offset]; // GOOD [FALSE POSITIVE] + arr[i] = arr[i+1] + arr[i+offset]; // GOOD } return arr[0]; } diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected index 09ec397861a..8268a1beaf4 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected @@ -129,7 +129,6 @@ edges | test.cpp:271:14:271:21 | ... + ... | test.cpp:271:14:271:21 | ... + ... | | test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | ... = ... | | test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | ... = ... | -| test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | ... = ... | | test.cpp:355:14:355:27 | new[] | test.cpp:356:15:356:23 | ... + ... | | test.cpp:355:14:355:27 | new[] | test.cpp:356:15:356:23 | ... + ... | | test.cpp:355:14:355:27 | new[] | test.cpp:357:24:357:30 | ... + ... | @@ -214,20 +213,21 @@ edges | test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:19 | ... = ... | | test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:19 | ... = ... | | test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:19 | ... = ... | -| test.cpp:652:14:652:27 | new[] | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:652:14:652:27 | new[] | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:652:14:652:27 | new[] | test.cpp:662:3:662:11 | ... = ... | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | ... = ... | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | ... = ... | -| test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:23 | ... = ... | | test.cpp:695:13:695:26 | new[] | test.cpp:698:5:698:10 | ... += ... | +| test.cpp:695:13:695:26 | new[] | test.cpp:698:5:698:10 | ... += ... | +| test.cpp:698:5:698:10 | ... += ... | test.cpp:698:5:698:10 | ... += ... | | test.cpp:698:5:698:10 | ... += ... | test.cpp:701:15:701:16 | * ... | | test.cpp:705:18:705:18 | q | test.cpp:705:18:705:18 | q | | test.cpp:705:18:705:18 | q | test.cpp:706:12:706:13 | * ... | | test.cpp:705:18:705:18 | q | test.cpp:706:12:706:13 | * ... | | test.cpp:711:13:711:26 | new[] | test.cpp:714:11:714:11 | q | | test.cpp:714:11:714:11 | q | test.cpp:705:18:705:18 | q | +| test.cpp:730:12:730:28 | new[] | test.cpp:732:16:732:26 | ... + ... | +| test.cpp:730:12:730:28 | new[] | test.cpp:732:16:732:26 | ... + ... | +| test.cpp:730:12:730:28 | new[] | test.cpp:733:5:733:12 | ... = ... | +| test.cpp:732:16:732:26 | ... + ... | test.cpp:732:16:732:26 | ... + ... | +| test.cpp:732:16:732:26 | ... + ... | test.cpp:733:5:733:12 | ... = ... | +| test.cpp:732:16:732:26 | ... + ... | test.cpp:733:5:733:12 | ... = ... | nodes | test.cpp:4:15:4:20 | call to malloc | semmle.label | call to malloc | | test.cpp:5:15:5:22 | ... + ... | semmle.label | ... + ... | @@ -320,8 +320,6 @@ nodes | test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... | | test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... | | test.cpp:274:5:274:10 | ... = ... | semmle.label | ... = ... | -| test.cpp:304:15:304:26 | new[] | semmle.label | new[] | -| test.cpp:308:5:308:29 | ... = ... | semmle.label | ... = ... | | test.cpp:355:14:355:27 | new[] | semmle.label | new[] | | test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... | | test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... | @@ -371,20 +369,19 @@ nodes | test.cpp:559:5:559:19 | ... = ... | semmle.label | ... = ... | | test.cpp:642:14:642:31 | new[] | semmle.label | new[] | | test.cpp:647:5:647:19 | ... = ... | semmle.label | ... = ... | -| test.cpp:652:14:652:27 | new[] | semmle.label | new[] | -| test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ | -| test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ | -| test.cpp:662:3:662:11 | ... = ... | semmle.label | ... = ... | -| test.cpp:667:14:667:31 | new[] | semmle.label | new[] | -| test.cpp:675:7:675:23 | ... = ... | semmle.label | ... = ... | | test.cpp:695:13:695:26 | new[] | semmle.label | new[] | | test.cpp:698:5:698:10 | ... += ... | semmle.label | ... += ... | +| test.cpp:698:5:698:10 | ... += ... | semmle.label | ... += ... | | test.cpp:701:15:701:16 | * ... | semmle.label | * ... | | test.cpp:705:18:705:18 | q | semmle.label | q | | test.cpp:705:18:705:18 | q | semmle.label | q | | test.cpp:706:12:706:13 | * ... | semmle.label | * ... | | test.cpp:711:13:711:26 | new[] | semmle.label | new[] | | test.cpp:714:11:714:11 | q | semmle.label | q | +| test.cpp:730:12:730:28 | new[] | semmle.label | new[] | +| test.cpp:732:16:732:26 | ... + ... | semmle.label | ... + ... | +| test.cpp:732:16:732:26 | ... + ... | semmle.label | ... + ... | +| test.cpp:733:5:733:12 | ... = ... | semmle.label | ... = ... | subpaths #select | test.cpp:6:14:6:15 | * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | @@ -406,7 +403,6 @@ subpaths | test.cpp:254:9:254:16 | ... = ... | test.cpp:248:24:248:30 | call to realloc | test.cpp:254:9:254:16 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:248:24:248:30 | call to realloc | call to realloc | test.cpp:254:11:254:11 | i | i | | test.cpp:264:13:264:14 | * ... | test.cpp:260:13:260:24 | new[] | test.cpp:264:13:264:14 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:260:13:260:24 | new[] | new[] | test.cpp:261:19:261:21 | len | len | | test.cpp:274:5:274:10 | ... = ... | test.cpp:270:13:270:24 | new[] | test.cpp:274:5:274:10 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:270:13:270:24 | new[] | new[] | test.cpp:271:19:271:21 | len | len | -| test.cpp:308:5:308:29 | ... = ... | test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:304:15:304:26 | new[] | new[] | test.cpp:308:8:308:10 | ... + ... | ... + ... | | test.cpp:358:14:358:26 | * ... | test.cpp:355:14:355:27 | new[] | test.cpp:358:14:358:26 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size | | test.cpp:359:14:359:32 | * ... | test.cpp:355:14:355:27 | new[] | test.cpp:359:14:359:32 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 2. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size | | test.cpp:384:13:384:16 | * ... | test.cpp:377:14:377:27 | new[] | test.cpp:384:13:384:16 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:377:14:377:27 | new[] | new[] | test.cpp:378:20:378:23 | size | size | @@ -418,7 +414,6 @@ subpaths | test.cpp:548:5:548:19 | ... = ... | test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:543:14:543:27 | new[] | new[] | test.cpp:548:8:548:14 | src_pos | src_pos | | test.cpp:559:5:559:19 | ... = ... | test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:554:14:554:27 | new[] | new[] | test.cpp:559:8:559:14 | src_pos | src_pos | | test.cpp:647:5:647:19 | ... = ... | test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:642:14:642:31 | new[] | new[] | test.cpp:647:8:647:14 | src_pos | src_pos | -| test.cpp:662:3:662:11 | ... = ... | test.cpp:652:14:652:27 | new[] | test.cpp:662:3:662:11 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:652:14:652:27 | new[] | new[] | test.cpp:653:19:653:22 | size | size | -| test.cpp:675:7:675:23 | ... = ... | test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:23 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:667:14:667:31 | new[] | new[] | test.cpp:675:10:675:18 | ... ++ | ... ++ | | test.cpp:701:15:701:16 | * ... | test.cpp:695:13:695:26 | new[] | test.cpp:701:15:701:16 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:695:13:695:26 | new[] | new[] | test.cpp:696:19:696:22 | size | size | | test.cpp:706:12:706:13 | * ... | test.cpp:711:13:711:26 | new[] | test.cpp:706:12:706:13 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:711:13:711:26 | new[] | new[] | test.cpp:712:19:712:22 | size | size | +| test.cpp:733:5:733:12 | ... = ... | test.cpp:730:12:730:28 | new[] | test.cpp:733:5:733:12 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:730:12:730:28 | new[] | new[] | test.cpp:732:21:732:25 | ... + ... | ... + ... | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp index 439de8c1749..a11e6abc879 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp @@ -305,7 +305,7 @@ void test21() { for (int i = 0; i < n; i += 2) { xs[i] = test21_get(i); // GOOD - xs[i+1] = test21_get(i+1); // $ alloc=L304 alloc=L304-1 deref=L308 // GOOD [FALSE POSITIVE] + xs[i+1] = test21_get(i+1); // GOOD } } @@ -659,7 +659,7 @@ void test32(unsigned size) { xs++; if (xs >= end) return; - xs[0] = 0; // $ deref=L656->L662+1 deref=L657->L662+1 GOOD [FALSE POSITIVE] + xs[0] = 0; // GOOD } void test33(unsigned size, unsigned src_pos) @@ -672,7 +672,7 @@ void test33(unsigned size, unsigned src_pos) while (dst_pos < size - 1) { dst_pos++; if (true) - xs[dst_pos++] = 0; // $ alloc=L667+1 deref=L675 // GOOD [FALSE POSITIVE] + xs[dst_pos++] = 0; // GOOD } } @@ -714,3 +714,31 @@ void test35(unsigned long size, char* q) deref(q); } } + +void test21_simple(bool b) { + int n = 0; + if (b) n = 2; + + int* xs = new int[n]; + + for (int i = 0; i < n; i += 2) { + xs[i+1] = 0; // GOOD + } +} + +void test36(unsigned size, unsigned n) { + int* p = new int[size + 2]; + if(n < size + 1) { + int* end = p + (n + 2); // $ alloc=L730+2 + *end = 0; // $ deref=L733 // BAD + } +} + +void test37(unsigned long n) +{ + int *p = new int[n]; + for (unsigned long i = n; i != 0u; i--) + { + p[n - i] = 0; // GOOD + } +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp index c5004157af1..c49d9092cd7 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp @@ -732,7 +732,7 @@ void test_does_not_write_source_to_dereference() { int x; does_not_write_source_to_dereference(&x); - sink(x); // $ ast,ir=733:7 SPURIOUS: ast,ir=726:11 + sink(x); // $ ast=733:7 ir SPURIOUS: ast=726:11 } void sometimes_calls_sink_eq(int x, int n) { diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp index 9810418a95e..2df0fc85bb6 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp @@ -134,7 +134,7 @@ void pointer_test() { sink(*p3); // $ ast,ir *p3 = 0; - sink(*p3); // $ SPURIOUS: ast,ir + sink(*p3); // $ SPURIOUS: ast } // --- return values --- diff --git a/cpp/ql/test/library-tests/floats/float128/errors.expected b/cpp/ql/test/library-tests/floats/float128/errors.expected deleted file mode 100644 index 2f163e66cb0..00000000000 --- a/cpp/ql/test/library-tests/floats/float128/errors.expected +++ /dev/null @@ -1,3 +0,0 @@ -| file://:0:0:0:0 | There was an error during this compilation | -| float128.cpp:1:39:1:39 | 128-bit floating-point types are not supported in this configuration | -| float128.cpp:2:30:2:30 | 128-bit floating-point types are not supported in this configuration | diff --git a/cpp/ql/test/library-tests/floats/float128/errors.ql b/cpp/ql/test/library-tests/floats/float128/errors.ql deleted file mode 100644 index 3fa864748e1..00000000000 --- a/cpp/ql/test/library-tests/floats/float128/errors.ql +++ /dev/null @@ -1,4 +0,0 @@ -import cpp - -from Diagnostic d -select d diff --git a/cpp/ql/test/library-tests/floats/float128/float128.cpp b/cpp/ql/test/library-tests/floats/float128/float128.cpp index 7c57a0be72a..6f0cc78ca9e 100644 --- a/cpp/ql/test/library-tests/floats/float128/float128.cpp +++ b/cpp/ql/test/library-tests/floats/float128/float128.cpp @@ -1,5 +1,5 @@ -typedef _Complex float __attribute__((mode(TC))) _Complex128; // [COMPILER ERROR AND ERROR-TYPE DUE TO __float128 BEING DISABLED] -typedef float __attribute__((mode(TF))) _Float128; // [COMPILER ERROR AND ERROR-TYPE DUE TO __float128 BEING DISABLED] +typedef _Complex float __attribute__((mode(TC))) _Complex128; +typedef float __attribute__((mode(TF))) _Float128; int main() { __float128 f = 1.0f; @@ -25,4 +25,3 @@ __float128 id(__float128 q) { return q; } -// semmle-extractor-options: --expect_errors diff --git a/cpp/ql/test/library-tests/floats/float128/usertypes.expected b/cpp/ql/test/library-tests/floats/float128/usertypes.expected index 42b6612aff9..5db79125493 100644 --- a/cpp/ql/test/library-tests/floats/float128/usertypes.expected +++ b/cpp/ql/test/library-tests/floats/float128/usertypes.expected @@ -1,5 +1,5 @@ -| float128.cpp:1:50:1:60 | _Complex128 | file://:0:0:0:0 | | -| float128.cpp:2:41:2:49 | _Float128 | file://:0:0:0:0 | | +| float128.cpp:1:50:1:60 | _Complex128 | file://:0:0:0:0 | float __complex__ | +| float128.cpp:2:41:2:49 | _Float128 | file://:0:0:0:0 | __float128 | | float128.cpp:13:29:13:54 | __is_floating_point_helper | float128.cpp:10:8:10:17 | false_type | | float128.cpp:14:19:14:51 | __is_floating_point_helper | float128.cpp:11:8:11:16 | true_type | | float128.cpp:15:19:15:52 | __is_floating_point_helper | float128.cpp:11:8:11:16 | true_type | diff --git a/cpp/ql/test/library-tests/ir/range-analysis/test.cpp b/cpp/ql/test/library-tests/ir/range-analysis/test.cpp index 2271953b7ab..ff9acbfae1a 100644 --- a/cpp/ql/test/library-tests/ir/range-analysis/test.cpp +++ b/cpp/ql/test/library-tests/ir/range-analysis/test.cpp @@ -95,3 +95,25 @@ void gotoLoop(bool b1, bool b2) } } } + +void test_sub(int x, int y, int n) { + if(x > 0 && x < 500) { + if(y > 0 && y < 10) { + range(x - y); // $ range=<=498 range=>=-8 + } + + if(n > 0 && n < 100) { + for (int i = 0; i < n; i++) + { + range(n - i); // $ range=">=Phi: i-97" range=<=99 range=>=-97 + range(i - n); // $ range="<=Phi: i-1" range=">=Phi: i-99" range=<=97 range=>=-99 + } + + for (int i = n; i != 0; i--) + { + range(n - i); // $ SPURIOUS: overflow=+ + range(i - n); // $ range=">=Phi: i-99" + } + } + } +} diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/VeryLikelyOverrunWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/VeryLikelyOverrunWrite.expected index c20cf040504..022ae91391e 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/VeryLikelyOverrunWrite.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/VeryLikelyOverrunWrite.expected @@ -1,10 +1,10 @@ -| tests2.cpp:17:3:17:8 | call to wcscpy | This 'call to wcscpy' operation requires 12 bytes but the destination is only 8 bytes. | -| tests2.cpp:22:3:22:8 | call to wcscpy | This 'call to wcscpy' operation requires 16 bytes but the destination is only 12 bytes. | -| tests2.cpp:27:3:27:8 | call to wcscpy | This 'call to wcscpy' operation requires 20 bytes but the destination is only 16 bytes. | -| tests2.cpp:31:3:31:8 | call to wcscpy | This 'call to wcscpy' operation requires 24 bytes but the destination is only 20 bytes. | -| tests2.cpp:36:3:36:8 | call to wcscpy | This 'call to wcscpy' operation requires 28 bytes but the destination is only 24 bytes. | -| tests2.cpp:41:3:41:8 | call to wcscpy | This 'call to wcscpy' operation requires 32 bytes but the destination is only 28 bytes. | -| tests2.cpp:46:3:46:8 | call to wcscpy | This 'call to wcscpy' operation requires 36 bytes but the destination is only 32 bytes. | +| tests2.cpp:18:3:18:8 | call to wcscpy | This 'call to wcscpy' operation requires 12 bytes but the destination is only 8 bytes. | +| tests2.cpp:23:3:23:8 | call to wcscpy | This 'call to wcscpy' operation requires 16 bytes but the destination is only 12 bytes. | +| tests2.cpp:28:3:28:8 | call to wcscpy | This 'call to wcscpy' operation requires 20 bytes but the destination is only 16 bytes. | +| tests2.cpp:32:3:32:8 | call to wcscpy | This 'call to wcscpy' operation requires 24 bytes but the destination is only 20 bytes. | +| tests2.cpp:37:3:37:8 | call to wcscpy | This 'call to wcscpy' operation requires 28 bytes but the destination is only 24 bytes. | +| tests2.cpp:42:3:42:8 | call to wcscpy | This 'call to wcscpy' operation requires 32 bytes but the destination is only 28 bytes. | +| tests2.cpp:47:3:47:8 | call to wcscpy | This 'call to wcscpy' operation requires 36 bytes but the destination is only 32 bytes. | | tests.c:54:3:54:9 | call to sprintf | This 'call to sprintf' operation requires 11 bytes but the destination is only 10 bytes. | | tests.c:58:3:58:9 | call to sprintf | This 'call to sprintf' operation requires 11 bytes but the destination is only 10 bytes. | | tests.c:62:17:62:24 | buffer10 | This 'scanf string argument' operation requires 11 bytes but the destination is only 10 bytes. | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/tests2.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/tests2.cpp index 6081191917d..97699982800 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/tests2.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/tests2.cpp @@ -6,6 +6,7 @@ void *realloc(void *ptr, size_t size); void *calloc(size_t nmemb, size_t size); void free(void *ptr); wchar_t *wcscpy(wchar_t *s1, const wchar_t *s2); +int snprintf(char *s, size_t n, const char *format, ...); // --- Semmle tests --- @@ -46,3 +47,18 @@ void tests2() { wcscpy(buffer, L"12345678"); // BAD: buffer overflow delete [] buffer; } + +char* dest1 = "a"; +char* dest2 = "abcdefghijklmnopqrstuvwxyz"; + +void test3() { + const char src[] = "abcdefghijkl"; + dest1 = (char*)malloc(sizeof(src)); + if (!dest1) + return; + snprintf(dest1, sizeof(src), "%s", src); // GOOD + dest2 = (char*)malloc(3); + if (!dest2) + return; + snprintf(dest2, sizeof(src), "%s", src); // BAD [NOT DETECTED]: buffer overflow +} diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index 9590e132229..fb07ae02f60 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -725,7 +725,7 @@ namespace Semmle.Autobuild.CSharp.Tests [Fact] public void TestWindowsCmdIgnoreErrors() { - actions.RunProcess["cmd.exe /C ^\"build.cmd --skip-tests^\""] = 3; + actions.RunProcess["cmd.exe /C ^\"build.cmd^ --skip-tests^\""] = 3; actions.RunProcess[@"cmd.exe /C C:\codeql\tools\java\bin\java -jar C:\codeql\csharp\tools\extractor-asp.jar ."] = 0; actions.RunProcess[@"cmd.exe /C C:\codeql\tools\codeql index --xml --extensions config"] = 0; actions.FileExists["csharp.log"] = true; @@ -744,9 +744,9 @@ namespace Semmle.Autobuild.CSharp.Tests public void TestWindowCSharpMsBuild() { actions.RunProcess[@"cmd.exe /C C:\Project\.nuget\nuget.exe restore C:\Project\test1.sln -DisableParallelProcessing"] = 0; - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.nuget\nuget.exe restore C:\Project\test2.sln -DisableParallelProcessing"] = 0; - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test2.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test2.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false; actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false; @@ -775,9 +775,9 @@ namespace Semmle.Autobuild.CSharp.Tests public void TestWindowCSharpMsBuildMultipleSolutions() { actions.RunProcess[@"cmd.exe /C nuget restore C:\Project\test1.csproj -DisableParallelProcessing"] = 0; - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.csproj /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.csproj /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; actions.RunProcess[@"cmd.exe /C nuget restore C:\Project\test2.csproj -DisableParallelProcessing"] = 0; - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test2.csproj /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test2.csproj /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Project\test1.csproj"] = true; actions.FileExists[@"C:\Project\test2.csproj"] = true; @@ -820,7 +820,7 @@ namespace Semmle.Autobuild.CSharp.Tests public void TestWindowCSharpMsBuildFailed() { actions.RunProcess[@"cmd.exe /C nuget restore C:\Project\test1.sln -DisableParallelProcessing"] = 0; - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 1; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 1; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false; actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false; @@ -846,8 +846,8 @@ namespace Semmle.Autobuild.CSharp.Tests [Fact] public void TestSkipNugetMsBuild() { - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test2.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test1.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\test2.sln /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false; actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false; @@ -1037,7 +1037,7 @@ namespace Semmle.Autobuild.CSharp.Tests { actions.RunProcess[@"cmd.exe /C nuget restore C:\Project\dirs.proj -DisableParallelProcessing"] = 1; actions.RunProcess[@"cmd.exe /C C:\Project\.nuget\nuget.exe restore C:\Project\dirs.proj -DisableParallelProcessing"] = 0; - actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\dirs.proj /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; + actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio^ 12.0\\VC\\vcvarsall.bat^\" && set Platform=&& type NUL && msbuild C:\\Project\\dirs.proj /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /P:Fu=Bar"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Project\a\test.csproj"] = true; actions.FileExists[@"C:\Project\dirs.proj"] = true; diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/CommandBuilder.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/CommandBuilder.cs index 2064380772f..263f35f2fff 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/CommandBuilder.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/CommandBuilder.cs @@ -55,7 +55,7 @@ namespace Semmle.Autobuild.Shared } private static readonly char[] specialChars = { ' ', '\t', '\n', '\v', '\"' }; - private static readonly char[] cmdMetacharacter = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|' }; + private static readonly char[] cmdMetacharacter = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|', ' ' }; /// /// Appends the given argument to the command line. diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs index f4aa614a345..f9dc5a45f62 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs @@ -8,14 +8,13 @@ using System.Threading.Tasks; using System.Collections.Concurrent; using System.Text; using System.Security.Cryptography; -using System.Text.RegularExpressions; namespace Semmle.BuildAnalyser { /// /// Main implementation of the build analysis. /// - internal sealed partial class BuildAnalysis : IDisposable + internal sealed class BuildAnalysis : IDisposable { private readonly AssemblyCache assemblyCache; private readonly ProgressMonitor progressMonitor; @@ -29,6 +28,9 @@ namespace Semmle.BuildAnalyser private readonly Options options; private readonly DirectoryInfo sourceDir; private readonly DotNet dotnet; + private readonly FileContent fileContent; + private readonly TemporaryDirectory packageDirectory; + /// /// Performs a C# build analysis. @@ -55,6 +57,9 @@ namespace Semmle.BuildAnalyser this.progressMonitor.FindingFiles(options.SrcDir); + packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName)); + + this.fileContent = new FileContent(packageDirectory, progressMonitor, () => GetFiles("*.*")); this.allSources = GetFiles("*.cs").ToArray(); var allProjects = GetFiles("*.csproj"); var solutions = options.SolutionFile is not null @@ -63,12 +68,19 @@ namespace Semmle.BuildAnalyser var dllDirNames = options.DllDirs.Select(Path.GetFullPath).ToList(); - // Find DLLs in the .Net Framework + // Find DLLs in the .Net / Asp.Net Framework if (options.ScanNetFrameworkDlls) { - var runtimeLocation = new Runtime(dotnet).GetRuntime(options.UseSelfContainedDotnet); - progressMonitor.Log(Util.Logging.Severity.Debug, $"Runtime location selected: {runtimeLocation}"); + var runtime = new Runtime(dotnet); + var runtimeLocation = runtime.GetRuntime(options.UseSelfContainedDotnet); + progressMonitor.LogInfo($".NET runtime location selected: {runtimeLocation}"); dllDirNames.Add(runtimeLocation); + + if (fileContent.UseAspNetDlls && runtime.GetAspRuntime() is string aspRuntime) + { + progressMonitor.LogInfo($"ASP.NET runtime location selected: {aspRuntime}"); + dllDirNames.Add(aspRuntime); + } } if (options.UseMscorlib) @@ -76,8 +88,6 @@ namespace Semmle.BuildAnalyser UseReference(typeof(object).Assembly.Location); } - packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName)); - if (options.UseNuGet) { dllDirNames.Add(packageDirectory.DirInfo.FullName); @@ -187,6 +197,7 @@ namespace Semmle.BuildAnalyser { finalAssemblyList[r.Name] = r; } + // Update the used references list usedReferences.Clear(); foreach (var r in finalAssemblyList.Select(r => r.Value.Filename)) @@ -210,24 +221,18 @@ namespace Semmle.BuildAnalyser /// Store that a particular reference file is used. /// /// The filename of the reference. - private void UseReference(string reference) - { - usedReferences[reference] = true; - } + private void UseReference(string reference) => usedReferences[reference] = true; /// /// Store that a particular source file is used (by a project file). /// /// The source file. - private void UseSource(FileInfo sourceFile) - { - sources[sourceFile.FullName] = sourceFile.Exists; - } + private void UseSource(FileInfo sourceFile) => sources[sourceFile.FullName] = sourceFile.Exists; /// /// The list of resolved reference files. /// - public IEnumerable ReferenceFiles => this.usedReferences.Keys; + public IEnumerable ReferenceFiles => usedReferences.Keys; /// /// The list of source files used in projects. @@ -242,7 +247,7 @@ namespace Semmle.BuildAnalyser /// /// List of assembly IDs which couldn't be resolved. /// - public IEnumerable UnresolvedReferences => this.unresolvedReferences.Select(r => r.Key); + public IEnumerable UnresolvedReferences => unresolvedReferences.Select(r => r.Key); /// /// List of source files which were mentioned in project files but @@ -256,12 +261,7 @@ namespace Semmle.BuildAnalyser /// /// The assembly ID. /// The project file making the reference. - private void UnresolvedReference(string id, string projectFile) - { - unresolvedReferences[id] = projectFile; - } - - private readonly TemporaryDirectory packageDirectory; + private void UnresolvedReference(string id, string projectFile) => unresolvedReferences[id] = projectFile; /// /// Reads all the source files and references from the given list of projects. @@ -318,10 +318,8 @@ namespace Semmle.BuildAnalyser } - private bool Restore(string target, string? pathToNugetConfig = null) - { - return dotnet.RestoreToDirectory(target, packageDirectory.DirInfo.FullName, pathToNugetConfig); - } + private bool Restore(string target, string? pathToNugetConfig = null) => + dotnet.RestoreToDirectory(target, packageDirectory.DirInfo.FullName, pathToNugetConfig); private void Restore(IEnumerable targets, string? pathToNugetConfig = null) { @@ -331,11 +329,9 @@ namespace Semmle.BuildAnalyser } } + private void DownloadMissingPackages(IEnumerable restoreTargets) { - var alreadyDownloadedPackages = Directory.GetDirectories(packageDirectory.DirInfo.FullName).Select(d => Path.GetFileName(d).ToLowerInvariant()).ToHashSet(); - var notYetDownloadedPackages = new HashSet(); - var nugetConfigs = GetFiles("nuget.config", recurseSubdirectories: true).ToArray(); string? nugetConfig = null; if (nugetConfigs.Length > 1) @@ -352,46 +348,7 @@ namespace Semmle.BuildAnalyser nugetConfig = nugetConfigs.FirstOrDefault(); } - var allFiles = GetFiles("*.*"); - foreach (var file in allFiles) - { - try - { - using var sr = new StreamReader(file); - ReadOnlySpan line; - while ((line = sr.ReadLine()) != null) - { - foreach (var valueMatch in PackageReference().EnumerateMatches(line)) - { - // We can't get the group from the ValueMatch, so doing it manually: - var match = line.Slice(valueMatch.Index, valueMatch.Length); - var includeIndex = match.IndexOf("Include", StringComparison.InvariantCultureIgnoreCase); - if (includeIndex == -1) - { - continue; - } - - match = match.Slice(includeIndex + "Include".Length + 1); - - var quoteIndex1 = match.IndexOf("\""); - var quoteIndex2 = match.Slice(quoteIndex1 + 1).IndexOf("\""); - - var packageName = match.Slice(quoteIndex1 + 1, quoteIndex2).ToString().ToLowerInvariant(); - if (!alreadyDownloadedPackages.Contains(packageName)) - { - notYetDownloadedPackages.Add(packageName); - } - } - } - } - catch (Exception ex) - { - progressMonitor.FailedToReadFile(file, ex); - continue; - } - } - - foreach (var package in notYetDownloadedPackages) + foreach (var package in fileContent.NotYetDownloadedPackages) { progressMonitor.NugetInstall(package); using var tempDir = new TemporaryDirectory(ComputeTempDirectory(package)); @@ -434,12 +391,6 @@ namespace Semmle.BuildAnalyser }); } - public void Dispose() - { - packageDirectory?.Dispose(); - } - - [GeneratedRegex("", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)] - private static partial Regex PackageReference(); + public void Dispose() => packageDirectory?.Dispose(); } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs index b9e641a9de1..e4346db2e1b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs @@ -10,7 +10,7 @@ namespace Semmle.BuildAnalyser bool RestoreToDirectory(string project, string directory, string? pathToNugetConfig = null); bool New(string folder); bool AddPackage(string folder, string package); - public IList GetListedRuntimes(); + IList GetListedRuntimes(); } /// @@ -78,7 +78,8 @@ namespace Semmle.BuildAnalyser public IList GetListedRuntimes() { - var args = "--list-runtimes"; + const string args = "--list-runtimes"; + progressMonitor.RunningProcess($"{dotnet} {args}"); var pi = new ProcessStartInfo(dotnet, args) { RedirectStandardOutput = true, @@ -90,6 +91,7 @@ namespace Semmle.BuildAnalyser progressMonitor.CommandFailed(dotnet, args, exitCode); return new List(); } + progressMonitor.LogInfo($"Found runtimes: {string.Join("\n", runtimes)}"); return runtimes; } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/FileContent.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/FileContent.cs new file mode 100644 index 00000000000..bb181c4ac08 --- /dev/null +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/FileContent.cs @@ -0,0 +1,166 @@ +using Semmle.Util; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Semmle.BuildAnalyser +{ + + // + // This class is used to read a set of files and decide different properties about the + // content (by reading the content of the files only once). + // The implementation is lazy, so the properties are only calculated when + // the first property is accessed. + // + internal partial class FileContent + { + private readonly ProgressMonitor progressMonitor; + private readonly IUnsafeFileReader unsafeFileReader; + private readonly Func> getFiles; + private readonly Func> getAlreadyDownloadedPackages; + private readonly HashSet notYetDownloadedPackages = new HashSet(); + private readonly Initializer initialize; + + public HashSet NotYetDownloadedPackages + { + get + { + initialize.Run(); + return notYetDownloadedPackages; + } + } + + private bool useAspNetDlls = false; + + /// + /// True if any file in the source directory indicates that ASP.NET is used. + /// The following heuristic is used to decide, if ASP.NET is used: + /// If any file in the source directory contains something like (this will most like be a .csproj file) + /// + /// + /// + public bool UseAspNetDlls + { + get + { + initialize.Run(); + return useAspNetDlls; + } + } + + internal FileContent(Func> getAlreadyDownloadedPackages, + ProgressMonitor progressMonitor, + Func> getFiles, + IUnsafeFileReader unsafeFileReader) + { + this.getAlreadyDownloadedPackages = getAlreadyDownloadedPackages; + this.progressMonitor = progressMonitor; + this.getFiles = getFiles; + this.unsafeFileReader = unsafeFileReader; + this.initialize = new Initializer(DoInitialize); + } + + + public FileContent(TemporaryDirectory packageDirectory, ProgressMonitor progressMonitor, Func> getFiles) : this(() => Directory.GetDirectories(packageDirectory.DirInfo.FullName) + .Select(d => Path.GetFileName(d) + .ToLowerInvariant()) + .ToHashSet(), progressMonitor, getFiles, new UnsafeFileReader()) + { } + + private static string GetGroup(ReadOnlySpan input, ValueMatch valueMatch, string groupPrefix) + { + var match = input.Slice(valueMatch.Index, valueMatch.Length); + var includeIndex = match.IndexOf(groupPrefix, StringComparison.InvariantCultureIgnoreCase); + if (includeIndex == -1) + { + return string.Empty; + } + + match = match.Slice(includeIndex + groupPrefix.Length + 1); + + var quoteIndex1 = match.IndexOf("\""); + var quoteIndex2 = match.Slice(quoteIndex1 + 1).IndexOf("\""); + + return match.Slice(quoteIndex1 + 1, quoteIndex2).ToString().ToLowerInvariant(); + } + + private static bool IsGroupMatch(ReadOnlySpan line, Regex regex, string groupPrefix, string value) + { + foreach (var valueMatch in regex.EnumerateMatches(line)) + { + // We can't get the group from the ValueMatch, so doing it manually: + if (GetGroup(line, valueMatch, groupPrefix) == value.ToLowerInvariant()) + { + return true; + } + } + return false; + } + + private void DoInitialize() + { + var alreadyDownloadedPackages = getAlreadyDownloadedPackages(); + foreach (var file in getFiles()) + { + try + { + foreach (ReadOnlySpan line in unsafeFileReader.ReadLines(file)) + { + + // Find the not yet downloaded packages. + foreach (var valueMatch in PackageReference().EnumerateMatches(line)) + { + // We can't get the group from the ValueMatch, so doing it manually: + var packageName = GetGroup(line, valueMatch, "Include"); + if (!string.IsNullOrEmpty(packageName) && !alreadyDownloadedPackages.Contains(packageName)) + { + notYetDownloadedPackages.Add(packageName); + } + } + + // Determine if ASP.NET is used. + if (!useAspNetDlls) + { + useAspNetDlls = + IsGroupMatch(line, ProjectSdk(), "Sdk", "Microsoft.NET.Sdk.Web") || + IsGroupMatch(line, FrameworkReference(), "Include", "Microsoft.AspNetCore.App"); + } + } + } + catch (Exception ex) + { + progressMonitor.FailedToReadFile(file, ex); + } + } + } + + [GeneratedRegex("", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)] + private static partial Regex PackageReference(); + + [GeneratedRegex("", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)] + private static partial Regex FrameworkReference(); + + [GeneratedRegex("<(.*\\s)?Project.*\\sSdk=\"(.*?)\".*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)] + private static partial Regex ProjectSdk(); + } +} + +internal interface IUnsafeFileReader +{ + IEnumerable ReadLines(string file); +} + +internal class UnsafeFileReader : IUnsafeFileReader +{ + public IEnumerable ReadLines(string file) + { + using var sr = new StreamReader(file); + string? line; + while ((line = sr.ReadLine()) != null) + { + yield return line; + } + } +} diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs index 233ba969fea..9ec75ea7cd4 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs @@ -12,121 +12,101 @@ namespace Semmle.BuildAnalyser this.logger = logger; } - public void FindingFiles(string dir) - { - logger.Log(Severity.Info, "Finding files in {0}...", dir); - } + public void Log(Severity severity, string message) => + logger.Log(severity, message); + + public void LogInfo(string message) => + logger.Log(Severity.Info, message); + + private void LogDebug(string message) => + logger.Log(Severity.Debug, message); + + private void LogError(string message) => + logger.Log(Severity.Error, message); + + public void FindingFiles(string dir) => + LogInfo($"Finding files in {dir}..."); public void IndexingReferences(int count) { - logger.Log(Severity.Info, "Indexing..."); - logger.Log(Severity.Debug, "Indexing {0} DLLs...", count); + LogInfo("Indexing..."); + LogDebug($"Indexing {count} DLLs..."); } public void UnresolvedReference(string id, string project) { - logger.Log(Severity.Info, "Unresolved reference {0}", id); - logger.Log(Severity.Debug, "Unresolved {0} referenced by {1}", id, project); + LogInfo($"Unresolved reference {id}"); + LogDebug($"Unresolved {id} referenced by {project}"); } - public void AnalysingSolution(string filename) - { - logger.Log(Severity.Info, $"Analyzing {filename}..."); - } + public void AnalysingSolution(string filename) => + LogInfo($"Analyzing {filename}..."); - public void FailedProjectFile(string filename, string reason) - { - logger.Log(Severity.Info, "Couldn't read project file {0}: {1}", filename, reason); - } + public void FailedProjectFile(string filename, string reason) => + LogInfo($"Couldn't read project file {filename}: {reason}"); public void FailedNugetCommand(string exe, string args, string message) { - logger.Log(Severity.Info, "Command failed: {0} {1}", exe, args); - logger.Log(Severity.Info, " {0}", message); + LogInfo($"Command failed: {exe} {args}"); + LogInfo($" {message}"); } - public void NugetInstall(string package) - { - logger.Log(Severity.Info, "Restoring {0}...", package); - } + public void NugetInstall(string package) => + LogInfo($"Restoring {package}..."); - public void ResolvedReference(string filename) - { - logger.Log(Severity.Info, "Resolved {0}", filename); - } + public void ResolvedReference(string filename) => + LogInfo($"Resolved {filename}"); public void Summary(int existingSources, int usedSources, int missingSources, int references, int unresolvedReferences, int resolvedConflicts, int totalProjects, int failedProjects, TimeSpan analysisTime) { - logger.Log(Severity.Info, ""); - logger.Log(Severity.Info, "Build analysis summary:"); - logger.Log(Severity.Info, "{0, 6} source files in the filesystem", existingSources); - logger.Log(Severity.Info, "{0, 6} source files in project files", usedSources); - logger.Log(Severity.Info, "{0, 6} sources missing from project files", missingSources); - logger.Log(Severity.Info, "{0, 6} resolved references", references); - logger.Log(Severity.Info, "{0, 6} unresolved references", unresolvedReferences); - logger.Log(Severity.Info, "{0, 6} resolved assembly conflicts", resolvedConflicts); - logger.Log(Severity.Info, "{0, 6} projects", totalProjects); - logger.Log(Severity.Info, "{0, 6} missing/failed projects", failedProjects); - logger.Log(Severity.Info, "Build analysis completed in {0}", analysisTime); + const int align = 6; + LogInfo(""); + LogInfo("Build analysis summary:"); + LogInfo($"{existingSources,align} source files in the filesystem"); + LogInfo($"{usedSources,align} source files in project files"); + LogInfo($"{missingSources,align} sources missing from project files"); + LogInfo($"{references,align} resolved references"); + LogInfo($"{unresolvedReferences,align} unresolved references"); + LogInfo($"{resolvedConflicts,align} resolved assembly conflicts"); + LogInfo($"{totalProjects,align} projects"); + LogInfo($"{failedProjects,align} missing/failed projects"); + LogInfo($"Build analysis completed in {analysisTime}"); } - public void Log(Severity severity, string message) - { - logger.Log(severity, message); - } + public void ResolvedConflict(string asm1, string asm2) => + LogDebug($"Resolved {asm1} as {asm2}"); - public void ResolvedConflict(string asm1, string asm2) - { - logger.Log(Severity.Debug, "Resolved {0} as {1}", asm1, asm2); - } + public void MissingProject(string projectFile) => + LogInfo($"Solution is missing {projectFile}"); - public void MissingProject(string projectFile) - { - logger.Log(Severity.Info, "Solution is missing {0}", projectFile); - } + public void CommandFailed(string exe, string arguments, int exitCode) => + LogError($"Command {exe} {arguments} failed with exit code {exitCode}"); - public void CommandFailed(string exe, string arguments, int exitCode) - { - logger.Log(Severity.Error, $"Command {exe} {arguments} failed with exit code {exitCode}"); - } + public void MissingNuGet() => + LogError("Missing nuget.exe"); - public void MissingNuGet() - { - logger.Log(Severity.Error, "Missing nuget.exe"); - } + public void MissingDotNet() => + LogError("Missing dotnet CLI"); - public void MissingDotNet() - { - logger.Log(Severity.Error, "Missing dotnet CLI"); - } + public void RunningProcess(string command) => + LogInfo($"Running {command}"); - public void RunningProcess(string command) - { - logger.Log(Severity.Info, $"Running {command}"); - } - - public void FailedToRestoreNugetPackage(string package) - { - logger.Log(Severity.Info, $"Failed to restore nuget package {package}"); - } + public void FailedToRestoreNugetPackage(string package) => + LogInfo($"Failed to restore nuget package {package}"); public void FailedToReadFile(string file, Exception ex) { - logger.Log(Severity.Info, $"Failed to read file {file}"); - logger.Log(Severity.Debug, $"Failed to read file {file}, exception: {ex}"); + LogInfo($"Failed to read file {file}"); + LogDebug($"Failed to read file {file}, exception: {ex}"); } - public void MultipleNugetConfig(string[] nugetConfigs) - { - logger.Log(Severity.Info, $"Found multiple nuget.config files: {string.Join(", ", nugetConfigs)}."); - } + public void MultipleNugetConfig(string[] nugetConfigs) => + LogInfo($"Found multiple nuget.config files: {string.Join(", ", nugetConfigs)}."); - internal void NoTopLevelNugetConfig() - { - logger.Log(Severity.Info, $"Could not find a top-level nuget.config file."); - } + internal void NoTopLevelNugetConfig() => + LogInfo("Could not find a top-level nuget.config file."); } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs index bb521523ead..5ea4172c93e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs @@ -18,11 +18,17 @@ namespace Semmle.Extraction.CSharp.Standalone private const string aspNetCoreApp = "Microsoft.AspNetCore.App"; private readonly IDotNet dotNet; + private readonly Lazy> newestRuntimes; + private Dictionary NewestRuntimes => newestRuntimes.Value; private static string ExecutingRuntime => RuntimeEnvironment.GetRuntimeDirectory(); - public Runtime(IDotNet dotNet) => this.dotNet = dotNet; + public Runtime(IDotNet dotNet) + { + this.dotNet = dotNet; + this.newestRuntimes = new(GetNewestRuntimes); + } - internal sealed class RuntimeVersion : IComparable + internal record RuntimeVersion : IComparable { private readonly string dir; private readonly Version version; @@ -71,15 +77,10 @@ namespace Semmle.Extraction.CSharp.Standalone return c; } - public override bool Equals(object? obj) => - obj is not null && obj is RuntimeVersion other && other.FullPath == FullPath; - - public override int GetHashCode() => FullPath.GetHashCode(); - public override string ToString() => FullPath; } - [GeneratedRegex(@"^(\S+)\s(\d+\.\d+\.\d+)(-([a-z]+)\.(\d+\.\d+\.\d+))?\s\[(\S+)\]$")] + [GeneratedRegex(@"^(\S+)\s(\d+\.\d+\.\d+)(-([a-z]+)\.(\d+\.\d+\.\d+))?\s\[(.+)\]$")] private static partial Regex RuntimeRegex(); /// @@ -97,7 +98,7 @@ namespace Semmle.Extraction.CSharp.Standalone var match = RuntimeRegex().Match(r); if (match.Success) { - runtimes.AddOrUpdate(match.Groups[1].Value, new RuntimeVersion(match.Groups[6].Value, match.Groups[2].Value, match.Groups[4].Value, match.Groups[5].Value)); + runtimes.AddOrUpdateToLatest(match.Groups[1].Value, new RuntimeVersion(match.Groups[6].Value, match.Groups[2].Value, match.Groups[4].Value, match.Groups[5].Value)); } }); @@ -145,33 +146,42 @@ namespace Semmle.Extraction.CSharp.Standalone } } - private IEnumerable GetRuntimes() + /// + /// Gets the .NET runtime location to use for extraction. + /// + public string GetRuntime(bool useSelfContained) { - // Gets the newest version of the installed runtimes. - var newestRuntimes = GetNewestRuntimes(); + if (useSelfContained) + { + return ExecutingRuntime; + } // Location of the newest .NET Core Runtime. - if (newestRuntimes.TryGetValue(netCoreApp, out var netCoreVersion)) + if (NewestRuntimes.TryGetValue(netCoreApp, out var netCoreVersion)) { - yield return netCoreVersion.FullPath; + return netCoreVersion.FullPath; } - // Location of the newest ASP.NET Core Runtime. - if (newestRuntimes.TryGetValue(aspNetCoreApp, out var aspNetCoreVersion)) + if (DesktopRuntimes.Any()) { - yield return aspNetCoreVersion.FullPath; + return DesktopRuntimes.First(); } - foreach (var r in DesktopRuntimes) - yield return r; - // A bad choice if it's the self-contained runtime distributed in codeql dist. - yield return ExecutingRuntime; + return ExecutingRuntime; } /// - /// Gets the .NET runtime location to use for extraction + /// Gets the ASP.NET runtime location to use for extraction, if one exists. /// - public string GetRuntime(bool useSelfContained) => useSelfContained ? ExecutingRuntime : GetRuntimes().First(); + public string? GetAspRuntime() + { + // Location of the newest ASP.NET Core Runtime. + if (NewestRuntimes.TryGetValue(aspNetCoreApp, out var aspNetCoreVersion)) + { + return aspNetCoreVersion.FullPath; + } + return null; + } } } diff --git a/csharp/extractor/Semmle.Extraction.Tests/FileContent.cs b/csharp/extractor/Semmle.Extraction.Tests/FileContent.cs new file mode 100644 index 00000000000..8567fcd83cb --- /dev/null +++ b/csharp/extractor/Semmle.Extraction.Tests/FileContent.cs @@ -0,0 +1,95 @@ +using Xunit; +using Semmle.BuildAnalyser; +using Semmle.Util.Logging; +using System.Collections.Generic; + +namespace Semmle.Extraction.Tests +{ + + internal class LoggerStub : ILogger + { + public void Log(Severity severity, string message) { } + + public void Dispose() { } + } + + internal class UnsafeFileReaderStub : IUnsafeFileReader + { + private readonly List lines; + + public UnsafeFileReaderStub(List lines) + { + this.lines = lines; + } + + public IEnumerable ReadLines(string file) + { + foreach (var line in lines) + { + yield return line; + } + } + } + + internal class TestFileContent : FileContent + { + public TestFileContent(List lines) : base(() => new HashSet(), + new ProgressMonitor(new LoggerStub()), + () => new List() { "test1.cs" }, + new UnsafeFileReaderStub(lines)) + { } + } + + public class FileContentTests + { + [Fact] + public void TestFileContent1() + { + // Setup + var lines = new List() + { + "", + "", + "", + "", + "" + }; + var fileContent = new TestFileContent(lines); + + // Execute + var notYetDownloadedPackages = fileContent.NotYetDownloadedPackages; + var useAspNetDlls = fileContent.UseAspNetDlls; + + // Verify + Assert.False(useAspNetDlls); + Assert.Equal(3, notYetDownloadedPackages.Count); + Assert.Contains("DotNetAnalyzers.DocumentationAnalyzers".ToLowerInvariant(), notYetDownloadedPackages); + Assert.Contains("Microsoft.CodeAnalysis.NetAnalyzers".ToLowerInvariant(), notYetDownloadedPackages); + Assert.Contains("StyleCop.Analyzers".ToLowerInvariant(), notYetDownloadedPackages); + } + + [Fact] + public void TestFileContent2() + { + // Setup + var lines = new List() + { + "", + "", + "", + "" + }; + var fileContent = new TestFileContent(lines); + + // Execute + var useAspNetDlls = fileContent.UseAspNetDlls; + var notYetDownloadedPackages = fileContent.NotYetDownloadedPackages; + + // Verify + Assert.True(useAspNetDlls); + Assert.Equal(2, notYetDownloadedPackages.Count); + Assert.Contains("Microsoft.CodeAnalysis.NetAnalyzers".ToLowerInvariant(), notYetDownloadedPackages); + Assert.Contains("StyleCop.Analyzers".ToLowerInvariant(), notYetDownloadedPackages); + } + } +} diff --git a/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs b/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs index 07102d9a758..1cfcba0efaf 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs @@ -100,5 +100,36 @@ namespace Semmle.Extraction.Tests Assert.Equal("/path/dotnet/shared/Microsoft.NETCore.App/8.0.0-rc.4.43280.8", FixExpectedPathOnWindows(netCoreApp.FullPath)); } + [Fact] + public void TestRuntime4() + { + // Setup + var listedRuntimes = new List + { + @"Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]", + @"Microsoft.AspNetCore.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]", + @"Microsoft.AspNetCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]", + @"Microsoft.NETCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]", + @"Microsoft.NETCore.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]", + @"Microsoft.NETCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]", + @"Microsoft.WindowsDesktop.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]", + @"Microsoft.WindowsDesktop.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]", + @"Microsoft.WindowsDesktop.App 7.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]" + }; + var dotnet = new DotNetStub(listedRuntimes); + var runtime = new Runtime(dotnet); + + // Execute + var runtimes = runtime.GetNewestRuntimes(); + + // Verify + Assert.Equal(3, runtimes.Count); + + Assert.True(runtimes.TryGetValue("Microsoft.AspNetCore.App", out var aspNetCoreApp)); + Assert.Equal(@"C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/7.0.2", FixExpectedPathOnWindows(aspNetCoreApp.FullPath)); + + Assert.True(runtimes.TryGetValue("Microsoft.NETCore.App", out var netCoreApp)); + Assert.Equal(@"C:/Program Files/dotnet/shared/Microsoft.NETCore.App/7.0.2", FixExpectedPathOnWindows(netCoreApp.FullPath)); + } } } diff --git a/csharp/extractor/Semmle.Util/DictionaryExtensions.cs b/csharp/extractor/Semmle.Util/DictionaryExtensions.cs index fa932f0cd9c..5ac4f3bfaa9 100644 --- a/csharp/extractor/Semmle.Util/DictionaryExtensions.cs +++ b/csharp/extractor/Semmle.Util/DictionaryExtensions.cs @@ -22,9 +22,9 @@ namespace Semmle.Util /// /// Adds a new value or replaces the existing value (if the new value is greater than the existing) - /// in dictionary for the given key. + /// in this dictionary for the given key. /// - public static void AddOrUpdate(this Dictionary dict, T1 key, T2 value) where T1 : notnull where T2 : IComparable + public static void AddOrUpdateToLatest(this Dictionary dict, T1 key, T2 value) where T1 : notnull where T2 : IComparable { if (!dict.TryGetValue(key, out var existing) || existing.CompareTo(value) < 0) { diff --git a/csharp/extractor/Semmle.Util/Initializer.cs b/csharp/extractor/Semmle.Util/Initializer.cs new file mode 100644 index 00000000000..179c7efbe12 --- /dev/null +++ b/csharp/extractor/Semmle.Util/Initializer.cs @@ -0,0 +1,28 @@ +using System; + +namespace Semmle.Util +{ + /// + /// An instance of this class is used to ensure that the provided + /// action is executed only once and on the first call to `Run`. + /// It is thread-safe. + /// + public class Initializer + { + private readonly Lazy doInit; + + public Initializer(Action action) + { + doInit = new Lazy(() => + { + action(); + return true; + }); + } + + public void Run() + { + var _ = doInit.Value; + } + } +} diff --git a/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md b/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md index 3de1098514d..1eeadc75491 100644 --- a/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md +++ b/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.6.2 + +No user-facing changes. + ## 1.6.1 No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/lib/change-notes/released/1.6.2.md b/csharp/ql/campaigns/Solorigate/lib/change-notes/released/1.6.2.md new file mode 100644 index 00000000000..bbe3747556f --- /dev/null +++ b/csharp/ql/campaigns/Solorigate/lib/change-notes/released/1.6.2.md @@ -0,0 +1,3 @@ +## 1.6.2 + +No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml b/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml index ef7a789e0cf..5f5beb68311 100644 --- a/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml +++ b/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 1.6.1 +lastReleaseVersion: 1.6.2 diff --git a/csharp/ql/campaigns/Solorigate/lib/qlpack.yml b/csharp/ql/campaigns/Solorigate/lib/qlpack.yml index 973c0efd969..290293042ab 100644 --- a/csharp/ql/campaigns/Solorigate/lib/qlpack.yml +++ b/csharp/ql/campaigns/Solorigate/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-solorigate-all -version: 1.6.2-dev +version: 1.6.3-dev groups: - csharp - solorigate diff --git a/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md b/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md index 3de1098514d..1eeadc75491 100644 --- a/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md +++ b/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.6.2 + +No user-facing changes. + ## 1.6.1 No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/src/change-notes/released/1.6.2.md b/csharp/ql/campaigns/Solorigate/src/change-notes/released/1.6.2.md new file mode 100644 index 00000000000..bbe3747556f --- /dev/null +++ b/csharp/ql/campaigns/Solorigate/src/change-notes/released/1.6.2.md @@ -0,0 +1,3 @@ +## 1.6.2 + +No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml b/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml index ef7a789e0cf..5f5beb68311 100644 --- a/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml +++ b/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 1.6.1 +lastReleaseVersion: 1.6.2 diff --git a/csharp/ql/campaigns/Solorigate/src/qlpack.yml b/csharp/ql/campaigns/Solorigate/src/qlpack.yml index 3947c215596..e04e1151d60 100644 --- a/csharp/ql/campaigns/Solorigate/src/qlpack.yml +++ b/csharp/ql/campaigns/Solorigate/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-solorigate-queries -version: 1.6.2-dev +version: 1.6.3-dev groups: - csharp - solorigate diff --git a/csharp/ql/integration-tests/all-platforms/standalone/Files.expected b/csharp/ql/integration-tests/all-platforms/standalone/Files.expected new file mode 100644 index 00000000000..2085aa342e7 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/standalone/Files.expected @@ -0,0 +1 @@ +| Program.cs:0:0:0:0 | Program.cs | diff --git a/csharp/ql/integration-tests/all-platforms/standalone/Files.ql b/csharp/ql/integration-tests/all-platforms/standalone/Files.ql new file mode 100644 index 00000000000..bea5557a25f --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/standalone/Files.ql @@ -0,0 +1,5 @@ +import csharp + +from File f +where f.fromSource() +select f diff --git a/csharp/ql/integration-tests/all-platforms/standalone/Program.cs b/csharp/ql/integration-tests/all-platforms/standalone/Program.cs new file mode 100644 index 00000000000..47eee48cc79 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/standalone/Program.cs @@ -0,0 +1 @@ +var dummy = "dummy"; \ No newline at end of file diff --git a/csharp/ql/integration-tests/all-platforms/standalone/standalone.csproj b/csharp/ql/integration-tests/all-platforms/standalone/standalone.csproj new file mode 100644 index 00000000000..7763ab016d4 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/standalone/standalone.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/csharp/ql/integration-tests/all-platforms/standalone/test.py b/csharp/ql/integration-tests/all-platforms/standalone/test.py new file mode 100644 index 00000000000..58074b430b2 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/standalone/test.py @@ -0,0 +1,3 @@ +from create_database_utils import * + +run_codeql_database_create([], lang="csharp", extra_args=["--extractor-option=buildless=true", "--extractor-option=cil=false"]) diff --git a/csharp/ql/lib/CHANGELOG.md b/csharp/ql/lib/CHANGELOG.md index b2a792d29a9..7806ffed612 100644 --- a/csharp/ql/lib/CHANGELOG.md +++ b/csharp/ql/lib/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.2 + +No user-facing changes. + ## 0.7.1 ### New Features diff --git a/csharp/ql/lib/Linq/Helpers.qll b/csharp/ql/lib/Linq/Helpers.qll index a628c717277..541f7a380e6 100644 --- a/csharp/ql/lib/Linq/Helpers.qll +++ b/csharp/ql/lib/Linq/Helpers.qll @@ -3,7 +3,9 @@ * Provides helper classes and methods related to LINQ. */ -import csharp +private import csharp +private import semmle.code.csharp.frameworks.system.collections.Generic as GenericCollections +private import semmle.code.csharp.frameworks.system.Collections as Collections //#################### PREDICATES #################### private Stmt firstStmt(ForeachStmt fes) { @@ -29,13 +31,40 @@ predicate isIEnumerableType(ValueOrRefType t) { ) } +/** + * A class of foreach statements where the iterable expression + * supports the use of the LINQ extension methods on `IEnumerable`. + */ +class ForeachStmtGenericEnumerable extends ForeachStmt { + ForeachStmtGenericEnumerable() { + exists(ValueOrRefType t | t = this.getIterableExpr().getType() | + t.getABaseType*().getUnboundDeclaration() instanceof + GenericCollections::SystemCollectionsGenericIEnumerableTInterface or + t.(ArrayType).getRank() = 1 + ) + } +} + +/** + * A class of foreach statements where the iterable expression + * supports the use of the LINQ extension methods on `IEnumerable`. + */ +class ForeachStmtEnumerable extends ForeachStmt { + ForeachStmtEnumerable() { + exists(ValueOrRefType t | t = this.getIterableExpr().getType() | + t.getABaseType*() instanceof Collections::SystemCollectionsIEnumerableInterface or + t.(ArrayType).getRank() = 1 + ) + } +} + /** * Holds if `foreach` statement `fes` could be converted to a `.All()` call. * That is, the `ForeachStmt` contains a single `if` with a condition that * accesses the loop variable and with a body that assigns `false` to a variable * and `break`s out of the `foreach`. */ -predicate missedAllOpportunity(ForeachStmt fes) { +predicate missedAllOpportunity(ForeachStmtGenericEnumerable fes) { exists(IfStmt is | // The loop contains an if statement with no else case, and nothing else. is = firstStmt(fes) and @@ -54,12 +83,12 @@ predicate missedAllOpportunity(ForeachStmt fes) { } /** - * Holds if `foreach` statement `fes` could be converted to a `.Cast()` call. + * Holds if the `foreach` statement `fes` can be converted to a `.Cast()` call. * That is, the loop variable is accessed only in the first statement of the - * block, and the access is a cast. The first statement needs to be a - * `LocalVariableDeclStmt`. + * block, the access is a cast, and the first statement is a + * local variable declaration statement `s`. */ -predicate missedCastOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) { +predicate missedCastOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclStmt s) { s = firstStmt(fes) and forex(VariableAccess va | va = fes.getVariable().getAnAccess() | va = s.getAVariableDeclExpr().getAChildExpr*() @@ -71,12 +100,12 @@ predicate missedCastOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) { } /** - * Holds if `foreach` statement `fes` could be converted to an `.OfType()` call. + * Holds if `foreach` statement `fes` can be converted to an `.OfType()` call. * That is, the loop variable is accessed only in the first statement of the - * block, and the access is a cast with the `as` operator. The first statement - * needs to be a `LocalVariableDeclStmt`. + * block, the access is a cast with the `as` operator, and the first statement + * is a local variable declaration statement `s`. */ -predicate missedOfTypeOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) { +predicate missedOfTypeOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclStmt s) { s = firstStmt(fes) and forex(VariableAccess va | va = fes.getVariable().getAnAccess() | va = s.getAVariableDeclExpr().getAChildExpr*() @@ -88,12 +117,12 @@ predicate missedOfTypeOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) { } /** - * Holds if `foreach` statement `fes` could be converted to a `.Select()` call. + * Holds if `foreach` statement `fes` can be converted to a `.Select()` call. * That is, the loop variable is accessed only in the first statement of the - * block, and the access is not a cast. The first statement needs to be a - * `LocalVariableDeclStmt`. + * block, the access is not a cast, and the first statement is a + * local variable declaration statement `s`. */ -predicate missedSelectOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) { +predicate missedSelectOpportunity(ForeachStmtGenericEnumerable fes, LocalVariableDeclStmt s) { s = firstStmt(fes) and forex(VariableAccess va | va = fes.getVariable().getAnAccess() | va = s.getAVariableDeclExpr().getAChildExpr*() @@ -107,7 +136,7 @@ predicate missedSelectOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) { * variable, and the body of the `if` is either a `continue` or there's nothing * else in the loop than the `if`. */ -predicate missedWhereOpportunity(ForeachStmt fes, IfStmt is) { +predicate missedWhereOpportunity(ForeachStmtGenericEnumerable fes, IfStmt is) { // The very first thing the foreach loop does is test its iteration variable. is = firstStmt(fes) and exists(VariableAccess va | diff --git a/csharp/ql/lib/change-notes/2023-08-04-netcore-identity-hardcoded-cred.md b/csharp/ql/lib/change-notes/2023-08-04-netcore-identity-hardcoded-cred.md new file mode 100644 index 00000000000..c442579627f --- /dev/null +++ b/csharp/ql/lib/change-notes/2023-08-04-netcore-identity-hardcoded-cred.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The query library for `cs/hardcoded-credentials` now excludes benign properties such as `UserNameClaimType` and `AllowedUserNameCharacters` from `Microsoft.AspNetCore.Identity` options classes. \ No newline at end of file diff --git a/csharp/ql/lib/change-notes/released/0.7.2.md b/csharp/ql/lib/change-notes/released/0.7.2.md new file mode 100644 index 00000000000..8693d609ec7 --- /dev/null +++ b/csharp/ql/lib/change-notes/released/0.7.2.md @@ -0,0 +1,3 @@ +## 0.7.2 + +No user-facing changes. diff --git a/csharp/ql/lib/codeql-pack.release.yml b/csharp/ql/lib/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/csharp/ql/lib/codeql-pack.release.yml +++ b/csharp/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/csharp/ql/lib/qlpack.yml b/csharp/ql/lib/qlpack.yml index c50ace576ac..24972ee9b5d 100644 --- a/csharp/ql/lib/qlpack.yml +++ b/csharp/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-all -version: 0.7.2-dev +version: 0.7.3-dev groups: csharp dbscheme: semmlecode.csharp.dbscheme extractor: csharp diff --git a/csharp/ql/lib/semmle/code/csharp/Stmt.qll b/csharp/ql/lib/semmle/code/csharp/Stmt.qll index 6bf826c675d..451f993db1f 100644 --- a/csharp/ql/lib/semmle/code/csharp/Stmt.qll +++ b/csharp/ql/lib/semmle/code/csharp/Stmt.qll @@ -861,6 +861,12 @@ class YieldReturnStmt extends YieldStmt { override string getAPrimaryQlClass() { result = "YieldReturnStmt" } } +bindingset[cfe1, cfe2] +pragma[inline_late] +private predicate sameCallable(ControlFlowElement cfe1, ControlFlowElement cfe2) { + cfe1.getEnclosingCallable() = cfe2.getEnclosingCallable() +} + /** * A `try` statement, for example * @@ -947,8 +953,7 @@ class TryStmt extends Stmt, @try_stmt { mid = this.getATriedElement() and not mid instanceof TryStmt and result = mid.getAChild() and - pragma[only_bind_into](mid.getEnclosingCallable()) = - pragma[only_bind_into](result.getEnclosingCallable()) + sameCallable(mid, result) ) } } diff --git a/csharp/ql/lib/semmle/code/csharp/security/dataflow/HardcodedCredentialsQuery.qll b/csharp/ql/lib/semmle/code/csharp/security/dataflow/HardcodedCredentialsQuery.qll index a5875fe001c..dd6669579e1 100644 --- a/csharp/ql/lib/semmle/code/csharp/security/dataflow/HardcodedCredentialsQuery.qll +++ b/csharp/ql/lib/semmle/code/csharp/security/dataflow/HardcodedCredentialsQuery.qll @@ -169,7 +169,7 @@ private class CredentialVar extends Assignable { exists(string name | name = this.getName() | name.regexpMatch("(?i).*pass(wd|word|code|phrase)(?!.*question).*") or - name.regexpMatch("(?i).*(puid|username|userid).*") + name.regexpMatch("(?i).*(puid|username|userid)(?!.*(characters|claimtype)).*") or name.regexpMatch("(?i).*(cert)(?!.*(format|name)).*") ) diff --git a/csharp/ql/src/CHANGELOG.md b/csharp/ql/src/CHANGELOG.md index 74584ff4772..99b5b5e5c3c 100644 --- a/csharp/ql/src/CHANGELOG.md +++ b/csharp/ql/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.2 + +No user-facing changes. + ## 0.7.1 No user-facing changes. diff --git a/csharp/ql/src/Linq/MissedAllOpportunity.ql b/csharp/ql/src/Linq/MissedAllOpportunity.ql index 54c2e7b0c46..54fe40faeec 100644 --- a/csharp/ql/src/Linq/MissedAllOpportunity.ql +++ b/csharp/ql/src/Linq/MissedAllOpportunity.ql @@ -10,7 +10,6 @@ * language-features */ -import csharp import Linq.Helpers /* @@ -31,7 +30,7 @@ import Linq.Helpers * bool allEven = lst.All(i => i % 2 == 0); */ -from ForeachStmt fes +from ForeachStmtGenericEnumerable fes where missedAllOpportunity(fes) select fes, "This foreach loop looks as if it might be testing whether every sequence element satisfies a predicate - consider using '.All(...)'." diff --git a/csharp/ql/src/Linq/MissedCastOpportunity.ql b/csharp/ql/src/Linq/MissedCastOpportunity.ql index 24d1c87bd2a..8d3de1f31b4 100644 --- a/csharp/ql/src/Linq/MissedCastOpportunity.ql +++ b/csharp/ql/src/Linq/MissedCastOpportunity.ql @@ -13,7 +13,7 @@ import csharp import Linq.Helpers -from ForeachStmt fes, LocalVariableDeclStmt s +from ForeachStmtEnumerable fes, LocalVariableDeclStmt s where missedCastOpportunity(fes, s) select fes, "This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'.", diff --git a/csharp/ql/src/Linq/MissedOfTypeOpportunity.ql b/csharp/ql/src/Linq/MissedOfTypeOpportunity.ql index 3a984d0cc3e..3d61acfa523 100644 --- a/csharp/ql/src/Linq/MissedOfTypeOpportunity.ql +++ b/csharp/ql/src/Linq/MissedOfTypeOpportunity.ql @@ -13,7 +13,7 @@ import csharp import Linq.Helpers -from ForeachStmt fes, LocalVariableDeclStmt s +from ForeachStmtEnumerable fes, LocalVariableDeclStmt s where missedOfTypeOpportunity(fes, s) select fes, "This foreach loop immediately uses 'as' to $@ - consider using '.OfType(...)' instead.", s, diff --git a/csharp/ql/src/Linq/MissedSelectOpportunity.ql b/csharp/ql/src/Linq/MissedSelectOpportunity.ql index 1714aed81da..9f36c3de82b 100644 --- a/csharp/ql/src/Linq/MissedSelectOpportunity.ql +++ b/csharp/ql/src/Linq/MissedSelectOpportunity.ql @@ -20,7 +20,7 @@ predicate oversized(LocalVariableDeclStmt s) { ) } -from ForeachStmt fes, LocalVariableDeclStmt s +from ForeachStmtGenericEnumerable fes, LocalVariableDeclStmt s where missedSelectOpportunity(fes, s) and not oversized(s) diff --git a/csharp/ql/src/Linq/MissedWhereOpportunity.ql b/csharp/ql/src/Linq/MissedWhereOpportunity.ql index 4c93b7ab6bf..401248d6176 100644 --- a/csharp/ql/src/Linq/MissedWhereOpportunity.ql +++ b/csharp/ql/src/Linq/MissedWhereOpportunity.ql @@ -12,7 +12,7 @@ import csharp import Linq.Helpers -from ForeachStmt fes, IfStmt is +from ForeachStmtGenericEnumerable fes, IfStmt is where missedWhereOpportunity(fes, is) and not missedAllOpportunity(fes) diff --git a/csharp/ql/src/change-notes/released/0.7.2.md b/csharp/ql/src/change-notes/released/0.7.2.md new file mode 100644 index 00000000000..8693d609ec7 --- /dev/null +++ b/csharp/ql/src/change-notes/released/0.7.2.md @@ -0,0 +1,3 @@ +## 0.7.2 + +No user-facing changes. diff --git a/csharp/ql/src/codeql-pack.release.yml b/csharp/ql/src/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/csharp/ql/src/codeql-pack.release.yml +++ b/csharp/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/csharp/ql/src/qlpack.yml b/csharp/ql/src/qlpack.yml index e24daa4f9ac..1ce08f3be25 100644 --- a/csharp/ql/src/qlpack.yml +++ b/csharp/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-queries -version: 0.7.2-dev +version: 0.7.3-dev groups: - csharp - queries diff --git a/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.cs b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.cs new file mode 100644 index 00000000000..fe5617c228a --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +class MissedCastOpportunity +{ + public void M1(List animals) + { + // BAD: Can be replaced with animals.Cast(). + foreach (Animal a in animals) + { + Dog d = (Dog)a; + d.Woof(); + } + } + + public void M2(NonEnumerableClass nec) + { + // GOOD: Not possible to use Linq here. + foreach (Animal a in nec) + { + Dog d = (Dog)a; + d.Woof(); + } + } + + public void M3(Animal[] animals) + { + // BAD: Can be replaced with animals.Cast(). + foreach (Animal animal in animals) + { + Dog d = (Dog)animal; + d.Woof(); + } + } + + public void M4(Array animals) + { + // BAD: Can be replaced with animals.Cast(). + foreach (Animal animal in animals) + { + Dog d = (Dog)animal; + d.Woof(); + } + } + + public void M5(IEnumerable animals) + { + // BAD: Can be replaced with animals.Cast(). + foreach (object animal in animals) + { + Dog d = (Dog)animal; + d.Woof(); + } + } + + public class NonEnumerableClass + { + public IEnumerator GetEnumerator() => throw null; + } + + public class Animal { } + + class Dog : Animal + { + private string name; + + public Dog(string name) + { + this.name = name; + } + + public void Woof() + { + Console.WriteLine("Woof! My name is " + name + "."); + } + } +} \ No newline at end of file diff --git a/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.expected b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.expected new file mode 100644 index 00000000000..7e08d734acc --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.expected @@ -0,0 +1,4 @@ +| MissedCastOpportunity.cs:10:9:14:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:12:13:12:27 | ... ...; | casts its iteration variable to another type | +| MissedCastOpportunity.cs:30:9:34:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:32:13:32:32 | ... ...; | casts its iteration variable to another type | +| MissedCastOpportunity.cs:40:9:44:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:42:13:42:32 | ... ...; | casts its iteration variable to another type | +| MissedCastOpportunity.cs:50:9:54:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:52:13:52:32 | ... ...; | casts its iteration variable to another type | diff --git a/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.qlref b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.qlref new file mode 100644 index 00000000000..8d70f999503 --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/MissedCastOpportunity.qlref @@ -0,0 +1 @@ +Linq/MissedCastOpportunity.ql diff --git a/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/options b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/options new file mode 100644 index 00000000000..75c39b4541b --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedCastOpportunity/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj diff --git a/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.cs b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.cs new file mode 100644 index 00000000000..d1326c70ee2 --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +class MissedWhereOpportunity +{ + public void M1(List lst) + { + // BAD: Can be replaced with lst.Where(e => e % 2 == 0) + foreach (int i in lst) + { + if (i % 2 != 0) + continue; + Console.WriteLine(i); + Console.WriteLine((i / 2)); + } + + // BAD: Can be replaced with lst.Where(e => e % 2 == 0) + foreach (int i in lst) + { + if (i % 2 == 0) + { + Console.WriteLine(i); + Console.WriteLine((i / 2)); + } + } + } + + public void M2(NonEnumerableClass nec) + { + // GOOD: Linq can't be used here. + foreach (int i in nec) + { + if (i % 2 == 0) + { + Console.WriteLine(i); + Console.WriteLine((i / 2)); + } + } + } + + public void M3(int[] arr) + { + // BAD: Can be replaced with arr.Where(e => e % 2 == 0) + foreach (var n in arr) + { + if (n % 2 == 0) + { + Console.WriteLine(n); + Console.WriteLine((n / 2)); + } + } + } + + public void M4(Array arr) + { + // GOOD: Linq can't be used here. + foreach (var element in arr) + { + if (element.GetHashCode() % 2 == 0) + { + Console.WriteLine(element); + } + } + } + + public void M5(IEnumerable elements) + { + // BAD: Can be replaced with elements.Where(e => e.GetHashCode() % 2 == 0) + foreach (var element in elements) + { + if (element.GetHashCode() % 2 == 0) + { + Console.WriteLine(element); + } + } + } + + public class NonEnumerableClass + { + public IEnumerator GetEnumerator() => throw null; + } +} diff --git a/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.expected b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.expected new file mode 100644 index 00000000000..5efde9aebed --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.expected @@ -0,0 +1,4 @@ +| MissedWhereOpportunity.cs:10:9:16:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:12:17:12:26 | ... != ... | implicitly filters its target sequence | +| MissedWhereOpportunity.cs:19:9:26:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:21:17:21:26 | ... == ... | implicitly filters its target sequence | +| MissedWhereOpportunity.cs:45:9:52:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:47:17:47:26 | ... == ... | implicitly filters its target sequence | +| MissedWhereOpportunity.cs:70:9:76:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:72:17:72:46 | ... == ... | implicitly filters its target sequence | diff --git a/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.qlref b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.qlref new file mode 100644 index 00000000000..4a08b459a6c --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/MissedWhereOpportunity.qlref @@ -0,0 +1 @@ +Linq/MissedWhereOpportunity.ql diff --git a/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/options b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/options new file mode 100644 index 00000000000..75c39b4541b --- /dev/null +++ b/csharp/ql/test/query-tests/Linq/MissedWhereOpportunity/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj diff --git a/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedConnectionString.expected b/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedConnectionString.expected index 1191b40ec28..7de4c593e25 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedConnectionString.expected +++ b/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedConnectionString.expected @@ -1,8 +1,8 @@ edges nodes -| HardcodedCredentials.cs:54:48:54:63 | "Password=12345" | semmle.label | "Password=12345" | -| HardcodedCredentials.cs:56:49:56:63 | "User Id=12345" | semmle.label | "User Id=12345" | +| HardcodedCredentials.cs:55:48:55:63 | "Password=12345" | semmle.label | "Password=12345" | +| HardcodedCredentials.cs:57:49:57:63 | "User Id=12345" | semmle.label | "User Id=12345" | subpaths #select -| HardcodedCredentials.cs:54:48:54:63 | "Password=12345" | HardcodedCredentials.cs:54:48:54:63 | "Password=12345" | HardcodedCredentials.cs:54:48:54:63 | "Password=12345" | 'ConnectionString' property includes hard-coded credentials set in $@. | HardcodedCredentials.cs:54:30:54:64 | object creation of type SqlConnection | object creation of type SqlConnection | -| HardcodedCredentials.cs:56:49:56:63 | "User Id=12345" | HardcodedCredentials.cs:56:49:56:63 | "User Id=12345" | HardcodedCredentials.cs:56:49:56:63 | "User Id=12345" | 'ConnectionString' property includes hard-coded credentials set in $@. | HardcodedCredentials.cs:56:31:56:64 | object creation of type SqlConnection | object creation of type SqlConnection | +| HardcodedCredentials.cs:55:48:55:63 | "Password=12345" | HardcodedCredentials.cs:55:48:55:63 | "Password=12345" | HardcodedCredentials.cs:55:48:55:63 | "Password=12345" | 'ConnectionString' property includes hard-coded credentials set in $@. | HardcodedCredentials.cs:55:30:55:64 | object creation of type SqlConnection | object creation of type SqlConnection | +| HardcodedCredentials.cs:57:49:57:63 | "User Id=12345" | HardcodedCredentials.cs:57:49:57:63 | "User Id=12345" | HardcodedCredentials.cs:57:49:57:63 | "User Id=12345" | 'ConnectionString' property includes hard-coded credentials set in $@. | HardcodedCredentials.cs:57:31:57:64 | object creation of type SqlConnection | object creation of type SqlConnection | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.cs b/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.cs index 71fea63ff4a..840d65b1965 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.cs +++ b/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.cs @@ -3,6 +3,7 @@ using System.Data.SqlClient; using System.Web; using System.Web.Security; using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Identity; public class HardcodedHandler : IHttpHandler { @@ -72,6 +73,21 @@ public class HardcodedHandler : IHttpHandler // BAD: Hard-coded user Membership.CreateUser("myusername", "mypassword"); + + var identityOptions = new IdentityOptions + { + User = new UserOptions + { + // GOOD: This is not a credential so hardcoding a string assignment is fine + AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+" + } + }; + + var claimsIdentityOptions = new ClaimsIdentityOptions + { + // GOOD: This is not a credential so hardcoding a string assignment is fine + UserNameClaimType = "username" + }; } class Foo diff --git a/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.expected b/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.expected index 49214e4426c..4dc81537e20 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.expected +++ b/csharp/ql/test/query-tests/Security Features/CWE-798/HardcodedCredentials.expected @@ -1,24 +1,24 @@ edges -| HardcodedCredentials.cs:47:30:47:60 | array creation of type Byte[] : Byte[] | HardcodedCredentials.cs:50:13:50:23 | access to local variable rawCertData | +| HardcodedCredentials.cs:48:30:48:60 | array creation of type Byte[] : Byte[] | HardcodedCredentials.cs:51:13:51:23 | access to local variable rawCertData | nodes -| HardcodedCredentials.cs:15:25:15:36 | "myPa55word" | semmle.label | "myPa55word" | -| HardcodedCredentials.cs:31:19:31:28 | "username" | semmle.label | "username" | -| HardcodedCredentials.cs:45:39:45:53 | "myNewPa55word" | semmle.label | "myNewPa55word" | -| HardcodedCredentials.cs:47:30:47:60 | array creation of type Byte[] : Byte[] | semmle.label | array creation of type Byte[] : Byte[] | -| HardcodedCredentials.cs:50:13:50:23 | access to local variable rawCertData | semmle.label | access to local variable rawCertData | -| HardcodedCredentials.cs:51:13:51:24 | "myPa55word" | semmle.label | "myPa55word" | -| HardcodedCredentials.cs:74:31:74:42 | "myusername" | semmle.label | "myusername" | -| HardcodedCredentials.cs:74:45:74:56 | "mypassword" | semmle.label | "mypassword" | +| HardcodedCredentials.cs:16:25:16:36 | "myPa55word" | semmle.label | "myPa55word" | +| HardcodedCredentials.cs:32:19:32:28 | "username" | semmle.label | "username" | +| HardcodedCredentials.cs:46:39:46:53 | "myNewPa55word" | semmle.label | "myNewPa55word" | +| HardcodedCredentials.cs:48:30:48:60 | array creation of type Byte[] : Byte[] | semmle.label | array creation of type Byte[] : Byte[] | +| HardcodedCredentials.cs:51:13:51:23 | access to local variable rawCertData | semmle.label | access to local variable rawCertData | +| HardcodedCredentials.cs:52:13:52:24 | "myPa55word" | semmle.label | "myPa55word" | +| HardcodedCredentials.cs:75:31:75:42 | "myusername" | semmle.label | "myusername" | +| HardcodedCredentials.cs:75:45:75:56 | "mypassword" | semmle.label | "mypassword" | | TestHardcodedCredentials.cs:21:31:21:42 | "myusername" | semmle.label | "myusername" | | TestHardcodedCredentials.cs:21:45:21:56 | "mypassword" | semmle.label | "mypassword" | | TestHardcodedCredentials.cs:26:19:26:28 | "username" | semmle.label | "username" | subpaths #select -| HardcodedCredentials.cs:15:25:15:36 | "myPa55word" | HardcodedCredentials.cs:15:25:15:36 | "myPa55word" | HardcodedCredentials.cs:15:25:15:36 | "myPa55word" | The hard-coded value "myPa55word" flows to $@ which is compared against $@. | HardcodedCredentials.cs:15:25:15:36 | "myPa55word" | "myPa55word" | HardcodedCredentials.cs:15:13:15:20 | access to local variable password | access to local variable password | -| HardcodedCredentials.cs:31:19:31:28 | "username" | HardcodedCredentials.cs:31:19:31:28 | "username" | HardcodedCredentials.cs:31:19:31:28 | "username" | The hard-coded value "username" flows to the $@ parameter in $@. | HardcodedCredentials.cs:31:19:31:28 | "username" | name | HardcodedCredentials.cs:29:31:43:13 | object creation of type MembershipUser | object creation of type MembershipUser | -| HardcodedCredentials.cs:45:39:45:53 | "myNewPa55word" | HardcodedCredentials.cs:45:39:45:53 | "myNewPa55word" | HardcodedCredentials.cs:45:39:45:53 | "myNewPa55word" | The hard-coded value "myNewPa55word" flows to the $@ parameter in $@. | HardcodedCredentials.cs:45:39:45:53 | "myNewPa55word" | newPassword | HardcodedCredentials.cs:45:9:45:54 | call to method ChangePassword | call to method ChangePassword | -| HardcodedCredentials.cs:47:30:47:60 | array creation of type Byte[] | HardcodedCredentials.cs:47:30:47:60 | array creation of type Byte[] : Byte[] | HardcodedCredentials.cs:50:13:50:23 | access to local variable rawCertData | This hard-coded value flows to the $@ parameter in $@. | HardcodedCredentials.cs:50:13:50:23 | access to local variable rawCertData | rawData | HardcodedCredentials.cs:49:33:51:25 | object creation of type X509Certificate2 | object creation of type X509Certificate2 | -| HardcodedCredentials.cs:51:13:51:24 | "myPa55word" | HardcodedCredentials.cs:51:13:51:24 | "myPa55word" | HardcodedCredentials.cs:51:13:51:24 | "myPa55word" | The hard-coded value "myPa55word" flows to the $@ parameter in $@. | HardcodedCredentials.cs:51:13:51:24 | "myPa55word" | password | HardcodedCredentials.cs:49:33:51:25 | object creation of type X509Certificate2 | object creation of type X509Certificate2 | -| HardcodedCredentials.cs:74:31:74:42 | "myusername" | HardcodedCredentials.cs:74:31:74:42 | "myusername" | HardcodedCredentials.cs:74:31:74:42 | "myusername" | The hard-coded value "myusername" flows to the $@ parameter in $@. | HardcodedCredentials.cs:74:31:74:42 | "myusername" | username | HardcodedCredentials.cs:74:9:74:57 | call to method CreateUser | call to method CreateUser | -| HardcodedCredentials.cs:74:45:74:56 | "mypassword" | HardcodedCredentials.cs:74:45:74:56 | "mypassword" | HardcodedCredentials.cs:74:45:74:56 | "mypassword" | The hard-coded value "mypassword" flows to the $@ parameter in $@. | HardcodedCredentials.cs:74:45:74:56 | "mypassword" | password | HardcodedCredentials.cs:74:9:74:57 | call to method CreateUser | call to method CreateUser | +| HardcodedCredentials.cs:16:25:16:36 | "myPa55word" | HardcodedCredentials.cs:16:25:16:36 | "myPa55word" | HardcodedCredentials.cs:16:25:16:36 | "myPa55word" | The hard-coded value "myPa55word" flows to $@ which is compared against $@. | HardcodedCredentials.cs:16:25:16:36 | "myPa55word" | "myPa55word" | HardcodedCredentials.cs:16:13:16:20 | access to local variable password | access to local variable password | +| HardcodedCredentials.cs:32:19:32:28 | "username" | HardcodedCredentials.cs:32:19:32:28 | "username" | HardcodedCredentials.cs:32:19:32:28 | "username" | The hard-coded value "username" flows to the $@ parameter in $@. | HardcodedCredentials.cs:32:19:32:28 | "username" | name | HardcodedCredentials.cs:30:31:44:13 | object creation of type MembershipUser | object creation of type MembershipUser | +| HardcodedCredentials.cs:46:39:46:53 | "myNewPa55word" | HardcodedCredentials.cs:46:39:46:53 | "myNewPa55word" | HardcodedCredentials.cs:46:39:46:53 | "myNewPa55word" | The hard-coded value "myNewPa55word" flows to the $@ parameter in $@. | HardcodedCredentials.cs:46:39:46:53 | "myNewPa55word" | newPassword | HardcodedCredentials.cs:46:9:46:54 | call to method ChangePassword | call to method ChangePassword | +| HardcodedCredentials.cs:48:30:48:60 | array creation of type Byte[] | HardcodedCredentials.cs:48:30:48:60 | array creation of type Byte[] : Byte[] | HardcodedCredentials.cs:51:13:51:23 | access to local variable rawCertData | This hard-coded value flows to the $@ parameter in $@. | HardcodedCredentials.cs:51:13:51:23 | access to local variable rawCertData | rawData | HardcodedCredentials.cs:50:33:52:25 | object creation of type X509Certificate2 | object creation of type X509Certificate2 | +| HardcodedCredentials.cs:52:13:52:24 | "myPa55word" | HardcodedCredentials.cs:52:13:52:24 | "myPa55word" | HardcodedCredentials.cs:52:13:52:24 | "myPa55word" | The hard-coded value "myPa55word" flows to the $@ parameter in $@. | HardcodedCredentials.cs:52:13:52:24 | "myPa55word" | password | HardcodedCredentials.cs:50:33:52:25 | object creation of type X509Certificate2 | object creation of type X509Certificate2 | +| HardcodedCredentials.cs:75:31:75:42 | "myusername" | HardcodedCredentials.cs:75:31:75:42 | "myusername" | HardcodedCredentials.cs:75:31:75:42 | "myusername" | The hard-coded value "myusername" flows to the $@ parameter in $@. | HardcodedCredentials.cs:75:31:75:42 | "myusername" | username | HardcodedCredentials.cs:75:9:75:57 | call to method CreateUser | call to method CreateUser | +| HardcodedCredentials.cs:75:45:75:56 | "mypassword" | HardcodedCredentials.cs:75:45:75:56 | "mypassword" | HardcodedCredentials.cs:75:45:75:56 | "mypassword" | The hard-coded value "mypassword" flows to the $@ parameter in $@. | HardcodedCredentials.cs:75:45:75:56 | "mypassword" | password | HardcodedCredentials.cs:75:9:75:57 | call to method CreateUser | call to method CreateUser | | TestHardcodedCredentials.cs:26:19:26:28 | "username" | TestHardcodedCredentials.cs:26:19:26:28 | "username" | TestHardcodedCredentials.cs:26:19:26:28 | "username" | The hard-coded value "username" flows to the $@ parameter in $@. | TestHardcodedCredentials.cs:26:19:26:28 | "username" | name | TestHardcodedCredentials.cs:24:31:38:13 | object creation of type MembershipUser | object creation of type MembershipUser | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-798/options b/csharp/ql/test/query-tests/Security Features/CWE-798/options index a361e4fde29..cb1a6209e55 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-798/options +++ b/csharp/ql/test/query-tests/Security Features/CWE-798/options @@ -1,4 +1,5 @@ semmle-extractor-options: /nostdlib /noconfig semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/System.Data.SqlClient/4.8.3/System.Data.SqlClient.csproj semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj semmle-extractor-options: ${testdir}/../../../resources/stubs/Microsoft.VisualStudio.TestTools.UnitTesting.cs diff --git a/docs/codeql/codeql-language-guides/customizing-library-models-for-java.rst b/docs/codeql/codeql-language-guides/customizing-library-models-for-java.rst index 950fdc7bf24..37e1fb35450 100644 --- a/docs/codeql/codeql-language-guides/customizing-library-models-for-java.rst +++ b/docs/codeql/codeql-language-guides/customizing-library-models-for-java.rst @@ -397,6 +397,8 @@ The following components are supported: - **SyntheticGlobal[**\ `name`\ **]** selects the synthetic global with name `name`. - **ArrayElement** selects the elements of an array. - **Element** selects the elements of a collection-like container. +- **WithoutElement** selects a collection-like container without its elements. This is for input only. +- **WithElement** selects the elements of a collection-like container, but points to the container itself. This is for input only. - **MapKey** selects the element keys of a map. - **MapValue** selects the element values of a map. diff --git a/docs/codeql/ql-language-reference/ql-language-specification.rst b/docs/codeql/ql-language-reference/ql-language-specification.rst index 04cddf80a87..7a61d87cf65 100644 --- a/docs/codeql/ql-language-reference/ql-language-specification.rst +++ b/docs/codeql/ql-language-reference/ql-language-specification.rst @@ -122,12 +122,19 @@ Global environments The global module environment has a single entry ``QlBuiltins``. -The global type environment has entries for the primitive types ``int``, ``float``, ``string``, ``boolean``, and ``date``, as well as any types defined in the database schema. +The global type environment has entries for the primitive types ``int``, ``float``, ``string``, ``boolean``, and ``date``. -The global predicate environment includes all the built-in classless predicates, as well as any extensional predicates declared in the database schema. +The global predicate environment includes all the built-in classless predicates. The three global signature environments are empty. +Database schema environments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The database schema type environment has entries for types declared in the database schema. + +The database schema predicate environment has entries for extensional predicates declared in the database schema. + The program is invalid if any of these environments is not definite. Module environments @@ -146,7 +153,7 @@ These are defined as follows (with X denoting the type of entity we are currentl 2. for each module which the current module directly imports (excluding ``private`` imports - see "`Import directives <#import-directives>`__"): all entries from the *exported X environment* that have a key not present in the *publically declared X environment* of the current module, and - 3. if X is ``predicates``, then for each module signature ``S`` that is implemented by the current module: an entry for each module signature default predicate in ``S`` that does not have the same name and arity as any of the entries in the **publically declared predicate environment** of the current module. + 3. if X is ``predicate``, then for each module signature ``S`` that is implemented by the current module: an entry for each module signature default predicate in ``S`` that does not have the same name and arity as any of the entries in the **publically declared predicate environment** of the current module. - The *visible X environment* of a module is the union of @@ -160,7 +167,9 @@ These are defined as follows (with X denoting the type of entity we are currentl 5. if there is an enclosing module: all entries from the *visible X environment* of the enclosing module that have a key not present in the *publically declared X environment* of the current module, and - 6. all parameters of the current module that are of type X. + 6. if there is no enclosing module and X is either ``type`` or ``predicate``: all entries from the *database schema X environment* that have a key not present in the *publically declared X environment* of the current module, and + + 7. all parameters of the current module that are of type X. The program is invalid if any of these environments is not definite. diff --git a/docs/codeql/reusables/supported-frameworks.rst b/docs/codeql/reusables/supported-frameworks.rst index 068bd27207f..bfe2d08451e 100644 --- a/docs/codeql/reusables/supported-frameworks.rst +++ b/docs/codeql/reusables/supported-frameworks.rst @@ -310,3 +310,4 @@ and the CodeQL library pack ``codeql/swift-all`` (`changelog `__, Database `SQLite.swift `__, Database `WebKit `__, User interface library + `UIKit `__, User interface library diff --git a/go/extractor/cli/go-autobuilder/go-autobuilder.go b/go/extractor/cli/go-autobuilder/go-autobuilder.go index 3f065dc3597..35bd4dca731 100644 --- a/go/extractor/cli/go-autobuilder/go-autobuilder.go +++ b/go/extractor/cli/go-autobuilder/go-autobuilder.go @@ -773,7 +773,7 @@ func installDependenciesAndBuild() { goModVersion, goModVersionFound := tryReadGoDirective(buildInfo) - if goModVersionFound && semver.Compare("v"+goModVersion, getEnvGoSemVer()) >= 0 { + if goModVersionFound && semver.Compare("v"+goModVersion, getEnvGoSemVer()) > 0 { diagnostics.EmitNewerGoVersionNeeded() } diff --git a/go/go.mod b/go/go.mod index 516bc228330..5c2de23af33 100644 --- a/go/go.mod +++ b/go/go.mod @@ -3,11 +3,11 @@ module github.com/github/codeql-go go 1.20 require ( - golang.org/x/mod v0.8.0 - golang.org/x/tools v0.6.0 + golang.org/x/mod v0.12.0 + golang.org/x/tools v0.11.1 ) require ( - golang.org/x/sys v0.5.0 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect ) diff --git a/go/go.sum b/go/go.sum index d92f42b1d05..062f29a6437 100644 --- a/go/go.sum +++ b/go/go.sum @@ -8,6 +8,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVD golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -23,6 +25,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9w golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -34,7 +38,11 @@ golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= +golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= diff --git a/go/ql/lib/CHANGELOG.md b/go/ql/lib/CHANGELOG.md index 92bc9a062ad..7477a48f7b2 100644 --- a/go/ql/lib/CHANGELOG.md +++ b/go/ql/lib/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.6.2 + +### Minor Analysis Improvements + +* Logrus' `WithContext` methods are no longer treated as if they output the values stored in that context to a log message. + ## 0.6.1 ### New Features diff --git a/go/ql/lib/change-notes/2023-07-28-logrus-with-context.md b/go/ql/lib/change-notes/released/0.6.2.md similarity index 74% rename from go/ql/lib/change-notes/2023-07-28-logrus-with-context.md rename to go/ql/lib/change-notes/released/0.6.2.md index 6417ca2e82a..89ba2910d9d 100644 --- a/go/ql/lib/change-notes/2023-07-28-logrus-with-context.md +++ b/go/ql/lib/change-notes/released/0.6.2.md @@ -1,4 +1,5 @@ ---- -category: minorAnalysis ---- +## 0.6.2 + +### Minor Analysis Improvements + * Logrus' `WithContext` methods are no longer treated as if they output the values stored in that context to a log message. diff --git a/go/ql/lib/codeql-pack.release.yml b/go/ql/lib/codeql-pack.release.yml index 80fb0899f64..5501a2a1cc5 100644 --- a/go/ql/lib/codeql-pack.release.yml +++ b/go/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.6.1 +lastReleaseVersion: 0.6.2 diff --git a/go/ql/lib/qlpack.yml b/go/ql/lib/qlpack.yml index 7447adf77b3..164d8d5b235 100644 --- a/go/ql/lib/qlpack.yml +++ b/go/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/go-all -version: 0.6.2-dev +version: 0.6.3-dev groups: go dbscheme: go.dbscheme extractor: go diff --git a/go/ql/lib/semmle/go/StringOps.qll b/go/ql/lib/semmle/go/StringOps.qll index 66e65a646ac..37a13a19714 100644 --- a/go/ql/lib/semmle/go/StringOps.qll +++ b/go/ql/lib/semmle/go/StringOps.qll @@ -3,7 +3,6 @@ */ import go -private import semmle.go.dataflow.DataFlowForStringsNewReplacer /** Provides predicates and classes for working with string operations. */ module StringOps { @@ -223,20 +222,10 @@ module StringOps { } } - /** - * A configuration for tracking flow from a call to `strings.NewReplacer` to - * the receiver of a call to `strings.Replacer.Replace` or - * `strings.Replacer.WriteString`. - */ - private class StringsNewReplacerConfiguration extends DataFlowForStringsNewReplacer::Configuration - { - StringsNewReplacerConfiguration() { this = "StringsNewReplacerConfiguration" } + private module StringsNewReplacerConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof StringsNewReplacerCall } - override predicate isSource(DataFlow::Node source) { - source instanceof StringsNewReplacerCall - } - - override predicate isSink(DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { exists(DataFlow::MethodCallNode call | sink = call.getReceiver() and call.getTarget().hasQualifiedName("strings", "Replacer", ["Replace", "WriteString"]) @@ -244,6 +233,12 @@ module StringOps { } } + /** + * Tracks data flow from a call to `strings.NewReplacer` to the receiver of + * a call to `strings.Replacer.Replace` or `strings.Replacer.WriteString`. + */ + private module StringsNewReplacerFlow = DataFlow::Global; + /** * A call to `strings.Replacer.Replace` or `strings.Replacer.WriteString`. */ @@ -251,11 +246,8 @@ module StringOps { string replacedString; StringsReplacerReplaceOrWriteString() { - exists( - StringsNewReplacerConfiguration config, StringsNewReplacerCall source, - DataFlow::Node sink, DataFlow::MethodCallNode call - | - config.hasFlow(source, sink) and + exists(StringsNewReplacerCall source, DataFlow::Node sink, DataFlow::MethodCallNode call | + StringsNewReplacerFlow::flow(source, sink) and sink = call.getReceiver() and replacedString = source.getAReplacedArgument().getStringValue() and ( diff --git a/go/ql/lib/semmle/go/dataflow/DataFlowForStringsNewReplacer.qll b/go/ql/lib/semmle/go/dataflow/DataFlowForStringsNewReplacer.qll deleted file mode 100644 index 4dc612d4ee8..00000000000 --- a/go/ql/lib/semmle/go/dataflow/DataFlowForStringsNewReplacer.qll +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Provides a library for local (intra-procedural) and global (inter-procedural) - * data flow analysis: deciding whether data can flow from a _source_ to a - * _sink_. - * - * Unless configured otherwise, _flow_ means that the exact value of - * the source may reach the sink. We do not track flow across pointer - * dereferences or array indexing. To track these types of flow, where the - * exact value may not be preserved, import - * `semmle.code.go.dataflow.TaintTracking`. - * - * To use global (interprocedural) data flow, extend the class - * `DataFlow::Configuration` as documented on that class. To use local - * (intraprocedural) data flow, invoke `DataFlow::localFlow` or - * `DataFlow::LocalFlowStep` with arguments of type `DataFlow::Node`. - */ - -import go - -/** - * Provides a library for local (intra-procedural) and global (inter-procedural) - * data flow analysis. - */ -module DataFlowForStringsNewReplacer { - import semmle.go.dataflow.internal.DataFlowImplForStringsNewReplacer - import Properties -} diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll deleted file mode 100644 index 1975ac9781f..00000000000 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll +++ /dev/null @@ -1,402 +0,0 @@ -/** - * DEPRECATED: Use `Global` and `GlobalWithState` instead. - * - * Provides a `Configuration` class backwards-compatible interface to the data - * flow library. - */ - -private import DataFlowImplCommon -private import DataFlowImplSpecific::Private -import DataFlowImplSpecific::Public -private import DataFlowImpl -import DataFlowImplCommonPublic -import FlowStateString -private import codeql.util.Unit - -/** - * A configuration of interprocedural data flow analysis. This defines - * sources, sinks, and any other configurable aspect of the analysis. Each - * use of the global data flow library must define its own unique extension - * of this abstract class. To create a configuration, extend this class with - * a subclass whose characteristic predicate is a unique singleton string. - * For example, write - * - * ```ql - * class MyAnalysisConfiguration extends DataFlow::Configuration { - * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } - * // Override `isSource` and `isSink`. - * // Optionally override `isBarrier`. - * // Optionally override `isAdditionalFlowStep`. - * } - * ``` - * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and - * the edges are those data-flow steps that preserve the value of the node - * along with any additional edges defined by `isAdditionalFlowStep`. - * Specifying nodes in `isBarrier` will remove those nodes from the graph, and - * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going - * and/or out-going edges from those nodes, respectively. - * - * Then, to query whether there is flow between some `source` and `sink`, - * write - * - * ```ql - * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) - * ``` - * - * Multiple configurations can coexist, but two classes extending - * `DataFlow::Configuration` should never depend on each other. One of them - * should instead depend on a `DataFlow2::Configuration`, a - * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. - */ -abstract class Configuration extends string { - bindingset[this] - Configuration() { any() } - - /** - * Holds if `source` is a relevant data flow source. - */ - predicate isSource(Node source) { none() } - - /** - * Holds if `source` is a relevant data flow source with the given initial - * `state`. - */ - predicate isSource(Node source, FlowState state) { none() } - - /** - * Holds if `sink` is a relevant data flow sink. - */ - predicate isSink(Node sink) { none() } - - /** - * Holds if `sink` is a relevant data flow sink accepting `state`. - */ - predicate isSink(Node sink, FlowState state) { none() } - - /** - * Holds if data flow through `node` is prohibited. This completely removes - * `node` from the data flow graph. - */ - predicate isBarrier(Node node) { none() } - - /** - * Holds if data flow through `node` is prohibited when the flow state is - * `state`. - */ - predicate isBarrier(Node node, FlowState state) { none() } - - /** Holds if data flow into `node` is prohibited. */ - predicate isBarrierIn(Node node) { none() } - - /** Holds if data flow out of `node` is prohibited. */ - predicate isBarrierOut(Node node) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited. - */ - deprecated predicate isBarrierGuard(BarrierGuard guard) { none() } - - /** - * DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead. - * - * Holds if data flow through nodes guarded by `guard` is prohibited when - * the flow state is `state` - */ - deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() } - - /** - * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. - */ - predicate isAdditionalFlowStep(Node node1, Node node2) { none() } - - /** - * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. - * This step is only applicable in `state1` and updates the flow state to `state2`. - */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { - none() - } - - /** - * Holds if an arbitrary number of implicit read steps of content `c` may be - * taken at `node`. - */ - predicate allowImplicitRead(Node node, ContentSet c) { none() } - - /** - * Gets the virtual dispatch branching limit when calculating field flow. - * This can be overridden to a smaller value to improve performance (a - * value of 0 disables field flow), or a larger value to get more results. - */ - int fieldFlowBranchLimit() { result = 2 } - - /** - * Gets a data flow configuration feature to add restrictions to the set of - * valid flow paths. - * - * - `FeatureHasSourceCallContext`: - * Assume that sources have some existing call context to disallow - * conflicting return-flow directly following the source. - * - `FeatureHasSinkCallContext`: - * Assume that sinks have some existing call context to disallow - * conflicting argument-to-parameter flow directly preceding the sink. - * - `FeatureEqualSourceSinkCallContext`: - * Implies both of the above and additionally ensures that the entire flow - * path preserves the call context. - * - * These features are generally not relevant for typical end-to-end data flow - * queries, but should only be used for constructing paths that need to - * somehow be pluggable in another path context. - */ - FlowFeature getAFeature() { none() } - - /** Holds if sources should be grouped in the result of `hasFlowPath`. */ - predicate sourceGrouping(Node source, string sourceGroup) { none() } - - /** Holds if sinks should be grouped in the result of `hasFlowPath`. */ - predicate sinkGrouping(Node sink, string sinkGroup) { none() } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - */ - predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - * - * The corresponding paths are generated from the end-points and the graph - * included in the module `PathGraph`. - */ - predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) } - - /** - * DEPRECATED: Use `FlowExploration` instead. - * - * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` - * measured in approximate number of interprocedural steps. - */ - deprecated int explorationLimit() { none() } - - /** - * Holds if hidden nodes should be included in the data flow graph. - * - * This feature should only be used for debugging or when the data flow graph - * is not visualized (for example in a `path-problem` query). - */ - predicate includeHiddenNodes() { none() } -} - -/** - * This class exists to prevent mutual recursion between the user-overridden - * member predicates of `Configuration` and the rest of the data-flow library. - * Good performance cannot be guaranteed in the presence of such recursion, so - * it should be replaced by using more than one copy of the data flow library. - */ -abstract private class ConfigurationRecursionPrevention extends Configuration { - bindingset[this] - ConfigurationRecursionPrevention() { any() } - - override predicate hasFlow(Node source, Node sink) { - strictcount(Node n | this.isSource(n)) < 0 - or - strictcount(Node n | this.isSource(n, _)) < 0 - or - strictcount(Node n | this.isSink(n)) < 0 - or - strictcount(Node n | this.isSink(n, _)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0 - or - super.hasFlow(source, sink) - } -} - -/** A bridge class to access the deprecated `isBarrierGuard`. */ -private class BarrierGuardGuardedNodeBridge extends Unit { - abstract predicate guardedNode(Node n, Configuration config); - - abstract predicate guardedNode(Node n, FlowState state, Configuration config); -} - -private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge { - deprecated override predicate guardedNode(Node n, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g) and - n = g.getAGuardedNode() - ) - } - - deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) { - exists(BarrierGuard g | - config.isBarrierGuard(g, state) and - n = g.getAGuardedNode() - ) - } -} - -private FlowState relevantState(Configuration config) { - config.isSource(_, result) or - config.isSink(_, result) or - config.isBarrier(_, result) or - config.isAdditionalFlowStep(_, result, _, _) or - config.isAdditionalFlowStep(_, _, _, result) -} - -private newtype TConfigState = - TMkConfigState(Configuration config, FlowState state) { - state = relevantState(config) or state instanceof FlowStateEmpty - } - -private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) } - -private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) } - -private predicate singleConfiguration() { 1 = strictcount(Configuration c) } - -private module Config implements FullStateConfigSig { - class FlowState = TConfigState; - - predicate isSource(Node source, FlowState state) { - getConfig(state).isSource(source, getState(state)) - or - getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty - } - - predicate isSink(Node sink) { none() } - - predicate isSink(Node sink, FlowState state) { - getConfig(state).isSink(sink, getState(state)) - or - getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty - } - - predicate isBarrier(Node node) { none() } - - predicate isBarrier(Node node, FlowState state) { - getConfig(state).isBarrier(node, getState(state)) or - getConfig(state).isBarrier(node) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or - any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state)) - } - - predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) } - - predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) } - - predicate isAdditionalFlowStep(Node node1, Node node2) { - singleConfiguration() and - any(Configuration config).isAdditionalFlowStep(node1, node2) - } - - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { - getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and - getConfig(state2) = getConfig(state1) - or - not singleConfiguration() and - getConfig(state1).isAdditionalFlowStep(node1, node2) and - state2 = state1 - } - - predicate allowImplicitRead(Node node, ContentSet c) { - any(Configuration config).allowImplicitRead(node, c) - } - - predicate neverSkip(Node node) { none() } - - int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } - - FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } - - predicate sourceGrouping(Node source, string sourceGroup) { - any(Configuration config).sourceGrouping(source, sourceGroup) - } - - predicate sinkGrouping(Node sink, string sinkGroup) { - any(Configuration config).sinkGrouping(sink, sinkGroup) - } - - predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() } -} - -private import Impl as I - -/** - * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. - * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. - */ -class PathNode instanceof I::PathNode { - /** Gets a textual representation of this element. */ - final string toString() { result = super.toString() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - final string toStringWithContext() { result = super.toStringWithContext() } - - /** - * 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/). - */ - final predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { result = super.getNode() } - - /** Gets the `FlowState` of this node. */ - final FlowState getState() { result = getState(super.getState()) } - - /** Gets the associated configuration. */ - final Configuration getConfiguration() { result = getConfig(super.getState()) } - - /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { result = super.getASuccessor() } - - /** Holds if this node is a source. */ - final predicate isSource() { super.isSource() } - - /** Holds if this node is a grouping of source nodes. */ - final predicate isSourceGroup(string group) { super.isSourceGroup(group) } - - /** Holds if this node is a grouping of sink nodes. */ - final predicate isSinkGroup(string group) { super.isSinkGroup(group) } -} - -module PathGraph = I::PathGraph; - -private predicate hasFlow(Node source, Node sink, Configuration config) { - exists(PathNode source0, PathNode sink0 | - hasFlowPath(source0, sink0, config) and - source0.getNode() = source and - sink0.getNode() = sink - ) -} - -private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) { - I::flowPath(source, sink) and source.getConfiguration() = config -} - -private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) } - -predicate flowsTo = hasFlow/3; diff --git a/go/ql/lib/semmle/go/security/AllocationSizeOverflow.qll b/go/ql/lib/semmle/go/security/AllocationSizeOverflow.qll index 1cc9334d556..64d4fb96baa 100644 --- a/go/ql/lib/semmle/go/security/AllocationSizeOverflow.qll +++ b/go/ql/lib/semmle/go/security/AllocationSizeOverflow.qll @@ -14,9 +14,11 @@ module AllocationSizeOverflow { import AllocationSizeOverflowCustomizations::AllocationSizeOverflow /** + * DEPRECATED: Use copies of `FindLargeLensConfig` and `FindLargeLensFlow` instead. + * * A taint-tracking configuration for identifying `len(...)` calls whose argument may be large. */ - class FindLargeLensConfiguration extends TaintTracking2::Configuration { + deprecated class FindLargeLensConfiguration extends TaintTracking2::Configuration { FindLargeLensConfiguration() { this = "AllocationSizeOverflow::FindLargeLens" } override predicate isSource(DataFlow::Node nd) { nd instanceof Source } @@ -30,16 +32,31 @@ module AllocationSizeOverflow { override predicate isSanitizer(DataFlow::Node nd) { nd instanceof Sanitizer } } + private module FindLargeLensConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node nd) { nd instanceof Source } + + predicate isSink(DataFlow::Node nd) { nd = Builtin::len().getACall().getArgument(0) } + + predicate isBarrier(DataFlow::Node nd) { nd instanceof Sanitizer } + } + + /** + * Tracks taint flow to find `len(...)` calls whose argument may be large. + */ + private module FindLargeLensFlow = TaintTracking::Global; + private DataFlow::CallNode getALargeLenCall() { - exists(FindLargeLensConfiguration config, DataFlow::Node lenArg | config.hasFlow(_, lenArg) | + exists(DataFlow::Node lenArg | FindLargeLensFlow::flow(_, lenArg) | result.getArgument(0) = lenArg ) } /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for identifying allocation-size overflows. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "AllocationSizeOverflow" } override predicate isSource(DataFlow::Node nd) { nd instanceof Source } @@ -70,4 +87,33 @@ module AllocationSizeOverflow { override predicate isSanitizer(DataFlow::Node nd) { nd instanceof Sanitizer } } + + /** + * Holds if `nd` is at a position where overflow might occur, and its result is used to compute + * allocation size `allocsz`. + */ + predicate isSinkWithAllocationSize(DataFlow::Node nd, DataFlow::Node allocsz) { + nd.(Sink).getAllocationSize() = allocsz + } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { isSinkWithAllocationSize(sink, _) } + + predicate isBarrier(DataFlow::Node nd) { nd instanceof Sanitizer } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + additionalStep(pred, succ) + or + exists(DataFlow::CallNode c | + c = getALargeLenCall() and + pred = c.getArgument(0) and + succ = c + ) + } + } + + /** Tracks taint flow to find allocation-size overflows. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/CleartextLogging.qll b/go/ql/lib/semmle/go/security/CleartextLogging.qll index 128680bc33a..f2dc659a764 100644 --- a/go/ql/lib/semmle/go/security/CleartextLogging.qll +++ b/go/ql/lib/semmle/go/security/CleartextLogging.qll @@ -17,6 +17,8 @@ module CleartextLogging { import CleartextLoggingCustomizations::CleartextLogging /** + * DEPRECATED: Use `Flow` instead. + * * A data-flow tracking configuration for clear-text logging of sensitive information. * * This configuration identifies flows from `Source`s, which are sources of @@ -25,7 +27,7 @@ module CleartextLogging { * added either by extending the relevant class, or by subclassing this configuration itself, * and amending the sources and sinks. */ - class Configuration extends DataFlow::Configuration { + deprecated class Configuration extends DataFlow::Configuration { Configuration() { this = "CleartextLogging" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -56,4 +58,43 @@ module CleartextLogging { not any(Protobuf::GetMethod gm).taintStep(src, trg) } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { + node instanceof Barrier + or + exists(DataFlow::CallNode call | node = call.getResult() | + call.getTarget() = Builtin::error().getType().getMethod("Error") + or + call.getTarget().(Method).hasQualifiedName("fmt", "Stringer", "String") + ) + } + + predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg) { + // A taint propagating data-flow edge through structs: a tainted write taints the entire struct. + exists(Write write | + write.writesField(trg.(DataFlow::PostUpdateNode).getPreUpdateNode(), _, src) + ) + or + // taint steps that do not include flow through fields. Field reads would produce FPs due to + // the additional taint step above that taints whole structs from individual field writes. + TaintTracking::localTaintStep(src, trg) and + not TaintTracking::fieldReadStep(src, trg) and + // Also exclude protobuf field fetches, since they amount to single field reads. + not any(Protobuf::GetMethod gm).taintStep(src, trg) + } + } + + /** + * Tracks data flow for reasoning about clear-text logging of sensitive + * information, from `Source`s, which are sources of sensitive data, to + * `Sink`s, which is an abstract class representing all the places sensitive + * data may be stored in cleartext. Additional sources or sinks can be added + * by extending the relevant class. + */ + module Flow = DataFlow::Global; } diff --git a/go/ql/lib/semmle/go/security/CommandInjection.qll b/go/ql/lib/semmle/go/security/CommandInjection.qll index 2b68b5563c6..12bd1e51296 100644 --- a/go/ql/lib/semmle/go/security/CommandInjection.qll +++ b/go/ql/lib/semmle/go/security/CommandInjection.qll @@ -17,10 +17,12 @@ module CommandInjection { import CommandInjectionCustomizations::CommandInjection /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about command-injection vulnerabilities * with sinks which are not sanitized by `--`. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "CommandInjection" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -39,6 +41,22 @@ module CommandInjection { } } + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { + exists(Sink s | sink = s | not s.doubleDashIsSanitizing()) + } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** + * Tracks taint flow for reasoning about command-injection vulnerabilities + * with sinks which are not sanitized by `--`. + */ + module Flow = TaintTracking::Global; + private class ArgumentArrayWithDoubleDash extends DataFlow::Node { int doubleDashIndex; @@ -79,10 +97,12 @@ module CommandInjection { } /** + * DEPRECATED: Use `DoubleDashSanitizingFlow` instead. + * * A taint-tracking configuration for reasoning about command-injection vulnerabilities * with sinks which are sanitized by `--`. */ - class DoubleDashSanitizingConfiguration extends TaintTracking::Configuration { + deprecated class DoubleDashSanitizingConfiguration extends TaintTracking::Configuration { DoubleDashSanitizingConfiguration() { this = "CommandInjectionWithDoubleDashSanitizer" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -101,4 +121,21 @@ module CommandInjection { guard instanceof SanitizerGuard } } + + private module DoubleDashSanitizingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { exists(Sink s | sink = s | s.doubleDashIsSanitizing()) } + + predicate isBarrier(DataFlow::Node node) { + node instanceof Sanitizer or + node = any(ArgumentArrayWithDoubleDash array).getASanitizedElement() + } + } + + /** + * Tracks taint flow for reasoning about command-injection vulnerabilities + * with sinks which are sanitized by `--`. + */ + module DoubleDashSanitizingFlow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/ExternalAPIs.qll b/go/ql/lib/semmle/go/security/ExternalAPIs.qll index 8a27ce28c2a..4d2f780d2ff 100644 --- a/go/ql/lib/semmle/go/security/ExternalAPIs.qll +++ b/go/ql/lib/semmle/go/security/ExternalAPIs.qll @@ -188,8 +188,12 @@ class UnknownExternalApiDataNode extends ExternalApiDataNode { /** DEPRECATED: Alias for UnknownExternalApiDataNode */ deprecated class UnknownExternalAPIDataNode = UnknownExternalApiDataNode; -/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. */ -class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration { +/** + * DEPRECATED: Use `UntrustedDataToExternalApiFlow` instead. + * + * A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. + */ +deprecated class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration { UntrustedDataToExternalApiConfig() { this = "UntrustedDataToExternalAPIConfig" } override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } @@ -197,11 +201,26 @@ class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode } } +private module UntrustedDataConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + + predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode } +} + +/** + * Tracks data flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. + */ +module UntrustedDataToExternalApiFlow = DataFlow::Global; + /** DEPRECATED: Alias for UntrustedDataToExternalApiConfig */ deprecated class UntrustedDataToExternalAPIConfig = UntrustedDataToExternalApiConfig; -/** A configuration for tracking flow from `RemoteFlowSource`s to `UnknownExternalApiDataNode`s. */ -class UntrustedDataToUnknownExternalApiConfig extends TaintTracking::Configuration { +/** + * DEPRECATED: Use `UntrustedDataToUnknownExternalApiFlow` instead. + * + * A configuration for tracking flow from `RemoteFlowSource`s to `UnknownExternalApiDataNode`s. + */ +deprecated class UntrustedDataToUnknownExternalApiConfig extends TaintTracking::Configuration { UntrustedDataToUnknownExternalApiConfig() { this = "UntrustedDataToUnknownExternalAPIConfig" } override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } @@ -212,14 +231,24 @@ class UntrustedDataToUnknownExternalApiConfig extends TaintTracking::Configurati /** DEPRECATED: Alias for UntrustedDataToUnknownExternalApiConfig */ deprecated class UntrustedDataToUnknownExternalAPIConfig = UntrustedDataToUnknownExternalApiConfig; +private module UntrustedDataToUnknownExternalApiConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + + predicate isSink(DataFlow::Node sink) { sink instanceof UnknownExternalApiDataNode } +} + +/** + * Tracks data flow from `RemoteFlowSource`s to `UnknownExternalApiDataNode`s. + */ +module UntrustedDataToUnknownExternalApiFlow = + DataFlow::Global; + /** A node representing untrusted data being passed to an external API. */ class UntrustedExternalApiDataNode extends ExternalApiDataNode { - UntrustedExternalApiDataNode() { any(UntrustedDataToExternalApiConfig c).hasFlow(_, this) } + UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) } /** Gets a source of untrusted data which is passed to this external API data node. */ - DataFlow::Node getAnUntrustedSource() { - any(UntrustedDataToExternalApiConfig c).hasFlow(result, this) - } + DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) } } /** DEPRECATED: Alias for UntrustedExternalApiDataNode */ diff --git a/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll b/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll index 298dd7c8513..f4161beb45d 100644 --- a/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll +++ b/go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll @@ -51,11 +51,13 @@ private predicate isIncorrectIntegerConversion(int sourceBitSize, int sinkBitSiz } /** + * DEPRECATED: use `Flow` instead. + * * A taint-tracking configuration for reasoning about when an integer * obtained from parsing a string flows to a type conversion to a smaller * integer types, which could cause unexpected values. */ -class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration { +deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration { boolean sinkIsSigned; int sourceBitSize; int sinkBitSize; @@ -148,6 +150,119 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration { } } +/** Flow state for ConversionWithoutBoundsCheckConfig. */ +newtype IntegerConversionFlowState = + /** Keep track of info about the source and potential sinks. */ + TFlowstate(boolean sinkIsSigned, int sourceBitSize, int sinkBitSize) { + sinkIsSigned in [true, false] and + isIncorrectIntegerConversion(sourceBitSize, sinkBitSize) + } + +/** Gets the bit size of the source. */ +int getSourceBitSize(IntegerConversionFlowState state) { state = TFlowstate(_, result, _) } + +private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConfigSig { + class FlowState = IntegerConversionFlowState; + + predicate isSource(DataFlow::Node source, FlowState state) { + exists( + DataFlow::CallNode c, IntegerParser::Range ip, int apparentBitSize, int effectiveBitSize + | + c.getTarget() = ip and source = c.getResult(0) + | + ( + apparentBitSize = ip.getTargetBitSize() + or + // If we are reading a variable, check if it is + // `strconv.IntSize`, and use 0 if it is. + exists(DataFlow::Node rawBitSize | rawBitSize = ip.getTargetBitSizeInput().getNode(c) | + if rawBitSize = any(Strconv::IntSize intSize).getARead() + then apparentBitSize = 0 + else apparentBitSize = rawBitSize.getIntValue() + ) + ) and + ( + if apparentBitSize = 0 + then effectiveBitSize = getIntTypeBitSize(source.getFile()) + else effectiveBitSize = apparentBitSize + ) and + // `effectiveBitSize` could be any value between 0 and 64, but we + // can round it up to the nearest size of an integer type without + // changing behavior. + exists(int sourceBitSize | + sourceBitSize = min(int b | b in [0, 8, 16, 32, 64] and b >= effectiveBitSize) + | + state = TFlowstate(_, sourceBitSize, _) + ) + ) + } + + /** + * Holds if `sink` is a typecast to an integer type with size `bitSize` (where + * 0 represents architecture-dependent) and the expression being typecast is + * not also in a right-shift expression. We allow this case because it is + * a common pattern to serialise `byte(v)`, `byte(v >> 8)`, and so on. + */ + additional predicate isSinkWithBitSize( + DataFlow::TypeCastNode sink, boolean sinkIsSigned, int bitSize + ) { + sink.asExpr() instanceof ConversionExpr and + exists(IntegerType integerType | sink.getResultType().getUnderlyingType() = integerType | + ( + bitSize = integerType.getSize() + or + not exists(integerType.getSize()) and + bitSize = getIntTypeBitSize(sink.getFile()) + ) and + if integerType instanceof SignedIntegerType then sinkIsSigned = true else sinkIsSigned = false + ) and + not exists(ShrExpr shrExpr | + shrExpr.getLeftOperand().getGlobalValueNumber() = + sink.getOperand().asExpr().getGlobalValueNumber() or + shrExpr.getLeftOperand().(AndExpr).getAnOperand().getGlobalValueNumber() = + sink.getOperand().asExpr().getGlobalValueNumber() + ) + } + + predicate isSink(DataFlow::Node sink, FlowState state) { + // We use the argument of the type conversion as the configuration sink so that we + // can sanitize the result of the conversion to prevent flow on to further sinks + // without needing to use `isSanitizerOut`, which doesn't work with flow states + // (and therefore the legacy `TaintTracking::Configuration` class). + exists(boolean sinkIsSigned, int sinkBitSize | + state = TFlowstate(sinkIsSigned, _, sinkBitSize) + | + isSinkWithBitSize(sink.getASuccessor(), sinkIsSigned, sinkBitSize) + ) + } + + predicate isBarrier(DataFlow::Node node, FlowState state) { + exists(boolean sinkIsSigned, int sourceBitSize, int sinkBitSize | + state = TFlowstate(sinkIsSigned, sourceBitSize, sinkBitSize) + | + // To catch flows that only happen on 32-bit architectures we + // consider an architecture-dependent sink bit size to be 32. + exists(UpperBoundCheckGuard g, int bitSize | + if sinkBitSize != 0 then bitSize = sinkBitSize else bitSize = 32 + | + node = DataFlow::BarrierGuard::getABarrierNodeForGuard(g) and + g.isBoundFor(bitSize, sinkIsSigned) + ) + or + exists(int bitSize | + isIncorrectIntegerConversion(sourceBitSize, bitSize) and + isSinkWithBitSize(node, sinkIsSigned, bitSize) + ) + ) + } +} + +/** + * Tracks taint flow from an integer obtained from parsing a string that flows + * to a type conversion to a smaller integer type, which could cause data loss. + */ +module Flow = TaintTracking::GlobalWithState; + private predicate upperBoundCheckGuard(DataFlow::Node g, Expr e, boolean branch) { g.(UpperBoundCheckGuard).checks(e, branch) } diff --git a/go/ql/lib/semmle/go/security/InsecureRandomness.qll b/go/ql/lib/semmle/go/security/InsecureRandomness.qll index 38916389c9b..f450d796dad 100644 --- a/go/ql/lib/semmle/go/security/InsecureRandomness.qll +++ b/go/ql/lib/semmle/go/security/InsecureRandomness.qll @@ -17,10 +17,12 @@ module InsecureRandomness { import InsecureRandomnessCustomizations::InsecureRandomness /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about random values that are * not cryptographically secure. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "InsecureRandomness" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -32,4 +34,21 @@ module InsecureRandomness { override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } } + + /** Holds if `sink` is a sink for this configuration with kind `kind`. */ + predicate isSinkWithKind(Sink sink, string kind) { kind = sink.getKind() } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { isSinkWithKind(sink, _) } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** + * Tracks taint flow from randomly generated values which are not + * cryptographically secure to cryptographic applications. + */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/LogInjection.qll b/go/ql/lib/semmle/go/security/LogInjection.qll index 70e0947c53b..854076d40e7 100644 --- a/go/ql/lib/semmle/go/security/LogInjection.qll +++ b/go/ql/lib/semmle/go/security/LogInjection.qll @@ -15,6 +15,8 @@ module LogInjection { import LogInjectionCustomizations::LogInjection /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about log injection vulnerabilities. */ deprecated class Configuration extends TaintTracking::Configuration { @@ -31,9 +33,7 @@ module LogInjection { } } - /** - * A taint-tracking configuration for reasoning about log injection vulnerabilities. - */ + /** Config for reasoning about log injection vulnerabilities. */ module Config implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -42,5 +42,6 @@ module LogInjection { predicate isBarrier(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer } } + /** Tracks taint flow for reasoning about log injection vulnerabilities. */ module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/OpenUrlRedirect.qll b/go/ql/lib/semmle/go/security/OpenUrlRedirect.qll index c0df90197c5..b5f3691a8d0 100644 --- a/go/ql/lib/semmle/go/security/OpenUrlRedirect.qll +++ b/go/ql/lib/semmle/go/security/OpenUrlRedirect.qll @@ -18,9 +18,11 @@ module OpenUrlRedirect { import OpenUrlRedirectCustomizations::OpenUrlRedirect /** + * DEPRECATED: Use `Flow` instead. + * * A data-flow configuration for reasoning about unvalidated URL redirections. */ - class Configuration extends DataFlow::Configuration { + deprecated class Configuration extends DataFlow::Configuration { Configuration() { this = "OpenUrlRedirect" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -63,4 +65,45 @@ module OpenUrlRedirect { guard instanceof BarrierGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Barrier } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + // taint steps that do not include flow through fields + TaintTracking::localTaintStep(pred, succ) and not TaintTracking::fieldReadStep(pred, succ) + or + // explicit extra taint steps for this query + any(AdditionalStep s).hasTaintStep(pred, succ) + or + // propagate to a URL when its host is assigned to + exists(Write w, Field f, SsaWithFields v | f.hasQualifiedName("net/url", "URL", "Host") | + w.writesField(v.getAUse(), f, pred) and succ = v.getAUse() + ) + or + // propagate out of most URL fields, but not `ForceQuery` and `Scheme` + exists(Field f, string fn | + f.hasQualifiedName("net/url", "URL", fn) and + not fn in ["ForceQuery", "Scheme"] + | + succ.(Read).readsField(pred, f) + ) + } + + predicate isBarrierOut(DataFlow::Node node) { + // block propagation of this unsafe value when its host is overwritten + exists(Write w, Field f | f.hasQualifiedName("net/url", "URL", "Host") | + w.writesField(node.getASuccessor(), f, _) + ) + or + hostnameSanitizingPrefixEdge(node, _) + } + } + + /** Tracks taint flow from unvalidated, untrusted data to URL redirections. */ + module Flow = DataFlow::Global; } diff --git a/go/ql/lib/semmle/go/security/ReflectedXss.qll b/go/ql/lib/semmle/go/security/ReflectedXss.qll index ba5d253c066..3e7e19b9920 100644 --- a/go/ql/lib/semmle/go/security/ReflectedXss.qll +++ b/go/ql/lib/semmle/go/security/ReflectedXss.qll @@ -17,9 +17,11 @@ module ReflectedXss { import ReflectedXssCustomizations::ReflectedXss /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about XSS. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "ReflectedXss" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -35,4 +37,15 @@ module ReflectedXss { guard instanceof SanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** Tracks taint flow from untrusted data to XSS attack vectors. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/RequestForgery.qll b/go/ql/lib/semmle/go/security/RequestForgery.qll index a4cea3efd8d..a7c5c457c6c 100644 --- a/go/ql/lib/semmle/go/security/RequestForgery.qll +++ b/go/ql/lib/semmle/go/security/RequestForgery.qll @@ -17,9 +17,11 @@ module RequestForgery { import RequestForgeryCustomizations::RequestForgery /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about request forgery. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "RequestForgery" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -47,4 +49,24 @@ module RequestForgery { super.isSanitizerGuard(guard) or guard instanceof SanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + + predicate isBarrierOut(DataFlow::Node node) { node instanceof SanitizerEdge } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + // propagate to a URL when its host is assigned to + exists(Write w, Field f, SsaWithFields v | f.hasQualifiedName("net/url", "URL", "Host") | + w.writesField(v.getAUse(), f, pred) and succ = v.getAUse() + ) + } + } + + /** Tracks taint flow from untrusted data to request forgery attack vectors. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/SafeUrlFlow.qll b/go/ql/lib/semmle/go/security/SafeUrlFlow.qll index c77f2dab4ad..f4d1a535bad 100644 --- a/go/ql/lib/semmle/go/security/SafeUrlFlow.qll +++ b/go/ql/lib/semmle/go/security/SafeUrlFlow.qll @@ -17,9 +17,11 @@ module SafeUrlFlow { import SafeUrlFlowCustomizations::SafeUrlFlow /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about safe URLs. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "SafeUrlFlow" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -42,4 +44,29 @@ module SafeUrlFlow { node instanceof SanitizerEdge } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + // propagate to a URL when its host is assigned to + exists(Write w, Field f, SsaWithFields v | f.hasQualifiedName("net/url", "URL", "Host") | + w.writesField(v.getAUse(), f, node1) and node2 = v.getAUse() + ) + } + + predicate isBarrierOut(DataFlow::Node node) { + // block propagation of this safe value when its host is overwritten + exists(Write w, Field f | f.hasQualifiedName("net/url", "URL", "Host") | + w.writesField(node.getASuccessor(), f, _) + ) + or + node instanceof SanitizerEdge + } + } + + /** Tracks taint flow for reasoning about safe URLs. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/SqlInjection.qll b/go/ql/lib/semmle/go/security/SqlInjection.qll index 24acf4cf594..002eea2e990 100644 --- a/go/ql/lib/semmle/go/security/SqlInjection.qll +++ b/go/ql/lib/semmle/go/security/SqlInjection.qll @@ -14,9 +14,11 @@ module SqlInjection { import SqlInjectionCustomizations::SqlInjection /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about SQL-injection vulnerabilities. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "SqlInjection" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -36,4 +38,19 @@ module SqlInjection { guard instanceof SanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + NoSql::isAdditionalMongoTaintStep(pred, succ) + } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** Tracks taint flow for reasoning about SQL-injection vulnerabilities. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/StoredCommand.qll b/go/ql/lib/semmle/go/security/StoredCommand.qll index fde23a26650..5c8443ee620 100644 --- a/go/ql/lib/semmle/go/security/StoredCommand.qll +++ b/go/ql/lib/semmle/go/security/StoredCommand.qll @@ -17,9 +17,11 @@ import CommandInjectionCustomizations */ module StoredCommand { /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about command-injection vulnerabilities. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "StoredCommand" } override predicate isSource(DataFlow::Node source) { @@ -39,4 +41,19 @@ module StoredCommand { guard instanceof CommandInjection::SanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source instanceof StoredXss::Source and + // exclude file names, since those are not generally an issue + not source instanceof StoredXss::FileNameSource + } + + predicate isSink(DataFlow::Node sink) { sink instanceof CommandInjection::Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof CommandInjection::Sanitizer } + } + + /** Tracks taint flow for reasoning about command-injection vulnerabilities. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/StoredXss.qll b/go/ql/lib/semmle/go/security/StoredXss.qll index 970e931ff81..ba2b4d4c085 100644 --- a/go/ql/lib/semmle/go/security/StoredXss.qll +++ b/go/ql/lib/semmle/go/security/StoredXss.qll @@ -17,9 +17,11 @@ module StoredXss { import StoredXssCustomizations::StoredXss /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about XSS. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "StoredXss" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -35,4 +37,15 @@ module StoredXss { guard instanceof SanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** Tracks taint flow for reasoning about XSS. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/StringBreak.qll b/go/ql/lib/semmle/go/security/StringBreak.qll index 327149fd2a3..fed3b9b1443 100644 --- a/go/ql/lib/semmle/go/security/StringBreak.qll +++ b/go/ql/lib/semmle/go/security/StringBreak.qll @@ -14,10 +14,12 @@ module StringBreak { import StringBreakCustomizations::StringBreak /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about unsafe-quoting vulnerabilities, * parameterized with the type of quote being tracked. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Quote quote; Configuration() { this = "StringBreak" + quote } @@ -31,4 +33,26 @@ module StringBreak { override predicate isSanitizer(DataFlow::Node nd) { quote = nd.(Sanitizer).getQuote() } } + + private module Config implements DataFlow::StateConfigSig { + /** The flow state that we track is the type of quote used. */ + class FlowState = Quote; + + predicate isSource(DataFlow::Node source, FlowState state) { + source instanceof Source and exists(state) + } + + predicate isSink(DataFlow::Node sink, FlowState state) { state = sink.(Sink).getQuote() } + + predicate isBarrier(DataFlow::Node node, FlowState state) { + state = node.(Sanitizer).getQuote() + } + } + + /** + * Tracks taint flow from untrusted data which may contain single or double + * quotes to uses where those quotes need to be escaped. The type of quote + * is accessible through the `Sink`. + */ + module Flow = TaintTracking::GlobalWithState; } diff --git a/go/ql/lib/semmle/go/security/TaintedPath.qll b/go/ql/lib/semmle/go/security/TaintedPath.qll index c753b039c15..26009554c24 100644 --- a/go/ql/lib/semmle/go/security/TaintedPath.qll +++ b/go/ql/lib/semmle/go/security/TaintedPath.qll @@ -12,9 +12,11 @@ module TaintedPath { import TaintedPathCustomizations::TaintedPath /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about path-traversal vulnerabilities. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "TaintedPath" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -26,4 +28,15 @@ module TaintedPath { node instanceof Sanitizer } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** Tracks taint flow for reasoning about path-traversal vulnerabilities. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/UnsafeUnzipSymlink.qll b/go/ql/lib/semmle/go/security/UnsafeUnzipSymlink.qll index 6465917d705..354bc70b8c9 100644 --- a/go/ql/lib/semmle/go/security/UnsafeUnzipSymlink.qll +++ b/go/ql/lib/semmle/go/security/UnsafeUnzipSymlink.qll @@ -14,9 +14,11 @@ module UnsafeUnzipSymlink { import UnsafeUnzipSymlinkCustomizations::UnsafeUnzipSymlink /** + * DEPRECATED: Use copies of `EvalSymlinksConfig` and `EvalSymlinksFlow` instead. + * * A taint-flow configuration tracking archive header fields flowing to a `path/filepath.EvalSymlinks` call. */ - class EvalSymlinksConfiguration extends TaintTracking2::Configuration { + deprecated class EvalSymlinksConfiguration extends TaintTracking2::Configuration { EvalSymlinksConfiguration() { this = "Archive header field symlinks resolved" } override predicate isSource(DataFlow::Node source) { source instanceof FilenameWithSymlinks } @@ -33,18 +35,35 @@ module UnsafeUnzipSymlink { } } + // Archive header field symlinks resolved + private module EvalSymlinksConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof FilenameWithSymlinks } + + predicate isSink(DataFlow::Node sink) { sink instanceof EvalSymlinksSink } + + predicate isBarrier(DataFlow::Node node) { node instanceof EvalSymlinksInvalidator } + } + + /** + * Tracks taint flow from archive header fields to + * `path/filepath.EvalSymlinks` calls. + */ + private module EvalSymlinksFlow = TaintTracking::Global; + /** * Holds if `node` is an archive header field read that flows to a `path/filepath.EvalSymlinks` call. */ private predicate symlinksEvald(DataFlow::Node node) { - exists(EvalSymlinksConfiguration c | c.hasFlow(getASimilarReadNode(node), _)) + EvalSymlinksFlow::flow(getASimilarReadNode(node), _) } /** + * DEPRECATED: Use `Flow` instead. + * * A taint-flow configuration tracking archive header fields flowing to an `os.Symlink` call, * which never flow to a `path/filepath.EvalSymlinks` call. */ - class SymlinkConfiguration extends TaintTracking::Configuration { + deprecated class SymlinkConfiguration extends TaintTracking::Configuration { SymlinkConfiguration() { this = "Unsafe unzipping of symlinks" } override predicate isSource(DataFlow::Node source) { @@ -63,4 +82,21 @@ module UnsafeUnzipSymlink { guard instanceof SymlinkSanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source instanceof FilenameWithSymlinks and + not symlinksEvald(source) + } + + predicate isSink(DataFlow::Node sink) { sink instanceof SymlinkSink } + + predicate isBarrier(DataFlow::Node node) { node instanceof SymlinkSanitizer } + } + + /** + * Tracks taint flow from archive header fields to an `os.Symlink` call, + * which never flow to a `path/filepath.EvalSymlinks` call. + */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/XPathInjection.qll b/go/ql/lib/semmle/go/security/XPathInjection.qll index c158a95442e..51e0c90dc89 100644 --- a/go/ql/lib/semmle/go/security/XPathInjection.qll +++ b/go/ql/lib/semmle/go/security/XPathInjection.qll @@ -14,9 +14,11 @@ module XPathInjection { import XPathInjectionCustomizations::XPathInjection /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about untrusted user input used in an XPath expression. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "XPathInjection" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -32,4 +34,18 @@ module XPathInjection { guard instanceof SanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** + * Tracks taint flow for reasoning about untrusted user input used in an + * XPath expression. + */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/lib/semmle/go/security/ZipSlip.qll b/go/ql/lib/semmle/go/security/ZipSlip.qll index 3b8ed8e359d..bbdc964e529 100644 --- a/go/ql/lib/semmle/go/security/ZipSlip.qll +++ b/go/ql/lib/semmle/go/security/ZipSlip.qll @@ -12,9 +12,11 @@ module ZipSlip { import ZipSlipCustomizations::ZipSlip /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about zip-slip vulnerabilities. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "ZipSlip" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -30,4 +32,15 @@ module ZipSlip { guard instanceof SanitizerGuard } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** Tracks taint flow for reasoning about zip-slip vulnerabilities. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/src/CHANGELOG.md b/go/ql/src/CHANGELOG.md index aa3b9019d46..4fd67d3e427 100644 --- a/go/ql/src/CHANGELOG.md +++ b/go/ql/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.2 + +No user-facing changes. + ## 0.6.1 No user-facing changes. diff --git a/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql b/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql index c78a9d4a36f..8846f52fb74 100644 --- a/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql +++ b/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql @@ -14,7 +14,6 @@ */ import go -import DataFlow::PathGraph /** * Holds if a `flag` for use with `os.OpenFile` implies that the resulting @@ -124,25 +123,27 @@ predicate isHandledSync(DataFlow::Node sink, DataFlow::CallNode syncCall) { not unhandledCall(syncCall) } -/** - * A data flow configuration which traces writable file handles resulting from calls to - * `os.OpenFile` to `os.File.Close` calls on them. - */ -class UnhandledFileCloseDataFlowConfiguration extends DataFlow::Configuration { - UnhandledFileCloseDataFlowConfiguration() { this = "UnhandledCloseWritableHandle" } +module UnhandledFileCloseConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { isWritableFileHandle(source, _) } - override predicate isSource(DataFlow::Node source) { isWritableFileHandle(source, _) } - - override predicate isSink(DataFlow::Node sink) { isCloseSink(sink, _) } + predicate isSink(DataFlow::Node sink) { isCloseSink(sink, _) } } +/** + * Tracks data flow for reasoning about which writable file handles resulting from calls to + * `os.OpenFile` have `os.File.Close` called on them. + */ +module UnhandledFileCloseFlow = DataFlow::Global; + +import UnhandledFileCloseFlow::PathGraph + from - UnhandledFileCloseDataFlowConfiguration cfg, DataFlow::PathNode source, - DataFlow::CallNode openCall, DataFlow::PathNode sink, DataFlow::CallNode closeCall + UnhandledFileCloseFlow::PathNode source, DataFlow::CallNode openCall, + UnhandledFileCloseFlow::PathNode sink, DataFlow::CallNode closeCall where // find data flow from an `os.OpenFile` call to an `os.File.Close` call // where the handle is writable - cfg.hasFlowPath(source, sink) and + UnhandledFileCloseFlow::flowPath(source, sink) and isWritableFileHandle(source.getNode(), openCall) and // get the `CallNode` corresponding to the sink isCloseSink(sink.getNode(), closeCall) diff --git a/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql b/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql index 80cfd2bc4f4..48731179127 100644 --- a/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql +++ b/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql @@ -13,7 +13,6 @@ */ import go -import DataFlow::PathGraph /** * Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`, @@ -80,10 +79,8 @@ predicate regexpGuardsError(RegexpPattern regexp) { ) } -class Config extends DataFlow::Configuration { - Config() { this = "IncompleteHostNameRegexp::Config" } - - predicate isSourceString(DataFlow::Node source, string hostPart) { +module IncompleteHostNameRegexpConfig implements DataFlow::ConfigSig { + additional predicate isSourceString(DataFlow::Node source, string hostPart) { exists(Expr e | e = source.asExpr() and isIncompleteHostNameRegexpPattern(e.getStringValue(), hostPart) @@ -95,9 +92,9 @@ class Config extends DataFlow::Configuration { ) } - override predicate isSource(DataFlow::Node source) { this.isSourceString(source, _) } + predicate isSource(DataFlow::Node source) { isSourceString(source, _) } - override predicate isSink(DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern and forall(Http::RequestHandler handler | regexpGuardsHandler(sink, handler) | not handler = getASafeHandler() @@ -106,8 +103,14 @@ class Config extends DataFlow::Configuration { } } -from Config c, DataFlow::PathNode source, DataFlow::PathNode sink, string hostPart -where c.hasFlowPath(source, sink) and c.isSourceString(source.getNode(), hostPart) +module Flow = DataFlow::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, string hostPart +where + Flow::flowPath(source, sink) and + IncompleteHostNameRegexpConfig::isSourceString(source.getNode(), hostPart) select source, source, sink, "This regular expression has an unescaped dot before '" + hostPart + "', " + "so it might match more hosts than expected when $@.", sink, "the regular expression is used" diff --git a/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql b/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql index f1b326f8a11..df93440ac52 100644 --- a/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql +++ b/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql @@ -60,10 +60,8 @@ predicate isInterestingUnanchoredRegexpString(string re, string msg) { "hosts may come before or after it." } -class Config extends DataFlow::Configuration { - Config() { this = "MissingRegexpAnchor::Config" } - - predicate isSourceString(DataFlow::Node source, string msg) { +module Config implements DataFlow::ConfigSig { + additional predicate isSourceString(DataFlow::Node source, string msg) { exists(Expr e | e = source.asExpr() | isInterestingUnanchoredRegexpString(e.getStringValue(), msg) or @@ -71,11 +69,13 @@ class Config extends DataFlow::Configuration { ) } - override predicate isSource(DataFlow::Node source) { this.isSourceString(source, _) } + predicate isSource(DataFlow::Node source) { isSourceString(source, _) } - override predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern } + predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern } } -from Config c, DataFlow::PathNode source, string msg -where c.hasFlowPath(source, _) and c.isSourceString(source.getNode(), msg) -select source.getNode(), msg +module Flow = DataFlow::Global; + +from DataFlow::Node source, string msg +where Flow::flow(source, _) and Config::isSourceString(source, msg) +select source, msg diff --git a/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql b/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql index 056b3c4e96c..81cc634346a 100644 --- a/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql +++ b/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql @@ -12,7 +12,6 @@ */ import go -import DataFlow::PathGraph /** * Holds if `source` corresponds to a string literal that contains an escaped `character`. @@ -28,11 +27,8 @@ predicate containsEscapedCharacter(DataFlow::Node source, string character) { ) } -/** A dataflow configuration that traces strings containing suspicious escape sequences to a use as a regular expression. */ -class Config extends DataFlow::Configuration { - Config() { this = "SuspiciousRegexpEscape" } - - predicate isSourceString(DataFlow::Node source, string report) { +module SuspiciousCharacterInRegexpConfig implements DataFlow::ConfigSig { + additional predicate isSourceString(DataFlow::Node source, string report) { containsEscapedCharacter(source, "a") and report = "the bell character \\a; did you mean \\\\a, the Vim alphabetic character class (use [[:alpha:]] instead) or \\\\A, the beginning of text?" @@ -41,12 +37,22 @@ class Config extends DataFlow::Configuration { report = "a literal backspace \\b; did you mean \\\\b, a word boundary?" } - override predicate isSource(DataFlow::Node source) { this.isSourceString(source, _) } + predicate isSource(DataFlow::Node source) { isSourceString(source, _) } - override predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern } + predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern } } -from Config c, DataFlow::PathNode source, DataFlow::PathNode sink, string report -where c.hasFlowPath(source, sink) and c.isSourceString(source.getNode(), report) +/** + * Tracks data flow from strings containing suspicious escape sequences to a + * use as a regular expression. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, string report +where + Flow::flowPath(source, sink) and + SuspiciousCharacterInRegexpConfig::isSourceString(source.getNode(), report) select source, source, sink, "This string literal that is $@ contains " + report, sink, "used as a regular expression" diff --git a/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql b/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql index d5c06a288b6..4ab22af3a45 100644 --- a/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql +++ b/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql @@ -11,10 +11,10 @@ import go import semmle.go.security.ExternalAPIs -import DataFlow::PathGraph +import UntrustedDataToExternalApiFlow::PathGraph -from UntrustedDataToExternalApiConfig config, DataFlow::PathNode source, DataFlow::PathNode sink -where config.hasFlowPath(source, sink) +from UntrustedDataToExternalApiFlow::PathNode source, UntrustedDataToExternalApiFlow::PathNode sink +where UntrustedDataToExternalApiFlow::flowPath(source, sink) select sink, source, sink, "Call to " + sink.getNode().(ExternalApiDataNode).getFunctionDescription() + " with untrusted data from $@.", source, source.toString() diff --git a/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql b/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql index 6a954628fae..23945e38d46 100644 --- a/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql +++ b/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql @@ -11,11 +11,12 @@ import go import semmle.go.security.ExternalAPIs -import DataFlow::PathGraph +import UntrustedDataToUnknownExternalApiFlow::PathGraph from - UntrustedDataToUnknownExternalApiConfig config, DataFlow::PathNode source, DataFlow::PathNode sink -where config.hasFlowPath(source, sink) + UntrustedDataToUnknownExternalApiFlow::PathNode source, + UntrustedDataToUnknownExternalApiFlow::PathNode sink +where UntrustedDataToUnknownExternalApiFlow::flowPath(source, sink) select sink, source, sink, "Call to " + sink.getNode().(UnknownExternalApiDataNode).getFunctionDescription() + " with untrusted data from $@.", source, source.toString() diff --git a/go/ql/src/Security/CWE-022/TaintedPath.ql b/go/ql/src/Security/CWE-022/TaintedPath.ql index 870fc2e97e1..fcad51b0342 100644 --- a/go/ql/src/Security/CWE-022/TaintedPath.ql +++ b/go/ql/src/Security/CWE-022/TaintedPath.ql @@ -16,10 +16,10 @@ */ import go -import semmle.go.security.TaintedPath::TaintedPath -import DataFlow::PathGraph +import semmle.go.security.TaintedPath +import TaintedPath::Flow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from TaintedPath::Flow::PathNode source, TaintedPath::Flow::PathNode sink +where TaintedPath::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(), "user-provided value" diff --git a/go/ql/src/Security/CWE-022/UnsafeUnzipSymlink.ql b/go/ql/src/Security/CWE-022/UnsafeUnzipSymlink.ql index a905149c149..5adfef28cee 100644 --- a/go/ql/src/Security/CWE-022/UnsafeUnzipSymlink.ql +++ b/go/ql/src/Security/CWE-022/UnsafeUnzipSymlink.ql @@ -15,11 +15,11 @@ */ import go -import DataFlow::PathGraph -import semmle.go.security.UnsafeUnzipSymlink::UnsafeUnzipSymlink +import semmle.go.security.UnsafeUnzipSymlink +import UnsafeUnzipSymlink::Flow::PathGraph -from SymlinkConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from UnsafeUnzipSymlink::Flow::PathNode source, UnsafeUnzipSymlink::Flow::PathNode sink +where UnsafeUnzipSymlink::Flow::flowPath(source, sink) select source.getNode(), source, sink, "Unresolved path from an archive header, which may point outside the archive root, is used in $@.", sink.getNode(), "symlink creation" diff --git a/go/ql/src/Security/CWE-022/ZipSlip.ql b/go/ql/src/Security/CWE-022/ZipSlip.ql index 5cfb3998f4d..1556d0809a8 100644 --- a/go/ql/src/Security/CWE-022/ZipSlip.ql +++ b/go/ql/src/Security/CWE-022/ZipSlip.ql @@ -13,11 +13,11 @@ */ import go -import semmle.go.security.ZipSlip::ZipSlip -import DataFlow::PathGraph +import semmle.go.security.ZipSlip +import ZipSlip::Flow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from ZipSlip::Flow::PathNode source, ZipSlip::Flow::PathNode sink +where ZipSlip::Flow::flowPath(source, sink) select source.getNode(), source, sink, "Unsanitized archive entry, which may contain '..', is used in a $@.", sink.getNode(), "file system operation" diff --git a/go/ql/src/Security/CWE-078/CommandInjection.ql b/go/ql/src/Security/CWE-078/CommandInjection.ql index 925f2b32ab2..9bcaf4c2493 100644 --- a/go/ql/src/Security/CWE-078/CommandInjection.ql +++ b/go/ql/src/Security/CWE-078/CommandInjection.ql @@ -13,11 +13,17 @@ import go import semmle.go.security.CommandInjection -import DataFlow::PathGraph -from - CommandInjection::Configuration cfg, CommandInjection::DoubleDashSanitizingConfiguration cfg2, - DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) or cfg2.hasFlowPath(source, sink) +module Flow = + DataFlow::MergePathGraph; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where + CommandInjection::Flow::flowPath(source.asPathNode1(), sink.asPathNode1()) or + CommandInjection::DoubleDashSanitizingFlow::flowPath(source.asPathNode2(), sink.asPathNode2()) select sink.getNode(), source, sink, "This command depends on a $@.", source.getNode(), "user-provided value" diff --git a/go/ql/src/Security/CWE-078/StoredCommand.ql b/go/ql/src/Security/CWE-078/StoredCommand.ql index c7675527cc8..000a8238870 100644 --- a/go/ql/src/Security/CWE-078/StoredCommand.ql +++ b/go/ql/src/Security/CWE-078/StoredCommand.ql @@ -13,9 +13,9 @@ import go import semmle.go.security.StoredCommand -import DataFlow::PathGraph +import StoredCommand::Flow::PathGraph -from StoredCommand::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from StoredCommand::Flow::PathNode source, StoredCommand::Flow::PathNode sink +where StoredCommand::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "This command depends on a $@.", source.getNode(), "stored value" diff --git a/go/ql/src/Security/CWE-079/ReflectedXss.ql b/go/ql/src/Security/CWE-079/ReflectedXss.ql index f8da256432a..0fca12ac285 100644 --- a/go/ql/src/Security/CWE-079/ReflectedXss.ql +++ b/go/ql/src/Security/CWE-079/ReflectedXss.ql @@ -13,14 +13,14 @@ */ import go -import semmle.go.security.ReflectedXss::ReflectedXss -import DataFlow::PathGraph +import semmle.go.security.ReflectedXss +import ReflectedXss::Flow::PathGraph from - Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string msg, string part, + ReflectedXss::Flow::PathNode source, ReflectedXss::Flow::PathNode sink, string msg, string part, Locatable partloc where - cfg.hasFlowPath(source, sink) and + ReflectedXss::Flow::flowPath(source, sink) and ( exists(string kind | kind = sink.getNode().(SharedXss::Sink).getSinkKind() | kind = "rawtemplate" and diff --git a/go/ql/src/Security/CWE-079/StoredXss.ql b/go/ql/src/Security/CWE-079/StoredXss.ql index 2649234515b..83628b31042 100644 --- a/go/ql/src/Security/CWE-079/StoredXss.ql +++ b/go/ql/src/Security/CWE-079/StoredXss.ql @@ -13,10 +13,10 @@ */ import go -import semmle.go.security.StoredXss::StoredXss -import DataFlow::PathGraph +import semmle.go.security.StoredXss +import StoredXss::Flow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from StoredXss::Flow::PathNode source, StoredXss::Flow::PathNode sink +where StoredXss::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.", source.getNode(), "stored value" diff --git a/go/ql/src/Security/CWE-089/SqlInjection.ql b/go/ql/src/Security/CWE-089/SqlInjection.ql index 0206f4e88cd..c11b5399256 100644 --- a/go/ql/src/Security/CWE-089/SqlInjection.ql +++ b/go/ql/src/Security/CWE-089/SqlInjection.ql @@ -13,9 +13,9 @@ import go import semmle.go.security.SqlInjection -import DataFlow::PathGraph +import SqlInjection::Flow::PathGraph -from SqlInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from SqlInjection::Flow::PathNode source, SqlInjection::Flow::PathNode sink +where SqlInjection::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "This query depends on a $@.", source.getNode(), "user-provided value" diff --git a/go/ql/src/Security/CWE-089/StringBreak.ql b/go/ql/src/Security/CWE-089/StringBreak.ql index bfd40e91f18..21513566444 100644 --- a/go/ql/src/Security/CWE-089/StringBreak.ql +++ b/go/ql/src/Security/CWE-089/StringBreak.ql @@ -17,10 +17,10 @@ import go import semmle.go.security.StringBreak -import DataFlow::PathGraph +import StringBreak::Flow::PathGraph -from StringBreak::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from StringBreak::Flow::PathNode source, StringBreak::Flow::PathNode sink +where StringBreak::Flow::flowPath(source, sink) select sink.getNode(), source, sink, - "If this $@ contains a " + cfg.getQuote().getType() + " quote, it could break out of " + - "the enclosing quotes.", source.getNode(), "JSON value" + "If this $@ contains a " + sink.getNode().(StringBreak::Sink).getQuote().getType() + + " quote, it could break out of " + "the enclosing quotes.", source.getNode(), "JSON value" diff --git a/go/ql/src/Security/CWE-190/AllocationSizeOverflow.ql b/go/ql/src/Security/CWE-190/AllocationSizeOverflow.ql index 4962a6626fc..869d0d59e9d 100644 --- a/go/ql/src/Security/CWE-190/AllocationSizeOverflow.ql +++ b/go/ql/src/Security/CWE-190/AllocationSizeOverflow.ql @@ -12,15 +12,15 @@ */ import go -import DataFlow::PathGraph import semmle.go.security.AllocationSizeOverflow +import AllocationSizeOverflow::Flow::PathGraph from - AllocationSizeOverflow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, + AllocationSizeOverflow::Flow::PathNode source, AllocationSizeOverflow::Flow::PathNode sink, DataFlow::Node allocsz where - cfg.hasFlowPath(source, sink) and - cfg.isSinkWithAllocationSize(sink.getNode(), allocsz) + AllocationSizeOverflow::Flow::flowPath(source, sink) and + AllocationSizeOverflow::isSinkWithAllocationSize(sink.getNode(), allocsz) select sink, source, sink, "This operation, which is used in an $@, involves a $@ and might overflow.", allocsz, "allocation", source, "potentially large value" diff --git a/go/ql/src/Security/CWE-209/StackTraceExposure.ql b/go/ql/src/Security/CWE-209/StackTraceExposure.ql index 08ef1fe49ff..3440fdba314 100644 --- a/go/ql/src/Security/CWE-209/StackTraceExposure.ql +++ b/go/ql/src/Security/CWE-209/StackTraceExposure.ql @@ -15,7 +15,6 @@ import go import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag -import DataFlow::PathGraph /** * A flag indicating the program is in debug or development mode, or that stack @@ -44,22 +43,16 @@ class DebugStackFunction extends Function { DebugStackFunction() { this.hasQualifiedName("runtime/debug", "Stack") } } -/** - * A taint-tracking configuration that looks for stack traces being written to - * an HTTP response body without an intervening debug- or development-mode conditional. - */ -class StackTraceExposureConfig extends TaintTracking::Configuration { - StackTraceExposureConfig() { this = "StackTraceExposureConfig" } - - override predicate isSource(DataFlow::Node node) { - node.(DataFlow::PostUpdateNode).getPreUpdateNode() = +module StackTraceExposureConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.(DataFlow::PostUpdateNode).getPreUpdateNode() = any(StackFunction f).getACall().getArgument(0) or - node = any(DebugStackFunction f).getACall().getResult() + source = any(DebugStackFunction f).getACall().getResult() } - override predicate isSink(DataFlow::Node node) { node instanceof Http::ResponseBody } + predicate isSink(DataFlow::Node sink) { sink instanceof Http::ResponseBody } - override predicate isSanitizer(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node) { // Sanitise everything controlled by an is-debug-mode check. // Imprecision: I don't try to guess which arm of a branch is intended // to mean debug mode, and which is production mode. @@ -71,8 +64,16 @@ class StackTraceExposureConfig extends TaintTracking::Configuration { } } -from StackTraceExposureConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +/** + * Tracks taint flow for reasoning about stack traces being written to an HTTP + * response body without an intervening debug- or development-mode conditional. + */ +module StackTraceExposureFlow = TaintTracking::Global; + +import StackTraceExposureFlow::PathGraph + +from StackTraceExposureFlow::PathNode source, StackTraceExposureFlow::PathNode sink +where StackTraceExposureFlow::flowPath(source, sink) select sink.getNode(), source, sink, "HTTP response depends on $@ and may be exposed to an external user.", source.getNode(), "stack trace information" diff --git a/go/ql/src/Security/CWE-312/CleartextLogging.ql b/go/ql/src/Security/CWE-312/CleartextLogging.ql index 52e09051dd8..fe7dc881451 100644 --- a/go/ql/src/Security/CWE-312/CleartextLogging.ql +++ b/go/ql/src/Security/CWE-312/CleartextLogging.ql @@ -14,10 +14,10 @@ */ import go -import semmle.go.security.CleartextLogging::CleartextLogging -import DataFlow::PathGraph +import semmle.go.security.CleartextLogging +import CleartextLogging::Flow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from CleartextLogging::Flow::PathNode source, CleartextLogging::Flow::PathNode sink +where CleartextLogging::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "$@ flows to a logging call.", source.getNode(), - "Sensitive data returned by " + source.getNode().(Source).describe() + "Sensitive data returned by " + source.getNode().(CleartextLogging::Source).describe() diff --git a/go/ql/src/Security/CWE-322/InsecureHostKeyCallback.ql b/go/ql/src/Security/CWE-322/InsecureHostKeyCallback.ql index 486283ddcb4..bca436dc299 100644 --- a/go/ql/src/Security/CWE-322/InsecureHostKeyCallback.ql +++ b/go/ql/src/Security/CWE-322/InsecureHostKeyCallback.ql @@ -11,7 +11,6 @@ */ import go -import DataFlow::PathGraph /** The `ssh.InsecureIgnoreHostKey` function, which allows connecting to any host regardless of its host key. */ class InsecureIgnoreHostKey extends Function { @@ -55,45 +54,48 @@ class InsecureHostKeyCallbackFunc extends HostKeyCallbackFunc { } } -/** - * A data-flow configuration for identifying `HostKeyCallbackFunc` instances that reach `ClientConfig.HostKeyCallback` fields. - */ -class HostKeyCallbackAssignmentConfig extends DataFlow::Configuration { - HostKeyCallbackAssignmentConfig() { this = "HostKeyCallbackAssignmentConfig" } - - override predicate isSource(DataFlow::Node source) { source instanceof HostKeyCallbackFunc } +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof HostKeyCallbackFunc } /** * Holds if `sink` is a value written by `write` to a field `ClientConfig.HostKeyCallback`. */ - predicate writeIsSink(DataFlow::Node sink, Write write) { + additional predicate writeIsSink(DataFlow::Node sink, Write write) { exists(Field f | f.hasQualifiedName(CryptoSsh::packagePath(), "ClientConfig", "HostKeyCallback") and write.writesField(_, f, sink) ) } - override predicate isSink(DataFlow::Node sink) { this.writeIsSink(sink, _) } + predicate isSink(DataFlow::Node sink) { writeIsSink(sink, _) } } +/** + * Tracks data flow to identify `HostKeyCallbackFunc` instances that reach + * `ClientConfig.HostKeyCallback` fields. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + /** * Holds if a secure host-check function reaches `sink` or another similar sink. * * A sink is considered similar if it writes to the same variable and field. */ -predicate hostCheckReachesSink(DataFlow::PathNode sink) { - exists(HostKeyCallbackAssignmentConfig config, DataFlow::PathNode source | +predicate hostCheckReachesSink(Flow::PathNode sink) { + exists(Flow::PathNode source | not source.getNode() instanceof InsecureHostKeyCallbackFunc and ( - config.hasFlowPath(source, sink) + Flow::flowPath(source, sink) or exists( - DataFlow::PathNode otherSink, Write sinkWrite, Write otherSinkWrite, + Flow::PathNode otherSink, Write sinkWrite, Write otherSinkWrite, SsaWithFields sinkAccessPath, SsaWithFields otherSinkAccessPath | - config.hasFlowPath(source, otherSink) and - config.writeIsSink(sink.getNode(), sinkWrite) and - config.writeIsSink(otherSink.getNode(), otherSinkWrite) and + Flow::flowPath(source, otherSink) and + Config::writeIsSink(sink.getNode(), sinkWrite) and + Config::writeIsSink(otherSink.getNode(), otherSinkWrite) and sinkWrite.writesField(sinkAccessPath.getAUse(), _, sink.getNode()) and otherSinkWrite.writesField(otherSinkAccessPath.getAUse(), _, otherSink.getNode()) and otherSinkAccessPath = sinkAccessPath.similar() @@ -102,9 +104,9 @@ predicate hostCheckReachesSink(DataFlow::PathNode sink) { ) } -from HostKeyCallbackAssignmentConfig config, DataFlow::PathNode source, DataFlow::PathNode sink +from Flow::PathNode source, Flow::PathNode sink where - config.hasFlowPath(source, sink) and + Flow::flowPath(source, sink) and source.getNode() instanceof InsecureHostKeyCallbackFunc and // Exclude cases where a good access-path function reaches the same or a similar sink // (these probably indicate optional host-checking) diff --git a/go/ql/src/Security/CWE-326/InsufficientKeySize.ql b/go/ql/src/Security/CWE-326/InsufficientKeySize.ql index f0485e91edd..19db3ef63ae 100644 --- a/go/ql/src/Security/CWE-326/InsufficientKeySize.ql +++ b/go/ql/src/Security/CWE-326/InsufficientKeySize.ql @@ -11,29 +11,30 @@ */ import go -import DataFlow::PathGraph -/** - * A data flow tracking configuration for tracking flow from RSA key length to - * calls to RSA key generation functions. - */ -class RsaKeyTrackingConfiguration extends DataFlow::Configuration { - RsaKeyTrackingConfiguration() { this = "RsaKeyTrackingConfiguration" } +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.getIntValue() < 2048 } - override predicate isSource(DataFlow::Node source) { source.getIntValue() < 2048 } - - override predicate isSink(DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { exists(DataFlow::CallNode c | sink = c.getArgument(1) and c.getTarget().hasQualifiedName("crypto/rsa", "GenerateKey") ) } - override predicate isBarrier(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node) { node = DataFlow::BarrierGuard::getABarrierNode() } } +/** + * Tracks data flow from an RSA key length to a calls to an RSA key generation + * function. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + /** * Holds if `g` is a comparison which guarantees that `e` is at least 2048 on `branch`, * considered as a barrier guard for key sizes. @@ -50,6 +51,6 @@ predicate comparisonBarrierGuard(DataFlow::Node g, Expr e, boolean branch) { ) } -from RsaKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) select sink, source, sink, "The size of this RSA key should be at least 2048 bits." diff --git a/go/ql/src/Security/CWE-327/InsecureTLS.ql b/go/ql/src/Security/CWE-327/InsecureTLS.ql index e8edadbb11d..a4a9ab1f549 100644 --- a/go/ql/src/Security/CWE-327/InsecureTLS.ql +++ b/go/ql/src/Security/CWE-327/InsecureTLS.ql @@ -12,7 +12,6 @@ */ import go -import DataFlow::PathGraph import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag /** @@ -51,17 +50,11 @@ int getASecureTlsVersion() { */ int getATlsVersion() { result = getASecureTlsVersion() or isInsecureTlsVersion(result, _, _) } -/** - * A taint-tracking configuration for tracking flow from TLS versions to the - * `tls.Config.MinVersion` and `tls.Config.MaxVersion` fields. - */ -class TlsVersionFlowConfig extends TaintTracking::Configuration { - TlsVersionFlowConfig() { this = "TlsVersionFlowConfig" } - +module TlsVersionFlowConfig implements DataFlow::ConfigSig { /** * Holds if `source` is a TLS version source yielding value `val`. */ - predicate intIsSource(DataFlow::Node source, int val) { + additional predicate intIsSource(DataFlow::Node source, int val) { val = source.getIntValue() and val = getATlsVersion() and not DataFlow::isReturnedWithError(source) @@ -70,25 +63,29 @@ class TlsVersionFlowConfig extends TaintTracking::Configuration { /** * Holds if `fieldWrite` writes `sink` to `base`.`fld`, where `fld` is a TLS version field. */ - predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { + additional predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { fld.hasQualifiedName("crypto/tls", "Config", ["MinVersion", "MaxVersion"]) and fieldWrite.writesField(base, fld, sink) } - override predicate isSource(DataFlow::Node source) { this.intIsSource(source, _) } + predicate isSource(DataFlow::Node source) { intIsSource(source, _) } - override predicate isSink(DataFlow::Node sink) { this.isSink(sink, _, _, _) } + predicate isSink(DataFlow::Node sink) { isSink(sink, _, _, _) } } +/** + * Tracks taint flow from TLS versions to the `tls.Config.MinVersion` and + * `tls.Config.MaxVersion` fields. + */ +module TlsVersionFlow = TaintTracking::Global; + /** * Holds if `config` exhibits a secure TLS version flowing from `source` to `sink`, which flows into `fld`. */ -predicate secureTlsVersionFlow( - TlsVersionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink, Field fld -) { +predicate secureTlsVersionFlow(DataFlow::Node source, DataFlow::Node sink, Field fld) { exists(int version | - config.hasFlowPath(source, sink) and - config.intIsSource(source.getNode(), version) and + TlsVersionFlow::flow(source, sink) and + TlsVersionFlowConfig::intIsSource(source, version) and not isInsecureTlsVersion(version, _, fld.getName()) ) } @@ -96,17 +93,17 @@ predicate secureTlsVersionFlow( /** * Holds if a secure TLS version reaches `sink`, which flows into `fld`. */ -predicate secureTlsVersionFlowsToSink(DataFlow::PathNode sink, Field fld) { - secureTlsVersionFlow(_, _, sink, fld) +predicate secureTlsVersionFlowsToSink(DataFlow::Node sink, Field fld) { + secureTlsVersionFlow(_, sink, fld) } /** * Holds if a secure TLS version may reach `accessPath`.`fld` */ predicate secureTlsVersionFlowsToField(SsaWithFields accessPath, Field fld) { - exists(TlsVersionFlowConfig config, DataFlow::PathNode sink, DataFlow::Node base | - secureTlsVersionFlow(config, _, sink, fld) and - config.isSink(sink.getNode(), fld, base, _) and + exists(DataFlow::Node sink, DataFlow::Node base | + secureTlsVersionFlow(_, sink, fld) and + TlsVersionFlowConfig::isSink(sink, fld, base, _) and accessPath.getAUse() = base ) } @@ -124,17 +121,18 @@ DataFlow::Node nodeOrDeref(DataFlow::Node node) { * to a field of `base`. `message` describes the specific problem found. */ predicate isInsecureTlsVersionFlow( - DataFlow::PathNode source, DataFlow::PathNode sink, string message, DataFlow::Node base + TlsVersionFlow::PathNode source, TlsVersionFlow::PathNode sink, string message, + DataFlow::Node base ) { - exists(TlsVersionFlowConfig cfg, int version, Field fld | - cfg.hasFlowPath(source, sink) and - cfg.intIsSource(source.getNode(), version) and - cfg.isSink(sink.getNode(), fld, base, _) and + exists(int version, Field fld | + TlsVersionFlow::flowPath(source, sink) and + TlsVersionFlowConfig::intIsSource(source.getNode(), version) and + TlsVersionFlowConfig::isSink(sink.getNode(), fld, base, _) and isInsecureTlsVersion(version, _, fld.getName()) and // Exclude cases where a secure TLS version can also flow to the same // sink, or to different sinks that refer to the same base and field, // which suggests a configurable security mode. - not secureTlsVersionFlowsToSink(sink, fld) and + not secureTlsVersionFlowsToSink(sink.getNode(), fld) and not exists(SsaWithFields insecureAccessPath, SsaWithFields secureAccessPath | nodeOrDeref(insecureAccessPath.getAUse()) = base and secureAccessPath = insecureAccessPath.similar() @@ -152,17 +150,11 @@ predicate isInsecureTlsVersionFlow( ) } -/** - * A taint-tracking configuration for tracking flow from insecure TLS cipher - * suites into a `tls.Config` struct, to the `CipherSuites` field. - */ -class TlsInsecureCipherSuitesFlowConfig extends TaintTracking::Configuration { - TlsInsecureCipherSuitesFlowConfig() { this = "TlsInsecureCipherSuitesFlowConfig" } - +module TlsInsecureCipherSuitesFlowConfig implements DataFlow::ConfigSig { /** * Holds if `source` reads an insecure TLS cipher suite named `suiteName`. */ - predicate isSourceValueEntity(DataFlow::Node source, string suiteName) { + additional predicate isSourceValueEntity(DataFlow::Node source, string suiteName) { exists(DataFlow::ValueEntity val | val.hasQualifiedName("crypto/tls", suiteName) and suiteName = @@ -179,7 +171,7 @@ class TlsInsecureCipherSuitesFlowConfig extends TaintTracking::Configuration { /** * Holds if `source` represents the result of `tls.InsecureCipherSuites()`. */ - predicate isSourceInsecureCipherSuites(DataFlow::Node source) { + additional predicate isSourceInsecureCipherSuites(DataFlow::Node source) { exists(Function insecureCipherSuites | insecureCipherSuites.hasQualifiedName("crypto/tls", "InsecureCipherSuites") | @@ -187,44 +179,54 @@ class TlsInsecureCipherSuitesFlowConfig extends TaintTracking::Configuration { ) } - override predicate isSource(DataFlow::Node source) { - this.isSourceInsecureCipherSuites(source) + predicate isSource(DataFlow::Node source) { + isSourceInsecureCipherSuites(source) or - this.isSourceValueEntity(source, _) + isSourceValueEntity(source, _) } /** * Holds if `fieldWrite` writes `sink` to `base`.`fld`, and `fld` is `tls.Config.CipherSuites`. */ - predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { + additional predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { fld.hasQualifiedName("crypto/tls", "Config", "CipherSuites") and fieldWrite.writesField(base, fld, sink) } - override predicate isSink(DataFlow::Node sink) { this.isSink(sink, _, _, _) } + predicate isSink(DataFlow::Node sink) { isSink(sink, _, _, _) } /** * Declare sinks as out-sanitizers in order to avoid producing superfluous paths where a cipher * is written to CipherSuites, then the list is further extended with either safe or tainted * suites. */ - override predicate isSanitizerOut(DataFlow::Node node) { - super.isSanitizerOut(node) or this.isSink(node) - } + predicate isBarrierOut(DataFlow::Node node) { isSink(node) } } +/** + * Tracks taint flow from insecure TLS cipher suites into the `CipherSuites` + * field of a `tls.Config` struct. + */ +module TlsInsecureCipherSuitesFlow = TaintTracking::Global; + /** * Holds if an insecure TLS cipher suite flows from `source` to `sink`, where `sink` * is written to the CipherSuites list of a `tls.Config` instance. `message` describes * the exact problem found. */ -predicate isInsecureTlsCipherFlow(DataFlow::PathNode source, DataFlow::PathNode sink, string message) { - exists(TlsInsecureCipherSuitesFlowConfig cfg | cfg.hasFlowPath(source, sink) | - exists(string name | cfg.isSourceValueEntity(source.getNode(), name) | +predicate isInsecureTlsCipherFlow( + TlsInsecureCipherSuitesFlow::PathNode source, TlsInsecureCipherSuitesFlow::PathNode sink, + string message +) { + TlsInsecureCipherSuitesFlow::flowPath(source, sink) and + ( + exists(string name | + TlsInsecureCipherSuitesFlowConfig::isSourceValueEntity(source.getNode(), name) + | message = "Use of an insecure cipher suite: " + name + "." ) or - cfg.isSourceInsecureCipherSuites(source.getNode()) and + TlsInsecureCipherSuitesFlowConfig::isSourceInsecureCipherSuites(source.getNode()) and message = "Use of an insecure cipher suite." ) } @@ -260,11 +262,17 @@ FlagKind securityOrTlsVersionFlag() { result = any(LegacyTlsVersionFlag f) } -from DataFlow::PathNode source, DataFlow::PathNode sink, string message +module Flow = + DataFlow::MergePathGraph; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, string message where ( - isInsecureTlsVersionFlow(source, sink, message, _) or - isInsecureTlsCipherFlow(source, sink, message) + isInsecureTlsVersionFlow(source.asPathNode1(), sink.asPathNode1(), message, _) or + isInsecureTlsCipherFlow(source.asPathNode2(), sink.asPathNode2(), message) ) and // Exclude sources or sinks guarded by a feature or legacy flag not [getASecurityFeatureFlagCheck(), getALegacyTlsVersionCheck()] diff --git a/go/ql/src/Security/CWE-338/InsecureRandomness.ql b/go/ql/src/Security/CWE-338/InsecureRandomness.ql index aa26e171e50..cc6bc42be71 100644 --- a/go/ql/src/Security/CWE-338/InsecureRandomness.ql +++ b/go/ql/src/Security/CWE-338/InsecureRandomness.ql @@ -11,19 +11,19 @@ */ import go -import semmle.go.security.InsecureRandomness::InsecureRandomness -import DataFlow::PathGraph +import semmle.go.security.InsecureRandomness +import InsecureRandomness::Flow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string kind +from InsecureRandomness::Flow::PathNode source, InsecureRandomness::Flow::PathNode sink, string kind where - cfg.hasFlowPath(source, sink) and - cfg.isSinkWithKind(sink.getNode(), kind) and + InsecureRandomness::Flow::flowPath(source, sink) and + InsecureRandomness::isSinkWithKind(sink.getNode(), kind) and ( kind != "A password-related function" or sink = - min(DataFlow::PathNode sink2, int line | - cfg.hasFlowPath(_, sink2) and + min(InsecureRandomness::Flow::PathNode sink2, int line | + InsecureRandomness::Flow::flowPath(_, sink2) and sink2.getNode().getRoot() = sink.getNode().getRoot() and sink2.hasLocationInfo(_, line, _, _, _) | diff --git a/go/ql/src/Security/CWE-352/ConstantOauth2State.ql b/go/ql/src/Security/CWE-352/ConstantOauth2State.ql index abe982f7fe5..c251c3e8b00 100644 --- a/go/ql/src/Security/CWE-352/ConstantOauth2State.ql +++ b/go/ql/src/Security/CWE-352/ConstantOauth2State.ql @@ -12,7 +12,6 @@ */ import go -import DataFlow::PathGraph /** * A method that creates a new URL that will send the user @@ -24,18 +23,12 @@ class AuthCodeUrl extends Method { } } -/** - * A flow of a constant string value to a call to `AuthCodeURL` as the - * `state` parameter. - */ -class ConstantStateFlowConf extends DataFlow::Configuration { - ConstantStateFlowConf() { this = "ConstantStateFlowConf" } - - predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { +module ConstantStateFlowConfig implements DataFlow::ConfigSig { + additional predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { exists(AuthCodeUrl m | call = m.getACall() | sink = call.getArgument(0)) } - override predicate isSource(DataFlow::Node source) { + predicate isSource(DataFlow::Node source) { source.isConst() and not DataFlow::isReturnedWithError(source) and // Avoid duplicate paths by not considering reads from constants as sources themselves: @@ -46,9 +39,17 @@ class ConstantStateFlowConf extends DataFlow::Configuration { ) } - override predicate isSink(DataFlow::Node sink) { this.isSinkCall(sink, _) } + predicate isSink(DataFlow::Node sink) { isSinkCall(sink, _) } } +/** + * Tracks data flow of a constant string value to a call to `AuthCodeURL` as + * the `state` parameter. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + /** * Holds if `pred` writes a URL to the `RedirectURL` field of the `succ` `Config` object. * @@ -77,17 +78,8 @@ string getAnOobOauth2Url() { result.matches("%://127.0.0.1%") } -/** - * A flow of a URL indicating the OAuth redirect doesn't point to a publicly - * accessible address, to the receiver of an `AuthCodeURL` call. - * - * Note we accept localhost and 127.0.0.1 on the assumption this is probably a transient - * listener; if it actually is a persistent server then that really is vulnerable to CSRF. - */ -class PrivateUrlFlowsToAuthCodeUrlCall extends DataFlow::Configuration { - PrivateUrlFlowsToAuthCodeUrlCall() { this = "PrivateUrlFlowsToConfig" } - - override predicate isSource(DataFlow::Node source) { +module PrivateUrlFlowsToAuthCodeUrlCallConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.getStringValue() = getAnOobOauth2Url() and // Avoid duplicate paths by excluding constant variable references from // themselves being sources: @@ -98,7 +90,7 @@ class PrivateUrlFlowsToAuthCodeUrlCall extends DataFlow::Configuration { ) } - override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { // Propagate from a RedirectURL field to a whole Config isUrlTaintingConfigStep(pred, succ) or @@ -113,13 +105,23 @@ class PrivateUrlFlowsToAuthCodeUrlCall extends DataFlow::Configuration { ) } - predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { + additional predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { exists(AuthCodeUrl m | call = m.getACall() | sink = call.getReceiver()) } - override predicate isSink(DataFlow::Node sink) { this.isSinkCall(sink, _) } + predicate isSink(DataFlow::Node sink) { isSinkCall(sink, _) } } +/** + * Tracks data flow from a URL indicating the OAuth redirect doesn't point to a publicly + * accessible address to the receiver of an `AuthCodeURL` call. + * + * Note we accept localhost and 127.0.0.1 on the assumption this is probably a transient + * listener; if it actually is a persistent server then that really is vulnerable to CSRF. + */ +module PrivateUrlFlowsToAuthCodeUrlCallFlow = + DataFlow::Global; + /** * Holds if a URL indicating the OAuth redirect doesn't point to a publicly * accessible address, to the receiver of an `AuthCodeURL` call. @@ -128,33 +130,27 @@ class PrivateUrlFlowsToAuthCodeUrlCall extends DataFlow::Configuration { * listener; if it actually is a persistent server then that really is vulnerable to CSRF. */ predicate privateUrlFlowsToAuthCodeUrlCall(DataFlow::CallNode call) { - exists(PrivateUrlFlowsToAuthCodeUrlCall flowConfig, DataFlow::Node receiver | - flowConfig.hasFlowTo(receiver) and - flowConfig.isSinkCall(receiver, call) + exists(DataFlow::Node receiver | + PrivateUrlFlowsToAuthCodeUrlCallFlow::flowTo(receiver) and + PrivateUrlFlowsToAuthCodeUrlCallConfig::isSinkCall(receiver, call) ) } -/** A flow from `golang.org/x/oauth2.Config.AuthCodeUrl`'s result to a logging function. */ -class FlowToPrint extends DataFlow::Configuration { - FlowToPrint() { this = "FlowToPrint" } - - predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { +module FlowToPrintConfig implements DataFlow::ConfigSig { + additional predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { exists(LoggerCall logCall | call = logCall | sink = logCall.getAMessageComponent()) } - override predicate isSource(DataFlow::Node source) { - source = any(AuthCodeUrl m).getACall().getResult() - } + predicate isSource(DataFlow::Node source) { source = any(AuthCodeUrl m).getACall().getResult() } - override predicate isSink(DataFlow::Node sink) { this.isSinkCall(sink, _) } + predicate isSink(DataFlow::Node sink) { isSinkCall(sink, _) } } +module FlowToPrintFlow = DataFlow::Global; + /** Holds if the provided `CallNode`'s result flows to an argument of a printer call. */ predicate resultFlowsToPrinter(DataFlow::CallNode authCodeUrlCall) { - exists(FlowToPrint cfg, DataFlow::PathNode source | - cfg.hasFlowPath(source, _) and - authCodeUrlCall.getResult() = source.getNode() - ) + FlowToPrintFlow::flow(authCodeUrlCall.getResult(), _) } /** Get a data-flow node that reads the value of `os.Stdin`. */ @@ -197,12 +193,10 @@ predicate seemsLikeDoneWithinATerminal(DataFlow::CallNode authCodeUrlCall) { containsCallToStdinScanner(authCodeUrlCall.getRoot()) } -from - ConstantStateFlowConf cfg, DataFlow::PathNode source, DataFlow::PathNode sink, - DataFlow::CallNode sinkCall +from Flow::PathNode source, Flow::PathNode sink, DataFlow::CallNode sinkCall where - cfg.hasFlowPath(source, sink) and - cfg.isSinkCall(sink.getNode(), sinkCall) and + Flow::flowPath(source, sink) and + ConstantStateFlowConfig::isSinkCall(sink.getNode(), sinkCall) and // Exclude cases that seem to be oauth flows done from within a terminal: not seemsLikeDoneWithinATerminal(sinkCall) and not privateUrlFlowsToAuthCodeUrlCall(sinkCall) diff --git a/go/ql/src/Security/CWE-601/BadRedirectCheck.ql b/go/ql/src/Security/CWE-601/BadRedirectCheck.ql index a04f197abab..bc60e1339eb 100644 --- a/go/ql/src/Security/CWE-601/BadRedirectCheck.ql +++ b/go/ql/src/Security/CWE-601/BadRedirectCheck.ql @@ -14,7 +14,6 @@ import go import semmle.go.security.OpenUrlRedirectCustomizations -import DataFlow::PathGraph StringOps::HasPrefix checkForLeadingSlash(SsaWithFields v) { exists(DataFlow::Node substr | @@ -91,16 +90,14 @@ predicate urlPath(DataFlow::Node nd) { ) } -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "BadRedirectCheck" } - - override predicate isSource(DataFlow::Node source) { this.isCheckedSource(source, _) } +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { isCheckedSource(source, _) } /** * Holds if `source` is the first node that flows into a use of a variable that is checked by a * bad redirect check `check`.. */ - predicate isCheckedSource(DataFlow::Node source, DataFlow::Node check) { + additional predicate isCheckedSource(DataFlow::Node source, DataFlow::Node check) { exists(SsaWithFields v | DataFlow::localFlow(source, v.getAUse()) and not exists(source.getAPredecessor()) and @@ -108,12 +105,12 @@ class Configuration extends TaintTracking::Configuration { ) } - override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { // this is very over-approximate, because most filtering is done by the isSource predicate - exists(Write w | w.writesField(succ, _, pred)) + exists(Write w | w.writesField(node2, _, node1)) } - override predicate isSanitizerOut(DataFlow::Node node) { + predicate isBarrierOut(DataFlow::Node node) { // assume this value is safe if something is prepended to it. exists(StringOps::Concatenation conc, int i, int j | i < j | node = conc.getOperand(j) and @@ -125,9 +122,11 @@ class Configuration extends TaintTracking::Configuration { ) } - override predicate isSink(DataFlow::Node sink) { sink instanceof OpenUrlRedirect::Sink } + predicate isSink(DataFlow::Node sink) { sink instanceof OpenUrlRedirect::Sink } } +module Flow = TaintTracking::Global; + /** * Holds there is a check `check` that is a bad redirect check, and `v` is either * checked directly by `check` or checked by a function that contains `check`. @@ -168,10 +167,12 @@ predicate isBadRedirectCheckWrapper(DataFlow::Node check, FuncDef f, FunctionInp ) } -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node check +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, DataFlow::Node check where - cfg.isCheckedSource(source.getNode(), check) and - cfg.hasFlowPath(source, sink) + Config::isCheckedSource(source.getNode(), check) and + Flow::flowPath(source, sink) select check, source, sink, "This is a check that $@, which flows into a $@, has a leading slash, but not that it does not have '/' or '\\' in its second position.", source.getNode(), "this value", sink.getNode(), "redirect" diff --git a/go/ql/src/Security/CWE-601/OpenUrlRedirect.ql b/go/ql/src/Security/CWE-601/OpenUrlRedirect.ql index d633161f916..17ec112955f 100644 --- a/go/ql/src/Security/CWE-601/OpenUrlRedirect.ql +++ b/go/ql/src/Security/CWE-601/OpenUrlRedirect.ql @@ -12,17 +12,15 @@ */ import go -import semmle.go.security.OpenUrlRedirect::OpenUrlRedirect +import semmle.go.security.OpenUrlRedirect import semmle.go.security.SafeUrlFlow -import DataFlow::PathGraph +import OpenUrlRedirect::Flow::PathGraph -from - Configuration cfg, SafeUrlFlow::Configuration scfg, DataFlow::PathNode source, - DataFlow::PathNode sink +from OpenUrlRedirect::Flow::PathNode source, OpenUrlRedirect::Flow::PathNode sink where - cfg.hasFlowPath(source, sink) and + OpenUrlRedirect::Flow::flowPath(source, sink) and // this excludes flow from safe parts of request URLs, for example the full URL when the // doing a redirect from `http://` to `https://` - not scfg.hasFlow(_, sink.getNode()) + not SafeUrlFlow::Flow::flow(_, sink.getNode()) select sink.getNode(), source, sink, "This path to an untrusted URL redirection depends on a $@.", source.getNode(), "user-provided value" diff --git a/go/ql/src/Security/CWE-640/EmailInjection.ql b/go/ql/src/Security/CWE-640/EmailInjection.ql index 74dee8ba179..c3f279a1a86 100644 --- a/go/ql/src/Security/CWE-640/EmailInjection.ql +++ b/go/ql/src/Security/CWE-640/EmailInjection.ql @@ -13,9 +13,9 @@ */ import go -import DataFlow::PathGraph import EmailInjection::EmailInjection +import Flow::PathGraph -from DataFlow::PathNode source, DataFlow::PathNode sink, Configuration config -where config.hasFlowPath(source, sink) +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) select sink, source, sink, "Email content may contain $@.", source.getNode(), "untrusted input" diff --git a/go/ql/src/Security/CWE-640/EmailInjection.qll b/go/ql/src/Security/CWE-640/EmailInjection.qll index 4cf8b382c98..479fe6dc055 100644 --- a/go/ql/src/Security/CWE-640/EmailInjection.qll +++ b/go/ql/src/Security/CWE-640/EmailInjection.qll @@ -17,13 +17,24 @@ module EmailInjection { import EmailInjectionCustomizations::EmailInjection /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about email-injection vulnerabilities. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "Email Injection" } override predicate isSource(DataFlow::Node source) { source instanceof Source } override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + } + + /** Tracks taint flow for reasoning about email-injection vulnerabilities. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/src/Security/CWE-643/XPathInjection.ql b/go/ql/src/Security/CWE-643/XPathInjection.ql index aa732debead..5dbaad158d8 100644 --- a/go/ql/src/Security/CWE-643/XPathInjection.ql +++ b/go/ql/src/Security/CWE-643/XPathInjection.ql @@ -12,17 +12,17 @@ */ import go -import semmle.go.security.XPathInjection::XPathInjection -import DataFlow::PathGraph +import semmle.go.security.XPathInjection +import XPathInjection::Flow::PathGraph /** Holds if `node` is either a string or a byte slice */ -predicate isStringOrByte(DataFlow::PathNode node) { +predicate isStringOrByte(XPathInjection::Flow::PathNode node) { exists(Type t | t = node.getNode().getType().getUnderlyingType() | t instanceof StringType or t instanceof ByteSliceType ) } -from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink -where config.hasFlowPath(source, sink) and isStringOrByte(sink) +from XPathInjection::Flow::PathNode source, XPathInjection::Flow::PathNode sink +where XPathInjection::Flow::flowPath(source, sink) and isStringOrByte(sink) select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(), "user-provided value" diff --git a/go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql b/go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql index 8513c673189..dd34af8e48b 100644 --- a/go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql +++ b/go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql @@ -14,18 +14,17 @@ */ import go -import DataFlow::PathGraph import semmle.go.security.IncorrectIntegerConversionLib +import Flow::PathGraph from - DataFlow::PathNode source, DataFlow::PathNode sink, ConversionWithoutBoundsCheckConfig cfg, - DataFlow::CallNode call, DataFlow::Node sinkConverted + Flow::PathNode source, Flow::PathNode sink, DataFlow::CallNode call, DataFlow::Node sinkConverted where - cfg.hasFlowPath(source, sink) and + Flow::flowPath(source, sink) and call.getResult(0) = source.getNode() and sinkConverted = sink.getNode().getASuccessor() select sinkConverted, source, sink, "Incorrect conversion of " + - describeBitSize(cfg.getSourceBitSize(), getIntTypeBitSize(source.getNode().getFile())) + - " from $@ to a lower bit size type " + sinkConverted.getType().getUnderlyingType().getName() + + describeBitSize(getSourceBitSize(sink.getState()), getIntTypeBitSize(source.getNode().getFile())) + + " from $@ to a lower bit size type " + sinkConverted.getType().getUnderlyingType().getName() + " without an upper bound check.", source, call.getTarget().getQualifiedName() diff --git a/go/ql/src/Security/CWE-918/RequestForgery.ql b/go/ql/src/Security/CWE-918/RequestForgery.ql index a9d0b10dc00..5a5c3265483 100644 --- a/go/ql/src/Security/CWE-918/RequestForgery.ql +++ b/go/ql/src/Security/CWE-918/RequestForgery.ql @@ -11,17 +11,16 @@ */ import go -import semmle.go.security.RequestForgery::RequestForgery +import semmle.go.security.RequestForgery import semmle.go.security.SafeUrlFlow -import DataFlow::PathGraph +import RequestForgery::Flow::PathGraph from - Configuration cfg, SafeUrlFlow::Configuration scfg, DataFlow::PathNode source, - DataFlow::PathNode sink, DataFlow::Node request + RequestForgery::Flow::PathNode source, RequestForgery::Flow::PathNode sink, DataFlow::Node request where - cfg.hasFlowPath(source, sink) and - request = sink.getNode().(Sink).getARequest() and + RequestForgery::Flow::flowPath(source, sink) and + request = sink.getNode().(RequestForgery::Sink).getARequest() and // this excludes flow from safe parts of request URLs, for example the full URL - not scfg.hasFlow(_, sink.getNode()) + not SafeUrlFlow::Flow::flow(_, sink.getNode()) select request, source, sink, "The $@ of this request depends on a $@.", sink.getNode(), - sink.getNode().(Sink).getKind(), source, "user-provided value" + sink.getNode().(RequestForgery::Sink).getKind(), source, "user-provided value" diff --git a/go/ql/src/change-notes/released/0.6.2.md b/go/ql/src/change-notes/released/0.6.2.md new file mode 100644 index 00000000000..43f80640fc5 --- /dev/null +++ b/go/ql/src/change-notes/released/0.6.2.md @@ -0,0 +1,3 @@ +## 0.6.2 + +No user-facing changes. diff --git a/go/ql/src/codeql-pack.release.yml b/go/ql/src/codeql-pack.release.yml index 80fb0899f64..5501a2a1cc5 100644 --- a/go/ql/src/codeql-pack.release.yml +++ b/go/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.6.1 +lastReleaseVersion: 0.6.2 diff --git a/go/ql/src/experimental/CWE-090/LDAPInjection.ql b/go/ql/src/experimental/CWE-090/LDAPInjection.ql index cdcc38ebd90..7da669aa612 100644 --- a/go/ql/src/experimental/CWE-090/LDAPInjection.ql +++ b/go/ql/src/experimental/CWE-090/LDAPInjection.ql @@ -12,9 +12,9 @@ import go import LDAPInjection -import DataFlow::PathGraph +import LdapInjectionFlow::PathGraph -from LdapInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink -where config.hasFlowPath(source, sink) +from LdapInjectionFlow::PathNode source, LdapInjectionFlow::PathNode sink +where LdapInjectionFlow::flowPath(source, sink) select sink.getNode(), source, sink, "LDAP query parameter depends on a $@.", source.getNode(), "user-provided value" diff --git a/go/ql/src/experimental/CWE-090/LDAPInjection.qll b/go/ql/src/experimental/CWE-090/LDAPInjection.qll index 975dc501b5b..483a1987626 100644 --- a/go/ql/src/experimental/CWE-090/LDAPInjection.qll +++ b/go/ql/src/experimental/CWE-090/LDAPInjection.qll @@ -1,5 +1,4 @@ import go -import DataFlow::PathGraph /** * A sanitizer function that prevents LDAP injection attacks. @@ -97,10 +96,12 @@ private class LdapClientDNSink extends LdapSink { } /** + * DEPRECATED: Use `LdapInjectionFlow` instead. + * * A taint-tracking configuration for reasoning about when an `UntrustedFlowSource` * flows into an argument or field that is vulnerable to LDAP injection. */ -class LdapInjectionConfiguration extends TaintTracking::Configuration { +deprecated class LdapInjectionConfiguration extends TaintTracking::Configuration { LdapInjectionConfiguration() { this = "Ldap injection" } override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } @@ -109,3 +110,17 @@ class LdapInjectionConfiguration extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof LdapSanitizer } } + +private module LdapInjectionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + + predicate isSink(DataFlow::Node sink) { sink instanceof LdapSink } + + predicate isBarrier(DataFlow::Node node) { node instanceof LdapSanitizer } +} + +/** + * Tracks taint flow for reasoning about when an `UntrustedFlowSource` flows + * into an argument or field that is vulnerable to LDAP injection. + */ +module LdapInjectionFlow = TaintTracking::Global; diff --git a/go/ql/src/experimental/CWE-1004/AuthCookie.qll b/go/ql/src/experimental/CWE-1004/AuthCookie.qll index 676249c7b8c..c995a8b2f68 100644 --- a/go/ql/src/experimental/CWE-1004/AuthCookie.qll +++ b/go/ql/src/experimental/CWE-1004/AuthCookie.qll @@ -65,10 +65,12 @@ private class SetCookieSink extends DataFlow::Node { } /** + * DEPRECATED: Use `NameToNetHttpCookieTrackingFlow` instead. + * * A taint-tracking configuration for tracking flow from sensitive names to * `net/http.SetCookie`. */ -class NameToNetHttpCookieTrackingConfiguration extends TaintTracking::Configuration { +deprecated class NameToNetHttpCookieTrackingConfiguration extends TaintTracking::Configuration { NameToNetHttpCookieTrackingConfiguration() { this = "NameToNetHttpCookieTrackingConfiguration" } override predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) } @@ -84,11 +86,30 @@ class NameToNetHttpCookieTrackingConfiguration extends TaintTracking::Configurat } } +private module NameToNetHttpCookieTrackingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) } + + predicate isSink(DataFlow::Node sink) { sink instanceof SetCookieSink } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(StructLit sl | + sl.getType() instanceof NetHttpCookieType and + getValueForFieldWrite(sl, "Name") = pred and + sl = succ.asExpr() + ) + } +} + +/** Tracks taint flow from sensitive names to `net/http.SetCookie`. */ +module NameToNetHttpCookieTrackingFlow = TaintTracking::Global; + /** + * DEPRECATED: Use `BoolToNetHttpCookieTrackingFlow` instead. + * * A taint-tracking configuration for tracking flow from `bool` assigned to * `HttpOnly` that flows into `net/http.SetCookie`. */ -class BoolToNetHttpCookieTrackingConfiguration extends TaintTracking::Configuration { +deprecated class BoolToNetHttpCookieTrackingConfiguration extends TaintTracking::Configuration { BoolToNetHttpCookieTrackingConfiguration() { this = "BoolToNetHttpCookieTrackingConfiguration" } override predicate isSource(DataFlow::Node source) { @@ -106,11 +127,35 @@ class BoolToNetHttpCookieTrackingConfiguration extends TaintTracking::Configurat } } +private module BoolToNetHttpCookieTrackingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.getType().getUnderlyingType() instanceof BoolType + } + + predicate isSink(DataFlow::Node sink) { sink instanceof SetCookieSink } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(StructLit sl | + sl.getType() instanceof NetHttpCookieType and + getValueForFieldWrite(sl, "HttpOnly") = pred and + sl = succ.asExpr() + ) + } +} + /** + * Tracks taint flow from a `bool` assigned to `HttpOnly` to + * `net/http.SetCookie`. + */ +module BoolToNetHttpCookieTrackingFlow = TaintTracking::Global; + +/** + * DEPRECATED: Use `BoolToGinSetCookieTrackingFlow` instead. + * * A taint-tracking configuration for tracking flow from `HttpOnly` set to * `false` to `gin-gonic/gin.Context.SetCookie`. */ -class BoolToGinSetCookieTrackingConfiguration extends DataFlow::Configuration { +deprecated class BoolToGinSetCookieTrackingConfiguration extends DataFlow::Configuration { BoolToGinSetCookieTrackingConfiguration() { this = "BoolToGinSetCookieTrackingConfiguration" } override predicate isSource(DataFlow::Node source) { source.getBoolValue() = false } @@ -127,11 +172,34 @@ class BoolToGinSetCookieTrackingConfiguration extends DataFlow::Configuration { } } +private module BoolToGinSetCookieTrackingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.getBoolValue() = false } + + predicate isSink(DataFlow::Node sink) { + exists(DataFlow::MethodCallNode mcn | + mcn.getTarget() instanceof GinContextSetCookieMethod and + mcn.getArgument(6) = sink and + exists(DataFlow::Node nameArg | + NameToGinSetCookieTrackingFlow::flowTo(nameArg) and + mcn.getArgument(0) = nameArg + ) + ) + } +} + /** + * Tracks data flow from `HttpOnly` set to `false` to + * `gin-gonic/gin.Context.SetCookie`. + */ +module BoolToGinSetCookieTrackingFlow = DataFlow::Global; + +/** + * DEPRECATED: Use `NameToGinSetCookieTrackingFlow` instead. + * * A taint-tracking configuration for tracking flow from sensitive names to * `gin-gonic/gin.Context.SetCookie`. */ -private class NameToGinSetCookieTrackingConfiguration extends DataFlow2::Configuration { +deprecated private class NameToGinSetCookieTrackingConfiguration extends DataFlow2::Configuration { NameToGinSetCookieTrackingConfiguration() { this = "NameToGinSetCookieTrackingConfiguration" } override predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) } @@ -144,6 +212,22 @@ private class NameToGinSetCookieTrackingConfiguration extends DataFlow2::Configu } } +private module NameToGinSetCookieTrackingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) } + + predicate isSink(DataFlow::Node sink) { + exists(DataFlow::MethodCallNode mcn | + mcn.getTarget() instanceof GinContextSetCookieMethod and + mcn.getArgument(0) = sink + ) + } +} + +/** + * Tracks taint flow from sensitive names to `gin-gonic/gin.Context.SetCookie`. + */ +private module NameToGinSetCookieTrackingFlow = DataFlow::Global; + /** * The receiver of `gorilla/sessions.Session.Save` call. */ @@ -168,10 +252,12 @@ private class GorillaStoreSaveSink extends DataFlow::Node { } /** + * DEPRECATED: Use `GorillaCookieStoreSaveTrackingFlow` instead. + * * A taint-tracking configuration for tracking flow from gorilla cookie store * creation to `gorilla/sessions.Session.Save`. */ -class GorillaCookieStoreSaveTrackingConfiguration extends DataFlow::Configuration { +deprecated class GorillaCookieStoreSaveTrackingConfiguration extends DataFlow::Configuration { GorillaCookieStoreSaveTrackingConfiguration() { this = "GorillaCookieStoreSaveTrackingConfiguration" } @@ -198,11 +284,42 @@ class GorillaCookieStoreSaveTrackingConfiguration extends DataFlow::Configuratio } } +private module GorillaCookieStoreSaveTrackingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source + .(DataFlow::CallNode) + .getTarget() + .hasQualifiedName(package("github.com/gorilla/sessions", ""), "NewCookieStore") + } + + predicate isSink(DataFlow::Node sink) { + sink instanceof GorillaSessionSaveSink or + sink instanceof GorillaStoreSaveSink + } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::MethodCallNode cn | + cn.getTarget() + .hasQualifiedName(package("github.com/gorilla/sessions", ""), "CookieStore", "Get") and + pred = cn.getReceiver() and + succ = cn.getResult(0) + ) + } +} + /** + * Tracks data flow from gorilla cookie store creation to + * `gorilla/sessions.Session.Save`. + */ +module GorillaCookieStoreSaveTrackingFlow = DataFlow::Global; + +/** + * DEPRECATED: Use `GorillaSessionOptionsTrackingFlow` instead. + * * A taint-tracking configuration for tracking flow from session options to * `gorilla/sessions.Session.Save`. */ -class GorillaSessionOptionsTrackingConfiguration extends TaintTracking::Configuration { +deprecated class GorillaSessionOptionsTrackingConfiguration extends TaintTracking::Configuration { GorillaSessionOptionsTrackingConfiguration() { this = "GorillaSessionOptionsTrackingConfiguration" } @@ -224,11 +341,39 @@ class GorillaSessionOptionsTrackingConfiguration extends TaintTracking::Configur } } +private module GorillaSessionOptionsTrackingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + exists(StructLit sl | + sl.getType().hasQualifiedName(package("github.com/gorilla/sessions", ""), "Options") and + source.asExpr() = sl + ) + } + + predicate isSink(DataFlow::Node sink) { sink instanceof GorillaSessionSaveSink } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(GorillaSessionOptionsField f, DataFlow::Write w, DataFlow::Node base | + w.writesField(base, f, pred) and + succ = base + ) + } +} + /** + * Tracks taint flow from session options to + * `gorilla/sessions.Session.Save`. + */ +module GorillaSessionOptionsTrackingFlow = + TaintTracking::Global; + +/** + * DEPRECATED: Use `BoolToGorillaSessionOptionsTrackingFlow` instead. + * * A taint-tracking configuration for tracking flow from a `bool` assigned to * `HttpOnly` to `gorilla/sessions.Session.Save`. */ -class BoolToGorillaSessionOptionsTrackingConfiguration extends TaintTracking::Configuration { +deprecated class BoolToGorillaSessionOptionsTrackingConfiguration extends TaintTracking::Configuration +{ BoolToGorillaSessionOptionsTrackingConfiguration() { this = "BoolToGorillaSessionOptionsTrackingConfiguration" } @@ -251,3 +396,30 @@ class BoolToGorillaSessionOptionsTrackingConfiguration extends TaintTracking::Co ) } } + +private module BoolToGorillaSessionOptionsTrackingConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.getType().getUnderlyingType() instanceof BoolType + } + + predicate isSink(DataFlow::Node sink) { sink instanceof GorillaSessionSaveSink } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(StructLit sl | + getValueForFieldWrite(sl, "HttpOnly") = pred and + sl = succ.asExpr() + ) + or + exists(GorillaSessionOptionsField f, DataFlow::Write w, DataFlow::Node base | + w.writesField(base, f, pred) and + succ = base + ) + } +} + +/** + * Tracks taint flow from a `bool` assigned to `HttpOnly` to + * `gorilla/sessions.Session.Save`. + */ +module BoolToGorillaSessionOptionsTrackingFlow = + TaintTracking::Global; diff --git a/go/ql/src/experimental/CWE-1004/CookieWithoutHttpOnly.ql b/go/ql/src/experimental/CWE-1004/CookieWithoutHttpOnly.ql index a16a446fe56..1b135ae87f9 100644 --- a/go/ql/src/experimental/CWE-1004/CookieWithoutHttpOnly.ql +++ b/go/ql/src/experimental/CWE-1004/CookieWithoutHttpOnly.ql @@ -15,25 +15,43 @@ import go import AuthCookie -import DataFlow::PathGraph + +module NetHttpCookieTrackingFlow = + DataFlow::MergePathGraph; + +module GorillaTrackingFlow = + DataFlow::MergePathGraph3; + +module MergedFlow = + DataFlow::MergePathGraph3; + +import MergedFlow::PathGraph /** Holds if `HttpOnly` of `net/http.SetCookie` is set to `false` or not set (default value is used). */ -predicate isNetHttpCookieFlow(DataFlow::PathNode source, DataFlow::PathNode sink) { - exists(DataFlow::PathNode sensitiveName, DataFlow::PathNode setCookieSink | - exists(NameToNetHttpCookieTrackingConfiguration cfg | - cfg.hasFlowPath(sensitiveName, setCookieSink) - ) and +predicate isNetHttpCookieFlow( + NetHttpCookieTrackingFlow::PathNode source, NetHttpCookieTrackingFlow::PathNode sink +) { + exists( + NameToNetHttpCookieTrackingFlow::PathNode sensitiveName, + NameToNetHttpCookieTrackingFlow::PathNode setCookieSink + | + NameToNetHttpCookieTrackingFlow::flowPath(sensitiveName, setCookieSink) and ( - not any(BoolToNetHttpCookieTrackingConfiguration cfg).hasFlowTo(setCookieSink.getNode()) and - source = sensitiveName and - sink = setCookieSink + not BoolToNetHttpCookieTrackingFlow::flowTo(sink.getNode()) and + source.asPathNode1() = sensitiveName and + sink.asPathNode1() = setCookieSink or - exists(BoolToNetHttpCookieTrackingConfiguration cfg, DataFlow::PathNode setCookieSink2 | - cfg.hasFlowPath(source, setCookieSink2) and - source.getNode().getBoolValue() = false and - sink = setCookieSink2 and - setCookieSink.getNode() = setCookieSink2.getNode() - ) + BoolToNetHttpCookieTrackingFlow::flowPath(source.asPathNode2(), sink.asPathNode2()) and + source.getNode().getBoolValue() = false and + setCookieSink.getNode() = sink.getNode() ) ) } @@ -42,44 +60,40 @@ predicate isNetHttpCookieFlow(DataFlow::PathNode source, DataFlow::PathNode sink * Holds if there is gorilla cookie store creation to `Save` path and * `HttpOnly` is set to `false` or not set (default value is used). */ -predicate isGorillaSessionsCookieFlow(DataFlow::PathNode source, DataFlow::PathNode sink) { - exists(DataFlow::PathNode cookieStoreCreate, DataFlow::PathNode sessionSave | - any(GorillaCookieStoreSaveTrackingConfiguration cfg).hasFlowPath(cookieStoreCreate, sessionSave) and +predicate isGorillaSessionsCookieFlow( + GorillaTrackingFlow::PathNode source, GorillaTrackingFlow::PathNode sink +) { + exists( + GorillaCookieStoreSaveTrackingFlow::PathNode cookieStoreCreate, + GorillaCookieStoreSaveTrackingFlow::PathNode sessionSave + | + GorillaCookieStoreSaveTrackingFlow::flowPath(cookieStoreCreate, sessionSave) and ( - not any(GorillaSessionOptionsTrackingConfiguration cfg).hasFlowTo(sessionSave.getNode()) and - source = cookieStoreCreate and - sink = sessionSave + not GorillaSessionOptionsTrackingFlow::flowTo(sink.getNode()) and + source.asPathNode1() = cookieStoreCreate and + sink.asPathNode1() = sessionSave or - exists( - GorillaSessionOptionsTrackingConfiguration cfg, DataFlow::PathNode options, - DataFlow::PathNode sessionSave2 - | - cfg.hasFlowPath(options, sessionSave2) and + exists(GorillaTrackingFlow::PathNode options, GorillaTrackingFlow::PathNode sessionSave2 | + GorillaSessionOptionsTrackingFlow::flowPath(options.asPathNode2(), + sessionSave2.asPathNode2()) and ( - not any(BoolToGorillaSessionOptionsTrackingConfiguration boolCfg) - .hasFlowTo(sessionSave.getNode()) and + not BoolToGorillaSessionOptionsTrackingFlow::flowTo(sink.getNode()) and sink = sessionSave2 and source = options and sessionSave.getNode() = sessionSave2.getNode() or - exists( - BoolToGorillaSessionOptionsTrackingConfiguration boolCfg, - DataFlow::PathNode sessionSave3 - | - boolCfg.hasFlowPath(source, sessionSave3) and - source.getNode().getBoolValue() = false and - sink = sessionSave3 and - sessionSave.getNode() = sessionSave3.getNode() - ) + BoolToGorillaSessionOptionsTrackingFlow::flowPath(source.asPathNode3(), sink.asPathNode3()) and + source.getNode().getBoolValue() = false and + sink.getNode() = sessionSave.getNode() ) ) ) ) } -from DataFlow::PathNode source, DataFlow::PathNode sink +from MergedFlow::PathNode source, MergedFlow::PathNode sink where - isNetHttpCookieFlow(source, sink) or - any(BoolToGinSetCookieTrackingConfiguration cfg).hasFlowPath(source, sink) or - isGorillaSessionsCookieFlow(source, sink) + isNetHttpCookieFlow(source.asPathNode1(), sink.asPathNode1()) or + BoolToGinSetCookieTrackingFlow::flowPath(source.asPathNode2(), sink.asPathNode2()) or + isGorillaSessionsCookieFlow(source.asPathNode3(), sink.asPathNode3()) select sink.getNode(), source, sink, "Cookie attribute 'HttpOnly' is not set to true." diff --git a/go/ql/src/experimental/CWE-203/Timing.ql b/go/ql/src/experimental/CWE-203/Timing.ql index b9cfa0f1a6d..057ff80acfb 100644 --- a/go/ql/src/experimental/CWE-203/Timing.ql +++ b/go/ql/src/experimental/CWE-203/Timing.ql @@ -10,7 +10,6 @@ */ import go -import DataFlow::PathGraph import semmle.go.security.SensitiveActions private predicate isBadResult(DataFlow::Node e) { @@ -97,17 +96,19 @@ private class SensitiveStringSink extends Sink { } } -class SecretTracking extends TaintTracking::Configuration { - SecretTracking() { this = "SecretTracking" } - - override predicate isSource(DataFlow::Node source) { +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource and not isBadResult(source) } - override predicate isSink(DataFlow::Node sink) { sink instanceof Sink and not isBadResult(sink) } + predicate isSink(DataFlow::Node sink) { sink instanceof Sink and not isBadResult(sink) } } -from SecretTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +module Flow = TaintTracking::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) select sink.getNode(), source, sink, "$@ may be vulnerable to timing attacks.", source.getNode(), "Hardcoded String" diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql index 3e6864c021f..02f47ba2b73 100644 --- a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql +++ b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql @@ -33,34 +33,34 @@ class PamStartFunc extends Function { PamStartFunc() { this.hasQualifiedName("github.com/msteinert/pam", ["StartFunc", "Start"]) } } -class PamStartToAcctMgmtConfig extends TaintTracking::Configuration { - PamStartToAcctMgmtConfig() { this = "PAM auth bypass (Start to AcctMgmt)" } - - override predicate isSource(DataFlow::Node source) { +// PAM auth bypass (Start to AcctMgmt) +module PamStartToAcctMgmtConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { exists(PamStartFunc p | p.getACall().getResult(0) = source) } - override predicate isSink(DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { exists(PamAcctMgmt p | p.getACall().getReceiver() = sink) } } -class PamStartToAuthenticateConfig extends TaintTracking::Configuration { - PamStartToAuthenticateConfig() { this = "PAM auth bypass (Start to Authenticate)" } +module PamStartToAcctMgmtFlow = TaintTracking::Global; - override predicate isSource(DataFlow::Node source) { +// PAM auth bypass (Start to Authenticate) +module PamStartToAuthenticateConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { exists(PamStartFunc p | p.getACall().getResult(0) = source) } - override predicate isSink(DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { exists(PamAuthenticate p | p.getACall().getReceiver() = sink) } } -from - PamStartToAcctMgmtConfig acctMgmtConfig, PamStartToAuthenticateConfig authConfig, - DataFlow::Node source, DataFlow::Node sink +module PamStartToAuthenticateFlow = TaintTracking::Global; + +from DataFlow::Node source, DataFlow::Node sink where not isInTestFile(source.asExpr()) and - (authConfig.hasFlow(source, sink) and not acctMgmtConfig.hasFlow(source, _)) + (PamStartToAuthenticateFlow::flow(source, sink) and not PamStartToAcctMgmtFlow::flow(source, _)) select source, "This Pam transaction may not be secure." diff --git a/go/ql/src/experimental/CWE-321/HardcodedKeys.ql b/go/ql/src/experimental/CWE-321/HardcodedKeys.ql index 47851d8b4b9..9e51b1e2ae6 100644 --- a/go/ql/src/experimental/CWE-321/HardcodedKeys.ql +++ b/go/ql/src/experimental/CWE-321/HardcodedKeys.ql @@ -11,9 +11,9 @@ import go import HardcodedKeysLib -import DataFlow::PathGraph +import HardcodedKeys::Flow::PathGraph -from HardcodedKeys::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from HardcodedKeys::Flow::PathNode source, HardcodedKeys::Flow::PathNode sink +where HardcodedKeys::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "$@ is used to sign a JWT token.", source.getNode(), "Hardcoded String" diff --git a/go/ql/src/experimental/CWE-321/HardcodedKeysLib.qll b/go/ql/src/experimental/CWE-321/HardcodedKeysLib.qll index 12af9544b0e..99aa37918a3 100644 --- a/go/ql/src/experimental/CWE-321/HardcodedKeysLib.qll +++ b/go/ql/src/experimental/CWE-321/HardcodedKeysLib.qll @@ -6,7 +6,6 @@ import go import StringOps -import DataFlow::PathGraph /** * Provides default sources, sinks and sanitizers for reasoning about @@ -363,9 +362,11 @@ module HardcodedKeys { } /** + * DEPRECATED: Use `Flow` instead. + * * A configuration depicting taint flow for studying JWT token signing vulnerabilities. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "Hard-coded JWT Signing Key" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -374,4 +375,15 @@ module HardcodedKeys { override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** Tracks taint flow for reasoning about JWT token signing vulnerabilities. */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithm.ql b/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithm.ql index 04a7dae5595..ffaaae8e02a 100644 --- a/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithm.ql +++ b/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithm.ql @@ -11,10 +11,10 @@ */ import go -import WeakCryptoAlgorithmCustomizations::WeakCryptoAlgorithm -import DataFlow::PathGraph +import WeakCryptoAlgorithmCustomizations +import WeakCryptoAlgorithm::Flow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from WeakCryptoAlgorithm::Flow::PathNode source, WeakCryptoAlgorithm::Flow::PathNode sink +where WeakCryptoAlgorithm::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "$@ is used in a weak cryptographic algorithm.", source.getNode(), "Sensitive data" diff --git a/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithmCustomizations.qll b/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithmCustomizations.qll index 7348d8afb8c..61a55bdd32b 100644 --- a/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithmCustomizations.qll +++ b/go/ql/src/experimental/CWE-327/WeakCryptoAlgorithmCustomizations.qll @@ -49,9 +49,11 @@ module WeakCryptoAlgorithm { } /** + * DEPRECATED: Use `Flow` instead. + * * A configuration depicting taint flow from sensitive information to weak cryptographic algorithms. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "WeakCryptoAlgorithm" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -63,4 +65,18 @@ module WeakCryptoAlgorithm { node instanceof Sanitizer } } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + /** + * Tracks taint flow from sensitive information to weak cryptographic + * algorithms. + */ + module Flow = TaintTracking::Global; } diff --git a/go/ql/src/experimental/CWE-369/DivideByZero.ql b/go/ql/src/experimental/CWE-369/DivideByZero.ql index 9e48e7f6fc4..b7478276f84 100644 --- a/go/ql/src/experimental/CWE-369/DivideByZero.ql +++ b/go/ql/src/experimental/CWE-369/DivideByZero.ql @@ -10,7 +10,6 @@ */ import go -import DataFlow::PathGraph import semmle.go.dataflow.internal.TaintTrackingUtil /** @@ -28,31 +27,34 @@ predicate divideByZeroSanitizerGuard(DataFlow::Node g, Expr e, boolean branch) { ) } -/** - * A taint-tracking configuration for reasoning about division by zero, where divisor is user-controlled and unchecked. - */ -class DivideByZeroCheckConfig extends TaintTracking::Configuration { - DivideByZeroCheckConfig() { this = "DivideByZeroCheckConfig" } +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - - override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { exists(Function f, DataFlow::CallNode cn | cn = f.getACall() | f.hasQualifiedName("strconv", ["Atoi", "ParseInt", "ParseUint", "ParseFloat"]) and - pred = cn.getArgument(0) and - succ = cn.getResult(0) + node1 = cn.getArgument(0) and + node2 = cn.getResult(0) ) } - override predicate isSanitizer(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node) { node = DataFlow::BarrierGuard::getABarrierNode() } - override predicate isSink(DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { sink = DataFlow::exprNode(any(QuoExpr e).getRightOperand()) } } -from DataFlow::PathNode source, DataFlow::PathNode sink, DivideByZeroCheckConfig cfg -where cfg.hasFlowPath(source, sink) +/** + * Tracks taint flow for reasoning about division by zero, where divisor is + * user-controlled and unchecked. + */ +module Flow = TaintTracking::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) select sink, source, sink, "This variable might be zero leading to a division-by-zero panic." diff --git a/go/ql/src/experimental/CWE-74/DsnInjection.ql b/go/ql/src/experimental/CWE-74/DsnInjection.ql index 3197d04534b..41e5dd58690 100644 --- a/go/ql/src/experimental/CWE-74/DsnInjection.ql +++ b/go/ql/src/experimental/CWE-74/DsnInjection.ql @@ -10,13 +10,13 @@ */ import go -import DataFlow::PathGraph import DsnInjectionCustomizations +import DsnInjectionFlow::PathGraph /** An untrusted flow source taken as a source for the `DsnInjection` taint-flow configuration. */ private class UntrustedFlowAsSource extends Source instanceof UntrustedFlowSource { } -from DsnInjection cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from DsnInjectionFlow::PathNode source, DsnInjectionFlow::PathNode sink +where DsnInjectionFlow::flowPath(source, sink) select sink.getNode(), source, sink, "Data-Source Name is built using $@.", source.getNode(), "untrusted user input" diff --git a/go/ql/src/experimental/CWE-74/DsnInjectionCustomizations.qll b/go/ql/src/experimental/CWE-74/DsnInjectionCustomizations.qll index e66cb7cba73..5417ddf4f3e 100644 --- a/go/ql/src/experimental/CWE-74/DsnInjectionCustomizations.qll +++ b/go/ql/src/experimental/CWE-74/DsnInjectionCustomizations.qll @@ -1,14 +1,17 @@ /** Provides a taint-tracking model to reason about Data-Source name injection vulnerabilities. */ import go -import DataFlow::PathGraph import semmle.go.dataflow.barrierguardutil.RegexpCheck /** A source for `DsnInjection` taint-flow configuration. */ abstract class Source extends DataFlow::Node { } -/** A taint-tracking configuration to reason about Data Source Name injection vulnerabilities. */ -class DsnInjection extends TaintTracking::Configuration { +/** + * DEPRECATED: Use `DsnInjectionFlow` instead. + * + * A taint-tracking configuration to reason about Data Source Name injection vulnerabilities. + */ +deprecated class DsnInjection extends TaintTracking::Configuration { DsnInjection() { this = "DsnInjection" } override predicate isSource(DataFlow::Node node) { node instanceof Source } @@ -25,6 +28,27 @@ class DsnInjection extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node node) { node instanceof RegexpCheckBarrier } } +private module DsnInjectionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { + exists(DataFlow::CallNode c | + c.getTarget().hasQualifiedName("database/sql", "Open") and + c.getArgument(0).getStringValue() = "mysql" + | + sink = c.getArgument(1) + ) + } + + predicate isBarrier(DataFlow::Node node) { node instanceof RegexpCheckBarrier } +} + +/** + * Tracks taint flow for reasoning about Data Source Name injection + * vulnerabilities. + */ +module DsnInjectionFlow = TaintTracking::Global; + /** A model of a function which decodes or unmarshals a tainted input, propagating taint from any argument to either the method receiver or return value. */ private class DecodeFunctionModel extends TaintTracking::FunctionModel { DecodeFunctionModel() { diff --git a/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql b/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql index ff4142f12d4..1744a25848b 100644 --- a/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql +++ b/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql @@ -10,15 +10,15 @@ */ import go -import DataFlow::PathGraph import DsnInjectionCustomizations +import DsnInjectionFlow::PathGraph -/** An argument passed via the command line taken as a source for the `DsnInjection` taint-flow configuration. */ +/** An argument passed via the command line taken as a source for the `DsnInjectionFlow` taint-flow. */ private class OsArgsSource extends Source { OsArgsSource() { this = any(Variable c | c.hasQualifiedName("os", "Args")).getARead() } } -from DsnInjection cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from DsnInjectionFlow::PathNode source, DsnInjectionFlow::PathNode sink +where DsnInjectionFlow::flowPath(source, sink) select sink.getNode(), source, sink, "This query depends on a $@.", source.getNode(), "user-provided value" diff --git a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql b/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql index 7b6b2bbe133..1471cf3689a 100644 --- a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql +++ b/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql @@ -11,7 +11,6 @@ */ import go -import DataFlow::PathGraph /** * Holds if the provided `untrusted` node flows into a conversion to a PassthroughType. @@ -21,10 +20,10 @@ import DataFlow::PathGraph predicate flowsFromUntrustedToConversion( DataFlow::Node untrusted, PassthroughTypeName targetType, DataFlow::Node conversionSink ) { - exists(FlowConfFromUntrustedToPassthroughTypeConversion cfg, DataFlow::Node source | - cfg.hasFlow(source, conversionSink) and + exists(DataFlow::Node source | + UntrustedToPassthroughTypeConversionFlow::flow(source, conversionSink) and source = untrusted and - targetType = cfg.getDstTypeName() + UntrustedToPassthroughTypeConversionConfig::isSinkToPassthroughType(conversionSink, targetType) ) } @@ -36,78 +35,47 @@ class PassthroughTypeName extends string { PassthroughTypeName() { this = ["HTML", "HTMLAttr", "JS", "JSStr", "CSS", "Srcset", "URL"] } } -/** - * A taint-tracking configuration for reasoning about when an UntrustedFlowSource - * is converted into a special "passthrough" type which will not be escaped by the template generator; - * this allows the injection of arbitrary content (html, css, js) into the generated - * output of the templates. - */ -class FlowConfFromUntrustedToPassthroughTypeConversion extends TaintTracking::Configuration { - PassthroughTypeName dstTypeName; +module UntrustedToPassthroughTypeConversionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - FlowConfFromUntrustedToPassthroughTypeConversion() { - this = "UntrustedToConversion" + dstTypeName - } - - /** - * Gets the name of conversion's destination type. - */ - PassthroughTypeName getDstTypeName() { result = dstTypeName } - - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - - private predicate isSinkToPassthroughType(DataFlow::TypeCastNode sink, PassthroughTypeName name) { + additional predicate isSinkToPassthroughType(DataFlow::TypeCastNode sink, PassthroughTypeName name) { exists(Type typ | typ = sink.getResultType() and typ.getUnderlyingType*().hasQualifiedName("html/template", name) ) } - override predicate isSink(DataFlow::Node sink) { this.isSinkToPassthroughType(sink, dstTypeName) } + predicate isSink(DataFlow::Node sink) { isSinkToPassthroughType(sink, _) } - override predicate isSanitizer(DataFlow::Node sanitizer) { - sanitizer instanceof SharedXss::Sanitizer or sanitizer.getType() instanceof NumericType + predicate isBarrier(DataFlow::Node node) { + node instanceof SharedXss::Sanitizer or node.getType() instanceof NumericType } } +/** + * Tracks taint flow for reasoning about when an `UntrustedFlowSource` is + * converted into a special "passthrough" type which will not be escaped by the + * template generator; this allows the injection of arbitrary content (html, + * css, js) into the generated output of the templates. + */ +module UntrustedToPassthroughTypeConversionFlow = + TaintTracking::Global; + /** * Holds if the provided `conversion` node flows into the provided `execSink`. */ predicate flowsFromConversionToExec( DataFlow::Node conversion, PassthroughTypeName targetType, DataFlow::Node execSink ) { - exists( - FlowConfPassthroughTypeConversionToTemplateExecutionCall cfg, DataFlow::Node source, - DataFlow::Node execSinkLocal - | - cfg.hasFlow(source, execSinkLocal) and - source = conversion and - execSink = execSinkLocal and - targetType = cfg.getDstTypeName() - ) + PassthroughTypeConversionToTemplateExecutionCallFlow::flow(conversion, execSink) and + PassthroughTypeConversionToTemplateExecutionCallConfig::isSourceConversionToPassthroughType(conversion, + targetType) } -/** - * A taint-tracking configuration for reasoning about when the result of a conversion - * to a PassthroughType flows to a template execution call. - */ -class FlowConfPassthroughTypeConversionToTemplateExecutionCall extends TaintTracking::Configuration { - PassthroughTypeName dstTypeName; +module PassthroughTypeConversionToTemplateExecutionCallConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { isSourceConversionToPassthroughType(source, _) } - FlowConfPassthroughTypeConversionToTemplateExecutionCall() { - this = "ConversionToExec" + dstTypeName - } - - /** - * Gets the name of conversion's destination type. - */ - PassthroughTypeName getDstTypeName() { result = dstTypeName } - - override predicate isSource(DataFlow::Node source) { - this.isSourceConversionToPassthroughType(source, dstTypeName) - } - - private predicate isSourceConversionToPassthroughType( + additional predicate isSourceConversionToPassthroughType( DataFlow::TypeCastNode source, PassthroughTypeName name ) { exists(Type typ | @@ -116,9 +84,16 @@ class FlowConfPassthroughTypeConversionToTemplateExecutionCall extends TaintTrac ) } - override predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) } + predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) } } +/** + * Tracks taint flow for reasoning about when the result of a conversion to a + * PassthroughType flows to a template execution call. + */ +module PassthroughTypeConversionToTemplateExecutionCallFlow = + TaintTracking::Global; + /** * Holds if the sink is a data value argument of a template execution call. */ @@ -133,41 +108,46 @@ predicate isSinkToTemplateExec(DataFlow::Node sink, DataFlow::CallNode call) { ) } -/** - * A taint-tracking configuration for reasoning about when an UntrustedFlowSource - * flows into a template executor call. - */ -class FlowConfFromUntrustedToTemplateExecutionCall extends TaintTracking::Configuration { - FlowConfFromUntrustedToTemplateExecutionCall() { - this = "FlowConfFromUntrustedToTemplateExecutionCall" - } +module FromUntrustedToTemplateExecutionCallConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - - override predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) } + predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) } } +/** + * Tracks taint flow from an `UntrustedFlowSource` into a template executor + * call. + */ +module FromUntrustedToTemplateExecutionCallFlow = + TaintTracking::Global; + +import FromUntrustedToTemplateExecutionCallFlow::PathGraph + /** * Holds if the provided `untrusted` node flows into the provided `execSink`. */ -predicate flowsFromUntrustedToExec(DataFlow::PathNode untrusted, DataFlow::PathNode execSink) { - exists(FlowConfFromUntrustedToTemplateExecutionCall cfg | cfg.hasFlowPath(untrusted, execSink)) +predicate flowsFromUntrustedToExec( + FromUntrustedToTemplateExecutionCallFlow::PathNode untrusted, + FromUntrustedToTemplateExecutionCallFlow::PathNode execSink +) { + FromUntrustedToTemplateExecutionCallFlow::flowPath(untrusted, execSink) } from - DataFlow::PathNode untrustedSource, DataFlow::PathNode templateExecCall, - PassthroughTypeName targetTypeName, DataFlow::PathNode conversion + FromUntrustedToTemplateExecutionCallFlow::PathNode untrustedSource, + FromUntrustedToTemplateExecutionCallFlow::PathNode templateExecCall, + PassthroughTypeName targetTypeName, DataFlow::Node conversion where // A = untrusted remote flow source // B = conversion to PassthroughType // C = template execution call // Flows: // A -> B - flowsFromUntrustedToConversion(untrustedSource.getNode(), targetTypeName, conversion.getNode()) and + flowsFromUntrustedToConversion(untrustedSource.getNode(), targetTypeName, conversion) and // B -> C - flowsFromConversionToExec(conversion.getNode(), targetTypeName, templateExecCall.getNode()) and + flowsFromConversionToExec(conversion, targetTypeName, templateExecCall.getNode()) and // A -> C flowsFromUntrustedToExec(untrustedSource, templateExecCall) select templateExecCall.getNode(), untrustedSource, templateExecCall, "Data from an $@ will not be auto-escaped because it was $@ to template." + targetTypeName, - untrustedSource.getNode(), "untrusted source", conversion.getNode(), "converted" + untrustedSource.getNode(), "untrusted source", conversion, "converted" diff --git a/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql b/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql index 5edc839f60c..554e271492e 100644 --- a/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql +++ b/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql @@ -17,13 +17,13 @@ import SensitiveConditionBypass from ControlFlow::ConditionGuardNode guard, DataFlow::Node sensitiveSink, - SensitiveExpr::Classification classification, Configuration config, DataFlow::PathNode source, - DataFlow::PathNode operand, ComparisonExpr comp + SensitiveExpr::Classification classification, DataFlow::Node source, DataFlow::Node operand, + ComparisonExpr comp where // there should be a flow between source and the operand sink - config.hasFlowPath(source, operand) and + Flow::flow(source, operand) and // both the operand should belong to the same comparison expression - operand.getNode().asExpr() = comp.getAnOperand() and + operand.asExpr() = comp.getAnOperand() and // get the ConditionGuardNode corresponding to the comparison expr. guard.getCondition() = comp and // the sink `sensitiveSink` should be sensitive, diff --git a/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.qll b/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.qll index 1134580a59b..ed56ac4af91 100644 --- a/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.qll +++ b/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.qll @@ -43,10 +43,12 @@ private class ConstComparisonExpr extends ComparisonExpr { } /** + * DEPRECATED: Use `Flow` instead. + * * A data-flow configuration for reasoning about * user-controlled bypassing of sensitive actions. */ -class Configuration extends TaintTracking::Configuration { +deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "Condtional Expression Check Bypass" } override predicate isSource(DataFlow::Node source) { @@ -66,3 +68,28 @@ class Configuration extends TaintTracking::Configuration { ) } } + +private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source instanceof UntrustedFlowSource + or + exists(DataFlow::FieldReadNode f | + f.getField().hasQualifiedName("net/http", "Request", "Host") + | + source = f + ) + } + + predicate isSink(DataFlow::Node sink) { + exists(ConstComparisonExpr c | + c.getAnOperand() = sink.asExpr() and + not c.isPotentialFalsePositive() + ) + } +} + +/** + * Tracks taint flow for reasoning about user-controlled bypassing of sensitive + * actions. + */ +module Flow = TaintTracking::Global; diff --git a/go/ql/src/experimental/CWE-840/ConditionalBypass.ql b/go/ql/src/experimental/CWE-840/ConditionalBypass.ql index 87bfac1c1d5..e19fa46f4ca 100644 --- a/go/ql/src/experimental/CWE-840/ConditionalBypass.ql +++ b/go/ql/src/experimental/CWE-840/ConditionalBypass.ql @@ -12,30 +12,28 @@ import go -/** - * A taint-tracking configuration for reasoning about conditional bypass. - */ -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "ConditionalBypass" } - - override predicate isSource(DataFlow::Node source) { +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource or source = any(Field f | f.hasQualifiedName("net/http", "Request", "Host")).getARead() } - override predicate isSink(DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { exists(ComparisonExpr c | c.getAnOperand() = sink.asExpr()) } } +/** Tracks taint flow for reasoning about conditional bypass. */ +module Flow = TaintTracking::Global; + from - Configuration config, DataFlow::PathNode lhsSource, DataFlow::PathNode lhs, - DataFlow::PathNode rhsSource, DataFlow::PathNode rhs, ComparisonExpr c + DataFlow::Node lhsSource, DataFlow::Node lhs, DataFlow::Node rhsSource, DataFlow::Node rhs, + ComparisonExpr c where - config.hasFlowPath(rhsSource, rhs) and - rhs.getNode().asExpr() = c.getRightOperand() and - config.hasFlowPath(lhsSource, lhs) and - lhs.getNode().asExpr() = c.getLeftOperand() -select c, "This comparison of a $@ with another $@ can be bypassed by a malicious user.", - lhsSource.getNode(), "user-controlled value", rhsSource.getNode(), "user-controlled value" + Flow::flow(rhsSource, rhs) and + rhs.asExpr() = c.getRightOperand() and + Flow::flow(lhsSource, lhs) and + lhs.asExpr() = c.getLeftOperand() +select c, "This comparison of a $@ with another $@ can be bypassed by a malicious user.", lhsSource, + "user-controlled value", rhsSource, "user-controlled value" diff --git a/go/ql/src/experimental/CWE-918/SSRF.ql b/go/ql/src/experimental/CWE-918/SSRF.ql index a58ac660385..faa03929530 100644 --- a/go/ql/src/experimental/CWE-918/SSRF.ql +++ b/go/ql/src/experimental/CWE-918/SSRF.ql @@ -12,12 +12,12 @@ import go import SSRF -import DataFlow::PathGraph +import ServerSideRequestForgery::Flow::PathGraph from - ServerSideRequestForgery::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, + ServerSideRequestForgery::Flow::PathNode source, ServerSideRequestForgery::Flow::PathNode sink, DataFlow::Node request where - cfg.hasFlowPath(source, sink) and + ServerSideRequestForgery::Flow::flowPath(source, sink) and request = sink.getNode().(ServerSideRequestForgery::Sink).getARequest() select request, source, sink, "The URL of this request depends on a user-provided value." diff --git a/go/ql/src/experimental/CWE-918/SSRF.qll b/go/ql/src/experimental/CWE-918/SSRF.qll index b6424511a21..e5360bbeba0 100644 --- a/go/ql/src/experimental/CWE-918/SSRF.qll +++ b/go/ql/src/experimental/CWE-918/SSRF.qll @@ -17,9 +17,11 @@ module ServerSideRequestForgery { private import semmle.go.dataflow.Properties /** + * DEPRECATED: Use `Flow` instead. + * * A taint-tracking configuration for reasoning about request forgery. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "SSRF" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -44,6 +46,26 @@ module ServerSideRequestForgery { } } + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + // propagate to a URL when its host is assigned to + exists(Write w, Field f, SsaWithFields v | f.hasQualifiedName("net/url", "URL", "Host") | + w.writesField(v.getAUse(), f, node1) and node2 = v.getAUse() + ) + } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + + predicate isBarrierOut(DataFlow::Node node) { node instanceof SanitizerEdge } + } + + /** Tracks taint flow for reasoning about request forgery vulnerabilities. */ + module Flow = TaintTracking::Global; + /** A data flow source for request forgery vulnerabilities. */ abstract class Source extends DataFlow::Node { } diff --git a/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql b/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql index 53e7c0e8730..a819627e4b1 100644 --- a/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql +++ b/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql @@ -51,18 +51,14 @@ class AllowCredentialsHeaderWrite extends Http::HeaderWrite { AllowCredentialsHeaderWrite() { this.getHeaderName() = headerAllowCredentials() } } -/** - * A taint-tracking configuration for reasoning about when an UntrustedFlowSource - * flows to a HeaderWrite that writes an `Access-Control-Allow-Origin` header's value. - */ -class FlowsUntrustedToAllowOriginHeader extends TaintTracking::Configuration { - FlowsUntrustedToAllowOriginHeader() { this = "from-untrusted-to-allow-origin-header-value" } +module UntrustedToAllowOriginHeaderConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + additional predicate isSinkHW(DataFlow::Node sink, AllowOriginHeaderWrite hw) { + sink = hw.getValue() + } - predicate isSinkHW(DataFlow::Node sink, AllowOriginHeaderWrite hw) { sink = hw.getValue() } - - override predicate isSanitizer(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node) { exists(ControlFlow::ConditionGuardNode cgn | cgn.ensures(any(AllowedFlag f).getAFlag().getANode(), _) | @@ -70,9 +66,15 @@ class FlowsUntrustedToAllowOriginHeader extends TaintTracking::Configuration { ) } - override predicate isSink(DataFlow::Node sink) { this.isSinkHW(sink, _) } + predicate isSink(DataFlow::Node sink) { isSinkHW(sink, _) } } +/** + * Tracks taint flowfor reasoning about when an `UntrustedFlowSource` flows to + * a `HeaderWrite` that writes an `Access-Control-Allow-Origin` header's value. + */ +module UntrustedToAllowOriginHeaderFlow = TaintTracking::Global; + /** * Holds if the provided `allowOriginHW` HeaderWrite's parent ResponseWriter * also has another HeaderWrite that sets a `Access-Control-Allow-Credentials` @@ -92,9 +94,9 @@ predicate allowCredentialsIsSetToTrue(AllowOriginHeaderWrite allowOriginHW) { * The `message` parameter is populated with the warning message to be returned by the query. */ predicate flowsFromUntrustedToAllowOrigin(AllowOriginHeaderWrite allowOriginHW, string message) { - exists(FlowsUntrustedToAllowOriginHeader cfg, DataFlow::Node sink | - cfg.hasFlowTo(sink) and - cfg.isSinkHW(sink, allowOriginHW) + exists(DataFlow::Node sink | + UntrustedToAllowOriginHeaderFlow::flowTo(sink) and + UntrustedToAllowOriginHeaderConfig::isSinkHW(sink, allowOriginHW) | message = headerAllowOrigin() + " header is set to a user-defined value, and " + @@ -120,18 +122,12 @@ class MapRead extends DataFlow::ElementReadNode { MapRead() { this.getBase().getType() instanceof MapType } } -/** - * A taint-tracking configuration for reasoning about when an UntrustedFlowSource - * flows somewhere. - */ -class FlowsFromUntrusted extends TaintTracking::Configuration { - FlowsFromUntrusted() { this = "from-untrusted" } +module FromUntrustedConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + predicate isSink(DataFlow::Node sink) { isSinkCgn(sink, _) } - override predicate isSink(DataFlow::Node sink) { this.isSinkCgn(sink, _) } - - predicate isSinkCgn(DataFlow::Node sink, ControlFlow::ConditionGuardNode cgn) { + additional predicate isSinkCgn(DataFlow::Node sink, ControlFlow::ConditionGuardNode cgn) { exists(IfStmt ifs | exists(Expr operand | operand = ifs.getCond().getAChildExpr*() and @@ -165,12 +161,18 @@ class FlowsFromUntrusted extends TaintTracking::Configuration { } } +/** + * Tracks taint flow for reasoning about when an `UntrustedFlowSource` flows + * somewhere. + */ +module FromUntrustedFlow = TaintTracking::Global; + /** * Holds if the provided `allowOriginHW` is also destination of a `UntrustedFlowSource`. */ predicate flowsToGuardedByCheckOnUntrusted(AllowOriginHeaderWrite allowOriginHW) { - exists(FlowsFromUntrusted cfg, DataFlow::Node sink, ControlFlow::ConditionGuardNode cgn | - cfg.hasFlowTo(sink) and cfg.isSinkCgn(sink, cgn) + exists(DataFlow::Node sink, ControlFlow::ConditionGuardNode cgn | + FromUntrustedFlow::flowTo(sink) and FromUntrustedConfig::isSinkCgn(sink, cgn) | cgn.dominates(allowOriginHW.getBasicBlock()) ) diff --git a/go/ql/src/experimental/Unsafe/WrongUsageOfUnsafe.ql b/go/ql/src/experimental/Unsafe/WrongUsageOfUnsafe.ql index f8151c0d134..e0f0098be4e 100644 --- a/go/ql/src/experimental/Unsafe/WrongUsageOfUnsafe.ql +++ b/go/ql/src/experimental/Unsafe/WrongUsageOfUnsafe.ql @@ -12,7 +12,6 @@ */ import go -import DataFlow::PathGraph /* * Returns the type after all aliases, named types, and pointer @@ -39,38 +38,41 @@ class ConversionToUnsafePointer extends DataFlow::TypeCastNode { } /* Type casting from a `unsafe.Pointer`.*/ -class UnsafeTypeCastingConf extends TaintTracking::Configuration { - UnsafeTypeCastingConf() { this = "UnsafeTypeCastingConf" } - - predicate conversionIsSource(DataFlow::Node source, ConversionToUnsafePointer conv) { +module UnsafeTypeCastingConfig implements DataFlow::ConfigSig { + additional predicate conversionIsSource(DataFlow::Node source, ConversionToUnsafePointer conv) { source = conv } - predicate typeCastNodeIsSink(DataFlow::Node sink, DataFlow::TypeCastNode ca) { + additional predicate typeCastNodeIsSink(DataFlow::Node sink, DataFlow::TypeCastNode ca) { ca.getOperand().getType() instanceof UnsafePointerType and sink = ca } - override predicate isSource(DataFlow::Node source) { this.conversionIsSource(source, _) } + predicate isSource(DataFlow::Node source) { conversionIsSource(source, _) } - override predicate isSink(DataFlow::Node sink) { this.typeCastNodeIsSink(sink, _) } + predicate isSink(DataFlow::Node sink) { typeCastNodeIsSink(sink, _) } } +/** Tracks taint flow for reasoning about type casting from a `unsafe.Pointer`. */ +module UnsafeTypeCastingFlow = TaintTracking::Global; + +import UnsafeTypeCastingFlow::PathGraph + /* * Type casting from a shorter array to a longer array * through the use of unsafe pointers. */ predicate castShortArrayToLongerArray( - DataFlow::PathNode source, DataFlow::PathNode sink, string message + UnsafeTypeCastingFlow::PathNode source, UnsafeTypeCastingFlow::PathNode sink, string message ) { exists( - UnsafeTypeCastingConf cfg, DataFlow::TypeCastNode castBig, ConversionToUnsafePointer castLittle, - ArrayType arrTo, ArrayType arrFrom, int arrFromSize + DataFlow::TypeCastNode castBig, ConversionToUnsafePointer castLittle, ArrayType arrTo, + ArrayType arrFrom, int arrFromSize | - cfg.hasFlowPath(source, sink) and - cfg.conversionIsSource(source.getNode(), castLittle) and - cfg.typeCastNodeIsSink(sink.getNode(), castBig) and + UnsafeTypeCastingFlow::flowPath(source, sink) and + UnsafeTypeCastingConfig::conversionIsSource(source.getNode(), castLittle) and + UnsafeTypeCastingConfig::typeCastNodeIsSink(sink.getNode(), castBig) and arrTo = getFinalType(castBig.getResultType()) and ( // Array (whole) to array: @@ -108,14 +110,16 @@ predicate castShortArrayToLongerArray( * through the use of unsafe pointers. */ -predicate castTypeToArray(DataFlow::PathNode source, DataFlow::PathNode sink, string message) { +predicate castTypeToArray( + UnsafeTypeCastingFlow::PathNode source, UnsafeTypeCastingFlow::PathNode sink, string message +) { exists( - UnsafeTypeCastingConf cfg, DataFlow::TypeCastNode castBig, ConversionToUnsafePointer castLittle, - ArrayType arrTo, Type typeFrom + DataFlow::TypeCastNode castBig, ConversionToUnsafePointer castLittle, ArrayType arrTo, + Type typeFrom | - cfg.hasFlowPath(source, sink) and - cfg.conversionIsSource(source.getNode(), castLittle) and - cfg.typeCastNodeIsSink(sink.getNode(), castBig) and + UnsafeTypeCastingFlow::flowPath(source, sink) and + UnsafeTypeCastingConfig::conversionIsSource(source.getNode(), castLittle) and + UnsafeTypeCastingConfig::typeCastNodeIsSink(sink.getNode(), castBig) and arrTo = getFinalType(castBig.getResultType()) and not typeFrom.getUnderlyingType() instanceof ArrayType and not typeFrom instanceof PointerType and @@ -137,15 +141,15 @@ predicate castTypeToArray(DataFlow::PathNode source, DataFlow::PathNode sink, st */ predicate castDifferentBitSizeNumbers( - DataFlow::PathNode source, DataFlow::PathNode sink, string message + UnsafeTypeCastingFlow::PathNode source, UnsafeTypeCastingFlow::PathNode sink, string message ) { exists( - UnsafeTypeCastingConf cfg, DataFlow::TypeCastNode castBig, ConversionToUnsafePointer castLittle, - NumericType numTo, NumericType numFrom + DataFlow::TypeCastNode castBig, ConversionToUnsafePointer castLittle, NumericType numTo, + NumericType numFrom | - cfg.hasFlowPath(source, sink) and - cfg.conversionIsSource(source.getNode(), castLittle) and - cfg.typeCastNodeIsSink(sink.getNode(), castBig) and + UnsafeTypeCastingFlow::flowPath(source, sink) and + UnsafeTypeCastingConfig::conversionIsSource(source.getNode(), castLittle) and + UnsafeTypeCastingConfig::typeCastNodeIsSink(sink.getNode(), castBig) and numTo = getFinalType(castBig.getResultType()) and numFrom = getFinalType(castLittle.getOperand().getType()) and // TODO: also consider cast from uint to int? @@ -171,7 +175,7 @@ int getNumericTypeSize(NumericType typ) { result = typ.getSize() } -from DataFlow::PathNode source, DataFlow::PathNode sink, string message +from UnsafeTypeCastingFlow::PathNode source, UnsafeTypeCastingFlow::PathNode sink, string message where castShortArrayToLongerArray(source, sink, message) or castTypeToArray(source, sink, message) or diff --git a/go/ql/src/qlpack.yml b/go/ql/src/qlpack.yml index 078d8f2a4bb..59759839a9f 100644 --- a/go/ql/src/qlpack.yml +++ b/go/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/go-queries -version: 0.6.2-dev +version: 0.6.3-dev groups: - go - queries diff --git a/go/ql/test/experimental/CWE-1004/CookieWithoutHttpOnly.expected b/go/ql/test/experimental/CWE-1004/CookieWithoutHttpOnly.expected index 1a22c7d1fc3..c7618b3108f 100644 --- a/go/ql/test/experimental/CWE-1004/CookieWithoutHttpOnly.expected +++ b/go/ql/test/experimental/CWE-1004/CookieWithoutHttpOnly.expected @@ -1,7 +1,8 @@ edges -| CookieWithoutHttpOnly.go:12:10:12:18 | "session" | CookieWithoutHttpOnly.go:15:20:15:21 | &... | -| CookieWithoutHttpOnly.go:12:10:12:18 | "session" | CookieWithoutHttpOnly.go:15:20:15:21 | &... | -| CookieWithoutHttpOnly.go:12:10:12:18 | "session" | CookieWithoutHttpOnly.go:15:21:15:21 | c | +| CookieWithoutHttpOnly.go:11:7:14:2 | struct literal | CookieWithoutHttpOnly.go:15:20:15:21 | &... | +| CookieWithoutHttpOnly.go:11:7:14:2 | struct literal | CookieWithoutHttpOnly.go:15:20:15:21 | &... | +| CookieWithoutHttpOnly.go:11:7:14:2 | struct literal | CookieWithoutHttpOnly.go:15:21:15:21 | c | +| CookieWithoutHttpOnly.go:12:10:12:18 | "session" | CookieWithoutHttpOnly.go:11:7:14:2 | struct literal | | CookieWithoutHttpOnly.go:15:20:15:21 | &... | CookieWithoutHttpOnly.go:15:20:15:21 | &... | | CookieWithoutHttpOnly.go:15:20:15:21 | &... | CookieWithoutHttpOnly.go:15:20:15:21 | &... | | CookieWithoutHttpOnly.go:15:20:15:21 | &... | CookieWithoutHttpOnly.go:15:21:15:21 | c | @@ -9,12 +10,14 @@ edges | CookieWithoutHttpOnly.go:15:20:15:21 | &... [pointer] | CookieWithoutHttpOnly.go:15:20:15:21 | &... | | CookieWithoutHttpOnly.go:15:20:15:21 | &... [pointer] | CookieWithoutHttpOnly.go:15:21:15:21 | c | | CookieWithoutHttpOnly.go:15:21:15:21 | c | CookieWithoutHttpOnly.go:15:20:15:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:20:13:20:21 | "session" | CookieWithoutHttpOnly.go:24:20:24:21 | &... | -| CookieWithoutHttpOnly.go:20:13:20:21 | "session" | CookieWithoutHttpOnly.go:24:20:24:21 | &... | -| CookieWithoutHttpOnly.go:20:13:20:21 | "session" | CookieWithoutHttpOnly.go:24:21:24:21 | c | -| CookieWithoutHttpOnly.go:22:13:22:17 | false | CookieWithoutHttpOnly.go:24:20:24:21 | &... | -| CookieWithoutHttpOnly.go:22:13:22:17 | false | CookieWithoutHttpOnly.go:24:20:24:21 | &... | -| CookieWithoutHttpOnly.go:22:13:22:17 | false | CookieWithoutHttpOnly.go:24:21:24:21 | c | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | CookieWithoutHttpOnly.go:24:20:24:21 | &... | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | CookieWithoutHttpOnly.go:24:20:24:21 | &... | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | CookieWithoutHttpOnly.go:24:20:24:21 | &... | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | CookieWithoutHttpOnly.go:24:20:24:21 | &... | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | CookieWithoutHttpOnly.go:24:21:24:21 | c | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | CookieWithoutHttpOnly.go:24:21:24:21 | c | +| CookieWithoutHttpOnly.go:20:13:20:21 | "session" | CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | +| CookieWithoutHttpOnly.go:22:13:22:17 | false | CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | | CookieWithoutHttpOnly.go:24:20:24:21 | &... | CookieWithoutHttpOnly.go:24:20:24:21 | &... | | CookieWithoutHttpOnly.go:24:20:24:21 | &... | CookieWithoutHttpOnly.go:24:20:24:21 | &... | | CookieWithoutHttpOnly.go:24:20:24:21 | &... | CookieWithoutHttpOnly.go:24:20:24:21 | &... | @@ -29,12 +32,14 @@ edges | CookieWithoutHttpOnly.go:24:20:24:21 | &... [pointer] | CookieWithoutHttpOnly.go:24:21:24:21 | c | | CookieWithoutHttpOnly.go:24:21:24:21 | c | CookieWithoutHttpOnly.go:24:20:24:21 | &... [pointer] | | CookieWithoutHttpOnly.go:24:21:24:21 | c | CookieWithoutHttpOnly.go:24:20:24:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:29:13:29:21 | "session" | CookieWithoutHttpOnly.go:33:20:33:21 | &... | -| CookieWithoutHttpOnly.go:29:13:29:21 | "session" | CookieWithoutHttpOnly.go:33:20:33:21 | &... | -| CookieWithoutHttpOnly.go:29:13:29:21 | "session" | CookieWithoutHttpOnly.go:33:21:33:21 | c | -| CookieWithoutHttpOnly.go:31:13:31:16 | true | CookieWithoutHttpOnly.go:33:20:33:21 | &... | -| CookieWithoutHttpOnly.go:31:13:31:16 | true | CookieWithoutHttpOnly.go:33:20:33:21 | &... | -| CookieWithoutHttpOnly.go:31:13:31:16 | true | CookieWithoutHttpOnly.go:33:21:33:21 | c | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | CookieWithoutHttpOnly.go:33:20:33:21 | &... | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | CookieWithoutHttpOnly.go:33:20:33:21 | &... | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | CookieWithoutHttpOnly.go:33:20:33:21 | &... | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | CookieWithoutHttpOnly.go:33:20:33:21 | &... | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | CookieWithoutHttpOnly.go:33:21:33:21 | c | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | CookieWithoutHttpOnly.go:33:21:33:21 | c | +| CookieWithoutHttpOnly.go:29:13:29:21 | "session" | CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | +| CookieWithoutHttpOnly.go:31:13:31:16 | true | CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | | CookieWithoutHttpOnly.go:33:20:33:21 | &... | CookieWithoutHttpOnly.go:33:20:33:21 | &... | | CookieWithoutHttpOnly.go:33:20:33:21 | &... | CookieWithoutHttpOnly.go:33:20:33:21 | &... | | CookieWithoutHttpOnly.go:33:20:33:21 | &... | CookieWithoutHttpOnly.go:33:20:33:21 | &... | @@ -49,12 +54,14 @@ edges | CookieWithoutHttpOnly.go:33:20:33:21 | &... [pointer] | CookieWithoutHttpOnly.go:33:21:33:21 | c | | CookieWithoutHttpOnly.go:33:21:33:21 | c | CookieWithoutHttpOnly.go:33:20:33:21 | &... [pointer] | | CookieWithoutHttpOnly.go:33:21:33:21 | c | CookieWithoutHttpOnly.go:33:20:33:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:38:10:38:18 | "session" | CookieWithoutHttpOnly.go:42:20:42:21 | &... | -| CookieWithoutHttpOnly.go:38:10:38:18 | "session" | CookieWithoutHttpOnly.go:42:20:42:21 | &... | -| CookieWithoutHttpOnly.go:38:10:38:18 | "session" | CookieWithoutHttpOnly.go:42:21:42:21 | c | -| CookieWithoutHttpOnly.go:41:15:41:18 | true | CookieWithoutHttpOnly.go:42:20:42:21 | &... | -| CookieWithoutHttpOnly.go:41:15:41:18 | true | CookieWithoutHttpOnly.go:42:20:42:21 | &... | -| CookieWithoutHttpOnly.go:41:15:41:18 | true | CookieWithoutHttpOnly.go:42:21:42:21 | c | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | CookieWithoutHttpOnly.go:42:20:42:21 | &... | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | CookieWithoutHttpOnly.go:42:20:42:21 | &... | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | CookieWithoutHttpOnly.go:42:20:42:21 | &... | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | CookieWithoutHttpOnly.go:42:20:42:21 | &... | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | CookieWithoutHttpOnly.go:42:21:42:21 | c | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | CookieWithoutHttpOnly.go:42:21:42:21 | c | +| CookieWithoutHttpOnly.go:38:10:38:18 | "session" | CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | +| CookieWithoutHttpOnly.go:41:15:41:18 | true | CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | | CookieWithoutHttpOnly.go:42:20:42:21 | &... | CookieWithoutHttpOnly.go:42:20:42:21 | &... | | CookieWithoutHttpOnly.go:42:20:42:21 | &... | CookieWithoutHttpOnly.go:42:20:42:21 | &... | | CookieWithoutHttpOnly.go:42:20:42:21 | &... | CookieWithoutHttpOnly.go:42:20:42:21 | &... | @@ -69,12 +76,14 @@ edges | CookieWithoutHttpOnly.go:42:20:42:21 | &... [pointer] | CookieWithoutHttpOnly.go:42:21:42:21 | c | | CookieWithoutHttpOnly.go:42:21:42:21 | c | CookieWithoutHttpOnly.go:42:20:42:21 | &... [pointer] | | CookieWithoutHttpOnly.go:42:21:42:21 | c | CookieWithoutHttpOnly.go:42:20:42:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:47:10:47:18 | "session" | CookieWithoutHttpOnly.go:51:20:51:21 | &... | -| CookieWithoutHttpOnly.go:47:10:47:18 | "session" | CookieWithoutHttpOnly.go:51:20:51:21 | &... | -| CookieWithoutHttpOnly.go:47:10:47:18 | "session" | CookieWithoutHttpOnly.go:51:21:51:21 | c | -| CookieWithoutHttpOnly.go:50:15:50:19 | false | CookieWithoutHttpOnly.go:51:20:51:21 | &... | -| CookieWithoutHttpOnly.go:50:15:50:19 | false | CookieWithoutHttpOnly.go:51:20:51:21 | &... | -| CookieWithoutHttpOnly.go:50:15:50:19 | false | CookieWithoutHttpOnly.go:51:21:51:21 | c | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | CookieWithoutHttpOnly.go:51:20:51:21 | &... | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | CookieWithoutHttpOnly.go:51:20:51:21 | &... | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | CookieWithoutHttpOnly.go:51:20:51:21 | &... | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | CookieWithoutHttpOnly.go:51:20:51:21 | &... | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | CookieWithoutHttpOnly.go:51:21:51:21 | c | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | CookieWithoutHttpOnly.go:51:21:51:21 | c | +| CookieWithoutHttpOnly.go:47:10:47:18 | "session" | CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | +| CookieWithoutHttpOnly.go:50:15:50:19 | false | CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | | CookieWithoutHttpOnly.go:51:20:51:21 | &... | CookieWithoutHttpOnly.go:51:20:51:21 | &... | | CookieWithoutHttpOnly.go:51:20:51:21 | &... | CookieWithoutHttpOnly.go:51:20:51:21 | &... | | CookieWithoutHttpOnly.go:51:20:51:21 | &... | CookieWithoutHttpOnly.go:51:20:51:21 | &... | @@ -89,18 +98,16 @@ edges | CookieWithoutHttpOnly.go:51:20:51:21 | &... [pointer] | CookieWithoutHttpOnly.go:51:21:51:21 | c | | CookieWithoutHttpOnly.go:51:21:51:21 | c | CookieWithoutHttpOnly.go:51:20:51:21 | &... [pointer] | | CookieWithoutHttpOnly.go:51:21:51:21 | c | CookieWithoutHttpOnly.go:51:20:51:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:55:2:55:4 | definition of val | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:55:2:55:4 | definition of val | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:55:2:55:4 | definition of val | CookieWithoutHttpOnly.go:61:21:61:21 | c | -| CookieWithoutHttpOnly.go:55:9:55:13 | false | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:55:9:55:13 | false | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:55:9:55:13 | false | CookieWithoutHttpOnly.go:61:21:61:21 | c | -| CookieWithoutHttpOnly.go:57:13:57:21 | "session" | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:57:13:57:21 | "session" | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:57:13:57:21 | "session" | CookieWithoutHttpOnly.go:61:21:61:21 | c | -| CookieWithoutHttpOnly.go:59:13:59:15 | val | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:59:13:59:15 | val | CookieWithoutHttpOnly.go:61:20:61:21 | &... | -| CookieWithoutHttpOnly.go:59:13:59:15 | val | CookieWithoutHttpOnly.go:61:21:61:21 | c | +| CookieWithoutHttpOnly.go:55:2:55:4 | definition of val | CookieWithoutHttpOnly.go:59:13:59:15 | val | +| CookieWithoutHttpOnly.go:55:9:55:13 | false | CookieWithoutHttpOnly.go:59:13:59:15 | val | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | CookieWithoutHttpOnly.go:61:20:61:21 | &... | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | CookieWithoutHttpOnly.go:61:20:61:21 | &... | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | CookieWithoutHttpOnly.go:61:20:61:21 | &... | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | CookieWithoutHttpOnly.go:61:20:61:21 | &... | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | CookieWithoutHttpOnly.go:61:21:61:21 | c | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | CookieWithoutHttpOnly.go:61:21:61:21 | c | +| CookieWithoutHttpOnly.go:57:13:57:21 | "session" | CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | +| CookieWithoutHttpOnly.go:59:13:59:15 | val | CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | | CookieWithoutHttpOnly.go:61:20:61:21 | &... | CookieWithoutHttpOnly.go:61:20:61:21 | &... | | CookieWithoutHttpOnly.go:61:20:61:21 | &... | CookieWithoutHttpOnly.go:61:20:61:21 | &... | | CookieWithoutHttpOnly.go:61:20:61:21 | &... | CookieWithoutHttpOnly.go:61:20:61:21 | &... | @@ -115,18 +122,16 @@ edges | CookieWithoutHttpOnly.go:61:20:61:21 | &... [pointer] | CookieWithoutHttpOnly.go:61:21:61:21 | c | | CookieWithoutHttpOnly.go:61:21:61:21 | c | CookieWithoutHttpOnly.go:61:20:61:21 | &... [pointer] | | CookieWithoutHttpOnly.go:61:21:61:21 | c | CookieWithoutHttpOnly.go:61:20:61:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:65:2:65:4 | definition of val | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:65:2:65:4 | definition of val | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:65:2:65:4 | definition of val | CookieWithoutHttpOnly.go:71:21:71:21 | c | -| CookieWithoutHttpOnly.go:65:9:65:12 | true | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:65:9:65:12 | true | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:65:9:65:12 | true | CookieWithoutHttpOnly.go:71:21:71:21 | c | -| CookieWithoutHttpOnly.go:67:13:67:21 | "session" | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:67:13:67:21 | "session" | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:67:13:67:21 | "session" | CookieWithoutHttpOnly.go:71:21:71:21 | c | -| CookieWithoutHttpOnly.go:69:13:69:15 | val | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:69:13:69:15 | val | CookieWithoutHttpOnly.go:71:20:71:21 | &... | -| CookieWithoutHttpOnly.go:69:13:69:15 | val | CookieWithoutHttpOnly.go:71:21:71:21 | c | +| CookieWithoutHttpOnly.go:65:2:65:4 | definition of val | CookieWithoutHttpOnly.go:69:13:69:15 | val | +| CookieWithoutHttpOnly.go:65:9:65:12 | true | CookieWithoutHttpOnly.go:69:13:69:15 | val | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | CookieWithoutHttpOnly.go:71:20:71:21 | &... | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | CookieWithoutHttpOnly.go:71:20:71:21 | &... | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | CookieWithoutHttpOnly.go:71:20:71:21 | &... | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | CookieWithoutHttpOnly.go:71:20:71:21 | &... | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | CookieWithoutHttpOnly.go:71:21:71:21 | c | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | CookieWithoutHttpOnly.go:71:21:71:21 | c | +| CookieWithoutHttpOnly.go:67:13:67:21 | "session" | CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | +| CookieWithoutHttpOnly.go:69:13:69:15 | val | CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | | CookieWithoutHttpOnly.go:71:20:71:21 | &... | CookieWithoutHttpOnly.go:71:20:71:21 | &... | | CookieWithoutHttpOnly.go:71:20:71:21 | &... | CookieWithoutHttpOnly.go:71:20:71:21 | &... | | CookieWithoutHttpOnly.go:71:20:71:21 | &... | CookieWithoutHttpOnly.go:71:20:71:21 | &... | @@ -141,18 +146,16 @@ edges | CookieWithoutHttpOnly.go:71:20:71:21 | &... [pointer] | CookieWithoutHttpOnly.go:71:21:71:21 | c | | CookieWithoutHttpOnly.go:71:21:71:21 | c | CookieWithoutHttpOnly.go:71:20:71:21 | &... [pointer] | | CookieWithoutHttpOnly.go:71:21:71:21 | c | CookieWithoutHttpOnly.go:71:20:71:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:75:2:75:4 | definition of val | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:75:2:75:4 | definition of val | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:75:2:75:4 | definition of val | CookieWithoutHttpOnly.go:81:21:81:21 | c | -| CookieWithoutHttpOnly.go:75:9:75:12 | true | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:75:9:75:12 | true | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:75:9:75:12 | true | CookieWithoutHttpOnly.go:81:21:81:21 | c | -| CookieWithoutHttpOnly.go:77:10:77:18 | "session" | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:77:10:77:18 | "session" | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:77:10:77:18 | "session" | CookieWithoutHttpOnly.go:81:21:81:21 | c | -| CookieWithoutHttpOnly.go:80:15:80:17 | val | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:80:15:80:17 | val | CookieWithoutHttpOnly.go:81:20:81:21 | &... | -| CookieWithoutHttpOnly.go:80:15:80:17 | val | CookieWithoutHttpOnly.go:81:21:81:21 | c | +| CookieWithoutHttpOnly.go:75:2:75:4 | definition of val | CookieWithoutHttpOnly.go:80:15:80:17 | val | +| CookieWithoutHttpOnly.go:75:9:75:12 | true | CookieWithoutHttpOnly.go:80:15:80:17 | val | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | CookieWithoutHttpOnly.go:81:20:81:21 | &... | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | CookieWithoutHttpOnly.go:81:20:81:21 | &... | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | CookieWithoutHttpOnly.go:81:20:81:21 | &... | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | CookieWithoutHttpOnly.go:81:20:81:21 | &... | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | CookieWithoutHttpOnly.go:81:21:81:21 | c | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | CookieWithoutHttpOnly.go:81:21:81:21 | c | +| CookieWithoutHttpOnly.go:77:10:77:18 | "session" | CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | +| CookieWithoutHttpOnly.go:80:15:80:17 | val | CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | | CookieWithoutHttpOnly.go:81:20:81:21 | &... | CookieWithoutHttpOnly.go:81:20:81:21 | &... | | CookieWithoutHttpOnly.go:81:20:81:21 | &... | CookieWithoutHttpOnly.go:81:20:81:21 | &... | | CookieWithoutHttpOnly.go:81:20:81:21 | &... | CookieWithoutHttpOnly.go:81:20:81:21 | &... | @@ -167,18 +170,16 @@ edges | CookieWithoutHttpOnly.go:81:20:81:21 | &... [pointer] | CookieWithoutHttpOnly.go:81:21:81:21 | c | | CookieWithoutHttpOnly.go:81:21:81:21 | c | CookieWithoutHttpOnly.go:81:20:81:21 | &... [pointer] | | CookieWithoutHttpOnly.go:81:21:81:21 | c | CookieWithoutHttpOnly.go:81:20:81:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:85:2:85:4 | definition of val | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:85:2:85:4 | definition of val | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:85:2:85:4 | definition of val | CookieWithoutHttpOnly.go:91:21:91:21 | c | -| CookieWithoutHttpOnly.go:85:9:85:13 | false | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:85:9:85:13 | false | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:85:9:85:13 | false | CookieWithoutHttpOnly.go:91:21:91:21 | c | -| CookieWithoutHttpOnly.go:87:10:87:18 | "session" | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:87:10:87:18 | "session" | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:87:10:87:18 | "session" | CookieWithoutHttpOnly.go:91:21:91:21 | c | -| CookieWithoutHttpOnly.go:90:15:90:17 | val | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:90:15:90:17 | val | CookieWithoutHttpOnly.go:91:20:91:21 | &... | -| CookieWithoutHttpOnly.go:90:15:90:17 | val | CookieWithoutHttpOnly.go:91:21:91:21 | c | +| CookieWithoutHttpOnly.go:85:2:85:4 | definition of val | CookieWithoutHttpOnly.go:90:15:90:17 | val | +| CookieWithoutHttpOnly.go:85:9:85:13 | false | CookieWithoutHttpOnly.go:90:15:90:17 | val | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | CookieWithoutHttpOnly.go:91:20:91:21 | &... | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | CookieWithoutHttpOnly.go:91:20:91:21 | &... | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | CookieWithoutHttpOnly.go:91:20:91:21 | &... | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | CookieWithoutHttpOnly.go:91:20:91:21 | &... | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | CookieWithoutHttpOnly.go:91:21:91:21 | c | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | CookieWithoutHttpOnly.go:91:21:91:21 | c | +| CookieWithoutHttpOnly.go:87:10:87:18 | "session" | CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | +| CookieWithoutHttpOnly.go:90:15:90:17 | val | CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | | CookieWithoutHttpOnly.go:91:20:91:21 | &... | CookieWithoutHttpOnly.go:91:20:91:21 | &... | | CookieWithoutHttpOnly.go:91:20:91:21 | &... | CookieWithoutHttpOnly.go:91:20:91:21 | &... | | CookieWithoutHttpOnly.go:91:20:91:21 | &... | CookieWithoutHttpOnly.go:91:20:91:21 | &... | @@ -193,9 +194,10 @@ edges | CookieWithoutHttpOnly.go:91:20:91:21 | &... [pointer] | CookieWithoutHttpOnly.go:91:21:91:21 | c | | CookieWithoutHttpOnly.go:91:21:91:21 | c | CookieWithoutHttpOnly.go:91:20:91:21 | &... [pointer] | | CookieWithoutHttpOnly.go:91:21:91:21 | c | CookieWithoutHttpOnly.go:91:20:91:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:99:15:99:19 | false | CookieWithoutHttpOnly.go:100:20:100:21 | &... | -| CookieWithoutHttpOnly.go:99:15:99:19 | false | CookieWithoutHttpOnly.go:100:20:100:21 | &... | -| CookieWithoutHttpOnly.go:99:15:99:19 | false | CookieWithoutHttpOnly.go:100:21:100:21 | c | +| CookieWithoutHttpOnly.go:95:7:98:2 | struct literal | CookieWithoutHttpOnly.go:100:20:100:21 | &... | +| CookieWithoutHttpOnly.go:95:7:98:2 | struct literal | CookieWithoutHttpOnly.go:100:20:100:21 | &... | +| CookieWithoutHttpOnly.go:95:7:98:2 | struct literal | CookieWithoutHttpOnly.go:100:21:100:21 | c | +| CookieWithoutHttpOnly.go:99:15:99:19 | false | CookieWithoutHttpOnly.go:95:7:98:2 | struct literal | | CookieWithoutHttpOnly.go:100:20:100:21 | &... | CookieWithoutHttpOnly.go:100:20:100:21 | &... | | CookieWithoutHttpOnly.go:100:20:100:21 | &... | CookieWithoutHttpOnly.go:100:20:100:21 | &... | | CookieWithoutHttpOnly.go:100:20:100:21 | &... | CookieWithoutHttpOnly.go:100:21:100:21 | c | @@ -203,12 +205,15 @@ edges | CookieWithoutHttpOnly.go:100:20:100:21 | &... [pointer] | CookieWithoutHttpOnly.go:100:20:100:21 | &... | | CookieWithoutHttpOnly.go:100:20:100:21 | &... [pointer] | CookieWithoutHttpOnly.go:100:21:100:21 | c | | CookieWithoutHttpOnly.go:100:21:100:21 | c | CookieWithoutHttpOnly.go:100:20:100:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:104:10:104:18 | "session" | CookieWithoutHttpOnly.go:110:20:110:21 | &... | -| CookieWithoutHttpOnly.go:104:10:104:18 | "session" | CookieWithoutHttpOnly.go:110:20:110:21 | &... | -| CookieWithoutHttpOnly.go:104:10:104:18 | "session" | CookieWithoutHttpOnly.go:110:21:110:21 | c | -| CookieWithoutHttpOnly.go:109:15:109:19 | false | CookieWithoutHttpOnly.go:110:20:110:21 | &... | -| CookieWithoutHttpOnly.go:109:15:109:19 | false | CookieWithoutHttpOnly.go:110:20:110:21 | &... | -| CookieWithoutHttpOnly.go:109:15:109:19 | false | CookieWithoutHttpOnly.go:110:21:110:21 | c | +| CookieWithoutHttpOnly.go:104:10:104:18 | "session" | CookieWithoutHttpOnly.go:106:10:106:13 | name | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | CookieWithoutHttpOnly.go:110:20:110:21 | &... | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | CookieWithoutHttpOnly.go:110:20:110:21 | &... | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | CookieWithoutHttpOnly.go:110:20:110:21 | &... | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | CookieWithoutHttpOnly.go:110:20:110:21 | &... | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | CookieWithoutHttpOnly.go:110:21:110:21 | c | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | CookieWithoutHttpOnly.go:110:21:110:21 | c | +| CookieWithoutHttpOnly.go:106:10:106:13 | name | CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | +| CookieWithoutHttpOnly.go:109:15:109:19 | false | CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | | CookieWithoutHttpOnly.go:110:20:110:21 | &... | CookieWithoutHttpOnly.go:110:20:110:21 | &... | | CookieWithoutHttpOnly.go:110:20:110:21 | &... | CookieWithoutHttpOnly.go:110:20:110:21 | &... | | CookieWithoutHttpOnly.go:110:20:110:21 | &... | CookieWithoutHttpOnly.go:110:20:110:21 | &... | @@ -223,15 +228,15 @@ edges | CookieWithoutHttpOnly.go:110:20:110:21 | &... [pointer] | CookieWithoutHttpOnly.go:110:21:110:21 | c | | CookieWithoutHttpOnly.go:110:21:110:21 | c | CookieWithoutHttpOnly.go:110:20:110:21 | &... [pointer] | | CookieWithoutHttpOnly.go:110:21:110:21 | c | CookieWithoutHttpOnly.go:110:20:110:21 | &... [pointer] | -| CookieWithoutHttpOnly.go:114:13:114:24 | "login_name" | CookieWithoutHttpOnly.go:120:20:120:21 | &... | -| CookieWithoutHttpOnly.go:114:13:114:24 | "login_name" | CookieWithoutHttpOnly.go:120:20:120:21 | &... | -| CookieWithoutHttpOnly.go:114:13:114:24 | "login_name" | CookieWithoutHttpOnly.go:120:21:120:21 | c | -| CookieWithoutHttpOnly.go:116:10:116:16 | session | CookieWithoutHttpOnly.go:120:20:120:21 | &... | -| CookieWithoutHttpOnly.go:116:10:116:16 | session | CookieWithoutHttpOnly.go:120:20:120:21 | &... | -| CookieWithoutHttpOnly.go:116:10:116:16 | session | CookieWithoutHttpOnly.go:120:21:120:21 | c | -| CookieWithoutHttpOnly.go:119:15:119:19 | false | CookieWithoutHttpOnly.go:120:20:120:21 | &... | -| CookieWithoutHttpOnly.go:119:15:119:19 | false | CookieWithoutHttpOnly.go:120:20:120:21 | &... | -| CookieWithoutHttpOnly.go:119:15:119:19 | false | CookieWithoutHttpOnly.go:120:21:120:21 | c | +| CookieWithoutHttpOnly.go:114:13:114:24 | "login_name" | CookieWithoutHttpOnly.go:116:10:116:16 | session | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | CookieWithoutHttpOnly.go:120:20:120:21 | &... | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | CookieWithoutHttpOnly.go:120:20:120:21 | &... | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | CookieWithoutHttpOnly.go:120:20:120:21 | &... | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | CookieWithoutHttpOnly.go:120:20:120:21 | &... | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | CookieWithoutHttpOnly.go:120:21:120:21 | c | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | CookieWithoutHttpOnly.go:120:21:120:21 | c | +| CookieWithoutHttpOnly.go:116:10:116:16 | session | CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | +| CookieWithoutHttpOnly.go:119:15:119:19 | false | CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | | CookieWithoutHttpOnly.go:120:20:120:21 | &... | CookieWithoutHttpOnly.go:120:20:120:21 | &... | | CookieWithoutHttpOnly.go:120:20:120:21 | &... | CookieWithoutHttpOnly.go:120:20:120:21 | &... | | CookieWithoutHttpOnly.go:120:20:120:21 | &... | CookieWithoutHttpOnly.go:120:20:120:21 | &... | @@ -253,26 +258,24 @@ edges | CookieWithoutHttpOnly.go:123:13:123:49 | call to NewCookieStore | CookieWithoutHttpOnly.go:170:16:170:20 | store | | CookieWithoutHttpOnly.go:123:13:123:49 | call to NewCookieStore | CookieWithoutHttpOnly.go:183:16:183:20 | store | | CookieWithoutHttpOnly.go:123:13:123:49 | call to NewCookieStore | CookieWithoutHttpOnly.go:195:16:195:20 | store | -| CookieWithoutHttpOnly.go:126:16:126:20 | store | CookieWithoutHttpOnly.go:129:2:129:8 | session | -| CookieWithoutHttpOnly.go:133:2:133:9 | definition of httpOnly | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | -| CookieWithoutHttpOnly.go:133:2:133:9 | definition of httpOnly | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | -| CookieWithoutHttpOnly.go:133:2:133:9 | definition of httpOnly | CookieWithoutHttpOnly.go:142:2:142:8 | session | -| CookieWithoutHttpOnly.go:133:14:133:18 | false | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | -| CookieWithoutHttpOnly.go:133:14:133:18 | false | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | -| CookieWithoutHttpOnly.go:133:14:133:18 | false | CookieWithoutHttpOnly.go:142:2:142:8 | session | +| CookieWithoutHttpOnly.go:126:2:126:43 | ... := ...[0] | CookieWithoutHttpOnly.go:129:2:129:8 | session | +| CookieWithoutHttpOnly.go:126:16:126:20 | store | CookieWithoutHttpOnly.go:126:2:126:43 | ... := ...[0] | +| CookieWithoutHttpOnly.go:133:2:133:9 | definition of httpOnly | CookieWithoutHttpOnly.go:139:13:139:20 | httpOnly | +| CookieWithoutHttpOnly.go:133:14:133:18 | false | CookieWithoutHttpOnly.go:139:13:139:20 | httpOnly | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:135:2:135:8 | session [pointer] | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:135:2:135:8 | session [pointer] | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:137:2:137:8 | session [pointer] | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:137:2:137:8 | session [pointer] | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:142:2:142:8 | session | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:142:2:142:8 | session | -| CookieWithoutHttpOnly.go:134:16:134:20 | store | CookieWithoutHttpOnly.go:142:2:142:8 | session | +| CookieWithoutHttpOnly.go:134:2:134:43 | ... := ...[0] | CookieWithoutHttpOnly.go:142:2:142:8 | session | +| CookieWithoutHttpOnly.go:134:16:134:20 | store | CookieWithoutHttpOnly.go:134:2:134:43 | ... := ...[0] | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | -| CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | -| CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | +| CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | session | +| CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | session | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:142:2:142:8 | session | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | CookieWithoutHttpOnly.go:142:2:142:8 | session | | CookieWithoutHttpOnly.go:135:2:135:8 | session [pointer] | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | @@ -281,54 +284,59 @@ edges | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | -| CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | -| CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | +| CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | session | +| CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:137:2:137:8 | session | | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:142:2:142:8 | session | | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | CookieWithoutHttpOnly.go:142:2:142:8 | session | +| CookieWithoutHttpOnly.go:137:2:137:8 | session | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | +| CookieWithoutHttpOnly.go:137:2:137:8 | session | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | | CookieWithoutHttpOnly.go:137:2:137:8 | session [pointer] | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | | CookieWithoutHttpOnly.go:137:2:137:8 | session [pointer] | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | -| CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | -| CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | -| CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | CookieWithoutHttpOnly.go:142:2:142:8 | session | -| CookieWithoutHttpOnly.go:139:13:139:20 | httpOnly | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | -| CookieWithoutHttpOnly.go:139:13:139:20 | httpOnly | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | -| CookieWithoutHttpOnly.go:139:13:139:20 | httpOnly | CookieWithoutHttpOnly.go:142:2:142:8 | session | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | CookieWithoutHttpOnly.go:137:2:137:8 | session | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | CookieWithoutHttpOnly.go:137:2:137:8 | session | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | CookieWithoutHttpOnly.go:137:20:140:2 | &... | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | +| CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | CookieWithoutHttpOnly.go:137:20:140:2 | &... | +| CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | CookieWithoutHttpOnly.go:137:20:140:2 | &... | +| CookieWithoutHttpOnly.go:139:13:139:20 | httpOnly | CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | | CookieWithoutHttpOnly.go:146:2:146:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:147:2:147:8 | session [pointer] | | CookieWithoutHttpOnly.go:146:2:146:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:149:2:149:8 | session [pointer] | | CookieWithoutHttpOnly.go:146:2:146:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:153:2:153:8 | session | -| CookieWithoutHttpOnly.go:146:16:146:20 | store | CookieWithoutHttpOnly.go:153:2:153:8 | session | +| CookieWithoutHttpOnly.go:146:2:146:43 | ... := ...[0] | CookieWithoutHttpOnly.go:153:2:153:8 | session | +| CookieWithoutHttpOnly.go:146:16:146:20 | store | CookieWithoutHttpOnly.go:146:2:146:43 | ... := ...[0] | | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | CookieWithoutHttpOnly.go:146:2:146:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | -| CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | +| CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | CookieWithoutHttpOnly.go:149:2:149:8 | session | | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | CookieWithoutHttpOnly.go:153:2:153:8 | session | | CookieWithoutHttpOnly.go:147:2:147:8 | session [pointer] | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | CookieWithoutHttpOnly.go:146:2:146:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | -| CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | +| CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | CookieWithoutHttpOnly.go:149:2:149:8 | session | | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | CookieWithoutHttpOnly.go:153:2:153:8 | session | +| CookieWithoutHttpOnly.go:149:2:149:8 | session | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | | CookieWithoutHttpOnly.go:149:2:149:8 | session [pointer] | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | -| CookieWithoutHttpOnly.go:149:21:151:2 | struct literal | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | -| CookieWithoutHttpOnly.go:149:21:151:2 | struct literal | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | -| CookieWithoutHttpOnly.go:149:21:151:2 | struct literal | CookieWithoutHttpOnly.go:153:2:153:8 | session | -| CookieWithoutHttpOnly.go:157:2:157:9 | definition of httpOnly | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | -| CookieWithoutHttpOnly.go:157:2:157:9 | definition of httpOnly | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | -| CookieWithoutHttpOnly.go:157:2:157:9 | definition of httpOnly | CookieWithoutHttpOnly.go:166:2:166:8 | session | -| CookieWithoutHttpOnly.go:157:14:157:17 | true | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | -| CookieWithoutHttpOnly.go:157:14:157:17 | true | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | -| CookieWithoutHttpOnly.go:157:14:157:17 | true | CookieWithoutHttpOnly.go:166:2:166:8 | session | +| CookieWithoutHttpOnly.go:149:20:151:2 | &... | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | +| CookieWithoutHttpOnly.go:149:20:151:2 | &... | CookieWithoutHttpOnly.go:149:2:149:8 | session | +| CookieWithoutHttpOnly.go:149:20:151:2 | &... | CookieWithoutHttpOnly.go:149:20:151:2 | &... | +| CookieWithoutHttpOnly.go:149:21:151:2 | struct literal | CookieWithoutHttpOnly.go:149:20:151:2 | &... | +| CookieWithoutHttpOnly.go:157:2:157:9 | definition of httpOnly | CookieWithoutHttpOnly.go:163:13:163:20 | httpOnly | +| CookieWithoutHttpOnly.go:157:14:157:17 | true | CookieWithoutHttpOnly.go:163:13:163:20 | httpOnly | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:159:2:159:8 | session [pointer] | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:159:2:159:8 | session [pointer] | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:161:2:161:8 | session [pointer] | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:161:2:161:8 | session [pointer] | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:166:2:166:8 | session | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:166:2:166:8 | session | -| CookieWithoutHttpOnly.go:158:16:158:20 | store | CookieWithoutHttpOnly.go:166:2:166:8 | session | +| CookieWithoutHttpOnly.go:158:2:158:43 | ... := ...[0] | CookieWithoutHttpOnly.go:166:2:166:8 | session | +| CookieWithoutHttpOnly.go:158:16:158:20 | store | CookieWithoutHttpOnly.go:158:2:158:43 | ... := ...[0] | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | -| CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | -| CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | +| CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | session | +| CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | session | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:166:2:166:8 | session | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | CookieWithoutHttpOnly.go:166:2:166:8 | session | | CookieWithoutHttpOnly.go:159:2:159:8 | session [pointer] | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | @@ -337,37 +345,39 @@ edges | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | -| CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | -| CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | +| CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | session | +| CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:161:2:161:8 | session | | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:166:2:166:8 | session | | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | CookieWithoutHttpOnly.go:166:2:166:8 | session | +| CookieWithoutHttpOnly.go:161:2:161:8 | session | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | +| CookieWithoutHttpOnly.go:161:2:161:8 | session | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | | CookieWithoutHttpOnly.go:161:2:161:8 | session [pointer] | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | | CookieWithoutHttpOnly.go:161:2:161:8 | session [pointer] | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | -| CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | -| CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | -| CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | CookieWithoutHttpOnly.go:166:2:166:8 | session | -| CookieWithoutHttpOnly.go:163:13:163:20 | httpOnly | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | -| CookieWithoutHttpOnly.go:163:13:163:20 | httpOnly | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | -| CookieWithoutHttpOnly.go:163:13:163:20 | httpOnly | CookieWithoutHttpOnly.go:166:2:166:8 | session | -| CookieWithoutHttpOnly.go:169:56:169:63 | argument corresponding to httpOnly | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | -| CookieWithoutHttpOnly.go:169:56:169:63 | argument corresponding to httpOnly | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | -| CookieWithoutHttpOnly.go:169:56:169:63 | argument corresponding to httpOnly | CookieWithoutHttpOnly.go:178:2:178:8 | session | -| CookieWithoutHttpOnly.go:169:56:169:63 | definition of httpOnly | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | -| CookieWithoutHttpOnly.go:169:56:169:63 | definition of httpOnly | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | -| CookieWithoutHttpOnly.go:169:56:169:63 | definition of httpOnly | CookieWithoutHttpOnly.go:178:2:178:8 | session | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | CookieWithoutHttpOnly.go:161:2:161:8 | session | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | CookieWithoutHttpOnly.go:161:2:161:8 | session | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | CookieWithoutHttpOnly.go:161:20:164:2 | &... | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | +| CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | CookieWithoutHttpOnly.go:161:20:164:2 | &... | +| CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | CookieWithoutHttpOnly.go:161:20:164:2 | &... | +| CookieWithoutHttpOnly.go:163:13:163:20 | httpOnly | CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | +| CookieWithoutHttpOnly.go:169:56:169:63 | argument corresponding to httpOnly | CookieWithoutHttpOnly.go:175:13:175:20 | httpOnly | +| CookieWithoutHttpOnly.go:169:56:169:63 | definition of httpOnly | CookieWithoutHttpOnly.go:175:13:175:20 | httpOnly | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:171:2:171:8 | session [pointer] | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:171:2:171:8 | session [pointer] | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:173:2:173:8 | session [pointer] | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:173:2:173:8 | session [pointer] | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:178:2:178:8 | session | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | CookieWithoutHttpOnly.go:178:2:178:8 | session | -| CookieWithoutHttpOnly.go:170:16:170:20 | store | CookieWithoutHttpOnly.go:178:2:178:8 | session | +| CookieWithoutHttpOnly.go:170:2:170:43 | ... := ...[0] | CookieWithoutHttpOnly.go:178:2:178:8 | session | +| CookieWithoutHttpOnly.go:170:16:170:20 | store | CookieWithoutHttpOnly.go:170:2:170:43 | ... := ...[0] | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | -| CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | -| CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | +| CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | session | +| CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | session | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:178:2:178:8 | session | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | CookieWithoutHttpOnly.go:178:2:178:8 | session | | CookieWithoutHttpOnly.go:171:2:171:8 | session [pointer] | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | @@ -376,26 +386,36 @@ edges | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | -| CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | -| CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | +| CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | session | +| CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:173:2:173:8 | session | | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:178:2:178:8 | session | | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | CookieWithoutHttpOnly.go:178:2:178:8 | session | +| CookieWithoutHttpOnly.go:173:2:173:8 | session | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | +| CookieWithoutHttpOnly.go:173:2:173:8 | session | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | | CookieWithoutHttpOnly.go:173:2:173:8 | session [pointer] | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | | CookieWithoutHttpOnly.go:173:2:173:8 | session [pointer] | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | -| CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | -| CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | -| CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | CookieWithoutHttpOnly.go:178:2:178:8 | session | -| CookieWithoutHttpOnly.go:175:13:175:20 | httpOnly | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | -| CookieWithoutHttpOnly.go:175:13:175:20 | httpOnly | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | -| CookieWithoutHttpOnly.go:175:13:175:20 | httpOnly | CookieWithoutHttpOnly.go:178:2:178:8 | session | -| CookieWithoutHttpOnly.go:183:16:183:20 | store | CookieWithoutHttpOnly.go:191:19:191:25 | session | -| CookieWithoutHttpOnly.go:195:16:195:20 | store | CookieWithoutHttpOnly.go:202:19:202:25 | session | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | CookieWithoutHttpOnly.go:173:2:173:8 | session | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | CookieWithoutHttpOnly.go:173:2:173:8 | session | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | CookieWithoutHttpOnly.go:173:20:176:2 | &... | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | +| CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | CookieWithoutHttpOnly.go:173:20:176:2 | &... | +| CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | CookieWithoutHttpOnly.go:173:20:176:2 | &... | +| CookieWithoutHttpOnly.go:175:13:175:20 | httpOnly | CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | +| CookieWithoutHttpOnly.go:183:2:183:43 | ... := ...[0] | CookieWithoutHttpOnly.go:191:19:191:25 | session | +| CookieWithoutHttpOnly.go:183:16:183:20 | store | CookieWithoutHttpOnly.go:183:2:183:43 | ... := ...[0] | +| CookieWithoutHttpOnly.go:195:2:195:43 | ... := ...[0] | CookieWithoutHttpOnly.go:202:19:202:25 | session | +| CookieWithoutHttpOnly.go:195:16:195:20 | store | CookieWithoutHttpOnly.go:195:2:195:43 | ... := ...[0] | nodes +| CookieWithoutHttpOnly.go:11:7:14:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:12:10:12:18 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:15:20:15:21 | &... | semmle.label | &... | | CookieWithoutHttpOnly.go:15:20:15:21 | &... | semmle.label | &... | | CookieWithoutHttpOnly.go:15:20:15:21 | &... [pointer] | semmle.label | &... [pointer] | | CookieWithoutHttpOnly.go:15:21:15:21 | c | semmle.label | c | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:19:7:23:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:20:13:20:21 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:22:13:22:17 | false | semmle.label | false | | CookieWithoutHttpOnly.go:24:20:24:21 | &... | semmle.label | &... | @@ -406,6 +426,8 @@ nodes | CookieWithoutHttpOnly.go:24:20:24:21 | &... [pointer] | semmle.label | &... [pointer] | | CookieWithoutHttpOnly.go:24:21:24:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:24:21:24:21 | c | semmle.label | c | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:28:7:32:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:29:13:29:21 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:31:13:31:16 | true | semmle.label | true | | CookieWithoutHttpOnly.go:33:20:33:21 | &... | semmle.label | &... | @@ -416,6 +438,8 @@ nodes | CookieWithoutHttpOnly.go:33:20:33:21 | &... [pointer] | semmle.label | &... [pointer] | | CookieWithoutHttpOnly.go:33:21:33:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:33:21:33:21 | c | semmle.label | c | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:37:7:40:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:38:10:38:18 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:41:15:41:18 | true | semmle.label | true | | CookieWithoutHttpOnly.go:42:20:42:21 | &... | semmle.label | &... | @@ -426,6 +450,8 @@ nodes | CookieWithoutHttpOnly.go:42:20:42:21 | &... [pointer] | semmle.label | &... [pointer] | | CookieWithoutHttpOnly.go:42:21:42:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:42:21:42:21 | c | semmle.label | c | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:46:7:49:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:47:10:47:18 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:50:15:50:19 | false | semmle.label | false | | CookieWithoutHttpOnly.go:51:20:51:21 | &... | semmle.label | &... | @@ -438,6 +464,8 @@ nodes | CookieWithoutHttpOnly.go:51:21:51:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:55:2:55:4 | definition of val | semmle.label | definition of val | | CookieWithoutHttpOnly.go:55:9:55:13 | false | semmle.label | false | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:56:7:60:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:57:13:57:21 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:59:13:59:15 | val | semmle.label | val | | CookieWithoutHttpOnly.go:61:20:61:21 | &... | semmle.label | &... | @@ -450,6 +478,8 @@ nodes | CookieWithoutHttpOnly.go:61:21:61:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:65:2:65:4 | definition of val | semmle.label | definition of val | | CookieWithoutHttpOnly.go:65:9:65:12 | true | semmle.label | true | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:66:7:70:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:67:13:67:21 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:69:13:69:15 | val | semmle.label | val | | CookieWithoutHttpOnly.go:71:20:71:21 | &... | semmle.label | &... | @@ -462,6 +492,8 @@ nodes | CookieWithoutHttpOnly.go:71:21:71:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:75:2:75:4 | definition of val | semmle.label | definition of val | | CookieWithoutHttpOnly.go:75:9:75:12 | true | semmle.label | true | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:76:7:79:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:77:10:77:18 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:80:15:80:17 | val | semmle.label | val | | CookieWithoutHttpOnly.go:81:20:81:21 | &... | semmle.label | &... | @@ -474,6 +506,8 @@ nodes | CookieWithoutHttpOnly.go:81:21:81:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:85:2:85:4 | definition of val | semmle.label | definition of val | | CookieWithoutHttpOnly.go:85:9:85:13 | false | semmle.label | false | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:86:7:89:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:87:10:87:18 | "session" | semmle.label | "session" | | CookieWithoutHttpOnly.go:90:15:90:17 | val | semmle.label | val | | CookieWithoutHttpOnly.go:91:20:91:21 | &... | semmle.label | &... | @@ -484,12 +518,16 @@ nodes | CookieWithoutHttpOnly.go:91:20:91:21 | &... [pointer] | semmle.label | &... [pointer] | | CookieWithoutHttpOnly.go:91:21:91:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:91:21:91:21 | c | semmle.label | c | +| CookieWithoutHttpOnly.go:95:7:98:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:99:15:99:19 | false | semmle.label | false | | CookieWithoutHttpOnly.go:100:20:100:21 | &... | semmle.label | &... | | CookieWithoutHttpOnly.go:100:20:100:21 | &... | semmle.label | &... | | CookieWithoutHttpOnly.go:100:20:100:21 | &... [pointer] | semmle.label | &... [pointer] | | CookieWithoutHttpOnly.go:100:21:100:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:104:10:104:18 | "session" | semmle.label | "session" | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:105:7:108:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:106:10:106:13 | name | semmle.label | name | | CookieWithoutHttpOnly.go:109:15:109:19 | false | semmle.label | false | | CookieWithoutHttpOnly.go:110:20:110:21 | &... | semmle.label | &... | | CookieWithoutHttpOnly.go:110:20:110:21 | &... | semmle.label | &... | @@ -500,6 +538,8 @@ nodes | CookieWithoutHttpOnly.go:110:21:110:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:110:21:110:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:114:13:114:24 | "login_name" | semmle.label | "login_name" | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | semmle.label | struct literal | +| CookieWithoutHttpOnly.go:115:7:118:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:116:10:116:16 | session | semmle.label | session | | CookieWithoutHttpOnly.go:119:15:119:19 | false | semmle.label | false | | CookieWithoutHttpOnly.go:120:20:120:21 | &... | semmle.label | &... | @@ -511,12 +551,14 @@ nodes | CookieWithoutHttpOnly.go:120:21:120:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:120:21:120:21 | c | semmle.label | c | | CookieWithoutHttpOnly.go:123:13:123:49 | call to NewCookieStore | semmle.label | call to NewCookieStore | +| CookieWithoutHttpOnly.go:126:2:126:43 | ... := ...[0] | semmle.label | ... := ...[0] | | CookieWithoutHttpOnly.go:126:16:126:20 | store | semmle.label | store | | CookieWithoutHttpOnly.go:129:2:129:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:133:2:133:9 | definition of httpOnly | semmle.label | definition of httpOnly | | CookieWithoutHttpOnly.go:133:14:133:18 | false | semmle.label | false | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | semmle.label | definition of session [pointer] | | CookieWithoutHttpOnly.go:134:2:134:8 | definition of session [pointer] | semmle.label | definition of session [pointer] | +| CookieWithoutHttpOnly.go:134:2:134:43 | ... := ...[0] | semmle.label | ... := ...[0] | | CookieWithoutHttpOnly.go:134:16:134:20 | store | semmle.label | store | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | semmle.label | implicit dereference | | CookieWithoutHttpOnly.go:135:2:135:8 | implicit dereference | semmle.label | implicit dereference | @@ -524,19 +566,27 @@ nodes | CookieWithoutHttpOnly.go:135:2:135:8 | session [pointer] | semmle.label | session [pointer] | | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | semmle.label | implicit dereference | | CookieWithoutHttpOnly.go:137:2:137:8 | implicit dereference | semmle.label | implicit dereference | +| CookieWithoutHttpOnly.go:137:2:137:8 | session | semmle.label | session | +| CookieWithoutHttpOnly.go:137:2:137:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:137:2:137:8 | session [pointer] | semmle.label | session [pointer] | | CookieWithoutHttpOnly.go:137:2:137:8 | session [pointer] | semmle.label | session [pointer] | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | semmle.label | &... | +| CookieWithoutHttpOnly.go:137:20:140:2 | &... | semmle.label | &... | +| CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:137:21:140:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:139:13:139:20 | httpOnly | semmle.label | httpOnly | | CookieWithoutHttpOnly.go:142:2:142:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:142:2:142:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:142:2:142:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:146:2:146:8 | definition of session [pointer] | semmle.label | definition of session [pointer] | +| CookieWithoutHttpOnly.go:146:2:146:43 | ... := ...[0] | semmle.label | ... := ...[0] | | CookieWithoutHttpOnly.go:146:16:146:20 | store | semmle.label | store | | CookieWithoutHttpOnly.go:147:2:147:8 | implicit dereference | semmle.label | implicit dereference | | CookieWithoutHttpOnly.go:147:2:147:8 | session [pointer] | semmle.label | session [pointer] | | CookieWithoutHttpOnly.go:149:2:149:8 | implicit dereference | semmle.label | implicit dereference | +| CookieWithoutHttpOnly.go:149:2:149:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:149:2:149:8 | session [pointer] | semmle.label | session [pointer] | +| CookieWithoutHttpOnly.go:149:20:151:2 | &... | semmle.label | &... | | CookieWithoutHttpOnly.go:149:21:151:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:153:2:153:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:153:2:153:8 | session | semmle.label | session | @@ -544,6 +594,7 @@ nodes | CookieWithoutHttpOnly.go:157:14:157:17 | true | semmle.label | true | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | semmle.label | definition of session [pointer] | | CookieWithoutHttpOnly.go:158:2:158:8 | definition of session [pointer] | semmle.label | definition of session [pointer] | +| CookieWithoutHttpOnly.go:158:2:158:43 | ... := ...[0] | semmle.label | ... := ...[0] | | CookieWithoutHttpOnly.go:158:16:158:20 | store | semmle.label | store | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | semmle.label | implicit dereference | | CookieWithoutHttpOnly.go:159:2:159:8 | implicit dereference | semmle.label | implicit dereference | @@ -551,8 +602,13 @@ nodes | CookieWithoutHttpOnly.go:159:2:159:8 | session [pointer] | semmle.label | session [pointer] | | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | semmle.label | implicit dereference | | CookieWithoutHttpOnly.go:161:2:161:8 | implicit dereference | semmle.label | implicit dereference | +| CookieWithoutHttpOnly.go:161:2:161:8 | session | semmle.label | session | +| CookieWithoutHttpOnly.go:161:2:161:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:161:2:161:8 | session [pointer] | semmle.label | session [pointer] | | CookieWithoutHttpOnly.go:161:2:161:8 | session [pointer] | semmle.label | session [pointer] | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | semmle.label | &... | +| CookieWithoutHttpOnly.go:161:20:164:2 | &... | semmle.label | &... | +| CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:161:21:164:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:163:13:163:20 | httpOnly | semmle.label | httpOnly | | CookieWithoutHttpOnly.go:166:2:166:8 | session | semmle.label | session | @@ -562,6 +618,7 @@ nodes | CookieWithoutHttpOnly.go:169:56:169:63 | definition of httpOnly | semmle.label | definition of httpOnly | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | semmle.label | definition of session [pointer] | | CookieWithoutHttpOnly.go:170:2:170:8 | definition of session [pointer] | semmle.label | definition of session [pointer] | +| CookieWithoutHttpOnly.go:170:2:170:43 | ... := ...[0] | semmle.label | ... := ...[0] | | CookieWithoutHttpOnly.go:170:16:170:20 | store | semmle.label | store | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | semmle.label | implicit dereference | | CookieWithoutHttpOnly.go:171:2:171:8 | implicit dereference | semmle.label | implicit dereference | @@ -569,15 +626,22 @@ nodes | CookieWithoutHttpOnly.go:171:2:171:8 | session [pointer] | semmle.label | session [pointer] | | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | semmle.label | implicit dereference | | CookieWithoutHttpOnly.go:173:2:173:8 | implicit dereference | semmle.label | implicit dereference | +| CookieWithoutHttpOnly.go:173:2:173:8 | session | semmle.label | session | +| CookieWithoutHttpOnly.go:173:2:173:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:173:2:173:8 | session [pointer] | semmle.label | session [pointer] | | CookieWithoutHttpOnly.go:173:2:173:8 | session [pointer] | semmle.label | session [pointer] | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | semmle.label | &... | +| CookieWithoutHttpOnly.go:173:20:176:2 | &... | semmle.label | &... | +| CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:173:21:176:2 | struct literal | semmle.label | struct literal | | CookieWithoutHttpOnly.go:175:13:175:20 | httpOnly | semmle.label | httpOnly | | CookieWithoutHttpOnly.go:178:2:178:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:178:2:178:8 | session | semmle.label | session | | CookieWithoutHttpOnly.go:178:2:178:8 | session | semmle.label | session | +| CookieWithoutHttpOnly.go:183:2:183:43 | ... := ...[0] | semmle.label | ... := ...[0] | | CookieWithoutHttpOnly.go:183:16:183:20 | store | semmle.label | store | | CookieWithoutHttpOnly.go:191:19:191:25 | session | semmle.label | session | +| CookieWithoutHttpOnly.go:195:2:195:43 | ... := ...[0] | semmle.label | ... := ...[0] | | CookieWithoutHttpOnly.go:195:16:195:20 | store | semmle.label | store | | CookieWithoutHttpOnly.go:202:19:202:25 | session | semmle.label | session | | CookieWithoutHttpOnly.go:214:66:214:70 | false | semmle.label | false | diff --git a/go/ql/test/experimental/CWE-369/DivideByZero.expected b/go/ql/test/experimental/CWE-369/DivideByZero.expected index 0fcc31dd1c5..0d979a79790 100644 --- a/go/ql/test/experimental/CWE-369/DivideByZero.expected +++ b/go/ql/test/experimental/CWE-369/DivideByZero.expected @@ -1,21 +1,31 @@ edges | DivideByZero.go:10:12:10:16 | selection of URL | DivideByZero.go:10:12:10:24 | call to Query | -| DivideByZero.go:10:12:10:24 | call to Query | DivideByZero.go:12:16:12:20 | value | +| DivideByZero.go:10:12:10:24 | call to Query | DivideByZero.go:11:27:11:32 | param1 | +| DivideByZero.go:11:2:11:33 | ... := ...[0] | DivideByZero.go:12:16:12:20 | value | +| DivideByZero.go:11:27:11:32 | param1 | DivideByZero.go:11:2:11:33 | ... := ...[0] | | DivideByZero.go:17:12:17:16 | selection of URL | DivideByZero.go:17:12:17:24 | call to Query | | DivideByZero.go:17:12:17:24 | call to Query | DivideByZero.go:18:11:18:24 | type conversion | | DivideByZero.go:18:11:18:24 | type conversion | DivideByZero.go:19:16:19:20 | value | | DivideByZero.go:24:12:24:16 | selection of URL | DivideByZero.go:24:12:24:24 | call to Query | -| DivideByZero.go:24:12:24:24 | call to Query | DivideByZero.go:26:16:26:20 | value | +| DivideByZero.go:24:12:24:24 | call to Query | DivideByZero.go:25:31:25:36 | param1 | +| DivideByZero.go:25:2:25:45 | ... := ...[0] | DivideByZero.go:26:16:26:20 | value | +| DivideByZero.go:25:31:25:36 | param1 | DivideByZero.go:25:2:25:45 | ... := ...[0] | | DivideByZero.go:31:12:31:16 | selection of URL | DivideByZero.go:31:12:31:24 | call to Query | -| DivideByZero.go:31:12:31:24 | call to Query | DivideByZero.go:33:16:33:20 | value | +| DivideByZero.go:31:12:31:24 | call to Query | DivideByZero.go:32:33:32:38 | param1 | +| DivideByZero.go:32:2:32:43 | ... := ...[0] | DivideByZero.go:33:16:33:20 | value | +| DivideByZero.go:32:33:32:38 | param1 | DivideByZero.go:32:2:32:43 | ... := ...[0] | | DivideByZero.go:38:12:38:16 | selection of URL | DivideByZero.go:38:12:38:24 | call to Query | -| DivideByZero.go:38:12:38:24 | call to Query | DivideByZero.go:40:16:40:20 | value | +| DivideByZero.go:38:12:38:24 | call to Query | DivideByZero.go:39:32:39:37 | param1 | +| DivideByZero.go:39:2:39:46 | ... := ...[0] | DivideByZero.go:40:16:40:20 | value | +| DivideByZero.go:39:32:39:37 | param1 | DivideByZero.go:39:2:39:46 | ... := ...[0] | | DivideByZero.go:54:12:54:16 | selection of URL | DivideByZero.go:54:12:54:24 | call to Query | | DivideByZero.go:54:12:54:24 | call to Query | DivideByZero.go:55:11:55:24 | type conversion | | DivideByZero.go:55:11:55:24 | type conversion | DivideByZero.go:57:17:57:21 | value | nodes | DivideByZero.go:10:12:10:16 | selection of URL | semmle.label | selection of URL | | DivideByZero.go:10:12:10:24 | call to Query | semmle.label | call to Query | +| DivideByZero.go:11:2:11:33 | ... := ...[0] | semmle.label | ... := ...[0] | +| DivideByZero.go:11:27:11:32 | param1 | semmle.label | param1 | | DivideByZero.go:12:16:12:20 | value | semmle.label | value | | DivideByZero.go:17:12:17:16 | selection of URL | semmle.label | selection of URL | | DivideByZero.go:17:12:17:24 | call to Query | semmle.label | call to Query | @@ -23,12 +33,18 @@ nodes | DivideByZero.go:19:16:19:20 | value | semmle.label | value | | DivideByZero.go:24:12:24:16 | selection of URL | semmle.label | selection of URL | | DivideByZero.go:24:12:24:24 | call to Query | semmle.label | call to Query | +| DivideByZero.go:25:2:25:45 | ... := ...[0] | semmle.label | ... := ...[0] | +| DivideByZero.go:25:31:25:36 | param1 | semmle.label | param1 | | DivideByZero.go:26:16:26:20 | value | semmle.label | value | | DivideByZero.go:31:12:31:16 | selection of URL | semmle.label | selection of URL | | DivideByZero.go:31:12:31:24 | call to Query | semmle.label | call to Query | +| DivideByZero.go:32:2:32:43 | ... := ...[0] | semmle.label | ... := ...[0] | +| DivideByZero.go:32:33:32:38 | param1 | semmle.label | param1 | | DivideByZero.go:33:16:33:20 | value | semmle.label | value | | DivideByZero.go:38:12:38:16 | selection of URL | semmle.label | selection of URL | | DivideByZero.go:38:12:38:24 | call to Query | semmle.label | call to Query | +| DivideByZero.go:39:2:39:46 | ... := ...[0] | semmle.label | ... := ...[0] | +| DivideByZero.go:39:32:39:37 | param1 | semmle.label | param1 | | DivideByZero.go:40:16:40:20 | value | semmle.label | value | | DivideByZero.go:54:12:54:16 | selection of URL | semmle.label | selection of URL | | DivideByZero.go:54:12:54:24 | call to Query | semmle.label | call to Query | diff --git a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected b/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected index ea2cdc54019..2b543ccde1d 100644 --- a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected +++ b/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected @@ -1,143 +1,65 @@ edges | HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | -| HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | -| HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | | HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | | HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | -| HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | -| HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | | HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | | HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | -| HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | -| HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | | HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | | HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | -| HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | -| HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | | HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | | HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | -| HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | -| HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | | HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | | HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | -| HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | -| HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | | HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | | HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | -| HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | -| HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | | HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | | HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | -| HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | -| HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | | HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | | HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | -| HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | -| HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | | HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | | HTMLTemplateEscapingPassthrough.go:75:17:75:31 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:76:38:76:44 | escaped | -| HTMLTemplateEscapingPassthrough.go:81:10:81:24 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:82:16:82:33 | type conversion | | HTMLTemplateEscapingPassthrough.go:81:10:81:24 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:84:38:84:40 | src | | HTMLTemplateEscapingPassthrough.go:89:10:89:24 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:91:64:91:66 | src | | HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | HTMLTemplateEscapingPassthrough.go:92:38:92:46 | converted | -| HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | HTMLTemplateEscapingPassthrough.go:92:38:92:46 | converted | | HTMLTemplateEscapingPassthrough.go:91:38:91:67 | call to HTMLEscapeString | HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | | HTMLTemplateEscapingPassthrough.go:91:64:91:66 | src | HTMLTemplateEscapingPassthrough.go:91:38:91:67 | call to HTMLEscapeString | -| HTMLTemplateEscapingPassthrough.go:101:9:101:14 | selection of Form | HTMLTemplateEscapingPassthrough.go:101:9:101:24 | call to Get | -| HTMLTemplateEscapingPassthrough.go:101:9:101:24 | call to Get | HTMLTemplateEscapingPassthrough.go:115:8:115:15 | call to getId | -| HTMLTemplateEscapingPassthrough.go:104:18:104:18 | definition of x | HTMLTemplateEscapingPassthrough.go:105:9:105:24 | type conversion | -| HTMLTemplateEscapingPassthrough.go:105:9:105:24 | type conversion | HTMLTemplateEscapingPassthrough.go:123:11:123:36 | call to passthrough | -| HTMLTemplateEscapingPassthrough.go:108:35:108:35 | definition of x | HTMLTemplateEscapingPassthrough.go:110:19:110:19 | x | -| HTMLTemplateEscapingPassthrough.go:115:8:115:15 | call to getId | HTMLTemplateEscapingPassthrough.go:116:15:116:15 | x | -| HTMLTemplateEscapingPassthrough.go:116:15:116:15 | x | HTMLTemplateEscapingPassthrough.go:104:18:104:18 | definition of x | -| HTMLTemplateEscapingPassthrough.go:123:11:123:36 | call to passthrough | HTMLTemplateEscapingPassthrough.go:108:35:108:35 | definition of x | nodes | HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | semmle.label | a | -| HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | semmle.label | a | -| HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | semmle.label | a | | HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | semmle.label | a | | HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | semmle.label | a | -| HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | semmle.label | a | -| HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | semmle.label | c | | HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | semmle.label | c | | HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | semmle.label | d | -| HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | semmle.label | d | -| HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | semmle.label | e | | HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | semmle.label | e | | HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | semmle.label | b | -| HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | semmle.label | b | -| HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | semmle.label | f | | HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | semmle.label | f | | HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | semmle.label | g | | HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | semmle.label | g | | HTMLTemplateEscapingPassthrough.go:75:17:75:31 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:76:38:76:44 | escaped | semmle.label | escaped | | HTMLTemplateEscapingPassthrough.go:81:10:81:24 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:81:10:81:24 | call to UserAgent | semmle.label | call to UserAgent | -| HTMLTemplateEscapingPassthrough.go:82:16:82:33 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:84:38:84:40 | src | semmle.label | src | | HTMLTemplateEscapingPassthrough.go:89:10:89:24 | call to UserAgent | semmle.label | call to UserAgent | | HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | semmle.label | type conversion | | HTMLTemplateEscapingPassthrough.go:91:38:91:67 | call to HTMLEscapeString | semmle.label | call to HTMLEscapeString | | HTMLTemplateEscapingPassthrough.go:91:64:91:66 | src | semmle.label | src | | HTMLTemplateEscapingPassthrough.go:92:38:92:46 | converted | semmle.label | converted | -| HTMLTemplateEscapingPassthrough.go:92:38:92:46 | converted | semmle.label | converted | -| HTMLTemplateEscapingPassthrough.go:101:9:101:14 | selection of Form | semmle.label | selection of Form | -| HTMLTemplateEscapingPassthrough.go:101:9:101:24 | call to Get | semmle.label | call to Get | -| HTMLTemplateEscapingPassthrough.go:104:18:104:18 | definition of x | semmle.label | definition of x | -| HTMLTemplateEscapingPassthrough.go:105:9:105:24 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:105:9:105:24 | type conversion | semmle.label | type conversion | -| HTMLTemplateEscapingPassthrough.go:108:35:108:35 | definition of x | semmle.label | definition of x | -| HTMLTemplateEscapingPassthrough.go:110:19:110:19 | x | semmle.label | x | -| HTMLTemplateEscapingPassthrough.go:115:8:115:15 | call to getId | semmle.label | call to getId | -| HTMLTemplateEscapingPassthrough.go:116:15:116:15 | x | semmle.label | x | -| HTMLTemplateEscapingPassthrough.go:123:11:123:36 | call to passthrough | semmle.label | call to passthrough | subpaths #select | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | Data from an $@ will not be auto-escaped because it was $@ to template.HTML | HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | converted | diff --git a/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.go b/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.go index 362eedf785a..f907439ac2a 100644 --- a/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.go +++ b/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.go @@ -17,7 +17,7 @@ func TaintTracking_ClevergoTechClevergoV052() { { fromString598 := source().(string) intoString631 := clevergo.CleanPath(fromString598) - sink(intoString631) // $ taintSink + sink(intoString631) // $ hasTaintFlow="intoString631" } } // Taint-tracking through method calls. @@ -30,13 +30,13 @@ func TaintTracking_ClevergoTechClevergoV052() { fromString165 := source().(string) var mediumObjCQL clevergo.Application intoURL150, _ := mediumObjCQL.RouteURL(fromString165, "") - sink(intoURL150) // $ taintSink + sink(intoURL150) // $ hasTaintFlow="intoURL150" } { fromString340 := source().(string) var mediumObjCQL clevergo.Application intoURL471, _ := mediumObjCQL.RouteURL("", fromString340) - sink(intoURL471) // $ taintSink + sink(intoURL471) // $ hasTaintFlow="intoURL471" } } } @@ -46,7 +46,7 @@ func TaintTracking_ClevergoTechClevergoV052() { { fromContext290 := source().(clevergo.Context) intoContext758 := fromContext290.Context() - sink(intoContext758) // $ taintSink + sink(intoContext758) // $ hasTaintFlow="intoContext758" } } // Taint-tracking through method calls on clevergo.tech/clevergo.Params. @@ -55,7 +55,7 @@ func TaintTracking_ClevergoTechClevergoV052() { { fromParams396 := source().(clevergo.Params) intoString707 := fromParams396.String("") - sink(intoString707) // $ taintSink untrustedFlowSource + sink(intoString707) // $ hasTaintFlow="intoString707" untrustedFlowSource } } } @@ -69,7 +69,7 @@ func TaintTracking_ClevergoTechClevergoV052() { var intoInterface718 interface{} var mediumObjCQL clevergo.Decoder mediumObjCQL.Decode(fromRequest912, intoInterface718) - sink(intoInterface718) // $ taintSink untrustedFlowSource + sink(intoInterface718) // $ hasTaintFlow="intoInterface718" untrustedFlowSource } } // Taint-tracking through method calls on clevergo.tech/clevergo.Renderer interface. @@ -80,7 +80,7 @@ func TaintTracking_ClevergoTechClevergoV052() { var intoWriter633 io.Writer var mediumObjCQL clevergo.Renderer mediumObjCQL.Render(intoWriter633, "", fromInterface972, nil) - sink(intoWriter633) // $ taintSink + sink(intoWriter633) // $ hasTaintFlow="intoWriter633" } } } diff --git a/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.ql b/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.ql index f5d74acdf56..d758666eccf 100644 --- a/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.ql +++ b/go/ql/test/experimental/frameworks/CleverGo/TaintTracking.ql @@ -1,31 +1,4 @@ import go -import TestUtilities.InlineExpectationsTest import experimental.frameworks.CleverGo - -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "test-configuration" } - - override predicate isSource(DataFlow::Node source) { - exists(Function fn | fn.hasQualifiedName(_, "source") | source = fn.getACall().getResult()) - } - - override predicate isSink(DataFlow::Node sink) { - exists(Function fn | fn.hasQualifiedName(_, "sink") | sink = fn.getACall().getAnArgument()) - } -} - -module TaintTrackingTest implements TestSig { - string getARelevantTag() { result = "taintSink" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "taintSink" and - exists(DataFlow::Node sink | any(Configuration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/experimental/frameworks/Fiber/TaintTracking.go b/go/ql/test/experimental/frameworks/Fiber/TaintTracking.go index 3b15aa1ea39..281d9afd3fb 100644 --- a/go/ql/test/experimental/frameworks/Fiber/TaintTracking.go +++ b/go/ql/test/experimental/frameworks/Fiber/TaintTracking.go @@ -15,7 +15,7 @@ func TaintTracking_GithubComGofiberFiberV1146() { { fromString656 := source().(string) intoError414 := fiber.NewError(0, fromString656) - sink(intoError414) // $ taintSink + sink(intoError414) // $ hasTaintFlow="intoError414" } } } @@ -28,79 +28,79 @@ func TaintTracking_GithubComGofiberUtilsV0010() { { fromString989 := source().(string) intoByte982 := utils.GetBytes(fromString989) - sink(intoByte982) // $ taintSink + sink(intoByte982) // $ hasTaintFlow="intoByte982" } // func GetString(b []byte) string { fromByte417 := source().([]byte) intoString584 := utils.GetString(fromByte417) - sink(intoString584) // $ taintSink + sink(intoString584) // $ hasTaintFlow="intoString584" } // func ImmutableString(s string) string { fromString991 := source().(string) intoString881 := utils.ImmutableString(fromString991) - sink(intoString881) // $ taintSink + sink(intoString881) // $ hasTaintFlow="intoString881" } // func ToLower(b string) string { fromString494 := source().(string) intoString873 := utils.ToLower(fromString494) - sink(intoString873) // $ taintSink + sink(intoString873) // $ hasTaintFlow="intoString873" } // func ToLowerBytes(b []byte) []byte { fromByte599 := source().([]byte) intoByte409 := utils.ToLowerBytes(fromByte599) - sink(intoByte409) // $ taintSink + sink(intoByte409) // $ hasTaintFlow="intoByte409" } // func ToUpper(b string) string { fromString246 := source().(string) intoString898 := utils.ToUpper(fromString246) - sink(intoString898) // $ taintSink + sink(intoString898) // $ hasTaintFlow="intoString898" } // func ToUpperBytes(b []byte) []byte { fromByte598 := source().([]byte) intoByte631 := utils.ToUpperBytes(fromByte598) - sink(intoByte631) // $ taintSink + sink(intoByte631) // $ hasTaintFlow="intoByte631" } // func Trim(s string, cutset byte) string { fromString165 := source().(string) intoString150 := utils.Trim(fromString165, 0) - sink(intoString150) // $ taintSink + sink(intoString150) // $ hasTaintFlow="intoString150" } // func TrimBytes(b []byte, cutset byte) []byte { fromByte340 := source().([]byte) intoByte471 := utils.TrimBytes(fromByte340, 0) - sink(intoByte471) // $ taintSink + sink(intoByte471) // $ hasTaintFlow="intoByte471" } // func TrimLeft(s string, cutset byte) string { fromString290 := source().(string) intoString758 := utils.TrimLeft(fromString290, 0) - sink(intoString758) // $ taintSink + sink(intoString758) // $ hasTaintFlow="intoString758" } // func TrimLeftBytes(b []byte, cutset byte) []byte { fromByte396 := source().([]byte) intoByte707 := utils.TrimLeftBytes(fromByte396, 0) - sink(intoByte707) // $ taintSink + sink(intoByte707) // $ hasTaintFlow="intoByte707" } // func TrimRight(s string, cutset byte) string { fromString912 := source().(string) intoString718 := utils.TrimRight(fromString912, 0) - sink(intoString718) // $ taintSink + sink(intoString718) // $ hasTaintFlow="intoString718" } // func TrimRightBytes(b []byte, cutset byte) []byte { fromByte972 := source().([]byte) intoByte633 := utils.TrimRightBytes(fromByte972, 0) - sink(intoByte633) // $ taintSink + sink(intoByte633) // $ hasTaintFlow="intoByte633" } } } diff --git a/go/ql/test/experimental/frameworks/Fiber/TaintTracking.ql b/go/ql/test/experimental/frameworks/Fiber/TaintTracking.ql index 5e2082acd60..0b985733390 100644 --- a/go/ql/test/experimental/frameworks/Fiber/TaintTracking.ql +++ b/go/ql/test/experimental/frameworks/Fiber/TaintTracking.ql @@ -1,31 +1,4 @@ import go -import TestUtilities.InlineExpectationsTest import experimental.frameworks.Fiber - -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "test-configuration" } - - override predicate isSource(DataFlow::Node source) { - exists(Function fn | fn.hasQualifiedName(_, "source") | source = fn.getACall().getResult()) - } - - override predicate isSink(DataFlow::Node sink) { - exists(Function fn | fn.hasQualifiedName(_, "sink") | sink = fn.getACall().getAnArgument()) - } -} - -module TaintTrackingTest implements TestSig { - string getARelevantTag() { result = "taintSink" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "taintSink" and - exists(DataFlow::Node sink | any(Configuration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/Flows.ql b/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/Flows.ql index 500572f5d45..1b27b27d6dc 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/Flows.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/Flows.ql @@ -1,56 +1,3 @@ import go -import TestUtilities.InlineExpectationsTest - -class DataConfiguration extends DataFlow::Configuration { - DataConfiguration() { this = "data-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module DataFlowTest implements TestSig { - string getARelevantTag() { result = "dataflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "dataflow" and - exists(DataFlow::Node sink | any(DataConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -class TaintConfiguration extends TaintTracking::Configuration { - TaintConfiguration() { this = "taint-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module TaintFlowTest implements TestSig { - string getARelevantTag() { result = "taintflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "taintflow" and - exists(DataFlow::Node sink | any(TaintConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest> +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/main.go b/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/main.go index 1c83b891680..6665fe7d2c1 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/main.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/ArrayConversion/main.go @@ -16,10 +16,10 @@ func main() { var a [4]string a[0] = source() alias := sliceToArray(a[:]) - sink(alias[0]) // $ taintflow + sink(alias[0]) // $ hasTaintFlow="index expression" // Compare with the standard dataflow support for arrays var b [4]string b[0] = source() - sink(b[0]) // $ taintflow + sink(b[0]) // $ hasTaintFlow="index expression" } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.expected b/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.expected index 523d27be250..860170d67de 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.expected @@ -1,3 +1,4 @@ +failures edges | test.go:9:9:9:11 | selection of c [collection] | test.go:9:7:9:11 | <-... | | test.go:13:16:13:16 | definition of s [pointer, c, collection] | test.go:16:2:16:2 | s [pointer, c, collection] | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.ql b/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.ql index 31f0fae8008..f750505693b 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/ChannelField/test.ql @@ -1,18 +1,10 @@ import go -import DataFlow::PathGraph +import TestUtilities.InlineFlowTest -class TestConfig extends DataFlow::Configuration { - TestConfig() { this = "test config" } +module Flow = DataFlow::Global; - override predicate isSource(DataFlow::Node source) { - source.(DataFlow::CallNode).getTarget().getName() = "source" - } +import Flow::PathGraph - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getTarget().getName() = "sink").getAnArgument() - } -} - -from DataFlow::PathNode source, DataFlow::PathNode sink, TestConfig c -where c.hasFlowPath(source, sink) +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) select source, source, sink, "path" diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/Flows.ql b/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/Flows.ql index e30f7bc28e3..1b64b928c3f 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/Flows.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/Flows.ql @@ -1,58 +1,5 @@ import go import semmle.go.dataflow.ExternalFlow import ModelValidation -import TestUtilities.InlineExpectationsTest - -class DataConfiguration extends DataFlow::Configuration { - DataConfiguration() { this = "data-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module DataFlowTest implements TestSig { - string getARelevantTag() { result = "dataflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "dataflow" and - exists(DataFlow::Node sink | any(DataConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -class TaintConfiguration extends TaintTracking::Configuration { - TaintConfiguration() { this = "taint-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module TaintFlowTest implements TestSig { - string getARelevantTag() { result = "taintflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "taintflow" and - exists(DataFlow::Node sink | any(TaintConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest> +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/main.go b/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/main.go index 79043e3f7bb..712f47c41e7 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/main.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/ExternalFlowVarArgs/main.go @@ -13,26 +13,26 @@ func sink(string) { func main() { s := source() - sink(test.FunctionWithParameter(s)) // $ taintflow dataflow + sink(test.FunctionWithParameter(s)) // $ hasValueFlow="call to FunctionWithParameter" stringSlice := []string{source()} - sink(stringSlice[0]) // $ taintflow dataflow + sink(stringSlice[0]) // $ hasValueFlow="index expression" s0 := "" s1 := source() sSlice := []string{s0, s1} - sink(test.FunctionWithParameter(sSlice[1])) // $ taintflow dataflow - sink(test.FunctionWithSliceParameter(sSlice)) // $ taintflow dataflow - sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ taintflow dataflow - sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ taintflow dataflow + sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter" + sink(test.FunctionWithSliceParameter(sSlice)) // $ hasValueFlow="call to FunctionWithSliceParameter" + sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasValueFlow="call to FunctionWithVarArgsParameter" + sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to FunctionWithVarArgsParameter" sliceOfStructs := []test.A{{Field: source()}} - sink(sliceOfStructs[0].Field) // $ taintflow dataflow + sink(sliceOfStructs[0].Field) // $ hasValueFlow="selection of Field" a0 := test.A{Field: ""} a1 := test.A{Field: source()} aSlice := []test.A{a0, a1} - sink(test.FunctionWithSliceOfStructsParameter(aSlice)) // $ taintflow dataflow - sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ taintflow dataflow - sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ taintflow dataflow + sink(test.FunctionWithSliceOfStructsParameter(aSlice)) // $ hasValueFlow="call to FunctionWithSliceOfStructsParameter" + sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ hasValueFlow="call to FunctionWithVarArgsOfStructsParameter" + sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ hasValueFlow="call to FunctionWithVarArgsOfStructsParameter" } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go b/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go index 285985c76f1..a7a595509f9 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go @@ -342,7 +342,7 @@ func test() { { s := source() if guardBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -351,7 +351,7 @@ func test() { { s := source() if guardBoolStmt(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -362,7 +362,7 @@ func test() { if juggleParams("other arg", s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -371,14 +371,14 @@ func test() { if guardBoolNeg(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolCmp(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -389,14 +389,14 @@ func test() { if guardBoolNegCmp(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLOrLhs(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -405,16 +405,16 @@ func test() { { s := source() if guardBoolLOrNegLhs(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLOrRhs(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -423,18 +423,18 @@ func test() { { s := source() if guardBoolLOrNegRhs(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLAndLhs(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -443,16 +443,16 @@ func test() { if guardBoolLAndNegLhs(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLAndRhs(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -461,14 +461,14 @@ func test() { if guardBoolLAndNegRhs(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -479,14 +479,14 @@ func test() { if guardBoolNegProxy(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolCmpProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -497,14 +497,14 @@ func test() { if guardBoolNegCmpProxy(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLOrLhsProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -513,16 +513,16 @@ func test() { { s := source() if guardBoolLOrNegLhsProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLOrRhsProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -531,18 +531,18 @@ func test() { { s := source() if guardBoolLOrNegRhsProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLAndLhsProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -551,16 +551,16 @@ func test() { if guardBoolLAndNegLhsProxy(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLAndRhsProxy(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -569,7 +569,7 @@ func test() { if guardBoolLAndNegRhsProxy(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -578,14 +578,14 @@ func test() { if guardProxyNilToBool(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardNeqProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -594,7 +594,7 @@ func test() { { s := source() if guardNotEqProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -603,7 +603,7 @@ func test() { { s := source() if guardLOrLhsProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -612,16 +612,16 @@ func test() { { s := source() if guardLOrNegLhsProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardLOrRhsProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -630,18 +630,18 @@ func test() { { s := source() if guardLOrNegRhsProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardLAndLhsProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -650,16 +650,16 @@ func test() { if guardLAndNegLhsProxyNilToBool(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardLAndRhsProxyNilToBool(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -668,7 +668,7 @@ func test() { if guardLAndNegRhsProxyNilToBool(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -677,7 +677,7 @@ func test() { if guard(s) == nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -686,14 +686,14 @@ func test() { if guardBoolProxyToNil(s) == nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolNegProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -704,14 +704,14 @@ func test() { if guardBoolCmpProxyToNil(s) == nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolNegCmpProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -722,16 +722,16 @@ func test() { if guardBoolLOrLhsProxyToNil(s) == nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLOrNegLhsProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -740,32 +740,32 @@ func test() { if guardBoolLOrRhsProxyToNil(s) == nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLOrNegRhsProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLAndLhsProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLAndNegLhsProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -774,16 +774,16 @@ func test() { { s := source() if guardBoolLAndRhsProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } { s := source() if guardBoolLAndNegRhsProxyToNil(s) == nil { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -794,7 +794,7 @@ func test() { if directProxyNil(s) == nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -803,7 +803,7 @@ func test() { if deeplyNestedConditionalLeft(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -812,7 +812,7 @@ func test() { if deeplyNestedConditionalMiddle(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -821,7 +821,7 @@ func test() { if deeplyNestedConditionalRight(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } @@ -832,7 +832,7 @@ func test() { s := source() isInvalid := guardBool(s) if isInvalid { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } else { sink(s) } @@ -842,9 +842,9 @@ func test() { s := source() isValid := !guardBool(s) if isValid { - sink(s) // $ SPURIOUS: dataflow=s + sink(s) // $ SPURIOUS: hasValueFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasValueFlow="s" } } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.ql b/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.ql index d5bc1496b3d..aaed5221e3e 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.ql @@ -1,5 +1,5 @@ import go -import TestUtilities.InlineExpectationsTest +import TestUtilities.InlineFlowTest predicate isBad(DataFlow::Node g, Expr e, boolean branch) { g.(DataFlow::CallNode).getTarget().getName() = "isBad" and @@ -7,34 +7,16 @@ predicate isBad(DataFlow::Node g, Expr e, boolean branch) { branch = false } -class TestConfig extends DataFlow::Configuration { - TestConfig() { this = "test config" } +module FlowWithBarrierConfig implements DataFlow::ConfigSig { + predicate isSource = DefaultFlowConfig::isSource/1; - override predicate isSource(DataFlow::Node source) { - source.(DataFlow::CallNode).getTarget().getName() = "source" - } + predicate isSink = DefaultFlowConfig::isSink/1; - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getTarget().getName() = "sink").getAnArgument() - } + predicate fieldFlowBranchLimit = DefaultFlowConfig::fieldFlowBranchLimit/0; - override predicate isBarrier(DataFlow::Node node) { + predicate isBarrier(DataFlow::Node node) { node = DataFlow::BarrierGuard::getABarrierNode() } } -module DataFlowTest implements TestSig { - string getARelevantTag() { result = "dataflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "dataflow" and - exists(DataFlow::Node sink | any(TestConfig c).hasFlow(_, sink) | - element = sink.toString() and - value = sink.toString() and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest +import ValueFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.expected b/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.expected index 2c7583cacb4..b87c37b6972 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.expected @@ -1,3 +1,4 @@ +failures edges | test.go:14:8:14:15 | call to source | test.go:15:34:15:35 | fi | | test.go:15:2:15:44 | ... := ...[0] | test.go:16:7:16:12 | header | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.ql b/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.ql index 72dcf3e4b54..be623538f14 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/HiddenNodes/test.ql @@ -1,22 +1,10 @@ -/** - * @kind path-problem - */ - import go -import DataFlow::PathGraph +import TestUtilities.InlineFlowTest -class Config extends TaintTracking::Configuration { - Config() { this = "config" } +module Flow = TaintTracking::Global; - override predicate isSource(DataFlow::Node n) { - n = any(DataFlow::CallNode call | call.getTarget().getName() = "source").getResult() - } +import Flow::PathGraph - override predicate isSink(DataFlow::Node n) { - n = any(DataFlow::CallNode call | call.getTarget().getName() = "sink").getAnArgument() - } -} - -from DataFlow::PathNode source, DataFlow::PathNode sink, Config c -where c.hasFlowPath(source, sink) +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) select source, source, sink, "Path" diff --git a/go/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql b/go/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql index b98fc915bb7..1ae056cd8da 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql @@ -1,16 +1,14 @@ import go -class MyConfiguration extends DataFlow::Configuration { - MyConfiguration() { this = "MyConfiguration" } - - override predicate isSource(DataFlow::Node nd) { +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node nd) { exists(ValueEntity v, Write w | v.getName().matches("source%") and w.writes(v, nd) ) } - override predicate isSink(DataFlow::Node nd) { + predicate isSink(DataFlow::Node nd) { exists(ValueEntity v, Write w | v.getName().matches("sink%") and w.writes(v, nd) @@ -18,6 +16,8 @@ class MyConfiguration extends DataFlow::Configuration { } } -from MyConfiguration cfg, DataFlow::Node source, DataFlow::Node sink -where cfg.hasFlow(source, sink) +module Flow = DataFlow::Global; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) select source, sink diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.go b/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.go index 93be90027c3..d0935eeccd8 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.go @@ -138,14 +138,14 @@ func main() { if switchStatementReturningTrueOnlyWhenConstant(s) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } } { s := source() if switchStatementReturningFalseOnlyWhenConstant("", s) { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } else { sink(s) } @@ -157,7 +157,7 @@ func main() { if err != nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } } @@ -166,7 +166,7 @@ func main() { if switchStatementReturningNilOnlyWhenConstant(s) == nil { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } } @@ -175,25 +175,25 @@ func main() { if multipleSwitchStatementReturningTrueOnlyWhenConstant(s, getRandomString()) { sink(s) } else { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } } { s := source() if switchStatementWithoutUsefulInfo(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } } { s := source() if switchStatementOverRandomString(s) { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } else { - sink(s) // $ dataflow=s + sink(s) // $ hasTaintFlow="s" } } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.ql b/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.ql index aa9b9c3e074..8a4a310c27c 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/ListOfConstantsSanitizerGuards/test.ql @@ -1,30 +1,3 @@ import go -import TestUtilities.InlineExpectationsTest - -class TestConfig extends TaintTracking::Configuration { - TestConfig() { this = "test config" } - - override predicate isSource(DataFlow::Node source) { - source.(DataFlow::CallNode).getTarget().getName() = "source" - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getTarget().getName() = "sink").getAnArgument() - } -} - -module DataFlowTest implements TestSig { - string getARelevantTag() { result = "dataflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "dataflow" and - exists(DataFlow::Node sink | any(TestConfig c).hasFlow(_, sink) | - element = sink.toString() and - value = sink.toString() and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest +import TestUtilities.InlineFlowTest +import TaintFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/DataFlowConfig.ql b/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/DataFlowConfig.ql index 8222ae35d87..e487e04949f 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/DataFlowConfig.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/DataFlowConfig.ql @@ -1,39 +1,3 @@ import go -import TestUtilities.InlineExpectationsTest - -class SourceFunction extends Function { - SourceFunction() { this.getName() = "source" } -} - -class SinkFunction extends Function { - SinkFunction() { this.getName() = "sink" } -} - -class TestConfig extends DataFlow::Configuration { - TestConfig() { this = "testconfig" } - - override predicate isSource(DataFlow::Node source) { - source = any(SourceFunction f).getACall().getAResult() - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(SinkFunction f).getACall().getAnArgument() - } -} - -module PromotedFieldsTest implements TestSig { - string getARelevantTag() { result = "promotedfields" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - exists(TestConfig config, DataFlow::PathNode sink | - config.hasFlowPath(_, sink) and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and - element = sink.toString() and - value = "" and - tag = "promotedfields" - ) - } -} - -import MakeTest +import TestUtilities.InlineFlowTest +import ValueFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/main.go b/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/main.go index d0272646eb1..30125af382e 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/main.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/PromotedFields/main.go @@ -22,130 +22,130 @@ func testPromotedFieldNamedInitialization() { outer := Outer{ Middle: Middle{Inner: Inner{source()}}, } - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" outerp := &Outer{ Middle: Middle{Inner: Inner{source()}}, } - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } func testPromotedFieldUnnamedInitialization() { outer := Outer{Middle{Inner{source()}}} - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" outerp := &Outer{Middle{Inner{source()}}} - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } func testPromotedFieldUnnamedInitializationFromVariable() { inner := Inner{source()} middle := Middle{inner} outer := Outer{middle} - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" innerp := Inner{source()} middlep := Middle{innerp} outerp := Outer{middlep} - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } func testPromotedFieldNamedInitializationFromVariable() { inner := Inner{source()} middle := Middle{Inner: inner} outer := Outer{Middle: middle} - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" innerp := Inner{source()} middlep := Middle{Inner: innerp} outerp := Outer{Middle: middlep} - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } func testPromotedFieldDirectAssignment() { var outer Outer outer.field = source() - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" var outerp Outer outerp.field = source() - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } func testPromotedFieldIndirectAssignment1() { var outer Outer outer.Inner.field = source() - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" var outerp Outer outerp.Inner.field = source() - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } func testPromotedFieldIndirectAssignment2() { var outer Outer outer.Middle.field = source() - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" var outerp Outer outerp.Middle.field = source() - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } func testPromotedFieldIndirectAssignment3() { var outer Outer outer.Middle.Inner.field = source() - sink(outer.field) // $ promotedfields - sink(outer.Inner.field) // $ promotedfields - sink(outer.Middle.field) // $ promotedfields - sink(outer.Middle.Inner.field) // $ promotedfields + sink(outer.field) // $ hasValueFlow="selection of field" + sink(outer.Inner.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.field) // $ hasValueFlow="selection of field" + sink(outer.Middle.Inner.field) // $ hasValueFlow="selection of field" var outerp Outer outerp.Middle.Inner.field = source() - sink(outerp.field) // $ promotedfields - sink(outerp.Inner.field) // $ promotedfields - sink(outerp.Middle.field) // $ promotedfields - sink(outerp.Middle.Inner.field) // $ promotedfields + sink(outerp.field) // $ hasValueFlow="selection of field" + sink(outerp.Inner.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.field) // $ hasValueFlow="selection of field" + sink(outerp.Middle.Inner.field) // $ hasValueFlow="selection of field" } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/PromotedMethods/DataFlowConfig.ql b/go/ql/test/library-tests/semmle/go/dataflow/PromotedMethods/DataFlowConfig.ql index fac89a50650..a4db3dc304d 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/PromotedMethods/DataFlowConfig.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/PromotedMethods/DataFlowConfig.ql @@ -1,33 +1,13 @@ import go -import TestUtilities.InlineExpectationsTest +import TestUtilities.InlineFlowTest -class SourceFunction extends Function { - SourceFunction() { this.getName() = "source" } -} - -class SinkFunction extends Function { - SinkFunction() { this.getName() = "sink" } -} - -class TestConfig extends DataFlow::Configuration { - TestConfig() { this = "testconfig" } - - override predicate isSource(DataFlow::Node source) { - source = any(SourceFunction f).getACall().getAResult() - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(SinkFunction f).getACall().getAnArgument() - } -} +module ValueFlow = DataFlow::Global; module PromotedMethodsTest implements TestSig { string getARelevantTag() { result = "promotedmethods" } predicate hasActualResult(Location location, string element, string tag, string value) { - exists(TestConfig config, DataFlow::Node source, DataFlow::Node sink | - config.hasFlow(source, sink) - | + exists(DataFlow::Node source, DataFlow::Node sink | ValueFlow::flow(source, sink) | sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and element = sink.toString() and diff --git a/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/DataFlow.ql b/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/DataFlow.ql index 9412fb13486..1b27b27d6dc 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/DataFlow.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/DataFlow.ql @@ -1,30 +1,3 @@ import go -import TestUtilities.InlineExpectationsTest - -class Configuration extends DataFlow::Configuration { - Configuration() { this = "test-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "src").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module DataFlowTest implements TestSig { - string getARelevantTag() { result = "dataflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "dataflow" and - exists(DataFlow::Node sink | any(Configuration c).hasFlow(_, sink) | - element = sink.toString() and - value = sink.toString() and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/test.go b/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/test.go index 36bc65aa023..469a9c7d6eb 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/test.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/TypeAssertions/test.go @@ -1,30 +1,30 @@ package main -func src() interface{} { +func source() interface{} { return "hi" } func sink(p interface{}) {} func test() (bool, *string) { - ptr := src() - sink(ptr) // $ dataflow=ptr + ptr := source() + sink(ptr) // $ hasValueFlow="ptr" cast := ptr.(*string) - sink(cast) // $ dataflow=cast + sink(cast) // $ hasValueFlow="cast" cast2, ok := ptr.(*string) if !ok { return true, nil } - sink(cast2) // $ dataflow=cast2 + sink(cast2) // $ hasValueFlow="cast2" var cast3, ok2 = ptr.(*string) if !ok2 { return true, nil } - sink(cast3) // $ dataflow=cast3 + sink(cast3) // $ hasValueFlow="cast3" cast2, ok = ptr.(*string) if !ok { return true, nil } - sink(cast2) // $ dataflow=cast2 + sink(cast2) // $ hasValueFlow="cast2" return true, nil } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/Flows.ql b/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/Flows.ql index 500572f5d45..1b27b27d6dc 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/Flows.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/Flows.ql @@ -1,56 +1,3 @@ import go -import TestUtilities.InlineExpectationsTest - -class DataConfiguration extends DataFlow::Configuration { - DataConfiguration() { this = "data-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module DataFlowTest implements TestSig { - string getARelevantTag() { result = "dataflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "dataflow" and - exists(DataFlow::Node sink | any(DataConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -class TaintConfiguration extends TaintTracking::Configuration { - TaintConfiguration() { this = "taint-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module TaintFlowTest implements TestSig { - string getARelevantTag() { result = "taintflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "taintflow" and - exists(DataFlow::Node sink | any(TaintConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest> +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/main.go b/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/main.go index 3c3d80f7342..8e3a498656a 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/main.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/VarArgs/main.go @@ -29,22 +29,22 @@ func functionWithVarArgsOfStructsParameter(s ...A) string { func main() { stringSlice := []string{source()} - sink(stringSlice[0]) // $ taintflow dataflow + sink(stringSlice[0]) // $ hasValueFlow="index expression" s0 := "" s1 := source() sSlice := []string{s0, s1} - sink(functionWithSliceParameter(sSlice)) // $ taintflow dataflow - sink(functionWithVarArgsParameter(sSlice...)) // $ taintflow dataflow - sink(functionWithVarArgsParameter(s0, s1)) // $ taintflow dataflow + sink(functionWithSliceParameter(sSlice)) // $ hasValueFlow="call to functionWithSliceParameter" + sink(functionWithVarArgsParameter(sSlice...)) // $ hasValueFlow="call to functionWithVarArgsParameter" + sink(functionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to functionWithVarArgsParameter" sliceOfStructs := []A{{f: source()}} - sink(sliceOfStructs[0].f) // $ taintflow dataflow + sink(sliceOfStructs[0].f) // $ hasValueFlow="selection of f" a0 := A{f: ""} a1 := A{f: source()} aSlice := []A{a0, a1} - sink(functionWithSliceOfStructsParameter(aSlice)) // $ taintflow dataflow - sink(functionWithVarArgsOfStructsParameter(aSlice...)) // $ taintflow dataflow - sink(functionWithVarArgsOfStructsParameter(a0, a1)) // $ taintflow dataflow + sink(functionWithSliceOfStructsParameter(aSlice)) // $ hasValueFlow="call to functionWithSliceOfStructsParameter" + sink(functionWithVarArgsOfStructsParameter(aSlice...)) // $ hasValueFlow="call to functionWithVarArgsOfStructsParameter" + sink(functionWithVarArgsOfStructsParameter(a0, a1)) // $ hasValueFlow="call to functionWithVarArgsOfStructsParameter" } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.expected b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.expected index 105b7026d0c..48de9172b36 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.expected @@ -1,3 +1,2 @@ failures -invalidModelRow testFailures diff --git a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql index 17194265598..ed0c5933944 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql +++ b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/Flows.ql @@ -1,8 +1,6 @@ import go -import semmle.go.dataflow.ExternalFlow -import ModelValidation -// import DataFlow::PartialPathGraph -import TestUtilities.InlineExpectationsTest +import TestUtilities.InlineFlowTest +import DefaultFlowTest class SummaryModelTest extends DataFlow::FunctionModel { FunctionInput inp; @@ -18,69 +16,15 @@ class SummaryModelTest extends DataFlow::FunctionModel { or this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsParameter") and (inp.isParameter(_) and outp.isResult()) - // or - // this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithSliceOfStructsParameter")) and - // (inp.isParameter(0) and outp.isResult) - // or - // this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsOfStructsParameter")) and - // (inp.isParameter(0) and outp.isResult) + or + this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithSliceOfStructsParameter") and + (inp.isParameter(0) and outp.isResult()) + or + this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsOfStructsParameter") and + (inp.isParameter(0) and outp.isResult()) } override predicate hasDataFlow(FunctionInput input, FunctionOutput output) { input = inp and output = outp } } - -class DataConfiguration extends DataFlow::Configuration { - DataConfiguration() { this = "data-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module DataFlowTest implements TestSig { - string getARelevantTag() { result = "dataflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "dataflow" and - exists(DataFlow::Node sink | any(DataConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -class TaintConfiguration extends TaintTracking::Configuration { - TaintConfiguration() { this = "taint-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = any(DataFlow::CallNode c | c.getCalleeName() = "source").getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getArgument(0) - } -} - -module TaintFlowTest implements TestSig { - string getARelevantTag() { result = "taintflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "taintflow" and - exists(DataFlow::Node sink | any(TaintConfiguration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest> diff --git a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/main.go b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/main.go index b9c163b695d..c561de0da2f 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/main.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/VarArgsWithFunctionModels/main.go @@ -13,26 +13,27 @@ func sink(string) { func main() { s := source() - sink(test.FunctionWithParameter(s)) // $ taintflow dataflow + sink(test.FunctionWithParameter(s)) // $ hasValueFlow="call to FunctionWithParameter" stringSlice := []string{source()} - sink(stringSlice[0]) // $ taintflow dataflow + sink(stringSlice[0]) // $ hasValueFlow="index expression" s0 := "" s1 := source() sSlice := []string{s0, s1} - sink(test.FunctionWithParameter(sSlice[1])) // $ taintflow dataflow - sink(test.FunctionWithSliceParameter(sSlice)) // $ taintflow MISSING: dataflow - sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ taintflow MISSING: dataflow - sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ MISSING: taintflow dataflow + sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter" + sink(test.FunctionWithSliceParameter(sSlice)) // $ hasTaintFlow="call to FunctionWithSliceParameter" MISSING: hasValueFlow="call to FunctionWithSliceParameter" + sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasTaintFlow="call to FunctionWithVarArgsParameter" MISSING: hasValueFlow="call to FunctionWithVarArgsParameter" + sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsParameter" sliceOfStructs := []test.A{{Field: source()}} - sink(sliceOfStructs[0].Field) // $ taintflow dataflow + sink(sliceOfStructs[0].Field) // $ hasValueFlow="selection of Field" + // The following tests all fail because FunctionModel doesn't interact with access paths a0 := test.A{Field: ""} a1 := test.A{Field: source()} aSlice := []test.A{a0, a1} - sink(test.FunctionWithSliceOfStructsParameter(aSlice)) // $ MISSING: taintflow dataflow - sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ MISSING: taintflow dataflow - sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ MISSING: taintflow dataflow + sink(test.FunctionWithSliceOfStructsParameter(aSlice)) // $ MISSING: hasValueFlow="call to FunctionWithSliceOfStructsParameter" + sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter" + sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter" } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Beego/OpenRedirect.expected b/go/ql/test/library-tests/semmle/go/frameworks/Beego/OpenRedirect.expected index ebdb44f02e2..34e534ba81c 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Beego/OpenRedirect.expected +++ b/go/ql/test/library-tests/semmle/go/frameworks/Beego/OpenRedirect.expected @@ -3,8 +3,6 @@ nodes | test.go:247:13:247:34 | call to GetString | semmle.label | call to GetString | | test.go:248:20:248:41 | call to GetString | semmle.label | call to GetString | | test.go:311:13:311:27 | call to URI | semmle.label | call to URI | -| test.go:311:13:311:27 | call to URI | semmle.label | call to URI | -| test.go:312:20:312:34 | call to URL | semmle.label | call to URL | | test.go:312:20:312:34 | call to URL | semmle.label | call to URL | subpaths #select diff --git a/go/ql/test/library-tests/semmle/go/frameworks/CouchbaseV1/test.ql b/go/ql/test/library-tests/semmle/go/frameworks/CouchbaseV1/test.ql index 0b1bef94328..d50a95c706f 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/CouchbaseV1/test.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/CouchbaseV1/test.ql @@ -7,7 +7,7 @@ module SqlInjectionTest implements TestSig { predicate hasActualResult(Location location, string element, string tag, string value) { tag = "sqlinjection" and - exists(DataFlow::Node sink | any(SqlInjection::Configuration c).hasFlow(_, sink) | + exists(DataFlow::Node sink | SqlInjection::Flow::flowTo(sink) | element = sink.toString() and value = sink.toString() and sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Echo/OpenRedirect.expected b/go/ql/test/library-tests/semmle/go/frameworks/Echo/OpenRedirect.expected index a6997814833..c907ed37229 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Echo/OpenRedirect.expected +++ b/go/ql/test/library-tests/semmle/go/frameworks/Echo/OpenRedirect.expected @@ -1,21 +1,26 @@ edges -| test.go:170:11:170:32 | call to Param | test.go:171:20:171:24 | param | -| test.go:176:11:176:32 | call to Param | test.go:180:20:180:28 | ...+... | -| test.go:188:10:188:26 | selection of URL | test.go:191:21:191:23 | url | -| test.go:188:10:188:26 | selection of URL | test.go:191:21:191:23 | url | -| test.go:188:10:188:26 | selection of URL | test.go:191:21:191:32 | call to String | -| test.go:191:21:191:23 | url | test.go:191:21:191:32 | call to String | +| test.go:170:2:170:6 | definition of param | test.go:171:20:171:24 | param | +| test.go:170:11:170:32 | call to Param | test.go:170:2:170:6 | definition of param | +| test.go:176:2:176:6 | definition of param | test.go:180:24:180:28 | param | +| test.go:176:11:176:32 | call to Param | test.go:176:2:176:6 | definition of param | +| test.go:180:24:180:28 | param | test.go:180:20:180:28 | ...+... | +| test.go:188:2:188:4 | definition of url | test.go:191:21:191:23 | url | +| test.go:188:9:188:26 | star expression | test.go:188:2:188:4 | definition of url | +| test.go:188:9:188:26 | star expression | test.go:188:10:188:26 | selection of URL | +| test.go:188:10:188:26 | selection of URL | test.go:188:9:188:26 | star expression | | test.go:191:21:191:23 | url | test.go:191:21:191:32 | call to String | nodes +| test.go:170:2:170:6 | definition of param | semmle.label | definition of param | | test.go:170:11:170:32 | call to Param | semmle.label | call to Param | | test.go:171:20:171:24 | param | semmle.label | param | +| test.go:176:2:176:6 | definition of param | semmle.label | definition of param | | test.go:176:11:176:32 | call to Param | semmle.label | call to Param | | test.go:180:20:180:28 | ...+... | semmle.label | ...+... | -| test.go:188:10:188:26 | selection of URL | semmle.label | selection of URL | +| test.go:180:24:180:28 | param | semmle.label | param | +| test.go:188:2:188:4 | definition of url | semmle.label | definition of url | +| test.go:188:9:188:26 | star expression | semmle.label | star expression | | test.go:188:10:188:26 | selection of URL | semmle.label | selection of URL | | test.go:191:21:191:23 | url | semmle.label | url | -| test.go:191:21:191:23 | url | semmle.label | url | -| test.go:191:21:191:32 | call to String | semmle.label | call to String | | test.go:191:21:191:32 | call to String | semmle.label | call to String | subpaths #select diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.expected b/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.expected index 54d739e8a90..5161978daeb 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.expected +++ b/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.expected @@ -1,3 +1,33 @@ +edges +| jsoniter.go:23:20:23:38 | call to getUntrustedBytes | jsoniter.go:27:17:27:30 | untrustedInput | +| jsoniter.go:23:20:23:38 | call to getUntrustedBytes | jsoniter.go:31:21:31:34 | untrustedInput | +| jsoniter.go:24:21:24:40 | call to getUntrustedString | jsoniter.go:35:27:35:41 | untrustedString | +| jsoniter.go:24:21:24:40 | call to getUntrustedString | jsoniter.go:39:31:39:45 | untrustedString | +| jsoniter.go:27:17:27:30 | untrustedInput | jsoniter.go:27:33:27:37 | &... | +| jsoniter.go:27:33:27:37 | &... | jsoniter.go:28:15:28:24 | selection of field | +| jsoniter.go:31:21:31:34 | untrustedInput | jsoniter.go:31:37:31:42 | &... | +| jsoniter.go:31:37:31:42 | &... | jsoniter.go:32:15:32:25 | selection of field | +| jsoniter.go:35:27:35:41 | untrustedString | jsoniter.go:35:44:35:49 | &... | +| jsoniter.go:35:44:35:49 | &... | jsoniter.go:36:15:36:25 | selection of field | +| jsoniter.go:39:31:39:45 | untrustedString | jsoniter.go:39:48:39:53 | &... | +| jsoniter.go:39:48:39:53 | &... | jsoniter.go:40:15:40:25 | selection of field | +nodes +| jsoniter.go:23:20:23:38 | call to getUntrustedBytes | semmle.label | call to getUntrustedBytes | +| jsoniter.go:24:21:24:40 | call to getUntrustedString | semmle.label | call to getUntrustedString | +| jsoniter.go:27:17:27:30 | untrustedInput | semmle.label | untrustedInput | +| jsoniter.go:27:33:27:37 | &... | semmle.label | &... | +| jsoniter.go:28:15:28:24 | selection of field | semmle.label | selection of field | +| jsoniter.go:31:21:31:34 | untrustedInput | semmle.label | untrustedInput | +| jsoniter.go:31:37:31:42 | &... | semmle.label | &... | +| jsoniter.go:32:15:32:25 | selection of field | semmle.label | selection of field | +| jsoniter.go:35:27:35:41 | untrustedString | semmle.label | untrustedString | +| jsoniter.go:35:44:35:49 | &... | semmle.label | &... | +| jsoniter.go:36:15:36:25 | selection of field | semmle.label | selection of field | +| jsoniter.go:39:31:39:45 | untrustedString | semmle.label | untrustedString | +| jsoniter.go:39:48:39:53 | &... | semmle.label | &... | +| jsoniter.go:40:15:40:25 | selection of field | semmle.label | selection of field | +subpaths +#select | jsoniter.go:28:15:28:24 | selection of field | jsoniter.go:23:20:23:38 | call to getUntrustedBytes | jsoniter.go:28:15:28:24 | selection of field | This command depends on $@. | jsoniter.go:23:20:23:38 | call to getUntrustedBytes | a user-provided value | | jsoniter.go:32:15:32:25 | selection of field | jsoniter.go:23:20:23:38 | call to getUntrustedBytes | jsoniter.go:32:15:32:25 | selection of field | This command depends on $@. | jsoniter.go:23:20:23:38 | call to getUntrustedBytes | a user-provided value | | jsoniter.go:36:15:36:25 | selection of field | jsoniter.go:24:21:24:40 | call to getUntrustedString | jsoniter.go:36:15:36:25 | selection of field | This command depends on $@. | jsoniter.go:24:21:24:40 | call to getUntrustedString | a user-provided value | diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.ql b/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.ql index a02f85932e3..12604046b15 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.ql @@ -1,5 +1,6 @@ import go import semmle.go.security.CommandInjection +import CommandInjection::Flow::PathGraph class UntrustedFunction extends Function { UntrustedFunction() { this.getName() = ["getUntrustedString", "getUntrustedBytes"] } @@ -9,7 +10,7 @@ class UntrustedSource extends DataFlow::Node, UntrustedFlowSource::Range { UntrustedSource() { this = any(UntrustedFunction f).getACall() } } -from CommandInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from CommandInjection::Flow::PathNode source, CommandInjection::Flow::PathNode sink +where CommandInjection::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(), "a user-provided value" diff --git a/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/TaintFlows.ql b/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/TaintFlows.ql index 998afbf88b5..a6ca153b653 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/TaintFlows.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/TaintFlows.ql @@ -1,33 +1,18 @@ import go -import TestUtilities.InlineExpectationsTest +import TestUtilities.InlineFlowTest -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "test-configuration" } - - override predicate isSource(DataFlow::Node source) { - source = - any(DataFlow::CallNode c | c.getCalleeName() in ["getTaintedByteArray", "getTaintedPatch"]) - .getResult(0) +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + exists(Function fn | fn.hasQualifiedName(_, ["getTaintedByteArray", "getTaintedPatch"]) | + source = fn.getACall().getResult() + ) } - override predicate isSink(DataFlow::Node sink) { - sink = - any(DataFlow::CallNode c | c.getCalleeName() in ["sinkByteArray", "sinkPatch"]).getArgument(0) - } -} - -module TaintFlowTest implements TestSig { - string getARelevantTag() { result = "taintflow" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "taintflow" and - exists(DataFlow::Node sink | any(Configuration c).hasFlow(_, sink) | - element = sink.toString() and - value = "" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) + predicate isSink(DataFlow::Node sink) { + exists(Function fn | fn.hasQualifiedName(_, ["sinkByteArray", "sinkPatch"]) | + sink = fn.getACall().getAnArgument() ) } } -import MakeTest +import TaintFlowTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/main.go b/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/main.go index 49619e105f2..8b26a1ee51f 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/main.go +++ b/go/ql/test/library-tests/semmle/go/frameworks/EvanphxJsonPatch/main.go @@ -5,7 +5,7 @@ package main import patch "github.com/evanphx/json-patch/v5" func getTaintedByteArray() []byte { - return make([]byte, 1, 1) + return make([]byte, 1) } func getTaintedPatch() patch.Patch { @@ -20,59 +20,59 @@ func sinkPatch(patch.Patch) { } func main() { - untaintedByteArray := make([]byte, 1, 1) + untaintedByteArray := make([]byte, 1) var untaintedPatch patch.Patch // func MergeMergePatches(patch1Data, patch2Data []byte) ([]byte, error) b1, _ := patch.MergeMergePatches(getTaintedByteArray(), untaintedByteArray) - sinkByteArray(b1) // $ taintflow + sinkByteArray(b1) // $ hasTaintFlow="b1" b2, _ := patch.MergeMergePatches(untaintedByteArray, getTaintedByteArray()) - sinkByteArray(b2) // $ taintflow + sinkByteArray(b2) // $ hasTaintFlow="b2" // func MergePatch(docData, patchData []byte) ([]byte, error) b3, _ := patch.MergePatch(getTaintedByteArray(), untaintedByteArray) - sinkByteArray(b3) // $ taintflow + sinkByteArray(b3) // $ hasTaintFlow="b3" b4, _ := patch.MergePatch(untaintedByteArray, getTaintedByteArray()) - sinkByteArray(b4) // $ taintflow + sinkByteArray(b4) // $ hasTaintFlow="b4" // func CreateMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) b5, _ := patch.CreateMergePatch(getTaintedByteArray(), untaintedByteArray) - sinkByteArray(b5) // $ taintflow + sinkByteArray(b5) // $ hasTaintFlow="b5" b6, _ := patch.CreateMergePatch(untaintedByteArray, getTaintedByteArray()) - sinkByteArray(b6) // $ taintflow + sinkByteArray(b6) // $ hasTaintFlow="b6" // func DecodePatch(buf []byte) (Patch, error) p7, _ := patch.DecodePatch(getTaintedByteArray()) - sinkPatch(p7) // $ taintflow + sinkPatch(p7) // $ hasTaintFlow="p7" // func (p Patch) Apply(doc []byte) ([]byte, error) b8, _ := untaintedPatch.Apply(getTaintedByteArray()) - sinkByteArray(b8) // $ taintflow + sinkByteArray(b8) // $ hasTaintFlow="b8" b9, _ := getTaintedPatch().Apply(untaintedByteArray) - sinkByteArray(b9) // $ taintflow + sinkByteArray(b9) // $ hasTaintFlow="b9" // func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) b10, _ := untaintedPatch.ApplyIndent(getTaintedByteArray(), " ") - sinkByteArray(b10) // $ taintflow + sinkByteArray(b10) // $ hasTaintFlow="b10" b11, _ := getTaintedPatch().ApplyIndent(untaintedByteArray, " ") - sinkByteArray(b11) // $ taintflow + sinkByteArray(b11) // $ hasTaintFlow="b11" // func (p Patch) ApplyWithOptions(doc []byte, options *ApplyOptions) ([]byte, error) b12, _ := untaintedPatch.ApplyWithOptions(getTaintedByteArray(), nil) - sinkByteArray(b12) // $ taintflow + sinkByteArray(b12) // $ hasTaintFlow="b12" b13, _ := getTaintedPatch().ApplyWithOptions(untaintedByteArray, nil) - sinkByteArray(b13) // $ taintflow + sinkByteArray(b13) // $ hasTaintFlow="b13" // func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyOptions) ([]byte, error) b14, _ := untaintedPatch.ApplyIndentWithOptions(getTaintedByteArray(), " ", nil) - sinkByteArray(b14) // $ taintflow + sinkByteArray(b14) // $ hasTaintFlow="b14" b15, _ := getTaintedPatch().ApplyIndentWithOptions(untaintedByteArray, " ", nil) - sinkByteArray(b15) // $ taintflow + sinkByteArray(b15) // $ hasTaintFlow="b15" } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.expected b/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.expected index e60cc051e17..b299a773b06 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.expected +++ b/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.expected @@ -1,3 +1,37 @@ +edges +| gorestful.go:15:15:15:44 | call to QueryParameters | gorestful.go:15:15:15:47 | index expression | +| gorestful.go:17:12:17:39 | call to BodyParameter | gorestful.go:18:15:18:17 | val | +| gorestful.go:21:15:21:38 | call to PathParameters | gorestful.go:21:15:21:45 | index expression | +| gorestful.go:23:21:23:24 | &... | gorestful.go:24:15:24:21 | selection of cmd | +| gorestful_v2.go:15:15:15:44 | call to QueryParameters | gorestful_v2.go:15:15:15:47 | index expression | +| gorestful_v2.go:17:12:17:39 | call to BodyParameter | gorestful_v2.go:18:15:18:17 | val | +| gorestful_v2.go:21:15:21:38 | call to PathParameters | gorestful_v2.go:21:15:21:45 | index expression | +| gorestful_v2.go:23:21:23:24 | &... | gorestful_v2.go:24:15:24:21 | selection of cmd | +nodes +| gorestful.go:15:15:15:44 | call to QueryParameters | semmle.label | call to QueryParameters | +| gorestful.go:15:15:15:47 | index expression | semmle.label | index expression | +| gorestful.go:16:15:16:43 | call to QueryParameter | semmle.label | call to QueryParameter | +| gorestful.go:17:12:17:39 | call to BodyParameter | semmle.label | call to BodyParameter | +| gorestful.go:18:15:18:17 | val | semmle.label | val | +| gorestful.go:19:15:19:44 | call to HeaderParameter | semmle.label | call to HeaderParameter | +| gorestful.go:20:15:20:42 | call to PathParameter | semmle.label | call to PathParameter | +| gorestful.go:21:15:21:38 | call to PathParameters | semmle.label | call to PathParameters | +| gorestful.go:21:15:21:45 | index expression | semmle.label | index expression | +| gorestful.go:23:21:23:24 | &... | semmle.label | &... | +| gorestful.go:24:15:24:21 | selection of cmd | semmle.label | selection of cmd | +| gorestful_v2.go:15:15:15:44 | call to QueryParameters | semmle.label | call to QueryParameters | +| gorestful_v2.go:15:15:15:47 | index expression | semmle.label | index expression | +| gorestful_v2.go:16:15:16:43 | call to QueryParameter | semmle.label | call to QueryParameter | +| gorestful_v2.go:17:12:17:39 | call to BodyParameter | semmle.label | call to BodyParameter | +| gorestful_v2.go:18:15:18:17 | val | semmle.label | val | +| gorestful_v2.go:19:15:19:44 | call to HeaderParameter | semmle.label | call to HeaderParameter | +| gorestful_v2.go:20:15:20:42 | call to PathParameter | semmle.label | call to PathParameter | +| gorestful_v2.go:21:15:21:38 | call to PathParameters | semmle.label | call to PathParameters | +| gorestful_v2.go:21:15:21:45 | index expression | semmle.label | index expression | +| gorestful_v2.go:23:21:23:24 | &... | semmle.label | &... | +| gorestful_v2.go:24:15:24:21 | selection of cmd | semmle.label | selection of cmd | +subpaths +#select | gorestful.go:15:15:15:47 | index expression | gorestful.go:15:15:15:44 | call to QueryParameters | gorestful.go:15:15:15:47 | index expression | This command depends on $@. | gorestful.go:15:15:15:44 | call to QueryParameters | a user-provided value | | gorestful.go:16:15:16:43 | call to QueryParameter | gorestful.go:16:15:16:43 | call to QueryParameter | gorestful.go:16:15:16:43 | call to QueryParameter | This command depends on $@. | gorestful.go:16:15:16:43 | call to QueryParameter | a user-provided value | | gorestful.go:18:15:18:17 | val | gorestful.go:17:12:17:39 | call to BodyParameter | gorestful.go:18:15:18:17 | val | This command depends on $@. | gorestful.go:17:12:17:39 | call to BodyParameter | a user-provided value | diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.ql b/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.ql index 4fa5f250650..b09ab2771cc 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/Gorestful/gorestful.ql @@ -1,7 +1,8 @@ import go import semmle.go.security.CommandInjection +import CommandInjection::Flow::PathGraph -from CommandInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) +from CommandInjection::Flow::PathNode source, CommandInjection::Flow::PathNode sink +where CommandInjection::Flow::flowPath(source, sink) select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(), "a user-provided value" diff --git a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/TaintFlowsInline.ql b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/TaintFlowsInline.ql index 793308837c1..1b27b27d6dc 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/TaintFlowsInline.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/TaintFlowsInline.ql @@ -1,39 +1,3 @@ import go -import TestUtilities.InlineExpectationsTest - -class SourceFunction extends Function { - SourceFunction() { this.getName() = "source" } -} - -class SinkFunction extends Function { - SinkFunction() { this.getName() = "sink" } -} - -class TestConfig extends TaintTracking::Configuration { - TestConfig() { this = "testconfig" } - - override predicate isSource(DataFlow::Node source) { - source = any(SourceFunction f).getACall().getResult(0) - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(SinkFunction f).getACall().getArgument(0) - } -} - -module K8sIoApiCoreV1Test implements TestSig { - string getARelevantTag() { result = "KsIoApiCoreV" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - exists(TestConfig config, DataFlow::PathNode sink | - config.hasFlowPath(_, sink) and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and - element = sink.toString() and - value = "" and - tag = "KsIoApiCoreV" - ) - } -} - -import MakeTest +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/main.go b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/main.go index 4518342c171..68c386bfe90 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/main.go +++ b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApiCoreV1/main.go @@ -9,86 +9,88 @@ import ( //go:generate depstubber -vendor k8s.io/apimachinery/pkg/runtime ProtobufMarshaller,ProtobufReverseMarshaller func source() interface{} { - return make([]byte, 1, 1) + return make([]byte, 1) } -func sink(...interface{}) { +func sink(interface{}) { } func main() { { // func (in *Secret) DeepCopy() *Secret - sink(source().(*corev1.Secret).DeepCopy()) // $ KsIoApiCoreV + sink(source().(*corev1.Secret).DeepCopy()) // $ hasTaintFlow="call to DeepCopy" } { // func (in *Secret) DeepCopyInto(out *Secret) var out *corev1.Secret source().(*corev1.Secret).DeepCopyInto(out) - sink(out) // $ KsIoApiCoreV + sink(out) // $ hasTaintFlow="out" } { // func (in *Secret) DeepCopyObject() runtime.Object - sink(source().(*corev1.Secret).DeepCopyObject()) // $ KsIoApiCoreV + sink(source().(*corev1.Secret).DeepCopyObject()) // $ hasTaintFlow="call to DeepCopyObject" } { // func (m *Secret) Marshal() (dAtA []byte, err error) - sink(source().(*corev1.Secret).Marshal()) // $ KsIoApiCoreV + out, _ := source().(*corev1.Secret).Marshal() + sink(out) // $ hasTaintFlow="out" } { // func (m *Secret) MarshalTo(dAtA []byte) (int, error) var dAtA []byte source().(*corev1.Secret).MarshalTo(dAtA) - sink(dAtA) // $ KsIoApiCoreV + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *Secret) MarshalToSizedBuffer(dAtA []byte) (int, error) var dAtA []byte source().(*corev1.Secret).MarshalToSizedBuffer(dAtA) - sink(dAtA) // $ KsIoApiCoreV + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *Secret) Unmarshal(dAtA []byte) error var dAtA []byte source().(*corev1.Secret).Unmarshal(dAtA) - sink(dAtA) // $ KsIoApiCoreV + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (in *SecretList) DeepCopy() *SecretList - sink(source().(*corev1.SecretList).DeepCopy()) // $ KsIoApiCoreV + sink(source().(*corev1.SecretList).DeepCopy()) // $ hasTaintFlow="call to DeepCopy" } { // func (in *SecretList) DeepCopyInto(out *SecretList) var out *corev1.SecretList source().(*corev1.SecretList).DeepCopyInto(out) - sink(out) // $ KsIoApiCoreV + sink(out) // $ hasTaintFlow="out" } { // func (in *SecretList) DeepCopyObject() runtime.Object - sink(source().(*corev1.SecretList).DeepCopyObject()) // $ KsIoApiCoreV + sink(source().(*corev1.SecretList).DeepCopyObject()) // $ hasTaintFlow="call to DeepCopyObject" } { // func (m *SecretList) Marshal() (dAtA []byte, err error) - sink(source().(*corev1.SecretList).Marshal()) // $ KsIoApiCoreV + out, _ := source().(*corev1.SecretList).Marshal() + sink(out) // $ hasTaintFlow="out" } { // func (m *SecretList) MarshalTo(dAtA []byte) (int, error) var dAtA []byte source().(*corev1.SecretList).MarshalTo(dAtA) - sink(dAtA) // $ KsIoApiCoreV + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *SecretList) MarshalToSizedBuffer(dAtA []byte) (int, error) var dAtA []byte source().(*corev1.SecretList).MarshalToSizedBuffer(dAtA) - sink(dAtA) // $ KsIoApiCoreV + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *SecretList) Unmarshal(dAtA []byte) error var dAtA []byte source().(*corev1.SecretList).Unmarshal(dAtA) - sink(dAtA) // $ KsIoApiCoreV + sink(dAtA) // $ hasTaintFlow="dAtA" } } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/TaintFlowsInline.ql b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/TaintFlowsInline.ql index 901dcb9eaf5..1b27b27d6dc 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/TaintFlowsInline.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/TaintFlowsInline.ql @@ -1,39 +1,3 @@ import go -import TestUtilities.InlineExpectationsTest - -class SourceFunction extends Function { - SourceFunction() { this.getName() = "source" } -} - -class SinkFunction extends Function { - SinkFunction() { this.getName() = "sink" } -} - -class TestConfig extends TaintTracking::Configuration { - TestConfig() { this = "testconfig" } - - override predicate isSource(DataFlow::Node source) { - source = any(SourceFunction f).getACall().getAResult() - } - - override predicate isSink(DataFlow::Node sink) { - sink = any(SinkFunction f).getACall().getAnArgument() - } -} - -module K8sIoApimachineryPkgRuntimeTest implements TestSig { - string getARelevantTag() { result = "KsIoApimachineryPkgRuntime" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - exists(TestConfig config, DataFlow::PathNode sink | - config.hasFlowPath(_, sink) and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and - element = sink.toString() and - value = "" and - tag = "KsIoApimachineryPkgRuntime" - ) - } -} - -import MakeTest +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/main.go b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/main.go index dd213c214e2..b19ff5c3043 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/main.go +++ b/go/ql/test/library-tests/semmle/go/frameworks/K8sIoApimachineryPkgRuntime/main.go @@ -31,93 +31,93 @@ func main() { // func Convert_Slice_string_To_Pointer_int64(in *[]string, out **int64, s conversion.Scope) error var out **int64 runtime.Convert_Slice_string_To_Pointer_int64(source().(*[]string), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func Convert_Slice_string_To_int(in *[]string, out *int, s conversion.Scope) error var out *int runtime.Convert_Slice_string_To_int(source().(*[]string), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func Convert_Slice_string_To_int64(in *[]string, out *int64, s conversion.Scope) error var out *int64 runtime.Convert_Slice_string_To_int64(source().(*[]string), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func Convert_Slice_string_To_string(in *[]string, out *string, s conversion.Scope) error var out *string runtime.Convert_Slice_string_To_string(source().(*[]string), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func Convert_runtime_Object_To_runtime_RawExtension(in *Object, out *RawExtension, s conversion.Scope) error var out *runtime.RawExtension runtime.Convert_runtime_Object_To_runtime_RawExtension(source().(*runtime.Object), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func Convert_runtime_RawExtension_To_runtime_Object(in *RawExtension, out *Object, s conversion.Scope) error var out *runtime.Object runtime.Convert_runtime_RawExtension_To_runtime_Object(source().(*runtime.RawExtension), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func Convert_string_To_Pointer_int64(in *string, out **int64, s conversion.Scope) error var out **int64 runtime.Convert_string_To_Pointer_int64(source().(*string), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func Convert_string_To_int64(in *string, out *int64, s conversion.Scope) error var out *int64 runtime.Convert_string_To_int64(source().(*string), out, s) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func DecodeInto(d Decoder, data []byte, into Object) error var o runtime.Object runtime.DecodeInto(decoder, source().([]byte), o) - sink(o) // $ KsIoApimachineryPkgRuntime + sink(o) // $ hasTaintFlow="o" } { // func DeepCopyJSON(x map[string]interface{}) map[string]interface{} - sink(runtime.DeepCopyJSON(source().(map[string]interface{}))) // $ KsIoApimachineryPkgRuntime + sink(runtime.DeepCopyJSON(source().(map[string]interface{}))) // $ hasTaintFlow="call to DeepCopyJSON" } { // func DeepCopyJSONValue(x interface{}) interface{} - sink(runtime.DeepCopyJSONValue(source().(map[string]interface{}))) // $ KsIoApimachineryPkgRuntime + sink(runtime.DeepCopyJSONValue(source().(map[string]interface{}))) // $ hasTaintFlow="call to DeepCopyJSONValue" } { // func Encode(e Encoder, obj Object) ([]byte, error) x, _ := runtime.Encode(encoder, source().(runtime.Object)) - sink(x) // $ KsIoApimachineryPkgRuntime + sink(x) // $ hasTaintFlow="x" } { // func EncodeOrDie(e Encoder, obj Object) string - sink(runtime.EncodeOrDie(encoder, source().(runtime.Object))) // $ KsIoApimachineryPkgRuntime + sink(runtime.EncodeOrDie(encoder, source().(runtime.Object))) // $ hasTaintFlow="call to EncodeOrDie" } { // func Field(v reflect.Value, fieldName string, dest interface{}) error var fieldName string var dest interface{} runtime.Field(source().(reflect.Value), fieldName, dest) - sink(dest) // $ KsIoApimachineryPkgRuntime + sink(dest) // $ hasTaintFlow="dest" } { // func FieldPtr(v reflect.Value, fieldName string, dest interface{}) error var fieldName string var dest interface{} runtime.FieldPtr(source().(reflect.Value), fieldName, dest) - sink(dest) // $ KsIoApimachineryPkgRuntime + sink(dest) // $ hasTaintFlow="dest" } { // func SetField(src interface{}, v reflect.Value, fieldName string) error var v reflect.Value var fieldName string runtime.SetField(source(), v, fieldName) - sink(v) // $ KsIoApimachineryPkgRuntime + sink(v) // $ hasTaintFlow="v" } { // CacheEncode(id Identifier, encode func(Object, io.Writer) error, w io.Writer) error @@ -125,19 +125,19 @@ func main() { var encode func(runtime.Object, io.Writer) error var w io.Writer source().(myCacheableObject).CacheEncode(id, encode, w) - sink(w) // $ KsIoApimachineryPkgRuntime + sink(w) // $ hasTaintFlow="w" } { // GetObject() Object - sink(source().(myCacheableObject).GetObject()) // $ KsIoApimachineryPkgRuntime + sink(source().(myCacheableObject).GetObject()) // $ hasTaintFlow="call to GetObject" } { // Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) var defaults *schema.GroupVersionKind var into runtime.Object x, _, _ := decoder.Decode(source().([]byte), defaults, into) - sink(x) // $ KsIoApimachineryPkgRuntime - sink(into) // $ KsIoApimachineryPkgRuntime + sink(x) // $ hasTaintFlow="x" + sink(into) // $ hasTaintFlow="into" } { // Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) @@ -145,47 +145,47 @@ func main() { var into runtime.Object var withoutVersionDecoder runtime.WithoutVersionDecoder x, _, _ := withoutVersionDecoder.Decode(source().([]byte), defaults, into) - sink(x) // $ KsIoApimachineryPkgRuntime - sink(into) // $ KsIoApimachineryPkgRuntime + sink(x) // $ hasTaintFlow="x" + sink(into) // $ hasTaintFlow="into" } { // Encode(obj Object, w io.Writer) error var w io.Writer encoder.Encode(source().(runtime.Object), w) - sink(w) // $ KsIoApimachineryPkgRuntime + sink(w) // $ hasTaintFlow="w" } { // Encode(obj Object, w io.Writer) error var w io.Writer var withVersionEncoder runtime.WithVersionEncoder withVersionEncoder.Encode(source().(runtime.Object), w) - sink(w) // $ KsIoApimachineryPkgRuntime + sink(w) // $ hasTaintFlow="w" } { var framer myFramer // NewFrameReader(r io.ReadCloser) io.ReadCloser - sink(framer.NewFrameReader(source().(io.ReadCloser))) // $ KsIoApimachineryPkgRuntime + sink(framer.NewFrameReader(source().(io.ReadCloser))) // $ hasTaintFlow="call to NewFrameReader" // NewFrameWriter(w io.Writer) io.Writer - sink(framer.NewFrameWriter(source().(io.Writer))) // $ KsIoApimachineryPkgRuntime + sink(framer.NewFrameWriter(source().(io.Writer))) // $ hasTaintFlow="call to NewFrameWriter" } { // DeepCopyObject() Object - sink(source().(runtime.Object).DeepCopyObject()) // $ KsIoApimachineryPkgRuntime + sink(source().(runtime.Object).DeepCopyObject()) // $ hasTaintFlow="call to DeepCopyObject" } { // func Decode(d Decoder, data []byte) (Object, error) o, _ := runtime.Decode(decoder, source().([]byte)) - sink(o) // $ KsIoApimachineryPkgRuntime + sink(o) // $ hasTaintFlow="o" } { // func NewEncodable(e Encoder, obj Object, versions ...schema.GroupVersion) Object - sink(runtime.NewEncodable(encoder, source().(runtime.Object))) // $ KsIoApimachineryPkgRuntime + sink(runtime.NewEncodable(encoder, source().(runtime.Object))) // $ hasTaintFlow="call to NewEncodable" } { // func NewEncodableList(e Encoder, objects []Object, versions ...schema.GroupVersion) []Object - sink(runtime.NewEncodableList(encoder, source().([]runtime.Object))) // $ KsIoApimachineryPkgRuntime + sink(runtime.NewEncodableList(encoder, source().([]runtime.Object))) // $ hasTaintFlow="call to NewEncodableList" } { // func UseOrCreateObject(t ObjectTyper, c ObjectCreater, gvk schema.GroupVersionKind, obj Object) (Object, error) @@ -193,7 +193,7 @@ func main() { var c runtime.ObjectCreater var gvk schema.GroupVersionKind o, _ := runtime.UseOrCreateObject(t, c, gvk, source().(runtime.Object)) - sink(o) // $ KsIoApimachineryPkgRuntime + sink(o) // $ hasTaintFlow="o" } { var objectConverter myObjectConverter @@ -201,12 +201,12 @@ func main() { // Convert(in, out, context interface{}) error var out, context interface{} objectConverter.Convert(source(), out, context) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" // ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error) var gv runtime.GroupVersioner o, _ := objectConverter.ConvertToVersion(source().(runtime.Object), gv) - sink(o) // $ KsIoApimachineryPkgRuntime + sink(o) // $ hasTaintFlow="o" } { var parameterCodec myParameterCodec @@ -215,110 +215,110 @@ func main() { var gv schema.GroupVersion var into runtime.Object parameterCodec.DecodeParameters(source().(url.Values), gv, into) - sink(into) // $ KsIoApimachineryPkgRuntime + sink(into) // $ hasTaintFlow="into" // EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) urlValues, _ := parameterCodec.EncodeParameters(source().(runtime.Object), gv) - sink(urlValues) // $ KsIoApimachineryPkgRuntime + sink(urlValues) // $ hasTaintFlow="urlValues" } { // MarshalTo(data []byte) (int, error) var data []byte source().(myProtobufMarshaller).MarshalTo(data) - sink(data) // $ KsIoApimachineryPkgRuntime + sink(data) // $ hasTaintFlow="data" } { // MarshalToSizedBuffer(data []byte) (int, error) var data []byte source().(myProtobufReverseMarshaller).MarshalToSizedBuffer(data) - sink(data) // $ KsIoApimachineryPkgRuntime + sink(data) // $ hasTaintFlow="data" } { // func (in *RawExtension) DeepCopy() *RawExtension - sink(source().(*runtime.RawExtension).DeepCopy()) // $ KsIoApimachineryPkgRuntime + sink(source().(*runtime.RawExtension).DeepCopy()) // $ hasTaintFlow="call to DeepCopy" } { // func (in *RawExtension) DeepCopyInto(out *RawExtension) var out *runtime.RawExtension source().(*runtime.RawExtension).DeepCopyInto(out) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func (m *RawExtension) Marshal() (dAtA []byte, err error) dAtA, _ := source().(*runtime.RawExtension).Marshal() - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *RawExtension) MarshalTo(dAtA []byte) (int, error) var dAtA []byte source().(*runtime.RawExtension).MarshalTo(dAtA) - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *RawExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) var dAtA []byte source().(*runtime.RawExtension).MarshalToSizedBuffer(dAtA) - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *RawExtension) Unmarshal(dAtA []byte) error var dAtA []byte source().(*runtime.RawExtension).Unmarshal(dAtA) - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (in *Unknown) DeepCopy() *Unknown - sink(source().(*runtime.Unknown).DeepCopy()) // $ KsIoApimachineryPkgRuntime + sink(source().(*runtime.Unknown).DeepCopy()) // $ hasTaintFlow="call to DeepCopy" } { // func (in *Unknown) DeepCopyObject() Object - sink(source().(*runtime.Unknown).DeepCopyObject()) // $ KsIoApimachineryPkgRuntime + sink(source().(*runtime.Unknown).DeepCopyObject()) // $ hasTaintFlow="call to DeepCopyObject" } { // func (in *Unknown) DeepCopyInto(out *Unknown) var out *runtime.Unknown source().(*runtime.Unknown).DeepCopyInto(out) - sink(out) // $ KsIoApimachineryPkgRuntime + sink(out) // $ hasTaintFlow="out" } { // func (m *Unknown) Marshal() (dAtA []byte, err error) dAtA, _ := source().(*runtime.Unknown).Marshal() - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *Unknown) MarshalTo(dAtA []byte) (int, error) var dAtA []byte source().(*runtime.Unknown).MarshalTo(dAtA) - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *Unknown) MarshalToSizedBuffer(dAtA []byte) (int, error) var dAtA []byte source().(*runtime.Unknown).MarshalToSizedBuffer(dAtA) - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64) (int, error) var dAtA []byte var b myProtobufMarshaller source().(*runtime.Unknown).NestedMarshalTo(dAtA, b, 1) - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // func (m *Unknown) Unmarshal(dAtA []byte) error var dAtA []byte source().(*runtime.Unknown).Unmarshal(dAtA) - sink(dAtA) // $ KsIoApimachineryPkgRuntime + sink(dAtA) // $ hasTaintFlow="dAtA" } { // UnstructuredContent() map[string]interface{} - sink(source().(myUnstructured).UnstructuredContent()) // $ KsIoApimachineryPkgRuntime + sink(source().(myUnstructured).UnstructuredContent()) // $ hasTaintFlow="call to UnstructuredContent" } { // SetUnstructuredContent(map[string]interface{}) var unstructured myUnstructured unstructured.SetUnstructuredContent(source().(map[string]interface{})) - sink(unstructured) // $ KsIoApimachineryPkgRuntime + sink(unstructured) // $ hasTaintFlow="unstructured" } } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.expected b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.expected index 4c70d92720b..48de9172b36 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.expected +++ b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.expected @@ -1,33 +1,2 @@ -| testDeprecatedApi.go:22:22:22:41 | call to getUntrustedString | testDeprecatedApi.go:26:12:26:21 | serialized | -| testDeprecatedApi.go:31:22:31:41 | call to getUntrustedString | testDeprecatedApi.go:37:12:37:21 | serialized | -| testDeprecatedApi.go:41:25:41:43 | call to getUntrustedBytes | testDeprecatedApi.go:45:13:45:29 | selection of Description | -| testDeprecatedApi.go:49:25:49:43 | call to getUntrustedBytes | testDeprecatedApi.go:53:13:53:34 | call to GetDescription | -| testDeprecatedApi.go:58:23:58:42 | call to getUntrustedString | testDeprecatedApi.go:65:12:65:21 | serialized | -| testDeprecatedApi.go:70:14:70:33 | call to getUntrustedString | testDeprecatedApi.go:77:12:77:21 | serialized | -| testDeprecatedApi.go:85:24:85:43 | call to getUntrustedString | testDeprecatedApi.go:89:12:89:21 | serialized | -| testDeprecatedApi.go:93:25:93:43 | call to getUntrustedBytes | testDeprecatedApi.go:97:13:97:31 | selection of Msg | -| testDeprecatedApi.go:104:22:104:41 | call to getUntrustedString | testDeprecatedApi.go:105:13:105:20 | selection of Id | -| testDeprecatedApi.go:112:22:112:41 | call to getUntrustedString | testDeprecatedApi.go:117:12:117:21 | serialized | -| testDeprecatedApi.go:133:29:133:48 | call to getUntrustedString | testDeprecatedApi.go:137:12:137:21 | serialized | -| testDeprecatedApi.go:143:20:143:39 | call to getUntrustedString | testDeprecatedApi.go:148:12:148:21 | serialized | -| testDeprecatedApi.go:152:25:152:43 | call to getUntrustedBytes | testDeprecatedApi.go:157:13:157:36 | index expression | -| testDeprecatedApi.go:161:25:161:43 | call to getUntrustedBytes | testDeprecatedApi.go:168:13:168:25 | index expression | -| testDeprecatedApi.go:176:24:176:43 | call to getUntrustedString | testDeprecatedApi.go:180:12:180:21 | serialized | -| testModernApi.go:11:22:11:41 | call to getUntrustedString | testModernApi.go:15:12:15:21 | serialized | -| testModernApi.go:20:22:20:41 | call to getUntrustedString | testModernApi.go:26:12:26:21 | serialized | -| testModernApi.go:30:25:30:43 | call to getUntrustedBytes | testModernApi.go:34:13:34:29 | selection of Description | -| testModernApi.go:38:25:38:43 | call to getUntrustedBytes | testModernApi.go:42:13:42:34 | call to GetDescription | -| testModernApi.go:47:23:47:42 | call to getUntrustedString | testModernApi.go:54:12:54:21 | serialized | -| testModernApi.go:59:22:59:41 | call to getUntrustedString | testModernApi.go:64:12:64:21 | serialized | -| testModernApi.go:71:22:71:41 | call to getUntrustedString | testModernApi.go:77:12:77:21 | serialized | -| testModernApi.go:98:14:98:33 | call to getUntrustedString | testModernApi.go:105:12:105:21 | serialized | -| testModernApi.go:113:24:113:43 | call to getUntrustedString | testModernApi.go:117:12:117:21 | serialized | -| testModernApi.go:121:25:121:43 | call to getUntrustedBytes | testModernApi.go:125:13:125:31 | selection of Msg | -| testModernApi.go:131:25:131:43 | call to getUntrustedBytes | testModernApi.go:135:13:135:29 | selection of Description | -| testModernApi.go:142:22:142:41 | call to getUntrustedString | testModernApi.go:143:13:143:20 | selection of Id | -| testModernApi.go:150:22:150:41 | call to getUntrustedString | testModernApi.go:155:12:155:21 | serialized | -| testModernApi.go:190:29:190:48 | call to getUntrustedString | testModernApi.go:194:12:194:21 | serialized | -| testModernApi.go:200:20:200:39 | call to getUntrustedString | testModernApi.go:205:12:205:21 | serialized | -| testModernApi.go:209:25:209:43 | call to getUntrustedBytes | testModernApi.go:214:13:214:36 | index expression | -| testModernApi.go:218:25:218:43 | call to getUntrustedBytes | testModernApi.go:225:13:225:25 | index expression | -| testModernApi.go:233:24:233:43 | call to getUntrustedString | testModernApi.go:237:12:237:21 | serialized | +failures +testFailures diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.ql b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.ql index 94a6bb709f2..fd4bdd857e6 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/TaintFlows.ql @@ -1,27 +1,18 @@ import go +import TestUtilities.InlineFlowTest -class UntrustedFunction extends Function { - UntrustedFunction() { this.getName() = ["getUntrustedString", "getUntrustedBytes"] } -} +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + exists(Function fn | fn.hasQualifiedName(_, ["getUntrustedString", "getUntrustedBytes"]) | + source = fn.getACall().getResult() + ) + } -class UntrustedSource extends DataFlow::Node, UntrustedFlowSource::Range { - UntrustedSource() { this = any(UntrustedFunction f).getACall() } -} - -class SinkFunction extends Function { - SinkFunction() { this.getName() = ["sinkString", "sinkBytes"] } -} - -class TestConfig extends TaintTracking::Configuration { - TestConfig() { this = "testconfig" } - - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - - override predicate isSink(DataFlow::Node sink) { - sink = any(SinkFunction f).getACall().getAnArgument() + predicate isSink(DataFlow::Node sink) { + exists(Function fn | fn.hasQualifiedName(_, ["sinkString", "sinkBytes"]) | + sink = fn.getACall().getAnArgument() + ) } } -from TaintTracking::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink -where config.hasFlowPath(source, sink) -select source, sink +import TaintFlowTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testDeprecatedApi.go b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testDeprecatedApi.go index ee998768a0b..85c0af0cbf3 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testDeprecatedApi.go +++ b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testDeprecatedApi.go @@ -2,6 +2,7 @@ package main import ( "codeql-go-tests/protobuf/protos/query" + "github.com/golang/protobuf/proto" ) @@ -23,7 +24,7 @@ func testMarshal() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testCloneThenMarshal() { @@ -34,7 +35,7 @@ func testCloneThenMarshal() { serialized, _ := proto.Marshal(queryClone) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testUnmarshalFieldAccess() { @@ -42,7 +43,7 @@ func testUnmarshalFieldAccess() { query := &query.Query{} proto.Unmarshal(untrustedSerialized, query) - sinkString(query.Description) // BAD + sinkString(query.Description) // $ hasTaintFlow="selection of Description" } func testUnmarshalGetter() { @@ -50,7 +51,7 @@ func testUnmarshalGetter() { query := &query.Query{} proto.Unmarshal(untrustedSerialized, query) - sinkString(query.GetDescription()) // BAD + sinkString(query.GetDescription()) // $ hasTaintFlow="call to GetDescription" } func testMergeThenMarshal() { @@ -62,7 +63,7 @@ func testMergeThenMarshal() { serialized, _ := proto.Marshal(query2) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testTaintedSubmessage() { @@ -74,7 +75,7 @@ func testTaintedSubmessage() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testTaintedSubmessageInPlace() { @@ -86,7 +87,7 @@ func testTaintedSubmessageInPlace() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testUnmarshalTaintedSubmessage() { @@ -94,7 +95,7 @@ func testUnmarshalTaintedSubmessage() { query := &query.Query{} proto.Unmarshal(untrustedSerialized, query) - sinkString(query.Alerts[0].Msg) // BAD + sinkString(query.Alerts[0].Msg) // $ hasTaintFlow="selection of Msg" } // This test should be ok, but is flagged because writing taint to a field of a Message @@ -102,7 +103,7 @@ func testUnmarshalTaintedSubmessage() { func testFieldConflationFalsePositive() { query := &query.Query{} query.Description = getUntrustedString() - sinkString(query.Id) // OK (but incorrectly tainted) + sinkString(query.Id) // $ SPURIOUS: hasTaintFlow="selection of Id" } // This test should be ok, but it flagged because our current implementation doesn't notice @@ -114,7 +115,7 @@ func testMessageReuseFalsePositive() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // OK (but incorrectly tainted) + sinkBytes(serialized) // $ SPURIOUS: hasTaintFlow="serialized" } // This test should be flagged, but we don't notice tainting via an alias of a field. @@ -125,7 +126,7 @@ func testSubmessageAliasFalseNegative() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD (but not noticed by our current implementation) + sinkBytes(serialized) // $ MISSING: hasTaintFlow="serialized" } func testTaintedMapFieldWrite() { @@ -134,7 +135,7 @@ func testTaintedMapFieldWrite() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testTaintedMapWriteWholeMap() { @@ -145,7 +146,7 @@ func testTaintedMapWriteWholeMap() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testTaintedMapFieldRead() { @@ -154,7 +155,7 @@ func testTaintedMapFieldRead() { proto.Unmarshal(untrustedSerialized, query) - sinkString(query.KeyValuePairs[123]) // BAD + sinkString(query.KeyValuePairs[123]) // $ hasTaintFlow="index expression" } func testTaintedMapFieldReadViaAlias() { @@ -165,7 +166,7 @@ func testTaintedMapFieldReadViaAlias() { alias := &query.KeyValuePairs - sinkString((*alias)[123]) // BAD + sinkString((*alias)[123]) // $ hasTaintFlow="index expression" } func testTaintedSubmessageInPlaceNonPointerBase() { @@ -177,5 +178,5 @@ func testTaintedSubmessageInPlaceNonPointerBase() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD (but not detected by our current analysis) + sinkBytes(serialized) // $ hasTaintFlow="serialized" } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testModernApi.go b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testModernApi.go index 6beac40a3ab..89d38b6425e 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testModernApi.go +++ b/go/ql/test/library-tests/semmle/go/frameworks/Protobuf/testModernApi.go @@ -2,6 +2,7 @@ package main import ( "codeql-go-tests/protobuf/protos/query" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/runtime/protoiface" ) @@ -12,7 +13,7 @@ func testMarshalModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testCloneThenMarshalModern() { @@ -23,7 +24,7 @@ func testCloneThenMarshalModern() { serialized, _ := proto.Marshal(queryClone) - sinkBytes(serialized) + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testUnmarshalFieldAccessModern() { @@ -31,7 +32,7 @@ func testUnmarshalFieldAccessModern() { query := &query.Query{} proto.Unmarshal(untrustedSerialized, query) - sinkString(query.Description) + sinkString(query.Description) // $ hasTaintFlow="selection of Description" } func testUnmarshalGetterModern() { @@ -39,7 +40,7 @@ func testUnmarshalGetterModern() { query := &query.Query{} proto.Unmarshal(untrustedSerialized, query) - sinkString(query.GetDescription()) + sinkString(query.GetDescription()) // $ hasTaintFlow="call to GetDescription" } func testMergeThenMarshalModern() { @@ -51,7 +52,7 @@ func testMergeThenMarshalModern() { serialized, _ := proto.Marshal(query2) - sinkBytes(serialized) + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testMarshalWithOptionsModern() { @@ -61,7 +62,7 @@ func testMarshalWithOptionsModern() { options := proto.MarshalOptions{} serialized, _ := options.Marshal(query) - sinkBytes(serialized) + sinkBytes(serialized) // $ hasTaintFlow="serialized" } // Tests only applicable to the modern API: @@ -74,7 +75,7 @@ func testMarshalAppend() { emptyArray := []byte{} serialized, _ := options.MarshalAppend(emptyArray, query) - sinkBytes(serialized) + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testMarshalState() { @@ -102,7 +103,7 @@ func testTaintedSubmessageModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testTaintedSubmessageInPlaceModern() { @@ -114,7 +115,7 @@ func testTaintedSubmessageInPlaceModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testUnmarshalTaintedSubmessageModern() { @@ -122,7 +123,7 @@ func testUnmarshalTaintedSubmessageModern() { query := &query.Query{} proto.Unmarshal(untrustedSerialized, query) - sinkString(query.Alerts[0].Msg) // BAD + sinkString(query.Alerts[0].Msg) // $ hasTaintFlow="selection of Msg" } func testUnmarshalOptions() { @@ -132,7 +133,7 @@ func testUnmarshalOptions() { query := &query.Query{} options.Unmarshal(untrustedSerialized, query) - sinkString(query.Description) // BAD + sinkString(query.Description) // $ hasTaintFlow="selection of Description" } // This test should be ok, but is flagged because writing taint to a field of a Message @@ -140,7 +141,7 @@ func testUnmarshalOptions() { func testFieldConflationFalsePositiveModern() { query := &query.Query{} query.Description = getUntrustedString() - sinkString(query.Id) // OK (but incorrectly tainted) + sinkString(query.Id) // $ SPURIOUS: hasTaintFlow="selection of Id" } // This test should be ok, but it flagged because our current implementation doesn't notice @@ -152,7 +153,7 @@ func testMessageReuseFalsePositiveModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // OK (but incorrectly tainted) + sinkBytes(serialized) // $ SPURIOUS: hasTaintFlow="serialized" } // This test should be flagged, but we don't notice tainting via an alias of a field. @@ -163,7 +164,7 @@ func testSubmessageAliasFalseNegativeModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD (but not noticed by our current implementation) + sinkBytes(serialized) // $ MISSING: hasTaintFlow="serialized" } // This test should be flagged, but we don't notice that marshalState2.Message is the @@ -182,7 +183,7 @@ func testMarshalStateFalseNegative() { marshalState2 := marshalState serialized, _ := options.MarshalState(marshalState2) - sinkBytes(serialized.Buf) // BAD (but not noticed by our current implementation) + sinkBytes(serialized.Buf) // $ MISSING: hasTaintFlow="selection of Buf" } func testTaintedMapFieldWriteModern() { @@ -191,7 +192,7 @@ func testTaintedMapFieldWriteModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testTaintedMapWriteWholeMapModern() { @@ -202,7 +203,7 @@ func testTaintedMapWriteWholeMapModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD + sinkBytes(serialized) // $ hasTaintFlow="serialized" } func testTaintedMapFieldReadModern() { @@ -211,7 +212,7 @@ func testTaintedMapFieldReadModern() { proto.Unmarshal(untrustedSerialized, query) - sinkString(query.KeyValuePairs[123]) // BAD + sinkString(query.KeyValuePairs[123]) // $ hasTaintFlow="index expression" } func testTaintedMapFieldReadViaAliasModern() { @@ -222,7 +223,7 @@ func testTaintedMapFieldReadViaAliasModern() { alias := &query.KeyValuePairs - sinkString((*alias)[123]) // BAD + sinkString((*alias)[123]) // $ hasTaintFlow="index expression" } func testTaintedSubmessageInPlaceNonPointerBaseModern() { @@ -234,5 +235,5 @@ func testTaintedSubmessageInPlaceNonPointerBaseModern() { serialized, _ := proto.Marshal(query) - sinkBytes(serialized) // BAD (but not detected by our current implementation) + sinkBytes(serialized) // $ hasTaintFlow="serialized" } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Revel/OpenRedirect.expected b/go/ql/test/library-tests/semmle/go/frameworks/Revel/OpenRedirect.expected index 22d78141d3f..d3215df275d 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Revel/OpenRedirect.expected +++ b/go/ql/test/library-tests/semmle/go/frameworks/Revel/OpenRedirect.expected @@ -1,8 +1,11 @@ edges +| EndToEnd.go:94:20:94:27 | implicit dereference | EndToEnd.go:94:20:94:27 | selection of Params | +| EndToEnd.go:94:20:94:27 | implicit dereference | EndToEnd.go:94:20:94:32 | selection of Form | +| EndToEnd.go:94:20:94:27 | selection of Params | EndToEnd.go:94:20:94:27 | implicit dereference | | EndToEnd.go:94:20:94:27 | selection of Params | EndToEnd.go:94:20:94:32 | selection of Form | -| EndToEnd.go:94:20:94:27 | selection of Params | EndToEnd.go:94:20:94:49 | call to Get | | EndToEnd.go:94:20:94:32 | selection of Form | EndToEnd.go:94:20:94:49 | call to Get | nodes +| EndToEnd.go:94:20:94:27 | implicit dereference | semmle.label | implicit dereference | | EndToEnd.go:94:20:94:27 | selection of Params | semmle.label | selection of Params | | EndToEnd.go:94:20:94:32 | selection of Form | semmle.label | selection of Form | | EndToEnd.go:94:20:94:49 | call to Get | semmle.label | call to Get | diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Revel/test.ql b/go/ql/test/library-tests/semmle/go/frameworks/Revel/test.ql index bae5b14ccd4..fce12204e61 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Revel/test.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/Revel/test.ql @@ -7,14 +7,14 @@ class Sink extends DataFlow::Node { } } -class TestConfig extends TaintTracking::Configuration { - TestConfig() { this = "testconfig" } +private module TestConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - - override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } } +private module TestFlow = TaintTracking::Global; + module MissingDataFlowTest implements TestSig { string getARelevantTag() { result = "noflow" } @@ -22,7 +22,7 @@ module MissingDataFlowTest implements TestSig { tag = "noflow" and value = "" and exists(Sink sink | - not any(TestConfig c).hasFlow(_, sink) and + not TestFlow::flowTo(sink) and sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and element = sink.toString() diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.expected b/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.expected index 404524ec3d6..48de9172b36 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.expected +++ b/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.expected @@ -1,17 +1,2 @@ -| test.go:26:16:26:35 | call to getUntrustedString | test.go:33:14:33:23 | sUntrusted | -| test.go:26:16:26:35 | call to getUntrustedString | test.go:35:14:35:23 | sUntrusted | -| test.go:26:16:26:35 | call to getUntrustedString | test.go:41:18:41:27 | sUntrusted | -| test.go:26:16:26:35 | call to getUntrustedString | test.go:51:13:51:16 | str3 | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:30:12:30:21 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:31:13:31:22 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:32:15:32:24 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:34:17:34:26 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:36:17:36:26 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:38:16:38:25 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:39:17:39:26 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:40:19:40:28 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:42:21:42:30 | pUntrusted | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:45:13:45:16 | str1 | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:48:13:48:16 | str2 | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:54:13:54:16 | str4 | -| test.go:28:16:28:35 | call to getUntrustedStruct | test.go:57:13:57:16 | str5 | +failures +testFailures diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.ql b/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.ql index fbd4149d6c9..afbd42aba17 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/Spew/TaintFlows.ql @@ -1,28 +1,18 @@ import go +import TestUtilities.InlineFlowTest -class UntrustedFunction extends Function { - UntrustedFunction() { this.getName() = ["getUntrustedString", "getUntrustedStruct"] } -} +module TestConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source = + any(Function f | f.getName() = ["getUntrustedString", "getUntrustedStruct"]) + .getACall() + .getResult() + } -class UntrustedSource extends DataFlow::Node, UntrustedFlowSource::Range { - UntrustedSource() { this = any(UntrustedFunction f).getACall() } -} - -class SinkFunction extends Function { - SinkFunction() { this.getName() = "sinkString" } -} - -class TestConfig extends TaintTracking::Configuration { - TestConfig() { this = "testconfig" } - - override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } - - override predicate isSink(DataFlow::Node sink) { - sink = any(SinkFunction f).getACall().getAnArgument() or + predicate isSink(DataFlow::Node sink) { + sink = any(Function f | f.getName() = "sinkString").getACall().getAnArgument() or sink = any(LoggerCall log).getAMessageComponent() } } -from TaintTracking::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink -where config.hasFlowPath(source, sink) -select source, sink +import FlowTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Spew/test.go b/go/ql/test/library-tests/semmle/go/frameworks/Spew/test.go index 513f1285b39..2696693c30d 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Spew/test.go +++ b/go/ql/test/library-tests/semmle/go/frameworks/Spew/test.go @@ -27,32 +27,32 @@ func testSpew(w io.Writer) { p := Person{} pUntrusted := getUntrustedStruct() - spew.Dump(pUntrusted) // NOT OK - spew.Print(pUntrusted) // NOT OK - spew.Println(pUntrusted) // NOT OK - spew.Errorf(sUntrusted, p) // NOT OK - spew.Errorf(s, pUntrusted) // NOT OK - spew.Printf(sUntrusted, p) // NOT OK - spew.Printf(s, pUntrusted) // NOT OK + spew.Dump(pUntrusted) // $ hasValueFlow="pUntrusted" + spew.Print(pUntrusted) // $ hasValueFlow="pUntrusted" + spew.Println(pUntrusted) // $ hasValueFlow="pUntrusted" + spew.Errorf(sUntrusted, p) // $ hasValueFlow="sUntrusted" + spew.Errorf(s, pUntrusted) // $ hasValueFlow="pUntrusted" + spew.Printf(sUntrusted, p) // $ hasValueFlow="sUntrusted" + spew.Printf(s, pUntrusted) // $ hasValueFlow="pUntrusted" - spew.Fdump(w, pUntrusted) // NOT OK - spew.Fprint(w, pUntrusted) // NOT OK - spew.Fprintln(w, pUntrusted) // NOT OK - spew.Fprintf(w, sUntrusted, p) // NOT OK - spew.Fprintf(w, s, pUntrusted) // NOT OK + spew.Fdump(w, pUntrusted) // $ hasValueFlow="pUntrusted" + spew.Fprint(w, pUntrusted) // $ hasValueFlow="pUntrusted" + spew.Fprintln(w, pUntrusted) // $ hasValueFlow="pUntrusted" + spew.Fprintf(w, sUntrusted, p) // $ hasValueFlow="sUntrusted" + spew.Fprintf(w, s, pUntrusted) // $ hasValueFlow="pUntrusted" str1 := spew.Sdump(pUntrusted) - sinkString(str1) // NOT OK + sinkString(str1) // $ hasTaintFlow="str1" str2 := spew.Sprint(pUntrusted) - sinkString(str2) // NOT OK + sinkString(str2) // $ hasTaintFlow="str2" str3 := spew.Sprintf(sUntrusted, p) - sinkString(str3) // NOT OK + sinkString(str3) // $ hasTaintFlow="str3" str4 := spew.Sprintf(s, pUntrusted) - sinkString(str4) // NOT OK + sinkString(str4) // $ hasTaintFlow="str4" str5 := spew.Sprintln(pUntrusted) - sinkString(str5) // NOT OK + sinkString(str5) // $ hasTaintFlow="str5" } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.ql b/go/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.ql index 035b57118a7..c794d4d9ea7 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.ql @@ -25,26 +25,24 @@ predicate callArgumentisSink(DataFlow::Node sink, DataFlow::CallNode call) { ) } -class FlowConf extends TaintTracking::Configuration { - FlowConf() { this = "FlowConf" } +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { callResultisSource(source, _) } - override predicate isSource(DataFlow::Node source) { callResultisSource(source, _) } - - override predicate isSink(DataFlow::Node sink) { callArgumentisSink(sink, _) } + predicate isSink(DataFlow::Node sink) { callArgumentisSink(sink, _) } } +module Flow = TaintTracking::Global; + /** * True if the result of the provided sourceCall flows to the corresponding sink, * both marked by the same numeric first argument. */ predicate flowsToSink(DataFlow::CallNode sourceCall) { - exists( - FlowConf cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::CallNode sinkCall - | - cfg.hasFlowPath(source, sink) and + exists(DataFlow::Node source, DataFlow::Node sink, DataFlow::CallNode sinkCall | + Flow::flow(source, sink) and ( - callResultisSource(source.getNode(), sourceCall) and - callArgumentisSink(sink.getNode(), sinkCall) and + callResultisSource(source, sourceCall) and + callArgumentisSink(sink, sinkCall) and sourceCall.getArgument(0).getIntValue() = sinkCall.getArgument(0).getIntValue() ) ) diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Twirp/tests.ql b/go/ql/test/library-tests/semmle/go/frameworks/Twirp/tests.ql index deb260a2cc3..e4909fa9fe9 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Twirp/tests.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/Twirp/tests.ql @@ -67,9 +67,7 @@ query predicate passingPositiveTests(string res, string expectation, InlineTest exists(Twirp::ServerConstructor n | t.inEntity(n)) or expectation = "ssrf" and - exists(RequestForgery::Configuration cfg, DataFlow::Node sink | - cfg.hasFlow(_, sink) and t.inNode(sink) - ) + exists(DataFlow::Node sink | RequestForgery::Flow::flowTo(sink) and t.inNode(sink)) ) } @@ -105,9 +103,7 @@ query predicate failingPositiveTests(string res, string expectation, InlineTest not exists(Twirp::ServerConstructor n | t.inEntity(n)) or expectation = "ssrf" and - not exists(RequestForgery::Configuration cfg, DataFlow::Node sink | - cfg.hasFlow(_, sink) and t.inNode(sink) - ) + not exists(DataFlow::Node sink | RequestForgery::Flow::flowTo(sink) and t.inNode(sink)) ) } @@ -143,9 +139,7 @@ query predicate passingNegativeTests(string res, string expectation, InlineTest not exists(Twirp::ServerConstructor n | t.inEntity(n)) or expectation = "!ssrf" and - not exists(RequestForgery::Configuration cfg, DataFlow::Node sink | - cfg.hasFlow(_, sink) and t.inNode(sink) - ) + not exists(DataFlow::Node sink | RequestForgery::Flow::flowTo(sink) and t.inNode(sink)) ) } @@ -181,8 +175,6 @@ query predicate failingNegativeTests(string res, string expectation, InlineTest exists(Twirp::ServerConstructor n | t.inEntity(n)) or expectation = "!ssrf" and - exists(RequestForgery::Configuration cfg, DataFlow::Node sink | - cfg.hasFlow(_, sink) and t.inNode(sink) - ) + exists(DataFlow::Node sink | RequestForgery::Flow::flowTo(sink) and t.inNode(sink)) ) } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Zap/TaintFlows.ql b/go/ql/test/library-tests/semmle/go/frameworks/Zap/TaintFlows.ql index 14650f8f20d..0b18ba244d5 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Zap/TaintFlows.ql +++ b/go/ql/test/library-tests/semmle/go/frameworks/Zap/TaintFlows.ql @@ -1,30 +1,12 @@ import go -import TestUtilities.InlineExpectationsTest +import TestUtilities.InlineFlowTest -class TestConfig extends TaintTracking::Configuration { - TestConfig() { this = "test config" } - - override predicate isSource(DataFlow::Node source) { +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.(DataFlow::CallNode).getTarget().getName() = ["getUntrustedData", "getUntrustedString"] } - override predicate isSink(DataFlow::Node sink) { - sink = any(LoggerCall log).getAMessageComponent() - } + predicate isSink(DataFlow::Node sink) { sink = any(LoggerCall log).getAMessageComponent() } } -module ZapTest implements TestSig { - string getARelevantTag() { result = "zap" } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "zap" and - exists(DataFlow::Node sink | any(TestConfig c).hasFlow(_, sink) | - element = sink.toString() and - value = "\"" + sink.toString() + "\"" and - sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), - location.getStartColumn(), location.getEndLine(), location.getEndColumn()) - ) - } -} - -import MakeTest +import FlowTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/Zap/test.go b/go/ql/test/library-tests/semmle/go/frameworks/Zap/test.go index d39dfbedb2b..3c58e9376cc 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/Zap/test.go +++ b/go/ql/test/library-tests/semmle/go/frameworks/Zap/test.go @@ -18,72 +18,72 @@ func getUntrustedString() string { func testZapLoggerDPanic() { logger, _ := zap.NewProduction() - logger.DPanic(getUntrustedString()) // $ zap="call to getUntrustedString" + logger.DPanic(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" } func testZapLoggerFatal() { logger := zap.NewExample() - logger.Fatal("msg", zap.String(getUntrustedString(), "value")) // $ zap="call to String" + logger.Fatal("msg", zap.String(getUntrustedString(), "value")) // $ hasTaintFlow="call to String" } func testZapLoggerPanic() { logger, _ := zap.NewDevelopment() - logger.Panic("msg", zap.Any("key", getUntrustedData())) // $ zap="call to Any" + logger.Panic("msg", zap.Any("key", getUntrustedData())) // $ hasTaintFlow="call to Any" } func testZapLoggerDebug(core zapcore.Core, byteArray []byte) { logger := zap.New(core) - logger.Debug(getUntrustedString()) // $ zap="call to getUntrustedString" - logger.Debug("msg", zap.Binary(getUntrustedString(), byteArray)) // $ zap="call to Binary" - logger.Debug("msg", zap.ByteString("key", getUntrustedData().([]byte))) // $ zap="call to ByteString" + logger.Debug(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" + logger.Debug("msg", zap.Binary(getUntrustedString(), byteArray)) // $ hasTaintFlow="call to Binary" + logger.Debug("msg", zap.ByteString("key", getUntrustedData().([]byte))) // $ hasTaintFlow="call to ByteString" } func testZapLoggerError(bss [][]byte) { logger := zap.L() - logger.Error(getUntrustedString()) // $ zap="call to getUntrustedString" - logger.Error("msg", zap.ByteStrings(getUntrustedString(), bss)) // $ zap="call to ByteStrings" - logger.Error("msg", zap.Error(getUntrustedData().(error))) // $ zap="call to Error" + logger.Error(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" + logger.Error("msg", zap.ByteStrings(getUntrustedString(), bss)) // $ hasTaintFlow="call to ByteStrings" + logger.Error("msg", zap.Error(getUntrustedData().(error))) // $ hasTaintFlow="call to Error" } func testZapLoggerInfo(logger *zap.Logger, errs []error) { - logger.Info(getUntrustedString()) // $ zap="call to getUntrustedString" - logger.Info("msg", zap.Errors(getUntrustedString(), errs)) // $ zap="call to Errors" - logger.Info("msg", zap.NamedError("key", getUntrustedData().(error))) // $ zap="call to NamedError" + logger.Info(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" + logger.Info("msg", zap.Errors(getUntrustedString(), errs)) // $ hasTaintFlow="call to Errors" + logger.Info("msg", zap.NamedError("key", getUntrustedData().(error))) // $ hasTaintFlow="call to NamedError" } func testZapLoggerWarn(logger *zap.Logger) { - logger.Warn(getUntrustedString()) // $ zap="call to getUntrustedString" - logger.Warn("msg", zap.Reflect(getUntrustedString(), nil)) // $ zap="call to Reflect" - logger.Warn("msg", zap.Stringp("key", getUntrustedData().(*string))) // $ zap="call to Stringp" - logger.Warn("msg", zap.Strings("key", getUntrustedData().([]string))) // $ zap="call to Strings" + logger.Warn(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" + logger.Warn("msg", zap.Reflect(getUntrustedString(), nil)) // $ hasTaintFlow="call to Reflect" + logger.Warn("msg", zap.Stringp("key", getUntrustedData().(*string))) // $ hasTaintFlow="call to Stringp" + logger.Warn("msg", zap.Strings("key", getUntrustedData().([]string))) // $ hasTaintFlow="call to Strings" } func testZapLoggerNop() { // We do not currently recognise that a logger made using NewNop() does not actually do any logging logger := zap.NewNop() - logger.Debug(getUntrustedString()) // $ SPURIOUS: zap="call to getUntrustedString" + logger.Debug(getUntrustedString()) // $ SPURIOUS: hasValueFlow="call to getUntrustedString" } func testLoggerNamed(logger *zap.Logger) { - namedLogger := logger.Named(getUntrustedString()) // $ zap="call to getUntrustedString" + namedLogger := logger.Named(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" namedLogger.Info("hello world") } func testLoggerWith(logger *zap.Logger) *zap.Logger { - logger1 := logger.With(zap.Any(getUntrustedString(), nil)) // $ zap="call to Any" + logger1 := logger.With(zap.Any(getUntrustedString(), nil)) // $ hasTaintFlow="call to Any" logger1.Info("hello world") - logger2 := logger.With(zap.String("key", getUntrustedString())) // $ zap="call to String" + logger2 := logger.With(zap.String("key", getUntrustedString())) // $ hasTaintFlow="call to String" logger2.Info("hello world") - logger3 := logger.With(zap.String("key", getUntrustedString())) // $ SPURIOUS: zap="call to String" + logger3 := logger.With(zap.String("key", getUntrustedString())) // $ SPURIOUS: hasTaintFlow="call to String" return logger3 } func getLoggerWithUntrustedField() *zap.Logger { - return zap.NewExample().With(zap.NamedError("key", getUntrustedData().(error))) // $ zap="call to NamedError" + return zap.NewExample().With(zap.NamedError("key", getUntrustedData().(error))) // $ hasTaintFlow="call to NamedError" } func getLoggerWithUntrustedFieldUnused() *zap.Logger { - return zap.NewExample().With(zap.NamedError("key", getUntrustedData().(error))) // $ SPURIOUS: zap="call to NamedError" + return zap.NewExample().With(zap.NamedError("key", getUntrustedData().(error))) // $ SPURIOUS: hasTaintFlow="call to NamedError" } func testLoggerWithAcrossFunctionBoundary() { @@ -91,91 +91,91 @@ func testLoggerWithAcrossFunctionBoundary() { } func testLoggerWithOptions(logger *zap.Logger) *zap.Logger { - logger1 := logger.WithOptions(zap.Fields(zap.Any(getUntrustedString(), nil))) // $ zap="call to Fields" + logger1 := logger.WithOptions(zap.Fields(zap.Any(getUntrustedString(), nil))) // $ hasTaintFlow="call to Fields" logger1.Info("hello world") - logger2 := logger.WithOptions(zap.Fields(zap.String("key", getUntrustedString()))) // $ zap="call to Fields" + logger2 := logger.WithOptions(zap.Fields(zap.String("key", getUntrustedString()))) // $ hasTaintFlow="call to Fields" logger2.Info("hello world") - logger3 := logger.WithOptions(zap.Fields(zap.String("key", getUntrustedString()))) // $ SPURIOUS: zap="call to Fields" + logger3 := logger.WithOptions(zap.Fields(zap.String("key", getUntrustedString()))) // $ SPURIOUS: hasTaintFlow="call to Fields" return logger3 } func testZapSugaredLoggerDPanic(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.DPanic(getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.DPanic(getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" } func testZapSugaredLoggerDPanicf(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.DPanicf(getUntrustedString()) // $ zap="call to getUntrustedString" + sugaredLogger.DPanicf(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" } func testZapSugaredLoggerDPanicw(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.DPanicw(getUntrustedString()) // $ zap="call to getUntrustedString" + sugaredLogger.DPanicw(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" } func testZapSugaredLoggerFatal(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.Fatal(getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.Fatal(getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" } func testZapSugaredLoggerFatalf(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.Fatalf(getUntrustedString()) // $ zap="call to getUntrustedString" + sugaredLogger.Fatalf(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" } func testZapSugaredLoggerFatalw(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.Fatalw(getUntrustedString()) // $ zap="call to getUntrustedString" + sugaredLogger.Fatalw(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" } func testZapSugaredLoggerPanic(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.Panic(getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.Panic(getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" } func testZapSugaredLoggerPanicf(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.Panicf(getUntrustedString()) // $ zap="call to getUntrustedString" + sugaredLogger.Panicf(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" } func testZapSugaredLoggerPanicw(sugaredLogger *zap.SugaredLogger) { - sugaredLogger.Panicw(getUntrustedString()) // $ zap="call to getUntrustedString" + sugaredLogger.Panicw(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" } func testZapSugaredLoggerDebug() { sugaredLogger := zap.S() - sugaredLogger.Debug(getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Debugf("msg", getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Debugw("msg", "key", getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.Debug(getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Debugf("msg", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Debugw("msg", "key", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" } func testZapSugaredLoggerError() { logger, _ := zap.NewProduction() sugaredLogger := logger.Sugar() - sugaredLogger.Error(getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Errorf("msg", getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Errorw("msg", "key", getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.Error(getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Errorf("msg", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Errorw("msg", "key", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" } func testZapSugaredLoggerInfo() { logger := zap.NewExample() sugaredLogger := logger.Sugar() - sugaredLogger.Info(getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Infof("msg", getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Infow("msg", "key", getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.Info(getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Infof("msg", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Infow("msg", "key", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" } func testZapSugaredLoggerWarn() { logger, _ := zap.NewDevelopment() sugaredLogger := logger.Sugar() - sugaredLogger.Warn(getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Warnf("msg", getUntrustedData()) // $ zap="call to getUntrustedData" - sugaredLogger.Warnw("msg", "key", getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.Warn(getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Warnf("msg", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" + sugaredLogger.Warnw("msg", "key", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" } func testZapSugaredLoggerNamed() { logger := zap.L() sugaredLogger := logger.Sugar() - sugaredLogger.Named(getUntrustedString()) // $ zap="call to getUntrustedString" + sugaredLogger.Named(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString" sugaredLogger.Info("msg") } func testZapSugaredLoggerWith() { logger := zap.L() sugaredLogger := logger.Sugar() - sugaredLogger.With("key", getUntrustedData()) // $ zap="call to getUntrustedData" + sugaredLogger.With("key", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData" sugaredLogger.Info("msg") } diff --git a/go/ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go b/go/ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go index 50ec41c081c..f1a72771104 100644 --- a/go/ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go +++ b/go/ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go @@ -1,5 +1,5 @@ -//go:build (linux && ignore) || amd64 -// +build linux,ignore amd64 +//go:build (linux && ignore) || amd64 || arm64 +// +build linux,ignore amd64 arm64 package main diff --git a/go/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/go/ql/test/query-tests/Security/CWE-089/SqlInjection.expected index c1ded7d69f8..a45df038896 100644 --- a/go/ql/test/query-tests/Security/CWE-089/SqlInjection.expected +++ b/go/ql/test/query-tests/Security/CWE-089/SqlInjection.expected @@ -69,20 +69,24 @@ edges | main.go:60:3:60:25 | selection of Category | main.go:59:7:60:26 | call to Sprintf | | main.go:60:4:60:15 | star expression [Category] | main.go:60:3:60:25 | selection of Category | | main.go:60:5:60:15 | RequestData [pointer, Category] | main.go:60:4:60:15 | star expression [Category] | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:57:22:57:29 | pipeline | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:61:27:61:32 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:63:23:63:28 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:64:22:64:27 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:66:32:66:37 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:69:17:69:22 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:70:20:70:25 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:71:29:71:34 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:72:30:72:35 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:73:29:73:34 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:78:23:78:28 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:79:23:79:28 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:80:22:80:27 | filter | -| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:81:18:81:25 | pipeline | +| mongoDB.go:40:20:40:30 | call to Referer | mongoDB.go:42:28:42:41 | untrustedInput | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:50:34:50:39 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:61:27:61:32 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:63:23:63:28 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:64:22:64:27 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:66:32:66:37 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:69:17:69:22 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:70:20:70:25 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:71:29:71:34 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:72:30:72:35 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:73:29:73:34 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:78:23:78:28 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:79:23:79:28 | filter | +| mongoDB.go:42:19:42:42 | struct literal | mongoDB.go:80:22:80:27 | filter | +| mongoDB.go:42:28:42:41 | untrustedInput | mongoDB.go:42:19:42:42 | struct literal | +| mongoDB.go:50:23:50:40 | struct literal | mongoDB.go:57:22:57:29 | pipeline | +| mongoDB.go:50:23:50:40 | struct literal | mongoDB.go:81:18:81:25 | pipeline | +| mongoDB.go:50:34:50:39 | filter | mongoDB.go:50:23:50:40 | struct literal | nodes | SqlInjection.go:10:7:11:30 | call to Sprintf | semmle.label | call to Sprintf | | SqlInjection.go:11:3:11:9 | selection of URL | semmle.label | selection of URL | @@ -163,6 +167,10 @@ nodes | main.go:60:5:60:15 | RequestData [pointer, Category] | semmle.label | RequestData [pointer, Category] | | main.go:61:11:61:11 | q | semmle.label | q | | mongoDB.go:40:20:40:30 | call to Referer | semmle.label | call to Referer | +| mongoDB.go:42:19:42:42 | struct literal | semmle.label | struct literal | +| mongoDB.go:42:28:42:41 | untrustedInput | semmle.label | untrustedInput | +| mongoDB.go:50:23:50:40 | struct literal | semmle.label | struct literal | +| mongoDB.go:50:34:50:39 | filter | semmle.label | filter | | mongoDB.go:57:22:57:29 | pipeline | semmle.label | pipeline | | mongoDB.go:61:27:61:32 | filter | semmle.label | filter | | mongoDB.go:63:23:63:28 | filter | semmle.label | filter | diff --git a/go/ql/test/query-tests/Security/CWE-190/AllocationSizeOverflow.expected b/go/ql/test/query-tests/Security/CWE-190/AllocationSizeOverflow.expected index 4dd0a7d7cf4..036c11ff51e 100644 --- a/go/ql/test/query-tests/Security/CWE-190/AllocationSizeOverflow.expected +++ b/go/ql/test/query-tests/Security/CWE-190/AllocationSizeOverflow.expected @@ -1,33 +1,53 @@ edges -| AllocationSizeOverflow.go:6:2:6:33 | ... := ...[0] | AllocationSizeOverflow.go:10:10:10:22 | call to len | -| tst2.go:9:2:9:37 | ... := ...[0] | tst2.go:10:22:10:30 | call to len | -| tst2.go:14:2:14:29 | ... := ...[0] | tst2.go:15:22:15:30 | call to len | -| tst3.go:6:2:6:31 | ... := ...[0] | tst3.go:7:22:7:34 | call to len | -| tst3.go:6:2:6:31 | ... := ...[0] | tst3.go:24:16:24:28 | call to len | -| tst3.go:6:2:6:31 | ... := ...[0] | tst3.go:32:16:32:28 | call to len | -| tst.go:14:2:14:30 | ... = ...[0] | tst.go:15:22:15:34 | call to len | -| tst.go:20:2:20:31 | ... = ...[0] | tst.go:21:22:21:34 | call to len | -| tst.go:26:2:26:31 | ... = ...[0] | tst.go:27:26:27:38 | call to len | -| tst.go:34:2:34:30 | ... = ...[0] | tst.go:35:22:35:34 | call to len | +| AllocationSizeOverflow.go:6:2:6:33 | ... := ...[0] | AllocationSizeOverflow.go:10:14:10:21 | jsonData | +| AllocationSizeOverflow.go:10:14:10:21 | jsonData | AllocationSizeOverflow.go:10:10:10:22 | call to len | +| tst2.go:9:2:9:37 | ... := ...[0] | tst2.go:10:26:10:29 | data | +| tst2.go:10:26:10:29 | data | tst2.go:10:22:10:30 | call to len | +| tst2.go:14:2:14:29 | ... := ...[0] | tst2.go:15:26:15:29 | data | +| tst2.go:15:26:15:29 | data | tst2.go:15:22:15:30 | call to len | +| tst3.go:6:2:6:31 | ... := ...[0] | tst3.go:7:26:7:33 | jsonData | +| tst3.go:6:2:6:31 | ... := ...[0] | tst3.go:24:20:24:27 | jsonData | +| tst3.go:6:2:6:31 | ... := ...[0] | tst3.go:32:20:32:27 | jsonData | +| tst3.go:7:26:7:33 | jsonData | tst3.go:7:22:7:34 | call to len | +| tst3.go:24:20:24:27 | jsonData | tst3.go:24:16:24:28 | call to len | +| tst3.go:32:20:32:27 | jsonData | tst3.go:32:16:32:28 | call to len | +| tst.go:14:2:14:30 | ... = ...[0] | tst.go:15:26:15:33 | jsonData | +| tst.go:15:26:15:33 | jsonData | tst.go:15:22:15:34 | call to len | +| tst.go:20:2:20:31 | ... = ...[0] | tst.go:21:26:21:33 | jsonData | +| tst.go:21:26:21:33 | jsonData | tst.go:21:22:21:34 | call to len | +| tst.go:26:2:26:31 | ... = ...[0] | tst.go:27:30:27:37 | jsonData | +| tst.go:27:30:27:37 | jsonData | tst.go:27:26:27:38 | call to len | +| tst.go:34:2:34:30 | ... = ...[0] | tst.go:35:26:35:33 | jsonData | +| tst.go:35:26:35:33 | jsonData | tst.go:35:22:35:34 | call to len | nodes | AllocationSizeOverflow.go:6:2:6:33 | ... := ...[0] | semmle.label | ... := ...[0] | | AllocationSizeOverflow.go:10:10:10:22 | call to len | semmle.label | call to len | +| AllocationSizeOverflow.go:10:14:10:21 | jsonData | semmle.label | jsonData | | tst2.go:9:2:9:37 | ... := ...[0] | semmle.label | ... := ...[0] | | tst2.go:10:22:10:30 | call to len | semmle.label | call to len | +| tst2.go:10:26:10:29 | data | semmle.label | data | | tst2.go:14:2:14:29 | ... := ...[0] | semmle.label | ... := ...[0] | | tst2.go:15:22:15:30 | call to len | semmle.label | call to len | +| tst2.go:15:26:15:29 | data | semmle.label | data | | tst3.go:6:2:6:31 | ... := ...[0] | semmle.label | ... := ...[0] | | tst3.go:7:22:7:34 | call to len | semmle.label | call to len | +| tst3.go:7:26:7:33 | jsonData | semmle.label | jsonData | | tst3.go:24:16:24:28 | call to len | semmle.label | call to len | +| tst3.go:24:20:24:27 | jsonData | semmle.label | jsonData | | tst3.go:32:16:32:28 | call to len | semmle.label | call to len | +| tst3.go:32:20:32:27 | jsonData | semmle.label | jsonData | | tst.go:14:2:14:30 | ... = ...[0] | semmle.label | ... = ...[0] | | tst.go:15:22:15:34 | call to len | semmle.label | call to len | +| tst.go:15:26:15:33 | jsonData | semmle.label | jsonData | | tst.go:20:2:20:31 | ... = ...[0] | semmle.label | ... = ...[0] | | tst.go:21:22:21:34 | call to len | semmle.label | call to len | +| tst.go:21:26:21:33 | jsonData | semmle.label | jsonData | | tst.go:26:2:26:31 | ... = ...[0] | semmle.label | ... = ...[0] | | tst.go:27:26:27:38 | call to len | semmle.label | call to len | +| tst.go:27:30:27:37 | jsonData | semmle.label | jsonData | | tst.go:34:2:34:30 | ... = ...[0] | semmle.label | ... = ...[0] | | tst.go:35:22:35:34 | call to len | semmle.label | call to len | +| tst.go:35:26:35:33 | jsonData | semmle.label | jsonData | subpaths #select | AllocationSizeOverflow.go:10:10:10:22 | call to len | AllocationSizeOverflow.go:6:2:6:33 | ... := ...[0] | AllocationSizeOverflow.go:10:10:10:22 | call to len | This operation, which is used in an $@, involves a $@ and might overflow. | AllocationSizeOverflow.go:11:25:11:28 | size | allocation | AllocationSizeOverflow.go:6:2:6:33 | ... := ...[0] | potentially large value | diff --git a/go/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected b/go/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected index d6ee7c04597..a835609db64 100644 --- a/go/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected +++ b/go/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected @@ -1,29 +1,46 @@ edges -| klog.go:20:30:20:37 | selection of Header | klog.go:22:15:22:20 | header | +| klog.go:20:3:25:3 | range statement[1] | klog.go:20:13:20:19 | definition of headers | +| klog.go:20:13:20:19 | definition of headers | klog.go:21:27:21:33 | headers | +| klog.go:20:30:20:37 | selection of Header | klog.go:20:3:25:3 | range statement[1] | +| klog.go:21:4:24:4 | range statement[1] | klog.go:21:11:21:16 | definition of header | +| klog.go:21:11:21:16 | definition of header | klog.go:22:15:22:20 | header | +| klog.go:21:27:21:33 | headers | klog.go:21:4:24:4 | range statement[1] | | klog.go:28:13:28:20 | selection of Header | klog.go:28:13:28:41 | call to Get | -| main.go:21:19:21:26 | password | main.go:22:29:22:34 | fields | +| main.go:20:2:20:7 | definition of fields | main.go:22:29:22:34 | fields | +| main.go:21:19:21:26 | password | main.go:20:2:20:7 | definition of fields | | overrides.go:9:9:9:16 | password | overrides.go:13:14:13:23 | call to String | | passwords.go:8:12:8:12 | definition of x | passwords.go:9:14:9:14 | x | | passwords.go:30:8:30:15 | password | passwords.go:8:12:8:12 | definition of x | | passwords.go:34:28:34:35 | password | passwords.go:34:14:34:35 | ...+... | -| passwords.go:37:13:37:13 | x | passwords.go:39:14:39:17 | obj1 | -| passwords.go:42:6:42:13 | password | passwords.go:44:14:44:17 | obj2 | -| passwords.go:48:11:48:18 | password | passwords.go:47:14:47:17 | obj3 | -| passwords.go:86:16:86:36 | call to make | passwords.go:88:14:88:26 | utilityObject | -| passwords.go:90:12:90:19 | password | passwords.go:91:23:91:28 | secret | +| passwords.go:36:2:36:5 | definition of obj1 | passwords.go:39:14:39:17 | obj1 | +| passwords.go:36:10:38:2 | struct literal | passwords.go:36:2:36:5 | definition of obj1 | +| passwords.go:37:13:37:13 | x | passwords.go:36:10:38:2 | struct literal | +| passwords.go:41:2:41:5 | definition of obj2 | passwords.go:44:14:44:17 | obj2 | +| passwords.go:41:10:43:2 | struct literal | passwords.go:41:2:41:5 | definition of obj2 | +| passwords.go:42:6:42:13 | password | passwords.go:41:10:43:2 | struct literal | +| passwords.go:46:6:46:9 | definition of obj3 | passwords.go:47:14:47:17 | obj3 | +| passwords.go:48:11:48:18 | password | passwords.go:46:6:46:9 | definition of obj3 | +| passwords.go:85:2:85:14 | definition of utilityObject | passwords.go:88:14:88:26 | utilityObject | +| passwords.go:85:19:87:2 | struct literal | passwords.go:85:2:85:14 | definition of utilityObject | +| passwords.go:86:16:86:36 | call to make | passwords.go:85:19:87:2 | struct literal | +| passwords.go:90:2:90:7 | definition of secret | passwords.go:91:23:91:28 | secret | +| passwords.go:90:12:90:19 | password | passwords.go:90:2:90:7 | definition of secret | | passwords.go:101:33:101:40 | password | passwords.go:101:15:101:40 | ...+... | | passwords.go:107:34:107:41 | password | passwords.go:107:16:107:41 | ...+... | | passwords.go:112:33:112:40 | password | passwords.go:112:15:112:40 | ...+... | -| passwords.go:116:28:116:36 | password1 | passwords.go:116:14:116:45 | ...+... | | passwords.go:116:28:116:36 | password1 | passwords.go:116:28:116:45 | call to String | | passwords.go:116:28:116:45 | call to String | passwords.go:116:14:116:45 | ...+... | -| passwords.go:118:12:123:2 | struct literal [x] | passwords.go:126:14:126:19 | config [x] | -| passwords.go:118:12:123:2 | struct literal [y] | passwords.go:127:14:127:19 | config [y] | -| passwords.go:119:13:119:13 | x | passwords.go:125:14:125:19 | config | +| passwords.go:118:2:118:7 | definition of config | passwords.go:125:14:125:19 | config | +| passwords.go:118:2:118:7 | definition of config [x] | passwords.go:126:14:126:19 | config [x] | +| passwords.go:118:2:118:7 | definition of config [y] | passwords.go:127:14:127:19 | config [y] | +| passwords.go:118:12:123:2 | struct literal | passwords.go:118:2:118:7 | definition of config | +| passwords.go:118:12:123:2 | struct literal [x] | passwords.go:118:2:118:7 | definition of config [x] | +| passwords.go:118:12:123:2 | struct literal [y] | passwords.go:118:2:118:7 | definition of config [y] | +| passwords.go:119:13:119:13 | x | passwords.go:118:12:123:2 | struct literal | +| passwords.go:121:13:121:20 | password | passwords.go:118:12:123:2 | struct literal | | passwords.go:121:13:121:20 | password | passwords.go:118:12:123:2 | struct literal [x] | -| passwords.go:121:13:121:20 | password | passwords.go:125:14:125:19 | config | +| passwords.go:122:13:122:25 | call to getPassword | passwords.go:118:12:123:2 | struct literal | | passwords.go:122:13:122:25 | call to getPassword | passwords.go:118:12:123:2 | struct literal [y] | -| passwords.go:122:13:122:25 | call to getPassword | passwords.go:125:14:125:19 | config | | passwords.go:126:14:126:19 | config [x] | passwords.go:126:14:126:21 | selection of x | | passwords.go:127:14:127:19 | config [y] | passwords.go:127:14:127:21 | selection of y | | protobuf.go:11:2:11:6 | definition of query [pointer, Description] | protobuf.go:12:2:12:6 | query [pointer, Description] | @@ -38,13 +55,19 @@ edges | protos/query/query.pb.go:119:10:119:10 | x [pointer, Description] | protos/query/query.pb.go:119:10:119:10 | implicit dereference [Description] | | util.go:16:9:16:18 | selection of password | passwords.go:28:14:28:28 | call to getPassword | nodes +| klog.go:20:3:25:3 | range statement[1] | semmle.label | range statement[1] | +| klog.go:20:13:20:19 | definition of headers | semmle.label | definition of headers | | klog.go:20:30:20:37 | selection of Header | semmle.label | selection of Header | +| klog.go:21:4:24:4 | range statement[1] | semmle.label | range statement[1] | +| klog.go:21:11:21:16 | definition of header | semmle.label | definition of header | +| klog.go:21:27:21:33 | headers | semmle.label | headers | | klog.go:22:15:22:20 | header | semmle.label | header | | klog.go:28:13:28:20 | selection of Header | semmle.label | selection of Header | | klog.go:28:13:28:41 | call to Get | semmle.label | call to Get | | main.go:15:14:15:21 | password | semmle.label | password | | main.go:17:12:17:19 | password | semmle.label | password | | main.go:18:17:18:24 | password | semmle.label | password | +| main.go:20:2:20:7 | definition of fields | semmle.label | definition of fields | | main.go:21:19:21:26 | password | semmle.label | password | | main.go:22:29:22:34 | fields | semmle.label | fields | | main.go:25:35:25:42 | password | semmle.label | password | @@ -60,15 +83,23 @@ nodes | passwords.go:32:12:32:19 | password | semmle.label | password | | passwords.go:34:14:34:35 | ...+... | semmle.label | ...+... | | passwords.go:34:28:34:35 | password | semmle.label | password | +| passwords.go:36:2:36:5 | definition of obj1 | semmle.label | definition of obj1 | +| passwords.go:36:10:38:2 | struct literal | semmle.label | struct literal | | passwords.go:37:13:37:13 | x | semmle.label | x | | passwords.go:39:14:39:17 | obj1 | semmle.label | obj1 | +| passwords.go:41:2:41:5 | definition of obj2 | semmle.label | definition of obj2 | +| passwords.go:41:10:43:2 | struct literal | semmle.label | struct literal | | passwords.go:42:6:42:13 | password | semmle.label | password | | passwords.go:44:14:44:17 | obj2 | semmle.label | obj2 | +| passwords.go:46:6:46:9 | definition of obj3 | semmle.label | definition of obj3 | | passwords.go:47:14:47:17 | obj3 | semmle.label | obj3 | | passwords.go:48:11:48:18 | password | semmle.label | password | | passwords.go:51:14:51:27 | fixed_password | semmle.label | fixed_password | +| passwords.go:85:2:85:14 | definition of utilityObject | semmle.label | definition of utilityObject | +| passwords.go:85:19:87:2 | struct literal | semmle.label | struct literal | | passwords.go:86:16:86:36 | call to make | semmle.label | call to make | | passwords.go:88:14:88:26 | utilityObject | semmle.label | utilityObject | +| passwords.go:90:2:90:7 | definition of secret | semmle.label | definition of secret | | passwords.go:90:12:90:19 | password | semmle.label | password | | passwords.go:91:23:91:28 | secret | semmle.label | secret | | passwords.go:101:15:101:40 | ...+... | semmle.label | ...+... | @@ -80,6 +111,10 @@ nodes | passwords.go:116:14:116:45 | ...+... | semmle.label | ...+... | | passwords.go:116:28:116:36 | password1 | semmle.label | password1 | | passwords.go:116:28:116:45 | call to String | semmle.label | call to String | +| passwords.go:118:2:118:7 | definition of config | semmle.label | definition of config | +| passwords.go:118:2:118:7 | definition of config [x] | semmle.label | definition of config [x] | +| passwords.go:118:2:118:7 | definition of config [y] | semmle.label | definition of config [y] | +| passwords.go:118:12:123:2 | struct literal | semmle.label | struct literal | | passwords.go:118:12:123:2 | struct literal [x] | semmle.label | struct literal [x] | | passwords.go:118:12:123:2 | struct literal [y] | semmle.label | struct literal [y] | | passwords.go:119:13:119:13 | x | semmle.label | x | diff --git a/go/ql/test/query-tests/Security/CWE-352/ConstantOauth2State.expected b/go/ql/test/query-tests/Security/CWE-352/ConstantOauth2State.expected index 35b84bf249a..9ef4c6d850e 100644 --- a/go/ql/test/query-tests/Security/CWE-352/ConstantOauth2State.expected +++ b/go/ql/test/query-tests/Security/CWE-352/ConstantOauth2State.expected @@ -11,15 +11,6 @@ edges | ConstantOauth2State.go:22:22:22:28 | "state" | ConstantOauth2State.go:65:26:65:39 | stateStringVar | | ConstantOauth2State.go:80:11:80:25 | call to newFixedState | ConstantOauth2State.go:81:26:81:30 | state | | ConstantOauth2State.go:86:9:86:15 | "state" | ConstantOauth2State.go:80:11:80:25 | call to newFixedState | -| ConstantOauth2State.go:147:9:147:42 | call to AuthCodeURL | ConstantOauth2State.go:148:54:148:56 | url | -| ConstantOauth2State.go:169:9:169:42 | call to AuthCodeURL | ConstantOauth2State.go:170:54:170:56 | url | -| ConstantOauth2State.go:191:9:191:42 | call to AuthCodeURL | ConstantOauth2State.go:192:54:192:56 | url | -| ConstantOauth2State.go:210:9:210:42 | call to AuthCodeURL | ConstantOauth2State.go:211:54:211:56 | url | -| ConstantOauth2State.go:232:9:232:42 | call to AuthCodeURL | ConstantOauth2State.go:233:28:233:30 | url | -| ConstantOauth2State.go:239:17:239:39 | "http://localhost:8080" | ConstantOauth2State.go:249:9:249:12 | conf | -| ConstantOauth2State.go:256:17:256:67 | call to Sprintf | ConstantOauth2State.go:266:9:266:12 | conf | -| ConstantOauth2State.go:256:38:256:60 | "http://localhost:8080" | ConstantOauth2State.go:256:17:256:67 | call to Sprintf | -| ConstantOauth2State.go:272:17:272:21 | "oob" | ConstantOauth2State.go:282:9:282:12 | conf | nodes | ConstantOauth2State.go:20:26:20:32 | "state" | semmle.label | "state" | | ConstantOauth2State.go:22:22:22:28 | "state" | semmle.label | "state" | @@ -29,30 +20,13 @@ nodes | ConstantOauth2State.go:80:11:80:25 | call to newFixedState | semmle.label | call to newFixedState | | ConstantOauth2State.go:81:26:81:30 | state | semmle.label | state | | ConstantOauth2State.go:86:9:86:15 | "state" | semmle.label | "state" | -| ConstantOauth2State.go:147:9:147:42 | call to AuthCodeURL | semmle.label | call to AuthCodeURL | | ConstantOauth2State.go:147:26:147:41 | stateStringConst | semmle.label | stateStringConst | -| ConstantOauth2State.go:148:54:148:56 | url | semmle.label | url | -| ConstantOauth2State.go:169:9:169:42 | call to AuthCodeURL | semmle.label | call to AuthCodeURL | | ConstantOauth2State.go:169:26:169:41 | stateStringConst | semmle.label | stateStringConst | -| ConstantOauth2State.go:170:54:170:56 | url | semmle.label | url | -| ConstantOauth2State.go:191:9:191:42 | call to AuthCodeURL | semmle.label | call to AuthCodeURL | | ConstantOauth2State.go:191:26:191:41 | stateStringConst | semmle.label | stateStringConst | -| ConstantOauth2State.go:192:54:192:56 | url | semmle.label | url | -| ConstantOauth2State.go:210:9:210:42 | call to AuthCodeURL | semmle.label | call to AuthCodeURL | | ConstantOauth2State.go:210:26:210:41 | stateStringConst | semmle.label | stateStringConst | -| ConstantOauth2State.go:211:54:211:56 | url | semmle.label | url | -| ConstantOauth2State.go:232:9:232:42 | call to AuthCodeURL | semmle.label | call to AuthCodeURL | | ConstantOauth2State.go:232:26:232:41 | stateStringConst | semmle.label | stateStringConst | -| ConstantOauth2State.go:233:28:233:30 | url | semmle.label | url | -| ConstantOauth2State.go:239:17:239:39 | "http://localhost:8080" | semmle.label | "http://localhost:8080" | -| ConstantOauth2State.go:249:9:249:12 | conf | semmle.label | conf | | ConstantOauth2State.go:249:26:249:41 | stateStringConst | semmle.label | stateStringConst | -| ConstantOauth2State.go:256:17:256:67 | call to Sprintf | semmle.label | call to Sprintf | -| ConstantOauth2State.go:256:38:256:60 | "http://localhost:8080" | semmle.label | "http://localhost:8080" | -| ConstantOauth2State.go:266:9:266:12 | conf | semmle.label | conf | | ConstantOauth2State.go:266:26:266:41 | stateStringConst | semmle.label | stateStringConst | -| ConstantOauth2State.go:272:17:272:21 | "oob" | semmle.label | "oob" | -| ConstantOauth2State.go:282:9:282:12 | conf | semmle.label | conf | | ConstantOauth2State.go:282:26:282:41 | stateStringConst | semmle.label | stateStringConst | subpaths #select diff --git a/go/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect/OpenUrlRedirect.expected b/go/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect/OpenUrlRedirect.expected index 2b12a344d17..d56e1f9c68b 100644 --- a/go/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect/OpenUrlRedirect.expected +++ b/go/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect/OpenUrlRedirect.expected @@ -1,151 +1,136 @@ edges | OpenUrlRedirect.go:10:23:10:28 | selection of Form | OpenUrlRedirect.go:10:23:10:42 | call to Get | +| stdlib.go:13:3:13:8 | definition of target | stdlib.go:15:30:15:35 | target | | stdlib.go:13:13:13:18 | selection of Form | stdlib.go:13:13:13:32 | call to Get | -| stdlib.go:13:13:13:18 | selection of Form | stdlib.go:15:30:15:35 | target | -| stdlib.go:13:13:13:32 | call to Get | stdlib.go:15:30:15:35 | target | +| stdlib.go:13:13:13:32 | call to Get | stdlib.go:13:3:13:8 | definition of target | +| stdlib.go:22:3:22:8 | definition of target | stdlib.go:24:30:24:35 | target | | stdlib.go:22:13:22:18 | selection of Form | stdlib.go:22:13:22:32 | call to Get | -| stdlib.go:22:13:22:18 | selection of Form | stdlib.go:24:30:24:35 | target | -| stdlib.go:22:13:22:32 | call to Get | stdlib.go:24:30:24:35 | target | +| stdlib.go:22:13:22:32 | call to Get | stdlib.go:22:3:22:8 | definition of target | +| stdlib.go:31:3:31:8 | definition of target | stdlib.go:35:34:35:39 | target | | stdlib.go:31:13:31:18 | selection of Form | stdlib.go:31:13:31:32 | call to Get | -| stdlib.go:31:13:31:18 | selection of Form | stdlib.go:35:30:35:39 | ...+... | -| stdlib.go:31:13:31:32 | call to Get | stdlib.go:35:30:35:39 | ...+... | +| stdlib.go:31:13:31:32 | call to Get | stdlib.go:31:3:31:8 | definition of target | +| stdlib.go:35:34:35:39 | target | stdlib.go:35:30:35:39 | ...+... | +| stdlib.go:44:3:44:8 | definition of target | stdlib.go:46:23:46:28 | target | | stdlib.go:44:13:44:18 | selection of Form | stdlib.go:44:13:44:32 | call to Get | -| stdlib.go:44:13:44:18 | selection of Form | stdlib.go:46:23:46:28 | target | -| stdlib.go:44:13:44:32 | call to Get | stdlib.go:46:23:46:28 | target | +| stdlib.go:44:13:44:32 | call to Get | stdlib.go:44:3:44:8 | definition of target | +| stdlib.go:64:3:64:8 | definition of target | stdlib.go:67:23:67:28 | target | | stdlib.go:64:13:64:18 | selection of Form | stdlib.go:64:13:64:32 | call to Get | -| stdlib.go:64:13:64:18 | selection of Form | stdlib.go:67:23:67:40 | ...+... | -| stdlib.go:64:13:64:32 | call to Get | stdlib.go:67:23:67:40 | ...+... | +| stdlib.go:64:13:64:32 | call to Get | stdlib.go:64:3:64:8 | definition of target | +| stdlib.go:67:23:67:28 | target | stdlib.go:67:23:67:37 | ...+... | +| stdlib.go:67:23:67:37 | ...+... | stdlib.go:67:23:67:40 | ...+... | +| stdlib.go:89:3:89:8 | definition of target | stdlib.go:90:3:90:8 | target | | stdlib.go:89:13:89:18 | selection of Form | stdlib.go:89:13:89:32 | call to Get | -| stdlib.go:89:13:89:18 | selection of Form | stdlib.go:92:23:92:28 | target | -| stdlib.go:89:13:89:32 | call to Get | stdlib.go:92:23:92:28 | target | -| stdlib.go:107:54:107:54 | definition of r [pointer, URL, pointer] | stdlib.go:112:4:112:4 | r [pointer, URL, pointer] | +| stdlib.go:89:13:89:32 | call to Get | stdlib.go:89:3:89:8 | definition of target | +| stdlib.go:90:3:90:8 | definition of target | stdlib.go:92:23:92:28 | target | +| stdlib.go:90:3:90:8 | target | stdlib.go:90:3:90:25 | ... += ... | +| stdlib.go:90:3:90:25 | ... += ... | stdlib.go:90:3:90:8 | definition of target | | stdlib.go:107:54:107:54 | definition of r [pointer, URL, pointer] | stdlib.go:112:4:112:4 | r [pointer, URL, pointer] | | stdlib.go:107:54:107:54 | definition of r [pointer, URL] | stdlib.go:112:4:112:4 | r [pointer, URL] | -| stdlib.go:107:54:107:54 | definition of r [pointer, URL] | stdlib.go:112:4:112:4 | r [pointer, URL] | -| stdlib.go:107:54:107:54 | definition of r [pointer, URL] | stdlib.go:113:24:113:24 | r [pointer, URL] | | stdlib.go:107:54:107:54 | definition of r [pointer, URL] | stdlib.go:113:24:113:24 | r [pointer, URL] | | stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | stdlib.go:107:54:107:54 | definition of r [pointer, URL, pointer] | -| stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | stdlib.go:107:54:107:54 | definition of r [pointer, URL, pointer] | -| stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | stdlib.go:112:4:112:8 | selection of URL [pointer] | | stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | stdlib.go:112:4:112:8 | selection of URL [pointer] | | stdlib.go:112:4:112:4 | implicit dereference [URL] | stdlib.go:107:54:107:54 | definition of r [pointer, URL] | -| stdlib.go:112:4:112:4 | implicit dereference [URL] | stdlib.go:107:54:107:54 | definition of r [pointer, URL] | -| stdlib.go:112:4:112:4 | implicit dereference [URL] | stdlib.go:112:4:112:8 | selection of URL | | stdlib.go:112:4:112:4 | implicit dereference [URL] | stdlib.go:112:4:112:8 | selection of URL | | stdlib.go:112:4:112:4 | r [pointer, URL, pointer] | stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | -| stdlib.go:112:4:112:4 | r [pointer, URL, pointer] | stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | | stdlib.go:112:4:112:4 | r [pointer, URL] | stdlib.go:112:4:112:4 | implicit dereference [URL] | -| stdlib.go:112:4:112:4 | r [pointer, URL] | stdlib.go:112:4:112:4 | implicit dereference [URL] | -| stdlib.go:112:4:112:8 | implicit dereference | stdlib.go:112:4:112:8 | implicit dereference | -| stdlib.go:112:4:112:8 | implicit dereference | stdlib.go:112:4:112:8 | implicit dereference | -| stdlib.go:112:4:112:8 | implicit dereference | stdlib.go:112:4:112:8 | selection of URL | | stdlib.go:112:4:112:8 | implicit dereference | stdlib.go:112:4:112:8 | selection of URL | | stdlib.go:112:4:112:8 | implicit dereference | stdlib.go:112:4:112:8 | selection of URL [pointer] | -| stdlib.go:112:4:112:8 | implicit dereference | stdlib.go:112:4:112:8 | selection of URL [pointer] | -| stdlib.go:112:4:112:8 | selection of URL | stdlib.go:112:4:112:4 | implicit dereference [URL] | | stdlib.go:112:4:112:8 | selection of URL | stdlib.go:112:4:112:4 | implicit dereference [URL] | | stdlib.go:112:4:112:8 | selection of URL | stdlib.go:112:4:112:8 | implicit dereference | -| stdlib.go:112:4:112:8 | selection of URL | stdlib.go:112:4:112:8 | implicit dereference | -| stdlib.go:112:4:112:8 | selection of URL | stdlib.go:112:4:112:8 | selection of URL | -| stdlib.go:112:4:112:8 | selection of URL | stdlib.go:112:4:112:8 | selection of URL | -| stdlib.go:112:4:112:8 | selection of URL [pointer] | stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | | stdlib.go:112:4:112:8 | selection of URL [pointer] | stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | | stdlib.go:112:4:112:8 | selection of URL [pointer] | stdlib.go:112:4:112:8 | implicit dereference | -| stdlib.go:112:4:112:8 | selection of URL [pointer] | stdlib.go:112:4:112:8 | implicit dereference | -| stdlib.go:113:24:113:24 | implicit dereference [URL] | stdlib.go:113:24:113:28 | selection of URL | | stdlib.go:113:24:113:24 | implicit dereference [URL] | stdlib.go:113:24:113:28 | selection of URL | | stdlib.go:113:24:113:24 | r [pointer, URL] | stdlib.go:113:24:113:24 | implicit dereference [URL] | -| stdlib.go:113:24:113:24 | r [pointer, URL] | stdlib.go:113:24:113:24 | implicit dereference [URL] | -| stdlib.go:113:24:113:28 | selection of URL | stdlib.go:113:24:113:37 | call to String | | stdlib.go:113:24:113:28 | selection of URL | stdlib.go:113:24:113:37 | call to String | +| stdlib.go:146:3:146:8 | definition of target | stdlib.go:152:3:152:3 | target = phi(def@146:3, def@149:4) | | stdlib.go:146:13:146:18 | selection of Form | stdlib.go:146:13:146:32 | call to Get | -| stdlib.go:146:13:146:18 | selection of Form | stdlib.go:152:23:152:28 | target | -| stdlib.go:146:13:146:32 | call to Get | stdlib.go:152:23:152:28 | target | -| stdlib.go:159:11:159:15 | selection of URL | stdlib.go:162:24:162:26 | url | -| stdlib.go:159:11:159:15 | selection of URL | stdlib.go:162:24:162:26 | url | -| stdlib.go:159:11:159:15 | selection of URL | stdlib.go:162:24:162:35 | call to String | +| stdlib.go:146:13:146:32 | call to Get | stdlib.go:146:3:146:8 | definition of target | +| stdlib.go:152:3:152:3 | target = phi(def@146:3, def@149:4) | stdlib.go:152:23:152:28 | target | +| stdlib.go:159:3:159:5 | definition of url | stdlib.go:162:24:162:26 | url | +| stdlib.go:159:10:159:15 | star expression | stdlib.go:159:3:159:5 | definition of url | +| stdlib.go:159:10:159:15 | star expression | stdlib.go:159:11:159:15 | selection of URL | +| stdlib.go:159:11:159:15 | selection of URL | stdlib.go:159:10:159:15 | star expression | | stdlib.go:162:24:162:26 | url | stdlib.go:162:24:162:35 | call to String | -| stdlib.go:162:24:162:26 | url | stdlib.go:162:24:162:35 | call to String | -| stdlib.go:173:35:173:39 | selection of URL | stdlib.go:173:24:173:52 | ...+... | -| stdlib.go:173:35:173:39 | selection of URL | stdlib.go:173:35:173:52 | call to RequestURI | | stdlib.go:173:35:173:39 | selection of URL | stdlib.go:173:35:173:52 | call to RequestURI | | stdlib.go:173:35:173:52 | call to RequestURI | stdlib.go:173:24:173:52 | ...+... | -| stdlib.go:173:35:173:52 | call to RequestURI | stdlib.go:173:24:173:52 | ...+... | -| stdlib.go:182:13:182:33 | call to FormValue | stdlib.go:184:23:184:28 | target | -| stdlib.go:190:3:190:57 | ... := ...[0] | stdlib.go:192:23:192:33 | selection of Path | -| stdlib.go:190:3:190:57 | ... := ...[0] | stdlib.go:194:23:194:28 | target | -| stdlib.go:190:3:190:57 | ... := ...[0] | stdlib.go:194:23:194:42 | call to EscapedPath | +| stdlib.go:182:3:182:8 | definition of target | stdlib.go:184:23:184:28 | target | +| stdlib.go:182:13:182:33 | call to FormValue | stdlib.go:182:3:182:8 | definition of target | +| stdlib.go:190:3:190:8 | definition of target | stdlib.go:192:23:192:28 | target | +| stdlib.go:190:3:190:8 | definition of target | stdlib.go:194:23:194:28 | target | +| stdlib.go:190:3:190:57 | ... := ...[0] | stdlib.go:190:3:190:8 | definition of target | | stdlib.go:190:36:190:56 | call to FormValue | stdlib.go:190:3:190:57 | ... := ...[0] | -| stdlib.go:190:36:190:56 | call to FormValue | stdlib.go:192:23:192:33 | selection of Path | -| stdlib.go:190:36:190:56 | call to FormValue | stdlib.go:194:23:194:28 | target | -| stdlib.go:190:36:190:56 | call to FormValue | stdlib.go:194:23:194:42 | call to EscapedPath | +| stdlib.go:192:23:192:28 | implicit dereference | stdlib.go:190:3:190:8 | definition of target | +| stdlib.go:192:23:192:28 | implicit dereference | stdlib.go:192:23:192:33 | selection of Path | +| stdlib.go:192:23:192:28 | target | stdlib.go:192:23:192:28 | implicit dereference | +| stdlib.go:192:23:192:28 | target | stdlib.go:192:23:192:33 | selection of Path | | stdlib.go:194:23:194:28 | target | stdlib.go:194:23:194:42 | call to EscapedPath | nodes | OpenUrlRedirect.go:10:23:10:28 | selection of Form | semmle.label | selection of Form | | OpenUrlRedirect.go:10:23:10:42 | call to Get | semmle.label | call to Get | +| stdlib.go:13:3:13:8 | definition of target | semmle.label | definition of target | | stdlib.go:13:13:13:18 | selection of Form | semmle.label | selection of Form | | stdlib.go:13:13:13:32 | call to Get | semmle.label | call to Get | | stdlib.go:15:30:15:35 | target | semmle.label | target | +| stdlib.go:22:3:22:8 | definition of target | semmle.label | definition of target | | stdlib.go:22:13:22:18 | selection of Form | semmle.label | selection of Form | | stdlib.go:22:13:22:32 | call to Get | semmle.label | call to Get | | stdlib.go:24:30:24:35 | target | semmle.label | target | +| stdlib.go:31:3:31:8 | definition of target | semmle.label | definition of target | | stdlib.go:31:13:31:18 | selection of Form | semmle.label | selection of Form | | stdlib.go:31:13:31:32 | call to Get | semmle.label | call to Get | | stdlib.go:35:30:35:39 | ...+... | semmle.label | ...+... | +| stdlib.go:35:34:35:39 | target | semmle.label | target | +| stdlib.go:44:3:44:8 | definition of target | semmle.label | definition of target | | stdlib.go:44:13:44:18 | selection of Form | semmle.label | selection of Form | | stdlib.go:44:13:44:32 | call to Get | semmle.label | call to Get | | stdlib.go:46:23:46:28 | target | semmle.label | target | +| stdlib.go:64:3:64:8 | definition of target | semmle.label | definition of target | | stdlib.go:64:13:64:18 | selection of Form | semmle.label | selection of Form | | stdlib.go:64:13:64:32 | call to Get | semmle.label | call to Get | +| stdlib.go:67:23:67:28 | target | semmle.label | target | +| stdlib.go:67:23:67:37 | ...+... | semmle.label | ...+... | | stdlib.go:67:23:67:40 | ...+... | semmle.label | ...+... | +| stdlib.go:89:3:89:8 | definition of target | semmle.label | definition of target | | stdlib.go:89:13:89:18 | selection of Form | semmle.label | selection of Form | | stdlib.go:89:13:89:32 | call to Get | semmle.label | call to Get | +| stdlib.go:90:3:90:8 | definition of target | semmle.label | definition of target | +| stdlib.go:90:3:90:8 | target | semmle.label | target | +| stdlib.go:90:3:90:25 | ... += ... | semmle.label | ... += ... | | stdlib.go:92:23:92:28 | target | semmle.label | target | | stdlib.go:107:54:107:54 | definition of r [pointer, URL, pointer] | semmle.label | definition of r [pointer, URL, pointer] | -| stdlib.go:107:54:107:54 | definition of r [pointer, URL, pointer] | semmle.label | definition of r [pointer, URL, pointer] | -| stdlib.go:107:54:107:54 | definition of r [pointer, URL] | semmle.label | definition of r [pointer, URL] | | stdlib.go:107:54:107:54 | definition of r [pointer, URL] | semmle.label | definition of r [pointer, URL] | | stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | semmle.label | implicit dereference [URL, pointer] | -| stdlib.go:112:4:112:4 | implicit dereference [URL, pointer] | semmle.label | implicit dereference [URL, pointer] | -| stdlib.go:112:4:112:4 | implicit dereference [URL] | semmle.label | implicit dereference [URL] | | stdlib.go:112:4:112:4 | implicit dereference [URL] | semmle.label | implicit dereference [URL] | | stdlib.go:112:4:112:4 | r [pointer, URL, pointer] | semmle.label | r [pointer, URL, pointer] | -| stdlib.go:112:4:112:4 | r [pointer, URL, pointer] | semmle.label | r [pointer, URL, pointer] | -| stdlib.go:112:4:112:4 | r [pointer, URL] | semmle.label | r [pointer, URL] | | stdlib.go:112:4:112:4 | r [pointer, URL] | semmle.label | r [pointer, URL] | | stdlib.go:112:4:112:8 | implicit dereference | semmle.label | implicit dereference | -| stdlib.go:112:4:112:8 | implicit dereference | semmle.label | implicit dereference | -| stdlib.go:112:4:112:8 | selection of URL | semmle.label | selection of URL | | stdlib.go:112:4:112:8 | selection of URL | semmle.label | selection of URL | | stdlib.go:112:4:112:8 | selection of URL [pointer] | semmle.label | selection of URL [pointer] | -| stdlib.go:112:4:112:8 | selection of URL [pointer] | semmle.label | selection of URL [pointer] | -| stdlib.go:113:24:113:24 | implicit dereference [URL] | semmle.label | implicit dereference [URL] | | stdlib.go:113:24:113:24 | implicit dereference [URL] | semmle.label | implicit dereference [URL] | | stdlib.go:113:24:113:24 | r [pointer, URL] | semmle.label | r [pointer, URL] | -| stdlib.go:113:24:113:24 | r [pointer, URL] | semmle.label | r [pointer, URL] | -| stdlib.go:113:24:113:28 | selection of URL | semmle.label | selection of URL | | stdlib.go:113:24:113:28 | selection of URL | semmle.label | selection of URL | | stdlib.go:113:24:113:37 | call to String | semmle.label | call to String | -| stdlib.go:113:24:113:37 | call to String | semmle.label | call to String | +| stdlib.go:146:3:146:8 | definition of target | semmle.label | definition of target | | stdlib.go:146:13:146:18 | selection of Form | semmle.label | selection of Form | | stdlib.go:146:13:146:32 | call to Get | semmle.label | call to Get | +| stdlib.go:152:3:152:3 | target = phi(def@146:3, def@149:4) | semmle.label | target = phi(def@146:3, def@149:4) | | stdlib.go:152:23:152:28 | target | semmle.label | target | -| stdlib.go:159:11:159:15 | selection of URL | semmle.label | selection of URL | +| stdlib.go:159:3:159:5 | definition of url | semmle.label | definition of url | +| stdlib.go:159:10:159:15 | star expression | semmle.label | star expression | | stdlib.go:159:11:159:15 | selection of URL | semmle.label | selection of URL | | stdlib.go:162:24:162:26 | url | semmle.label | url | -| stdlib.go:162:24:162:26 | url | semmle.label | url | -| stdlib.go:162:24:162:35 | call to String | semmle.label | call to String | | stdlib.go:162:24:162:35 | call to String | semmle.label | call to String | | stdlib.go:173:24:173:52 | ...+... | semmle.label | ...+... | -| stdlib.go:173:24:173:52 | ...+... | semmle.label | ...+... | -| stdlib.go:173:35:173:39 | selection of URL | semmle.label | selection of URL | | stdlib.go:173:35:173:39 | selection of URL | semmle.label | selection of URL | | stdlib.go:173:35:173:52 | call to RequestURI | semmle.label | call to RequestURI | -| stdlib.go:173:35:173:52 | call to RequestURI | semmle.label | call to RequestURI | +| stdlib.go:182:3:182:8 | definition of target | semmle.label | definition of target | | stdlib.go:182:13:182:33 | call to FormValue | semmle.label | call to FormValue | | stdlib.go:184:23:184:28 | target | semmle.label | target | +| stdlib.go:190:3:190:8 | definition of target | semmle.label | definition of target | | stdlib.go:190:3:190:57 | ... := ...[0] | semmle.label | ... := ...[0] | | stdlib.go:190:36:190:56 | call to FormValue | semmle.label | call to FormValue | +| stdlib.go:192:23:192:28 | implicit dereference | semmle.label | implicit dereference | +| stdlib.go:192:23:192:28 | target | semmle.label | target | | stdlib.go:192:23:192:33 | selection of Path | semmle.label | selection of Path | | stdlib.go:194:23:194:28 | target | semmle.label | target | | stdlib.go:194:23:194:42 | call to EscapedPath | semmle.label | call to EscapedPath | diff --git a/go/ql/test/query-tests/Security/CWE-681/IncorrectIntegerConversion.ql b/go/ql/test/query-tests/Security/CWE-681/IncorrectIntegerConversion.ql index 3e7ccd8d8c1..52c9ac35894 100644 --- a/go/ql/test/query-tests/Security/CWE-681/IncorrectIntegerConversion.ql +++ b/go/ql/test/query-tests/Security/CWE-681/IncorrectIntegerConversion.ql @@ -8,7 +8,7 @@ module TestIncorrectIntegerConversion implements TestSig { predicate hasActualResult(Location location, string element, string tag, string value) { tag = "hasValueFlow" and exists(DataFlow::Node sink, DataFlow::Node sinkConverted | - any(ConversionWithoutBoundsCheckConfig config).hasFlowTo(sink) and + Flow::flowTo(sink) and sinkConverted = sink.getASuccessor() | sinkConverted diff --git a/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected index dacc8e4d46f..b843de1c0b1 100644 --- a/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected +++ b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -6,7 +6,17 @@ edges | tst.go:10:13:10:35 | call to FormValue | tst.go:24:66:24:72 | tainted | | tst.go:10:13:10:35 | call to FormValue | tst.go:27:11:27:29 | ...+... | | tst.go:10:13:10:35 | call to FormValue | tst.go:29:11:29:40 | ...+... | -| tst.go:10:13:10:35 | call to FormValue | tst.go:37:11:37:11 | u | +| tst.go:10:13:10:35 | call to FormValue | tst.go:36:11:36:17 | tainted | +| tst.go:35:2:35:2 | definition of u [pointer] | tst.go:36:2:36:2 | u [pointer] | +| tst.go:36:2:36:2 | implicit dereference | tst.go:35:2:35:2 | definition of u [pointer] | +| tst.go:36:2:36:2 | implicit dereference | tst.go:36:2:36:2 | u | +| tst.go:36:2:36:2 | implicit dereference | tst.go:37:11:37:11 | u | +| tst.go:36:2:36:2 | u | tst.go:36:2:36:2 | implicit dereference | +| tst.go:36:2:36:2 | u | tst.go:36:2:36:2 | u | +| tst.go:36:2:36:2 | u | tst.go:37:11:37:11 | u | +| tst.go:36:2:36:2 | u [pointer] | tst.go:36:2:36:2 | implicit dereference | +| tst.go:36:11:36:17 | tainted | tst.go:36:2:36:2 | u | +| tst.go:36:11:36:17 | tainted | tst.go:37:11:37:11 | u | | tst.go:37:11:37:11 | u | tst.go:37:11:37:20 | call to String | | websocket.go:60:21:60:31 | call to Referer | websocket.go:65:27:65:40 | untrustedInput | | websocket.go:74:21:74:31 | call to Referer | websocket.go:78:36:78:49 | untrustedInput | @@ -27,6 +37,11 @@ nodes | tst.go:24:66:24:72 | tainted | semmle.label | tainted | | tst.go:27:11:27:29 | ...+... | semmle.label | ...+... | | tst.go:29:11:29:40 | ...+... | semmle.label | ...+... | +| tst.go:35:2:35:2 | definition of u [pointer] | semmle.label | definition of u [pointer] | +| tst.go:36:2:36:2 | implicit dereference | semmle.label | implicit dereference | +| tst.go:36:2:36:2 | u | semmle.label | u | +| tst.go:36:2:36:2 | u [pointer] | semmle.label | u [pointer] | +| tst.go:36:11:36:17 | tainted | semmle.label | tainted | | tst.go:37:11:37:11 | u | semmle.label | u | | tst.go:37:11:37:20 | call to String | semmle.label | call to String | | websocket.go:60:21:60:31 | call to Referer | semmle.label | call to Referer | diff --git a/go/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go b/go/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go index 2681af35af1..150f887e7a4 100644 --- a/go/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go +++ b/go/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go @@ -13,7 +13,7 @@ import ( "sync" ) -// Regexp is a wrapper around regexp.Regexp, where the underlying regexp will be +// Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be // compiled the first time it is needed. type Regexp struct { str string diff --git a/go/vendor/golang.org/x/mod/modfile/print.go b/go/vendor/golang.org/x/mod/modfile/print.go index 524f93022ac..2a0123d4b91 100644 --- a/go/vendor/golang.org/x/mod/modfile/print.go +++ b/go/vendor/golang.org/x/mod/modfile/print.go @@ -16,7 +16,13 @@ import ( func Format(f *FileSyntax) []byte { pr := &printer{} pr.file(f) - return pr.Bytes() + + // remove trailing blank lines + b := pr.Bytes() + for len(b) > 0 && b[len(b)-1] == '\n' && (len(b) == 1 || b[len(b)-2] == '\n') { + b = b[:len(b)-1] + } + return b } // A printer collects the state during printing of a file or expression. @@ -59,7 +65,11 @@ func (p *printer) newline() { } p.trim() - p.printf("\n") + if b := p.Bytes(); len(b) == 0 || (len(b) >= 2 && b[len(b)-1] == '\n' && b[len(b)-2] == '\n') { + // skip the blank line at top of file or after a blank line + } else { + p.printf("\n") + } for i := 0; i < p.margin; i++ { p.printf("\t") } diff --git a/go/vendor/golang.org/x/mod/modfile/read.go b/go/vendor/golang.org/x/mod/modfile/read.go index a503bc2105d..5b5bb5e115b 100644 --- a/go/vendor/golang.org/x/mod/modfile/read.go +++ b/go/vendor/golang.org/x/mod/modfile/read.go @@ -65,7 +65,7 @@ type Comments struct { } // Comment returns the receiver. This isn't useful by itself, but -// a Comments struct is embedded into all the expression +// a [Comments] struct is embedded into all the expression // implementation types, and this gives each of those a Comment // method to satisfy the Expr interface. func (c *Comments) Comment() *Comments { diff --git a/go/vendor/golang.org/x/mod/modfile/rule.go b/go/vendor/golang.org/x/mod/modfile/rule.go index 6bcde8fabe3..930b6c59bc9 100644 --- a/go/vendor/golang.org/x/mod/modfile/rule.go +++ b/go/vendor/golang.org/x/mod/modfile/rule.go @@ -5,17 +5,17 @@ // Package modfile implements a parser and formatter for go.mod files. // // The go.mod syntax is described in -// https://golang.org/cmd/go/#hdr-The_go_mod_file. +// https://pkg.go.dev/cmd/go/#hdr-The_go_mod_file. // -// The Parse and ParseLax functions both parse a go.mod file and return an +// The [Parse] and [ParseLax] functions both parse a go.mod file and return an // abstract syntax tree. ParseLax ignores unknown statements and may be used to // parse go.mod files that may have been developed with newer versions of Go. // -// The File struct returned by Parse and ParseLax represent an abstract -// go.mod file. File has several methods like AddNewRequire and DropReplace -// that can be used to programmatically edit a file. +// The [File] struct returned by Parse and ParseLax represent an abstract +// go.mod file. File has several methods like [File.AddNewRequire] and +// [File.DropReplace] that can be used to programmatically edit a file. // -// The Format function formats a File back to a byte slice which can be +// The [Format] function formats a File back to a byte slice which can be // written to a file. package modfile @@ -35,12 +35,13 @@ import ( // A File is the parsed, interpreted form of a go.mod file. type File struct { - Module *Module - Go *Go - Require []*Require - Exclude []*Exclude - Replace []*Replace - Retract []*Retract + Module *Module + Go *Go + Toolchain *Toolchain + Require []*Require + Exclude []*Exclude + Replace []*Replace + Retract []*Retract Syntax *FileSyntax } @@ -58,6 +59,12 @@ type Go struct { Syntax *Line } +// A Toolchain is the toolchain statement. +type Toolchain struct { + Name string // "go1.21rc1" + Syntax *Line +} + // An Exclude is a single exclude statement. type Exclude struct { Mod module.Version @@ -219,7 +226,7 @@ var dontFixRetract VersionFixer = func(_, vers string) (string, error) { // data is the content of the file. // // fix is an optional function that canonicalizes module versions. -// If fix is nil, all module versions must be canonical (module.CanonicalVersion +// If fix is nil, all module versions must be canonical ([module.CanonicalVersion] // must return the same string). func Parse(file string, data []byte, fix VersionFixer) (*File, error) { return parseToFile(file, data, fix, true) @@ -296,9 +303,13 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parse return f, nil } -var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) +var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`) var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`) +// Toolchains must be named beginning with `go1`, +// like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. +var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`) + func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) { // If strict is false, this module is a dependency. // We ignore all unknown directives as well as main-module-only @@ -364,6 +375,21 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a f.Go = &Go{Syntax: line} f.Go.Version = args[0] + case "toolchain": + if f.Toolchain != nil { + errorf("repeated toolchain statement") + return + } + if len(args) != 1 { + errorf("toolchain directive expects exactly one argument") + return + } else if strict && !ToolchainRE.MatchString(args[0]) { + errorf("invalid toolchain version '%s': must match format go1.23 or local", args[0]) + return + } + f.Toolchain = &Toolchain{Syntax: line} + f.Toolchain.Name = args[0] + case "module": if f.Module != nil { errorf("repeated module statement") @@ -612,6 +638,22 @@ func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, f.Go = &Go{Syntax: line} f.Go.Version = args[0] + case "toolchain": + if f.Toolchain != nil { + errorf("repeated toolchain statement") + return + } + if len(args) != 1 { + errorf("toolchain directive expects exactly one argument") + return + } else if !ToolchainRE.MatchString(args[0]) { + errorf("invalid toolchain version '%s': must match format go1.23 or local", args[0]) + return + } + + f.Toolchain = &Toolchain{Syntax: line} + f.Toolchain.Name = args[0] + case "use": if len(args) != 1 { errorf("usage: %s local/dir", verb) @@ -881,7 +923,7 @@ func (f *File) Format() ([]byte, error) { } // Cleanup cleans up the file f after any edit operations. -// To avoid quadratic behavior, modifications like DropRequire +// To avoid quadratic behavior, modifications like [File.DropRequire] // clear the entry but do not remove it from the slice. // Cleanup cleans out all the cleared entries. func (f *File) Cleanup() { @@ -926,7 +968,7 @@ func (f *File) Cleanup() { func (f *File) AddGoStmt(version string) error { if !GoVersionRE.MatchString(version) { - return fmt.Errorf("invalid language version string %q", version) + return fmt.Errorf("invalid language version %q", version) } if f.Go == nil { var hint Expr @@ -944,6 +986,44 @@ func (f *File) AddGoStmt(version string) error { return nil } +// DropGoStmt deletes the go statement from the file. +func (f *File) DropGoStmt() { + if f.Go != nil { + f.Go.Syntax.markRemoved() + f.Go = nil + } +} + +// DropToolchainStmt deletes the toolchain statement from the file. +func (f *File) DropToolchainStmt() { + if f.Toolchain != nil { + f.Toolchain.Syntax.markRemoved() + f.Toolchain = nil + } +} + +func (f *File) AddToolchainStmt(name string) error { + if !ToolchainRE.MatchString(name) { + return fmt.Errorf("invalid toolchain name %q", name) + } + if f.Toolchain == nil { + var hint Expr + if f.Go != nil && f.Go.Syntax != nil { + hint = f.Go.Syntax + } else if f.Module != nil && f.Module.Syntax != nil { + hint = f.Module.Syntax + } + f.Toolchain = &Toolchain{ + Name: name, + Syntax: f.Syntax.addLine(hint, "toolchain", name), + } + } else { + f.Toolchain.Name = name + f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) + } + return nil +} + // AddRequire sets the first require line for path to version vers, // preserving any existing comments for that line and removing all // other lines for path. @@ -995,8 +1075,8 @@ func (f *File) AddNewRequire(path, vers string, indirect bool) { // The requirements in req must specify at most one distinct version for each // module path. // -// If any existing requirements may be removed, the caller should call Cleanup -// after all edits are complete. +// If any existing requirements may be removed, the caller should call +// [File.Cleanup] after all edits are complete. func (f *File) SetRequire(req []*Require) { type elem struct { version string @@ -1387,13 +1467,21 @@ func (f *File) DropRetract(vi VersionInterval) error { func (f *File) SortBlocks() { f.removeDups() // otherwise sorting is unsafe + // semanticSortForExcludeVersionV is the Go version (plus leading "v") at which + // lines in exclude blocks start to use semantic sort instead of lexicographic sort. + // See go.dev/issue/60028. + const semanticSortForExcludeVersionV = "v1.21" + useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0 + for _, stmt := range f.Syntax.Stmt { block, ok := stmt.(*LineBlock) if !ok { continue } less := lineLess - if block.Token[0] == "retract" { + if block.Token[0] == "exclude" && useSemanticSortForExclude { + less = lineExcludeLess + } else if block.Token[0] == "retract" { less = lineRetractLess } sort.SliceStable(block.Line, func(i, j int) bool { @@ -1496,6 +1584,22 @@ func lineLess(li, lj *Line) bool { return len(li.Token) < len(lj.Token) } +// lineExcludeLess reports whether li should be sorted before lj for lines in +// an "exclude" block. +func lineExcludeLess(li, lj *Line) bool { + if len(li.Token) != 2 || len(lj.Token) != 2 { + // Not a known exclude specification. + // Fall back to sorting lexicographically. + return lineLess(li, lj) + } + // An exclude specification has two tokens: ModulePath and Version. + // Compare module path by string order and version by semver rules. + if pi, pj := li.Token[0], lj.Token[0]; pi != pj { + return pi < pj + } + return semver.Compare(li.Token[1], lj.Token[1]) < 0 +} + // lineRetractLess returns whether li should be sorted before lj for lines in // a "retract" block. It treats each line as a version interval. Single versions // are compared as if they were intervals with the same low and high version. diff --git a/go/vendor/golang.org/x/mod/modfile/work.go b/go/vendor/golang.org/x/mod/modfile/work.go index 0c0e521525a..d7b99376ebe 100644 --- a/go/vendor/golang.org/x/mod/modfile/work.go +++ b/go/vendor/golang.org/x/mod/modfile/work.go @@ -12,9 +12,10 @@ import ( // A WorkFile is the parsed, interpreted form of a go.work file. type WorkFile struct { - Go *Go - Use []*Use - Replace []*Replace + Go *Go + Toolchain *Toolchain + Use []*Use + Replace []*Replace Syntax *FileSyntax } @@ -33,7 +34,7 @@ type Use struct { // data is the content of the file. // // fix is an optional function that canonicalizes module versions. -// If fix is nil, all module versions must be canonical (module.CanonicalVersion +// If fix is nil, all module versions must be canonical ([module.CanonicalVersion] // must return the same string). func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) { fs, err := parse(file, data) @@ -82,7 +83,7 @@ func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) { } // Cleanup cleans up the file f after any edit operations. -// To avoid quadratic behavior, modifications like DropRequire +// To avoid quadratic behavior, modifications like [WorkFile.DropRequire] // clear the entry but do not remove it from the slice. // Cleanup cleans out all the cleared entries. func (f *WorkFile) Cleanup() { @@ -109,7 +110,7 @@ func (f *WorkFile) Cleanup() { func (f *WorkFile) AddGoStmt(version string) error { if !GoVersionRE.MatchString(version) { - return fmt.Errorf("invalid language version string %q", version) + return fmt.Errorf("invalid language version %q", version) } if f.Go == nil { stmt := &Line{Token: []string{"go", version}} @@ -117,7 +118,7 @@ func (f *WorkFile) AddGoStmt(version string) error { Version: version, Syntax: stmt, } - // Find the first non-comment-only block that's and add + // Find the first non-comment-only block and add // the go statement before it. That will keep file comments at the top. i := 0 for i = 0; i < len(f.Syntax.Stmt); i++ { @@ -133,6 +134,56 @@ func (f *WorkFile) AddGoStmt(version string) error { return nil } +func (f *WorkFile) AddToolchainStmt(name string) error { + if !ToolchainRE.MatchString(name) { + return fmt.Errorf("invalid toolchain name %q", name) + } + if f.Toolchain == nil { + stmt := &Line{Token: []string{"toolchain", name}} + f.Toolchain = &Toolchain{ + Name: name, + Syntax: stmt, + } + // Find the go line and add the toolchain line after it. + // Or else find the first non-comment-only block and add + // the toolchain line before it. That will keep file comments at the top. + i := 0 + for i = 0; i < len(f.Syntax.Stmt); i++ { + if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" { + i++ + goto Found + } + } + for i = 0; i < len(f.Syntax.Stmt); i++ { + if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok { + break + } + } + Found: + f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...) + } else { + f.Toolchain.Name = name + f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) + } + return nil +} + +// DropGoStmt deletes the go statement from the file. +func (f *WorkFile) DropGoStmt() { + if f.Go != nil { + f.Go.Syntax.markRemoved() + f.Go = nil + } +} + +// DropToolchainStmt deletes the toolchain statement from the file. +func (f *WorkFile) DropToolchainStmt() { + if f.Toolchain != nil { + f.Toolchain.Syntax.markRemoved() + f.Toolchain = nil + } +} + func (f *WorkFile) AddUse(diskPath, modulePath string) error { need := true for _, d := range f.Use { diff --git a/go/vendor/golang.org/x/mod/module/module.go b/go/vendor/golang.org/x/mod/module/module.go index e9dec6e6148..2a364b229b9 100644 --- a/go/vendor/golang.org/x/mod/module/module.go +++ b/go/vendor/golang.org/x/mod/module/module.go @@ -4,7 +4,7 @@ // Package module defines the module.Version type along with support code. // -// The module.Version type is a simple Path, Version pair: +// The [module.Version] type is a simple Path, Version pair: // // type Version struct { // Path string @@ -12,7 +12,7 @@ // } // // There are no restrictions imposed directly by use of this structure, -// but additional checking functions, most notably Check, verify that +// but additional checking functions, most notably [Check], verify that // a particular path, version pair is valid. // // # Escaped Paths @@ -140,7 +140,7 @@ type ModuleError struct { Err error } -// VersionError returns a ModuleError derived from a Version and error, +// VersionError returns a [ModuleError] derived from a [Version] and error, // or err itself if it is already such an error. func VersionError(v Version, err error) error { var mErr *ModuleError @@ -169,7 +169,7 @@ func (e *ModuleError) Unwrap() error { return e.Err } // An InvalidVersionError indicates an error specific to a version, with the // module path unknown or specified externally. // -// A ModuleError may wrap an InvalidVersionError, but an InvalidVersionError +// A [ModuleError] may wrap an InvalidVersionError, but an InvalidVersionError // must not wrap a ModuleError. type InvalidVersionError struct { Version string @@ -193,8 +193,8 @@ func (e *InvalidVersionError) Error() string { func (e *InvalidVersionError) Unwrap() error { return e.Err } // An InvalidPathError indicates a module, import, or file path doesn't -// satisfy all naming constraints. See CheckPath, CheckImportPath, -// and CheckFilePath for specific restrictions. +// satisfy all naming constraints. See [CheckPath], [CheckImportPath], +// and [CheckFilePath] for specific restrictions. type InvalidPathError struct { Kind string // "module", "import", or "file" Path string @@ -294,7 +294,7 @@ func fileNameOK(r rune) bool { } // CheckPath checks that a module path is valid. -// A valid module path is a valid import path, as checked by CheckImportPath, +// A valid module path is a valid import path, as checked by [CheckImportPath], // with three additional constraints. // First, the leading path element (up to the first slash, if any), // by convention a domain name, must contain only lower-case ASCII letters, @@ -380,7 +380,7 @@ const ( // checkPath returns an error describing why the path is not valid. // Because these checks apply to module, import, and file paths, // and because other checks may be applied, the caller is expected to wrap -// this error with InvalidPathError. +// this error with [InvalidPathError]. func checkPath(path string, kind pathKind) error { if !utf8.ValidString(path) { return fmt.Errorf("invalid UTF-8") @@ -532,7 +532,7 @@ var badWindowsNames = []string{ // they require ".vN" instead of "/vN", and for all N, not just N >= 2. // SplitPathVersion returns with ok = false when presented with // a path whose last path element does not satisfy the constraints -// applied by CheckPath, such as "example.com/pkg/v1" or "example.com/pkg/v1.2". +// applied by [CheckPath], such as "example.com/pkg/v1" or "example.com/pkg/v1.2". func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { if strings.HasPrefix(path, "gopkg.in/") { return splitGopkgIn(path) @@ -582,7 +582,7 @@ func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { // MatchPathMajor reports whether the semantic version v // matches the path major version pathMajor. // -// MatchPathMajor returns true if and only if CheckPathMajor returns nil. +// MatchPathMajor returns true if and only if [CheckPathMajor] returns nil. func MatchPathMajor(v, pathMajor string) bool { return CheckPathMajor(v, pathMajor) == nil } @@ -622,7 +622,7 @@ func CheckPathMajor(v, pathMajor string) error { // PathMajorPrefix returns the major-version tag prefix implied by pathMajor. // An empty PathMajorPrefix allows either v0 or v1. // -// Note that MatchPathMajor may accept some versions that do not actually begin +// Note that [MatchPathMajor] may accept some versions that do not actually begin // with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1' // pathMajor, even though that pathMajor implies 'v1' tagging. func PathMajorPrefix(pathMajor string) string { @@ -643,7 +643,7 @@ func PathMajorPrefix(pathMajor string) string { } // CanonicalVersion returns the canonical form of the version string v. -// It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible". +// It is the same as [semver.Canonical] except that it preserves the special build suffix "+incompatible". func CanonicalVersion(v string) string { cv := semver.Canonical(v) if semver.Build(v) == "+incompatible" { @@ -652,8 +652,8 @@ func CanonicalVersion(v string) string { return cv } -// Sort sorts the list by Path, breaking ties by comparing Version fields. -// The Version fields are interpreted as semantic versions (using semver.Compare) +// Sort sorts the list by Path, breaking ties by comparing [Version] fields. +// The Version fields are interpreted as semantic versions (using [semver.Compare]) // optionally followed by a tie-breaking suffix introduced by a slash character, // like in "v0.0.1/go.mod". func Sort(list []Version) { @@ -793,7 +793,7 @@ func unescapeString(escaped string) (string, bool) { } // MatchPrefixPatterns reports whether any path prefix of target matches one of -// the glob patterns (as defined by path.Match) in the comma-separated globs +// the glob patterns (as defined by [path.Match]) in the comma-separated globs // list. This implements the algorithm used when matching a module path to the // GOPRIVATE environment variable, as described by 'go help module-private'. // diff --git a/go/vendor/golang.org/x/mod/module/pseudo.go b/go/vendor/golang.org/x/mod/module/pseudo.go index f04ad378869..9cf19d3254e 100644 --- a/go/vendor/golang.org/x/mod/module/pseudo.go +++ b/go/vendor/golang.org/x/mod/module/pseudo.go @@ -125,7 +125,7 @@ func IsPseudoVersion(v string) bool { } // IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base, -// timestamp, and revision, as returned by ZeroPseudoVersion. +// timestamp, and revision, as returned by [ZeroPseudoVersion]. func IsZeroPseudoVersion(v string) bool { return v == ZeroPseudoVersion(semver.Major(v)) } diff --git a/go/vendor/golang.org/x/mod/semver/semver.go b/go/vendor/golang.org/x/mod/semver/semver.go index a30a22bf20f..9a2dfd33a77 100644 --- a/go/vendor/golang.org/x/mod/semver/semver.go +++ b/go/vendor/golang.org/x/mod/semver/semver.go @@ -140,7 +140,7 @@ func Compare(v, w string) int { // Max canonicalizes its arguments and then returns the version string // that compares greater. // -// Deprecated: use Compare instead. In most cases, returning a canonicalized +// Deprecated: use [Compare] instead. In most cases, returning a canonicalized // version is not expected or desired. func Max(v, w string) string { v = Canonical(v) @@ -151,7 +151,7 @@ func Max(v, w string) string { return w } -// ByVersion implements sort.Interface for sorting semantic version strings. +// ByVersion implements [sort.Interface] for sorting semantic version strings. type ByVersion []string func (vs ByVersion) Len() int { return len(vs) } @@ -164,7 +164,7 @@ func (vs ByVersion) Less(i, j int) bool { return vs[i] < vs[j] } -// Sort sorts a list of semantic version strings using ByVersion. +// Sort sorts a list of semantic version strings using [ByVersion]. func Sort(list []string) { sort.Sort(ByVersion(list)) } diff --git a/go/vendor/golang.org/x/sys/execabs/execabs.go b/go/vendor/golang.org/x/sys/execabs/execabs.go index b981cfbb4ae..3bf40fdfecd 100644 --- a/go/vendor/golang.org/x/sys/execabs/execabs.go +++ b/go/vendor/golang.org/x/sys/execabs/execabs.go @@ -63,7 +63,7 @@ func LookPath(file string) (string, error) { } func fixCmd(name string, cmd *exec.Cmd) { - if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) { + if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) && !isGo119ErrFieldSet(cmd) { // exec.Command was called with a bare binary name and // exec.LookPath returned a path which is not absolute. // Set cmd.lookPathErr and clear cmd.Path so that it diff --git a/go/vendor/golang.org/x/sys/execabs/execabs_go118.go b/go/vendor/golang.org/x/sys/execabs/execabs_go118.go index 6ab5f50894e..2000064a812 100644 --- a/go/vendor/golang.org/x/sys/execabs/execabs_go118.go +++ b/go/vendor/golang.org/x/sys/execabs/execabs_go118.go @@ -7,6 +7,12 @@ package execabs +import "os/exec" + func isGo119ErrDot(err error) bool { return false } + +func isGo119ErrFieldSet(cmd *exec.Cmd) bool { + return false +} diff --git a/go/vendor/golang.org/x/sys/execabs/execabs_go119.go b/go/vendor/golang.org/x/sys/execabs/execabs_go119.go index 46c5b525e7b..f364b341892 100644 --- a/go/vendor/golang.org/x/sys/execabs/execabs_go119.go +++ b/go/vendor/golang.org/x/sys/execabs/execabs_go119.go @@ -15,3 +15,7 @@ import ( func isGo119ErrDot(err error) bool { return errors.Is(err, exec.ErrDot) } + +func isGo119ErrFieldSet(cmd *exec.Cmd) bool { + return cmd.Err != nil +} diff --git a/go/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/go/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go index 165ede0f8f3..03543bd4bb8 100644 --- a/go/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go +++ b/go/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -128,15 +128,14 @@ func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, // (from "version"). Select appropriate importer. if len(data) > 0 { switch data[0] { - case 'i': + case 'v', 'c', 'd': // binary, till go1.10 + return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0]) + + case 'i': // indexed, till go1.19 _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) return pkg, err - case 'v', 'c', 'd': - _, pkg, err := gcimporter.BImportData(fset, imports, data, path) - return pkg, err - - case 'u': + case 'u': // unified, from go1.20 _, pkg, err := gcimporter.UImportData(fset, imports, data[1:], path) return pkg, err diff --git a/go/vendor/golang.org/x/tools/go/packages/golist.go b/go/vendor/golang.org/x/tools/go/packages/golist.go index 6bb7168d2e3..58230038a7c 100644 --- a/go/vendor/golang.org/x/tools/go/packages/golist.go +++ b/go/vendor/golang.org/x/tools/go/packages/golist.go @@ -625,7 +625,12 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse } if pkg.PkgPath == "unsafe" { - pkg.GoFiles = nil // ignore fake unsafe.go file + pkg.CompiledGoFiles = nil // ignore fake unsafe.go file (#59929) + } else if len(pkg.CompiledGoFiles) == 0 { + // Work around for pre-go.1.11 versions of go list. + // TODO(matloob): they should be handled by the fallback. + // Can we delete this? + pkg.CompiledGoFiles = pkg.GoFiles } // Assume go list emits only absolute paths for Dir. @@ -663,16 +668,12 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse response.Roots = append(response.Roots, pkg.ID) } - // Work around for pre-go.1.11 versions of go list. - // TODO(matloob): they should be handled by the fallback. - // Can we delete this? - if len(pkg.CompiledGoFiles) == 0 { - pkg.CompiledGoFiles = pkg.GoFiles - } - // Temporary work-around for golang/go#39986. Parse filenames out of // error messages. This happens if there are unrecoverable syntax // errors in the source, so we can't match on a specific error message. + // + // TODO(rfindley): remove this heuristic, in favor of considering + // InvalidGoFiles from the list driver. if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) { addFilenameFromPos := func(pos string) bool { split := strings.Split(pos, ":") @@ -891,6 +892,15 @@ func golistargs(cfg *Config, words []string, goVersion int) []string { // probably because you'd just get the TestMain. fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0 && !usesExportData(cfg)), } + + // golang/go#60456: with go1.21 and later, go list serves pgo variants, which + // can be costly to compute and may result in redundant processing for the + // caller. Disable these variants. If someone wants to add e.g. a NeedPGO + // mode flag, that should be a separate proposal. + if goVersion >= 21 { + fullargs = append(fullargs, "-pgo=off") + } + fullargs = append(fullargs, cfg.BuildFlags...) fullargs = append(fullargs, "--") fullargs = append(fullargs, words...) diff --git a/go/vendor/golang.org/x/tools/go/packages/packages.go b/go/vendor/golang.org/x/tools/go/packages/packages.go index 0f1505b808a..da1a27eea62 100644 --- a/go/vendor/golang.org/x/tools/go/packages/packages.go +++ b/go/vendor/golang.org/x/tools/go/packages/packages.go @@ -308,6 +308,9 @@ type Package struct { TypeErrors []types.Error // GoFiles lists the absolute file paths of the package's Go source files. + // It may include files that should not be compiled, for example because + // they contain non-matching build tags, are documentary pseudo-files such as + // unsafe/unsafe.go or builtin/builtin.go, or are subject to cgo preprocessing. GoFiles []string // CompiledGoFiles lists the absolute file paths of the package's source @@ -627,7 +630,7 @@ func newLoader(cfg *Config) *loader { return ld } -// refine connects the supplied packages into a graph and then adds type and +// refine connects the supplied packages into a graph and then adds type // and syntax information as requested by the LoadMode. func (ld *loader) refine(response *driverResponse) ([]*Package, error) { roots := response.Roots @@ -1040,6 +1043,9 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { Error: appendError, Sizes: ld.sizes, } + if lpkg.Module != nil && lpkg.Module.GoVersion != "" { + typesinternal.SetGoVersion(tc, "go"+lpkg.Module.GoVersion) + } if (ld.Mode & typecheckCgo) != 0 { if !typesinternal.SetUsesCgo(tc) { appendError(Error{ diff --git a/go/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/go/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go new file mode 100644 index 00000000000..c725d839ba1 --- /dev/null +++ b/go/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -0,0 +1,824 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package objectpath defines a naming scheme for types.Objects +// (that is, named entities in Go programs) relative to their enclosing +// package. +// +// Type-checker objects are canonical, so they are usually identified by +// their address in memory (a pointer), but a pointer has meaning only +// within one address space. By contrast, objectpath names allow the +// identity of an object to be sent from one program to another, +// establishing a correspondence between types.Object variables that are +// distinct but logically equivalent. +// +// A single object may have multiple paths. In this example, +// +// type A struct{ X int } +// type B A +// +// the field X has two paths due to its membership of both A and B. +// The For(obj) function always returns one of these paths, arbitrarily +// but consistently. +package objectpath + +import ( + "fmt" + "go/types" + "sort" + "strconv" + "strings" + _ "unsafe" + + "golang.org/x/tools/internal/typeparams" +) + +// A Path is an opaque name that identifies a types.Object +// relative to its package. Conceptually, the name consists of a +// sequence of destructuring operations applied to the package scope +// to obtain the original object. +// The name does not include the package itself. +type Path string + +// Encoding +// +// An object path is a textual and (with training) human-readable encoding +// of a sequence of destructuring operators, starting from a types.Package. +// The sequences represent a path through the package/object/type graph. +// We classify these operators by their type: +// +// PO package->object Package.Scope.Lookup +// OT object->type Object.Type +// TT type->type Type.{Elem,Key,Params,Results,Underlying} [EKPRU] +// TO type->object Type.{At,Field,Method,Obj} [AFMO] +// +// All valid paths start with a package and end at an object +// and thus may be defined by the regular language: +// +// objectpath = PO (OT TT* TO)* +// +// The concrete encoding follows directly: +// - The only PO operator is Package.Scope.Lookup, which requires an identifier. +// - The only OT operator is Object.Type, +// which we encode as '.' because dot cannot appear in an identifier. +// - The TT operators are encoded as [EKPRUTC]; +// one of these (TypeParam) requires an integer operand, +// which is encoded as a string of decimal digits. +// - The TO operators are encoded as [AFMO]; +// three of these (At,Field,Method) require an integer operand, +// which is encoded as a string of decimal digits. +// These indices are stable across different representations +// of the same package, even source and export data. +// The indices used are implementation specific and may not correspond to +// the argument to the go/types function. +// +// In the example below, +// +// package p +// +// type T interface { +// f() (a string, b struct{ X int }) +// } +// +// field X has the path "T.UM0.RA1.F0", +// representing the following sequence of operations: +// +// p.Lookup("T") T +// .Type().Underlying().Method(0). f +// .Type().Results().At(1) b +// .Type().Field(0) X +// +// The encoding is not maximally compact---every R or P is +// followed by an A, for example---but this simplifies the +// encoder and decoder. +const ( + // object->type operators + opType = '.' // .Type() (Object) + + // type->type operators + opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map) + opKey = 'K' // .Key() (Map) + opParams = 'P' // .Params() (Signature) + opResults = 'R' // .Results() (Signature) + opUnderlying = 'U' // .Underlying() (Named) + opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature) + opConstraint = 'C' // .Constraint() (TypeParam) + + // type->object operators + opAt = 'A' // .At(i) (Tuple) + opField = 'F' // .Field(i) (Struct) + opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored) + opObj = 'O' // .Obj() (Named, TypeParam) +) + +// For is equivalent to new(Encoder).For(obj). +// +// It may be more efficient to reuse a single Encoder across several calls. +func For(obj types.Object) (Path, error) { + return new(Encoder).For(obj) +} + +// An Encoder amortizes the cost of encoding the paths of multiple objects. +// The zero value of an Encoder is ready to use. +type Encoder struct { + scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects + namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods() + skipMethodSorting bool +} + +// Exposed to gopls via golang.org/x/tools/internal/typesinternal +// TODO(golang/go#61443): eliminate this parameter one way or the other. +// +//go:linkname skipMethodSorting +func skipMethodSorting(enc *Encoder) { + enc.skipMethodSorting = true +} + +// For returns the path to an object relative to its package, +// or an error if the object is not accessible from the package's Scope. +// +// The For function guarantees to return a path only for the following objects: +// - package-level types +// - exported package-level non-types +// - methods +// - parameter and result variables +// - struct fields +// These objects are sufficient to define the API of their package. +// The objects described by a package's export data are drawn from this set. +// +// The set of objects accessible from a package's Scope depends on +// whether the package was produced by type-checking syntax, or +// reading export data; the latter may have a smaller Scope since +// export data trims objects that are not reachable from an exported +// declaration. For example, the For function will return a path for +// an exported method of an unexported type that is not reachable +// from any public declaration; this path will cause the Object +// function to fail if called on a package loaded from export data. +// TODO(adonovan): is this a bug or feature? Should this package +// compute accessibility in the same way? +// +// For does not return a path for predeclared names, imported package +// names, local names, and unexported package-level names (except +// types). +// +// Example: given this definition, +// +// package p +// +// type T interface { +// f() (a string, b struct{ X int }) +// } +// +// For(X) would return a path that denotes the following sequence of operations: +// +// p.Scope().Lookup("T") (TypeName T) +// .Type().Underlying().Method(0). (method Func f) +// .Type().Results().At(1) (field Var b) +// .Type().Field(0) (field Var X) +// +// where p is the package (*types.Package) to which X belongs. +func (enc *Encoder) For(obj types.Object) (Path, error) { + pkg := obj.Pkg() + + // This table lists the cases of interest. + // + // Object Action + // ------ ------ + // nil reject + // builtin reject + // pkgname reject + // label reject + // var + // package-level accept + // func param/result accept + // local reject + // struct field accept + // const + // package-level accept + // local reject + // func + // package-level accept + // init functions reject + // concrete method accept + // interface method accept + // type + // package-level accept + // local reject + // + // The only accessible package-level objects are members of pkg itself. + // + // The cases are handled in four steps: + // + // 1. reject nil and builtin + // 2. accept package-level objects + // 3. reject obviously invalid objects + // 4. search the API for the path to the param/result/field/method. + + // 1. reference to nil or builtin? + if pkg == nil { + return "", fmt.Errorf("predeclared %s has no path", obj) + } + scope := pkg.Scope() + + // 2. package-level object? + if scope.Lookup(obj.Name()) == obj { + // Only exported objects (and non-exported types) have a path. + // Non-exported types may be referenced by other objects. + if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() { + return "", fmt.Errorf("no path for non-exported %v", obj) + } + return Path(obj.Name()), nil + } + + // 3. Not a package-level object. + // Reject obviously non-viable cases. + switch obj := obj.(type) { + case *types.TypeName: + if _, ok := obj.Type().(*typeparams.TypeParam); !ok { + // With the exception of type parameters, only package-level type names + // have a path. + return "", fmt.Errorf("no path for %v", obj) + } + case *types.Const, // Only package-level constants have a path. + *types.Label, // Labels are function-local. + *types.PkgName: // PkgNames are file-local. + return "", fmt.Errorf("no path for %v", obj) + + case *types.Var: + // Could be: + // - a field (obj.IsField()) + // - a func parameter or result + // - a local var. + // Sadly there is no way to distinguish + // a param/result from a local + // so we must proceed to the find. + + case *types.Func: + // A func, if not package-level, must be a method. + if recv := obj.Type().(*types.Signature).Recv(); recv == nil { + return "", fmt.Errorf("func is not a method: %v", obj) + } + + if path, ok := enc.concreteMethod(obj); ok { + // Fast path for concrete methods that avoids looping over scope. + return path, nil + } + + default: + panic(obj) + } + + // 4. Search the API for the path to the var (field/param/result) or method. + + // First inspect package-level named types. + // In the presence of path aliases, these give + // the best paths because non-types may + // refer to types, but not the reverse. + empty := make([]byte, 0, 48) // initial space + objs := enc.scopeObjects(scope) + for _, o := range objs { + tname, ok := o.(*types.TypeName) + if !ok { + continue // handle non-types in second pass + } + + path := append(empty, o.Name()...) + path = append(path, opType) + + T := o.Type() + + if tname.IsAlias() { + // type alias + if r := find(obj, T, path, nil); r != nil { + return Path(r), nil + } + } else { + if named, _ := T.(*types.Named); named != nil { + if r := findTypeParam(obj, typeparams.ForNamed(named), path, nil); r != nil { + // generic named type + return Path(r), nil + } + } + // defined (named) type + if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil { + return Path(r), nil + } + } + } + + // Then inspect everything else: + // non-types, and declared methods of defined types. + for _, o := range objs { + path := append(empty, o.Name()...) + if _, ok := o.(*types.TypeName); !ok { + if o.Exported() { + // exported non-type (const, var, func) + if r := find(obj, o.Type(), append(path, opType), nil); r != nil { + return Path(r), nil + } + } + continue + } + + // Inspect declared methods of defined types. + if T, ok := o.Type().(*types.Named); ok { + path = append(path, opType) + if !enc.skipMethodSorting { + // Note that method index here is always with respect + // to canonical ordering of methods, regardless of how + // they appear in the underlying type. + for i, m := range enc.namedMethods(T) { + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return Path(path2), nil // found declared method + } + if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { + return Path(r), nil + } + } + } else { + // This branch must match the logic in the branch above, using go/types + // APIs without sorting. + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return Path(path2), nil // found declared method + } + if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { + return Path(r), nil + } + } + } + } + } + + return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path()) +} + +func appendOpArg(path []byte, op byte, arg int) []byte { + path = append(path, op) + path = strconv.AppendInt(path, int64(arg), 10) + return path +} + +// concreteMethod returns the path for meth, which must have a non-nil receiver. +// The second return value indicates success and may be false if the method is +// an interface method or if it is an instantiated method. +// +// This function is just an optimization that avoids the general scope walking +// approach. You are expected to fall back to the general approach if this +// function fails. +func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { + // Concrete methods can only be declared on package-scoped named types. For + // that reason we can skip the expensive walk over the package scope: the + // path will always be package -> named type -> method. We can trivially get + // the type name from the receiver, and only have to look over the type's + // methods to find the method index. + // + // Methods on generic types require special consideration, however. Consider + // the following package: + // + // L1: type S[T any] struct{} + // L2: func (recv S[A]) Foo() { recv.Bar() } + // L3: func (recv S[B]) Bar() { } + // L4: type Alias = S[int] + // L5: func _[T any]() { var s S[int]; s.Foo() } + // + // The receivers of methods on generic types are instantiations. L2 and L3 + // instantiate S with the type-parameters A and B, which are scoped to the + // respective methods. L4 and L5 each instantiate S with int. Each of these + // instantiations has its own method set, full of methods (and thus objects) + // with receivers whose types are the respective instantiations. In other + // words, we have + // + // S[A].Foo, S[A].Bar + // S[B].Foo, S[B].Bar + // S[int].Foo, S[int].Bar + // + // We may thus be trying to produce object paths for any of these objects. + // + // S[A].Foo and S[B].Bar are the origin methods, and their paths are S.Foo + // and S.Bar, which are the paths that this function naturally produces. + // + // S[A].Bar, S[B].Foo, and both methods on S[int] are instantiations that + // don't correspond to the origin methods. For S[int], this is significant. + // The most precise object path for S[int].Foo, for example, is Alias.Foo, + // not S.Foo. Our function, however, would produce S.Foo, which would + // resolve to a different object. + // + // For S[A].Bar and S[B].Foo it could be argued that S.Bar and S.Foo are + // still the correct paths, since only the origin methods have meaningful + // paths. But this is likely only true for trivial cases and has edge cases. + // Since this function is only an optimization, we err on the side of giving + // up, deferring to the slower but definitely correct algorithm. Most users + // of objectpath will only be giving us origin methods, anyway, as referring + // to instantiated methods is usually not useful. + + if typeparams.OriginMethod(meth) != meth { + return "", false + } + + recvT := meth.Type().(*types.Signature).Recv().Type() + if ptr, ok := recvT.(*types.Pointer); ok { + recvT = ptr.Elem() + } + + named, ok := recvT.(*types.Named) + if !ok { + return "", false + } + + if types.IsInterface(named) { + // Named interfaces don't have to be package-scoped + // + // TODO(dominikh): opt: if scope.Lookup(name) == named, then we can apply this optimization to interface + // methods, too, I think. + return "", false + } + + // Preallocate space for the name, opType, opMethod, and some digits. + name := named.Obj().Name() + path := make([]byte, 0, len(name)+8) + path = append(path, name...) + path = append(path, opType) + + if !enc.skipMethodSorting { + for i, m := range enc.namedMethods(named) { + if m == meth { + path = appendOpArg(path, opMethod, i) + return Path(path), true + } + } + } else { + // This branch must match the logic of the branch above, using go/types + // APIs without sorting. + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m == meth { + path = appendOpArg(path, opMethod, i) + return Path(path), true + } + } + } + + // Due to golang/go#59944, go/types fails to associate the receiver with + // certain methods on cgo types. + // + // TODO(rfindley): replace this panic once golang/go#59944 is fixed in all Go + // versions gopls supports. + return "", false + // panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named))) +} + +// find finds obj within type T, returning the path to it, or nil if not found. +// +// The seen map is used to short circuit cycles through type parameters. If +// nil, it will be allocated as necessary. +func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte { + switch T := T.(type) { + case *types.Basic, *types.Named: + // Named types belonging to pkg were handled already, + // so T must belong to another package. No path. + return nil + case *types.Pointer: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Slice: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Array: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Chan: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Map: + if r := find(obj, T.Key(), append(path, opKey), seen); r != nil { + return r + } + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Signature: + if r := findTypeParam(obj, typeparams.ForSignature(T), path, seen); r != nil { + return r + } + if r := find(obj, T.Params(), append(path, opParams), seen); r != nil { + return r + } + return find(obj, T.Results(), append(path, opResults), seen) + case *types.Struct: + for i := 0; i < T.NumFields(); i++ { + fld := T.Field(i) + path2 := appendOpArg(path, opField, i) + if fld == obj { + return path2 // found field var + } + if r := find(obj, fld.Type(), append(path2, opType), seen); r != nil { + return r + } + } + return nil + case *types.Tuple: + for i := 0; i < T.Len(); i++ { + v := T.At(i) + path2 := appendOpArg(path, opAt, i) + if v == obj { + return path2 // found param/result var + } + if r := find(obj, v.Type(), append(path2, opType), seen); r != nil { + return r + } + } + return nil + case *types.Interface: + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return path2 // found interface method + } + if r := find(obj, m.Type(), append(path2, opType), seen); r != nil { + return r + } + } + return nil + case *typeparams.TypeParam: + name := T.Obj() + if name == obj { + return append(path, opObj) + } + if seen[name] { + return nil + } + if seen == nil { + seen = make(map[*types.TypeName]bool) + } + seen[name] = true + if r := find(obj, T.Constraint(), append(path, opConstraint), seen); r != nil { + return r + } + return nil + } + panic(T) +} + +func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte { + for i := 0; i < list.Len(); i++ { + tparam := list.At(i) + path2 := appendOpArg(path, opTypeParam, i) + if r := find(obj, tparam, path2, seen); r != nil { + return r + } + } + return nil +} + +// Object returns the object denoted by path p within the package pkg. +func Object(pkg *types.Package, p Path) (types.Object, error) { + return object(pkg, p, false) +} + +// Note: the skipMethodSorting parameter must match the value of +// Encoder.skipMethodSorting used during encoding. +func object(pkg *types.Package, p Path, skipMethodSorting bool) (types.Object, error) { + if p == "" { + return nil, fmt.Errorf("empty path") + } + + pathstr := string(p) + var pkgobj, suffix string + if dot := strings.IndexByte(pathstr, opType); dot < 0 { + pkgobj = pathstr + } else { + pkgobj = pathstr[:dot] + suffix = pathstr[dot:] // suffix starts with "." + } + + obj := pkg.Scope().Lookup(pkgobj) + if obj == nil { + return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj) + } + + // abstraction of *types.{Pointer,Slice,Array,Chan,Map} + type hasElem interface { + Elem() types.Type + } + // abstraction of *types.{Named,Signature} + type hasTypeParams interface { + TypeParams() *typeparams.TypeParamList + } + // abstraction of *types.{Named,TypeParam} + type hasObj interface { + Obj() *types.TypeName + } + + // The loop state is the pair (t, obj), + // exactly one of which is non-nil, initially obj. + // All suffixes start with '.' (the only object->type operation), + // followed by optional type->type operations, + // then a type->object operation. + // The cycle then repeats. + var t types.Type + for suffix != "" { + code := suffix[0] + suffix = suffix[1:] + + // Codes [AFM] have an integer operand. + var index int + switch code { + case opAt, opField, opMethod, opTypeParam: + rest := strings.TrimLeft(suffix, "0123456789") + numerals := suffix[:len(suffix)-len(rest)] + suffix = rest + i, err := strconv.Atoi(numerals) + if err != nil { + return nil, fmt.Errorf("invalid path: bad numeric operand %q for code %q", numerals, code) + } + index = int(i) + case opObj: + // no operand + default: + // The suffix must end with a type->object operation. + if suffix == "" { + return nil, fmt.Errorf("invalid path: ends with %q, want [AFMO]", code) + } + } + + if code == opType { + if t != nil { + return nil, fmt.Errorf("invalid path: unexpected %q in type context", opType) + } + t = obj.Type() + obj = nil + continue + } + + if t == nil { + return nil, fmt.Errorf("invalid path: code %q in object context", code) + } + + // Inv: t != nil, obj == nil + + switch code { + case opElem: + hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want pointer, slice, array, chan or map)", code, t, t) + } + t = hasElem.Elem() + + case opKey: + mapType, ok := t.(*types.Map) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want map)", code, t, t) + } + t = mapType.Key() + + case opParams: + sig, ok := t.(*types.Signature) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) + } + t = sig.Params() + + case opResults: + sig, ok := t.(*types.Signature) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) + } + t = sig.Results() + + case opUnderlying: + named, ok := t.(*types.Named) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t) + } + t = named.Underlying() + + case opTypeParam: + hasTypeParams, ok := t.(hasTypeParams) // Named, Signature + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t) + } + tparams := hasTypeParams.TypeParams() + if n := tparams.Len(); index >= n { + return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + } + t = tparams.At(index) + + case opConstraint: + tparam, ok := t.(*typeparams.TypeParam) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t) + } + t = tparam.Constraint() + + case opAt: + tuple, ok := t.(*types.Tuple) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t) + } + if n := tuple.Len(); index >= n { + return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + } + obj = tuple.At(index) + t = nil + + case opField: + structType, ok := t.(*types.Struct) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want struct)", code, t, t) + } + if n := structType.NumFields(); index >= n { + return nil, fmt.Errorf("field index %d out of range [0-%d)", index, n) + } + obj = structType.Field(index) + t = nil + + case opMethod: + switch t := t.(type) { + case *types.Interface: + if index >= t.NumMethods() { + return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods()) + } + obj = t.Method(index) // Id-ordered + + case *types.Named: + if index >= t.NumMethods() { + return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods()) + } + if skipMethodSorting { + obj = t.Method(index) + } else { + methods := namedMethods(t) // (unmemoized) + obj = methods[index] // Id-ordered + } + + default: + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t) + } + t = nil + + case opObj: + hasObj, ok := t.(hasObj) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t) + } + obj = hasObj.Obj() + t = nil + + default: + return nil, fmt.Errorf("invalid path: unknown code %q", code) + } + } + + if obj.Pkg() != pkg { + return nil, fmt.Errorf("path denotes %s, which belongs to a different package", obj) + } + + return obj, nil // success +} + +// namedMethods returns the methods of a Named type in ascending Id order. +func namedMethods(named *types.Named) []*types.Func { + methods := make([]*types.Func, named.NumMethods()) + for i := range methods { + methods[i] = named.Method(i) + } + sort.Slice(methods, func(i, j int) bool { + return methods[i].Id() < methods[j].Id() + }) + return methods +} + +// namedMethods is a memoization of the namedMethods function. Callers must not modify the result. +func (enc *Encoder) namedMethods(named *types.Named) []*types.Func { + m := enc.namedMethodsMemo + if m == nil { + m = make(map[*types.Named][]*types.Func) + enc.namedMethodsMemo = m + } + methods, ok := m[named] + if !ok { + methods = namedMethods(named) // allocates and sorts + m[named] = methods + } + return methods +} + +// scopeObjects is a memoization of scope objects. +// Callers must not modify the result. +func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object { + m := enc.scopeMemo + if m == nil { + m = make(map[*types.Scope][]types.Object) + enc.scopeMemo = m + } + objs, ok := m[scope] + if !ok { + names := scope.Names() // allocates and sorts + objs = make([]types.Object, len(names)) + for i, name := range names { + objs[i] = scope.Lookup(name) + } + m[scope] = objs + } + return objs +} diff --git a/go/vendor/golang.org/x/tools/internal/event/tag/tag.go b/go/vendor/golang.org/x/tools/internal/event/tag/tag.go new file mode 100644 index 00000000000..581b26c2041 --- /dev/null +++ b/go/vendor/golang.org/x/tools/internal/event/tag/tag.go @@ -0,0 +1,59 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tag provides the labels used for telemetry throughout gopls. +package tag + +import ( + "golang.org/x/tools/internal/event/keys" +) + +var ( + // create the label keys we use + Method = keys.NewString("method", "") + StatusCode = keys.NewString("status.code", "") + StatusMessage = keys.NewString("status.message", "") + RPCID = keys.NewString("id", "") + RPCDirection = keys.NewString("direction", "") + File = keys.NewString("file", "") + Directory = keys.New("directory", "") + URI = keys.New("URI", "") + Package = keys.NewString("package", "") // sorted comma-separated list of Package IDs + PackagePath = keys.NewString("package_path", "") + Query = keys.New("query", "") + Snapshot = keys.NewUInt64("snapshot", "") + Operation = keys.NewString("operation", "") + + Position = keys.New("position", "") + Category = keys.NewString("category", "") + PackageCount = keys.NewInt("packages", "") + Files = keys.New("files", "") + Port = keys.NewInt("port", "") + Type = keys.New("type", "") + HoverKind = keys.NewString("hoverkind", "") + + NewServer = keys.NewString("new_server", "A new server was added") + EndServer = keys.NewString("end_server", "A server was shut down") + + ServerID = keys.NewString("server", "The server ID an event is related to") + Logfile = keys.NewString("logfile", "") + DebugAddress = keys.NewString("debug_address", "") + GoplsPath = keys.NewString("gopls_path", "") + ClientID = keys.NewString("client_id", "") + + Level = keys.NewInt("level", "The logging level") +) + +var ( + // create the stats we measure + Started = keys.NewInt64("started", "Count of started RPCs.") + ReceivedBytes = keys.NewInt64("received_bytes", "Bytes received.") //, unit.Bytes) + SentBytes = keys.NewInt64("sent_bytes", "Bytes sent.") //, unit.Bytes) + Latency = keys.NewFloat64("latency_ms", "Elapsed time in milliseconds") //, unit.Milliseconds) +) + +const ( + Inbound = "in" + Outbound = "out" +) diff --git a/go/vendor/golang.org/x/tools/internal/gcimporter/bexport.go b/go/vendor/golang.org/x/tools/internal/gcimporter/bexport.go deleted file mode 100644 index 30582ed6d3d..00000000000 --- a/go/vendor/golang.org/x/tools/internal/gcimporter/bexport.go +++ /dev/null @@ -1,852 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Binary package export. -// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go; -// see that file for specification of the format. - -package gcimporter - -import ( - "bytes" - "encoding/binary" - "fmt" - "go/constant" - "go/token" - "go/types" - "math" - "math/big" - "sort" - "strings" -) - -// If debugFormat is set, each integer and string value is preceded by a marker -// and position information in the encoding. This mechanism permits an importer -// to recognize immediately when it is out of sync. The importer recognizes this -// mode automatically (i.e., it can import export data produced with debugging -// support even if debugFormat is not set at the time of import). This mode will -// lead to massively larger export data (by a factor of 2 to 3) and should only -// be enabled during development and debugging. -// -// NOTE: This flag is the first flag to enable if importing dies because of -// (suspected) format errors, and whenever a change is made to the format. -const debugFormat = false // default: false - -// Current export format version. Increase with each format change. -// -// Note: The latest binary (non-indexed) export format is at version 6. -// This exporter is still at level 4, but it doesn't matter since -// the binary importer can handle older versions just fine. -// -// 6: package height (CL 105038) -- NOT IMPLEMENTED HERE -// 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMENTED HERE -// 4: type name objects support type aliases, uses aliasTag -// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used) -// 2: removed unused bool in ODCL export (compiler only) -// 1: header format change (more regular), export package for _ struct fields -// 0: Go1.7 encoding -const exportVersion = 4 - -// trackAllTypes enables cycle tracking for all types, not just named -// types. The existing compiler invariants assume that unnamed types -// that are not completely set up are not used, or else there are spurious -// errors. -// If disabled, only named types are tracked, possibly leading to slightly -// less efficient encoding in rare cases. It also prevents the export of -// some corner-case type declarations (but those are not handled correctly -// with with the textual export format either). -// TODO(gri) enable and remove once issues caused by it are fixed -const trackAllTypes = false - -type exporter struct { - fset *token.FileSet - out bytes.Buffer - - // object -> index maps, indexed in order of serialization - strIndex map[string]int - pkgIndex map[*types.Package]int - typIndex map[types.Type]int - - // position encoding - posInfoFormat bool - prevFile string - prevLine int - - // debugging support - written int // bytes written - indent int // for trace -} - -// internalError represents an error generated inside this package. -type internalError string - -func (e internalError) Error() string { return "gcimporter: " + string(e) } - -func internalErrorf(format string, args ...interface{}) error { - return internalError(fmt.Sprintf(format, args...)) -} - -// BExportData returns binary export data for pkg. -// If no file set is provided, position info will be missing. -func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { - if !debug { - defer func() { - if e := recover(); e != nil { - if ierr, ok := e.(internalError); ok { - err = ierr - return - } - // Not an internal error; panic again. - panic(e) - } - }() - } - - p := exporter{ - fset: fset, - strIndex: map[string]int{"": 0}, // empty string is mapped to 0 - pkgIndex: make(map[*types.Package]int), - typIndex: make(map[types.Type]int), - posInfoFormat: true, // TODO(gri) might become a flag, eventually - } - - // write version info - // The version string must start with "version %d" where %d is the version - // number. Additional debugging information may follow after a blank; that - // text is ignored by the importer. - p.rawStringln(fmt.Sprintf("version %d", exportVersion)) - var debug string - if debugFormat { - debug = "debug" - } - p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly - p.bool(trackAllTypes) - p.bool(p.posInfoFormat) - - // --- generic export data --- - - // populate type map with predeclared "known" types - for index, typ := range predeclared() { - p.typIndex[typ] = index - } - if len(p.typIndex) != len(predeclared()) { - return nil, internalError("duplicate entries in type map?") - } - - // write package data - p.pkg(pkg, true) - if trace { - p.tracef("\n") - } - - // write objects - objcount := 0 - scope := pkg.Scope() - for _, name := range scope.Names() { - if !token.IsExported(name) { - continue - } - if trace { - p.tracef("\n") - } - p.obj(scope.Lookup(name)) - objcount++ - } - - // indicate end of list - if trace { - p.tracef("\n") - } - p.tag(endTag) - - // for self-verification only (redundant) - p.int(objcount) - - if trace { - p.tracef("\n") - } - - // --- end of export data --- - - return p.out.Bytes(), nil -} - -func (p *exporter) pkg(pkg *types.Package, emptypath bool) { - if pkg == nil { - panic(internalError("unexpected nil pkg")) - } - - // if we saw the package before, write its index (>= 0) - if i, ok := p.pkgIndex[pkg]; ok { - p.index('P', i) - return - } - - // otherwise, remember the package, write the package tag (< 0) and package data - if trace { - p.tracef("P%d = { ", len(p.pkgIndex)) - defer p.tracef("} ") - } - p.pkgIndex[pkg] = len(p.pkgIndex) - - p.tag(packageTag) - p.string(pkg.Name()) - if emptypath { - p.string("") - } else { - p.string(pkg.Path()) - } -} - -func (p *exporter) obj(obj types.Object) { - switch obj := obj.(type) { - case *types.Const: - p.tag(constTag) - p.pos(obj) - p.qualifiedName(obj) - p.typ(obj.Type()) - p.value(obj.Val()) - - case *types.TypeName: - if obj.IsAlias() { - p.tag(aliasTag) - p.pos(obj) - p.qualifiedName(obj) - } else { - p.tag(typeTag) - } - p.typ(obj.Type()) - - case *types.Var: - p.tag(varTag) - p.pos(obj) - p.qualifiedName(obj) - p.typ(obj.Type()) - - case *types.Func: - p.tag(funcTag) - p.pos(obj) - p.qualifiedName(obj) - sig := obj.Type().(*types.Signature) - p.paramList(sig.Params(), sig.Variadic()) - p.paramList(sig.Results(), false) - - default: - panic(internalErrorf("unexpected object %v (%T)", obj, obj)) - } -} - -func (p *exporter) pos(obj types.Object) { - if !p.posInfoFormat { - return - } - - file, line := p.fileLine(obj) - if file == p.prevFile { - // common case: write line delta - // delta == 0 means different file or no line change - delta := line - p.prevLine - p.int(delta) - if delta == 0 { - p.int(-1) // -1 means no file change - } - } else { - // different file - p.int(0) - // Encode filename as length of common prefix with previous - // filename, followed by (possibly empty) suffix. Filenames - // frequently share path prefixes, so this can save a lot - // of space and make export data size less dependent on file - // path length. The suffix is unlikely to be empty because - // file names tend to end in ".go". - n := commonPrefixLen(p.prevFile, file) - p.int(n) // n >= 0 - p.string(file[n:]) // write suffix only - p.prevFile = file - p.int(line) - } - p.prevLine = line -} - -func (p *exporter) fileLine(obj types.Object) (file string, line int) { - if p.fset != nil { - pos := p.fset.Position(obj.Pos()) - file = pos.Filename - line = pos.Line - } - return -} - -func commonPrefixLen(a, b string) int { - if len(a) > len(b) { - a, b = b, a - } - // len(a) <= len(b) - i := 0 - for i < len(a) && a[i] == b[i] { - i++ - } - return i -} - -func (p *exporter) qualifiedName(obj types.Object) { - p.string(obj.Name()) - p.pkg(obj.Pkg(), false) -} - -func (p *exporter) typ(t types.Type) { - if t == nil { - panic(internalError("nil type")) - } - - // Possible optimization: Anonymous pointer types *T where - // T is a named type are common. We could canonicalize all - // such types *T to a single type PT = *T. This would lead - // to at most one *T entry in typIndex, and all future *T's - // would be encoded as the respective index directly. Would - // save 1 byte (pointerTag) per *T and reduce the typIndex - // size (at the cost of a canonicalization map). We can do - // this later, without encoding format change. - - // if we saw the type before, write its index (>= 0) - if i, ok := p.typIndex[t]; ok { - p.index('T', i) - return - } - - // otherwise, remember the type, write the type tag (< 0) and type data - if trackAllTypes { - if trace { - p.tracef("T%d = {>\n", len(p.typIndex)) - defer p.tracef("<\n} ") - } - p.typIndex[t] = len(p.typIndex) - } - - switch t := t.(type) { - case *types.Named: - if !trackAllTypes { - // if we don't track all types, track named types now - p.typIndex[t] = len(p.typIndex) - } - - p.tag(namedTag) - p.pos(t.Obj()) - p.qualifiedName(t.Obj()) - p.typ(t.Underlying()) - if !types.IsInterface(t) { - p.assocMethods(t) - } - - case *types.Array: - p.tag(arrayTag) - p.int64(t.Len()) - p.typ(t.Elem()) - - case *types.Slice: - p.tag(sliceTag) - p.typ(t.Elem()) - - case *dddSlice: - p.tag(dddTag) - p.typ(t.elem) - - case *types.Struct: - p.tag(structTag) - p.fieldList(t) - - case *types.Pointer: - p.tag(pointerTag) - p.typ(t.Elem()) - - case *types.Signature: - p.tag(signatureTag) - p.paramList(t.Params(), t.Variadic()) - p.paramList(t.Results(), false) - - case *types.Interface: - p.tag(interfaceTag) - p.iface(t) - - case *types.Map: - p.tag(mapTag) - p.typ(t.Key()) - p.typ(t.Elem()) - - case *types.Chan: - p.tag(chanTag) - p.int(int(3 - t.Dir())) // hack - p.typ(t.Elem()) - - default: - panic(internalErrorf("unexpected type %T: %s", t, t)) - } -} - -func (p *exporter) assocMethods(named *types.Named) { - // Sort methods (for determinism). - var methods []*types.Func - for i := 0; i < named.NumMethods(); i++ { - methods = append(methods, named.Method(i)) - } - sort.Sort(methodsByName(methods)) - - p.int(len(methods)) - - if trace && methods != nil { - p.tracef("associated methods {>\n") - } - - for i, m := range methods { - if trace && i > 0 { - p.tracef("\n") - } - - p.pos(m) - name := m.Name() - p.string(name) - if !exported(name) { - p.pkg(m.Pkg(), false) - } - - sig := m.Type().(*types.Signature) - p.paramList(types.NewTuple(sig.Recv()), false) - p.paramList(sig.Params(), sig.Variadic()) - p.paramList(sig.Results(), false) - p.int(0) // dummy value for go:nointerface pragma - ignored by importer - } - - if trace && methods != nil { - p.tracef("<\n} ") - } -} - -type methodsByName []*types.Func - -func (x methodsByName) Len() int { return len(x) } -func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() } - -func (p *exporter) fieldList(t *types.Struct) { - if trace && t.NumFields() > 0 { - p.tracef("fields {>\n") - defer p.tracef("<\n} ") - } - - p.int(t.NumFields()) - for i := 0; i < t.NumFields(); i++ { - if trace && i > 0 { - p.tracef("\n") - } - p.field(t.Field(i)) - p.string(t.Tag(i)) - } -} - -func (p *exporter) field(f *types.Var) { - if !f.IsField() { - panic(internalError("field expected")) - } - - p.pos(f) - p.fieldName(f) - p.typ(f.Type()) -} - -func (p *exporter) iface(t *types.Interface) { - // TODO(gri): enable importer to load embedded interfaces, - // then emit Embeddeds and ExplicitMethods separately here. - p.int(0) - - n := t.NumMethods() - if trace && n > 0 { - p.tracef("methods {>\n") - defer p.tracef("<\n} ") - } - p.int(n) - for i := 0; i < n; i++ { - if trace && i > 0 { - p.tracef("\n") - } - p.method(t.Method(i)) - } -} - -func (p *exporter) method(m *types.Func) { - sig := m.Type().(*types.Signature) - if sig.Recv() == nil { - panic(internalError("method expected")) - } - - p.pos(m) - p.string(m.Name()) - if m.Name() != "_" && !token.IsExported(m.Name()) { - p.pkg(m.Pkg(), false) - } - - // interface method; no need to encode receiver. - p.paramList(sig.Params(), sig.Variadic()) - p.paramList(sig.Results(), false) -} - -func (p *exporter) fieldName(f *types.Var) { - name := f.Name() - - if f.Anonymous() { - // anonymous field - we distinguish between 3 cases: - // 1) field name matches base type name and is exported - // 2) field name matches base type name and is not exported - // 3) field name doesn't match base type name (alias name) - bname := basetypeName(f.Type()) - if name == bname { - if token.IsExported(name) { - name = "" // 1) we don't need to know the field name or package - } else { - name = "?" // 2) use unexported name "?" to force package export - } - } else { - // 3) indicate alias and export name as is - // (this requires an extra "@" but this is a rare case) - p.string("@") - } - } - - p.string(name) - if name != "" && !token.IsExported(name) { - p.pkg(f.Pkg(), false) - } -} - -func basetypeName(typ types.Type) string { - switch typ := deref(typ).(type) { - case *types.Basic: - return typ.Name() - case *types.Named: - return typ.Obj().Name() - default: - return "" // unnamed type - } -} - -func (p *exporter) paramList(params *types.Tuple, variadic bool) { - // use negative length to indicate unnamed parameters - // (look at the first parameter only since either all - // names are present or all are absent) - n := params.Len() - if n > 0 && params.At(0).Name() == "" { - n = -n - } - p.int(n) - for i := 0; i < params.Len(); i++ { - q := params.At(i) - t := q.Type() - if variadic && i == params.Len()-1 { - t = &dddSlice{t.(*types.Slice).Elem()} - } - p.typ(t) - if n > 0 { - name := q.Name() - p.string(name) - if name != "_" { - p.pkg(q.Pkg(), false) - } - } - p.string("") // no compiler-specific info - } -} - -func (p *exporter) value(x constant.Value) { - if trace { - p.tracef("= ") - } - - switch x.Kind() { - case constant.Bool: - tag := falseTag - if constant.BoolVal(x) { - tag = trueTag - } - p.tag(tag) - - case constant.Int: - if v, exact := constant.Int64Val(x); exact { - // common case: x fits into an int64 - use compact encoding - p.tag(int64Tag) - p.int64(v) - return - } - // uncommon case: large x - use float encoding - // (powers of 2 will be encoded efficiently with exponent) - p.tag(floatTag) - p.float(constant.ToFloat(x)) - - case constant.Float: - p.tag(floatTag) - p.float(x) - - case constant.Complex: - p.tag(complexTag) - p.float(constant.Real(x)) - p.float(constant.Imag(x)) - - case constant.String: - p.tag(stringTag) - p.string(constant.StringVal(x)) - - case constant.Unknown: - // package contains type errors - p.tag(unknownTag) - - default: - panic(internalErrorf("unexpected value %v (%T)", x, x)) - } -} - -func (p *exporter) float(x constant.Value) { - if x.Kind() != constant.Float { - panic(internalErrorf("unexpected constant %v, want float", x)) - } - // extract sign (there is no -0) - sign := constant.Sign(x) - if sign == 0 { - // x == 0 - p.int(0) - return - } - // x != 0 - - var f big.Float - if v, exact := constant.Float64Val(x); exact { - // float64 - f.SetFloat64(v) - } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { - // TODO(gri): add big.Rat accessor to constant.Value. - r := valueToRat(num) - f.SetRat(r.Quo(r, valueToRat(denom))) - } else { - // Value too large to represent as a fraction => inaccessible. - // TODO(gri): add big.Float accessor to constant.Value. - f.SetFloat64(math.MaxFloat64) // FIXME - } - - // extract exponent such that 0.5 <= m < 1.0 - var m big.Float - exp := f.MantExp(&m) - - // extract mantissa as *big.Int - // - set exponent large enough so mant satisfies mant.IsInt() - // - get *big.Int from mant - m.SetMantExp(&m, int(m.MinPrec())) - mant, acc := m.Int(nil) - if acc != big.Exact { - panic(internalError("internal error")) - } - - p.int(sign) - p.int(exp) - p.string(string(mant.Bytes())) -} - -func valueToRat(x constant.Value) *big.Rat { - // Convert little-endian to big-endian. - // I can't believe this is necessary. - bytes := constant.Bytes(x) - for i := 0; i < len(bytes)/2; i++ { - bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] - } - return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) -} - -func (p *exporter) bool(b bool) bool { - if trace { - p.tracef("[") - defer p.tracef("= %v] ", b) - } - - x := 0 - if b { - x = 1 - } - p.int(x) - return b -} - -// ---------------------------------------------------------------------------- -// Low-level encoders - -func (p *exporter) index(marker byte, index int) { - if index < 0 { - panic(internalError("invalid index < 0")) - } - if debugFormat { - p.marker('t') - } - if trace { - p.tracef("%c%d ", marker, index) - } - p.rawInt64(int64(index)) -} - -func (p *exporter) tag(tag int) { - if tag >= 0 { - panic(internalError("invalid tag >= 0")) - } - if debugFormat { - p.marker('t') - } - if trace { - p.tracef("%s ", tagString[-tag]) - } - p.rawInt64(int64(tag)) -} - -func (p *exporter) int(x int) { - p.int64(int64(x)) -} - -func (p *exporter) int64(x int64) { - if debugFormat { - p.marker('i') - } - if trace { - p.tracef("%d ", x) - } - p.rawInt64(x) -} - -func (p *exporter) string(s string) { - if debugFormat { - p.marker('s') - } - if trace { - p.tracef("%q ", s) - } - // if we saw the string before, write its index (>= 0) - // (the empty string is mapped to 0) - if i, ok := p.strIndex[s]; ok { - p.rawInt64(int64(i)) - return - } - // otherwise, remember string and write its negative length and bytes - p.strIndex[s] = len(p.strIndex) - p.rawInt64(-int64(len(s))) - for i := 0; i < len(s); i++ { - p.rawByte(s[i]) - } -} - -// marker emits a marker byte and position information which makes -// it easy for a reader to detect if it is "out of sync". Used for -// debugFormat format only. -func (p *exporter) marker(m byte) { - p.rawByte(m) - // Enable this for help tracking down the location - // of an incorrect marker when running in debugFormat. - if false && trace { - p.tracef("#%d ", p.written) - } - p.rawInt64(int64(p.written)) -} - -// rawInt64 should only be used by low-level encoders. -func (p *exporter) rawInt64(x int64) { - var tmp [binary.MaxVarintLen64]byte - n := binary.PutVarint(tmp[:], x) - for i := 0; i < n; i++ { - p.rawByte(tmp[i]) - } -} - -// rawStringln should only be used to emit the initial version string. -func (p *exporter) rawStringln(s string) { - for i := 0; i < len(s); i++ { - p.rawByte(s[i]) - } - p.rawByte('\n') -} - -// rawByte is the bottleneck interface to write to p.out. -// rawByte escapes b as follows (any encoding does that -// hides '$'): -// -// '$' => '|' 'S' -// '|' => '|' '|' -// -// Necessary so other tools can find the end of the -// export data by searching for "$$". -// rawByte should only be used by low-level encoders. -func (p *exporter) rawByte(b byte) { - switch b { - case '$': - // write '$' as '|' 'S' - b = 'S' - fallthrough - case '|': - // write '|' as '|' '|' - p.out.WriteByte('|') - p.written++ - } - p.out.WriteByte(b) - p.written++ -} - -// tracef is like fmt.Printf but it rewrites the format string -// to take care of indentation. -func (p *exporter) tracef(format string, args ...interface{}) { - if strings.ContainsAny(format, "<>\n") { - var buf bytes.Buffer - for i := 0; i < len(format); i++ { - // no need to deal with runes - ch := format[i] - switch ch { - case '>': - p.indent++ - continue - case '<': - p.indent-- - continue - } - buf.WriteByte(ch) - if ch == '\n' { - for j := p.indent; j > 0; j-- { - buf.WriteString(". ") - } - } - } - format = buf.String() - } - fmt.Printf(format, args...) -} - -// Debugging support. -// (tagString is only used when tracing is enabled) -var tagString = [...]string{ - // Packages - -packageTag: "package", - - // Types - -namedTag: "named type", - -arrayTag: "array", - -sliceTag: "slice", - -dddTag: "ddd", - -structTag: "struct", - -pointerTag: "pointer", - -signatureTag: "signature", - -interfaceTag: "interface", - -mapTag: "map", - -chanTag: "chan", - - // Values - -falseTag: "false", - -trueTag: "true", - -int64Tag: "int64", - -floatTag: "float", - -fractionTag: "fraction", - -complexTag: "complex", - -stringTag: "string", - -unknownTag: "unknown", - - // Type aliases - -aliasTag: "alias", -} diff --git a/go/vendor/golang.org/x/tools/internal/gcimporter/bimport.go b/go/vendor/golang.org/x/tools/internal/gcimporter/bimport.go index b85de014700..d98b0db2a9a 100644 --- a/go/vendor/golang.org/x/tools/internal/gcimporter/bimport.go +++ b/go/vendor/golang.org/x/tools/internal/gcimporter/bimport.go @@ -2,340 +2,24 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file is a copy of $GOROOT/src/go/internal/gcimporter/bimport.go. +// This file contains the remaining vestiges of +// $GOROOT/src/go/internal/gcimporter/bimport.go. package gcimporter import ( - "encoding/binary" "fmt" - "go/constant" "go/token" "go/types" - "sort" - "strconv" - "strings" "sync" - "unicode" - "unicode/utf8" ) -type importer struct { - imports map[string]*types.Package - data []byte - importpath string - buf []byte // for reading strings - version int // export format version - - // object lists - strList []string // in order of appearance - pathList []string // in order of appearance - pkgList []*types.Package // in order of appearance - typList []types.Type // in order of appearance - interfaceList []*types.Interface // for delayed completion only - trackAllTypes bool - - // position encoding - posInfoFormat bool - prevFile string - prevLine int - fake fakeFileSet - - // debugging support - debugFormat bool - read int // bytes read -} - -// BImportData imports a package from the serialized package data -// and returns the number of bytes consumed and a reference to the package. -// If the export data version is not recognized or the format is otherwise -// compromised, an error is returned. -func BImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { - // catch panics and return them as errors - const currentVersion = 6 - version := -1 // unknown version - defer func() { - if e := recover(); e != nil { - // Return a (possibly nil or incomplete) package unchanged (see #16088). - if version > currentVersion { - err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) - } else { - err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) - } - } - }() - - p := importer{ - imports: imports, - data: data, - importpath: path, - version: version, - strList: []string{""}, // empty string is mapped to 0 - pathList: []string{""}, // empty string is mapped to 0 - fake: fakeFileSet{ - fset: fset, - files: make(map[string]*fileInfo), - }, - } - defer p.fake.setLines() // set lines for files in fset - - // read version info - var versionstr string - if b := p.rawByte(); b == 'c' || b == 'd' { - // Go1.7 encoding; first byte encodes low-level - // encoding format (compact vs debug). - // For backward-compatibility only (avoid problems with - // old installed packages). Newly compiled packages use - // the extensible format string. - // TODO(gri) Remove this support eventually; after Go1.8. - if b == 'd' { - p.debugFormat = true - } - p.trackAllTypes = p.rawByte() == 'a' - p.posInfoFormat = p.int() != 0 - versionstr = p.string() - if versionstr == "v1" { - version = 0 - } - } else { - // Go1.8 extensible encoding - // read version string and extract version number (ignore anything after the version number) - versionstr = p.rawStringln(b) - if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" { - if v, err := strconv.Atoi(s[1]); err == nil && v > 0 { - version = v - } - } - } - p.version = version - - // read version specific flags - extend as necessary - switch p.version { - // case currentVersion: - // ... - // fallthrough - case currentVersion, 5, 4, 3, 2, 1: - p.debugFormat = p.rawStringln(p.rawByte()) == "debug" - p.trackAllTypes = p.int() != 0 - p.posInfoFormat = p.int() != 0 - case 0: - // Go1.7 encoding format - nothing to do here - default: - errorf("unknown bexport format version %d (%q)", p.version, versionstr) - } - - // --- generic export data --- - - // populate typList with predeclared "known" types - p.typList = append(p.typList, predeclared()...) - - // read package data - pkg = p.pkg() - - // read objects of phase 1 only (see cmd/compile/internal/gc/bexport.go) - objcount := 0 - for { - tag := p.tagOrIndex() - if tag == endTag { - break - } - p.obj(tag) - objcount++ - } - - // self-verification - if count := p.int(); count != objcount { - errorf("got %d objects; want %d", objcount, count) - } - - // ignore compiler-specific import data - - // complete interfaces - // TODO(gri) re-investigate if we still need to do this in a delayed fashion - for _, typ := range p.interfaceList { - typ.Complete() - } - - // record all referenced packages as imports - list := append(([]*types.Package)(nil), p.pkgList[1:]...) - sort.Sort(byPath(list)) - pkg.SetImports(list) - - // package was imported completely and without errors - pkg.MarkComplete() - - return p.read, pkg, nil -} - func errorf(format string, args ...interface{}) { panic(fmt.Sprintf(format, args...)) } -func (p *importer) pkg() *types.Package { - // if the package was seen before, i is its index (>= 0) - i := p.tagOrIndex() - if i >= 0 { - return p.pkgList[i] - } - - // otherwise, i is the package tag (< 0) - if i != packageTag { - errorf("unexpected package tag %d version %d", i, p.version) - } - - // read package data - name := p.string() - var path string - if p.version >= 5 { - path = p.path() - } else { - path = p.string() - } - if p.version >= 6 { - p.int() // package height; unused by go/types - } - - // we should never see an empty package name - if name == "" { - errorf("empty package name in import") - } - - // an empty path denotes the package we are currently importing; - // it must be the first package we see - if (path == "") != (len(p.pkgList) == 0) { - errorf("package path %q for pkg index %d", path, len(p.pkgList)) - } - - // if the package was imported before, use that one; otherwise create a new one - if path == "" { - path = p.importpath - } - pkg := p.imports[path] - if pkg == nil { - pkg = types.NewPackage(path, name) - p.imports[path] = pkg - } else if pkg.Name() != name { - errorf("conflicting names %s and %s for package %q", pkg.Name(), name, path) - } - p.pkgList = append(p.pkgList, pkg) - - return pkg -} - -// objTag returns the tag value for each object kind. -func objTag(obj types.Object) int { - switch obj.(type) { - case *types.Const: - return constTag - case *types.TypeName: - return typeTag - case *types.Var: - return varTag - case *types.Func: - return funcTag - default: - errorf("unexpected object: %v (%T)", obj, obj) // panics - panic("unreachable") - } -} - -func sameObj(a, b types.Object) bool { - // Because unnamed types are not canonicalized, we cannot simply compare types for - // (pointer) identity. - // Ideally we'd check equality of constant values as well, but this is good enough. - return objTag(a) == objTag(b) && types.Identical(a.Type(), b.Type()) -} - -func (p *importer) declare(obj types.Object) { - pkg := obj.Pkg() - if alt := pkg.Scope().Insert(obj); alt != nil { - // This can only trigger if we import a (non-type) object a second time. - // Excluding type aliases, this cannot happen because 1) we only import a package - // once; and b) we ignore compiler-specific export data which may contain - // functions whose inlined function bodies refer to other functions that - // were already imported. - // However, type aliases require reexporting the original type, so we need - // to allow it (see also the comment in cmd/compile/internal/gc/bimport.go, - // method importer.obj, switch case importing functions). - // TODO(gri) review/update this comment once the gc compiler handles type aliases. - if !sameObj(obj, alt) { - errorf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", obj, alt) - } - } -} - -func (p *importer) obj(tag int) { - switch tag { - case constTag: - pos := p.pos() - pkg, name := p.qualifiedName() - typ := p.typ(nil, nil) - val := p.value() - p.declare(types.NewConst(pos, pkg, name, typ, val)) - - case aliasTag: - // TODO(gri) verify type alias hookup is correct - pos := p.pos() - pkg, name := p.qualifiedName() - typ := p.typ(nil, nil) - p.declare(types.NewTypeName(pos, pkg, name, typ)) - - case typeTag: - p.typ(nil, nil) - - case varTag: - pos := p.pos() - pkg, name := p.qualifiedName() - typ := p.typ(nil, nil) - p.declare(types.NewVar(pos, pkg, name, typ)) - - case funcTag: - pos := p.pos() - pkg, name := p.qualifiedName() - params, isddd := p.paramList() - result, _ := p.paramList() - sig := types.NewSignature(nil, params, result, isddd) - p.declare(types.NewFunc(pos, pkg, name, sig)) - - default: - errorf("unexpected object tag %d", tag) - } -} - const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go -func (p *importer) pos() token.Pos { - if !p.posInfoFormat { - return token.NoPos - } - - file := p.prevFile - line := p.prevLine - delta := p.int() - line += delta - if p.version >= 5 { - if delta == deltaNewFile { - if n := p.int(); n >= 0 { - // file changed - file = p.path() - line = n - } - } - } else { - if delta == 0 { - if n := p.int(); n >= 0 { - // file changed - file = p.prevFile[:n] + p.string() - line = p.int() - } - } - } - p.prevFile = file - p.prevLine = line - - return p.fake.pos(file, line, 0) -} - // Synthesize a token.Pos type fakeFileSet struct { fset *token.FileSet @@ -389,205 +73,6 @@ var ( fakeLinesOnce sync.Once ) -func (p *importer) qualifiedName() (pkg *types.Package, name string) { - name = p.string() - pkg = p.pkg() - return -} - -func (p *importer) record(t types.Type) { - p.typList = append(p.typList, t) -} - -// A dddSlice is a types.Type representing ...T parameters. -// It only appears for parameter types and does not escape -// the importer. -type dddSlice struct { - elem types.Type -} - -func (t *dddSlice) Underlying() types.Type { return t } -func (t *dddSlice) String() string { return "..." + t.elem.String() } - -// parent is the package which declared the type; parent == nil means -// the package currently imported. The parent package is needed for -// exported struct fields and interface methods which don't contain -// explicit package information in the export data. -// -// A non-nil tname is used as the "owner" of the result type; i.e., -// the result type is the underlying type of tname. tname is used -// to give interface methods a named receiver type where possible. -func (p *importer) typ(parent *types.Package, tname *types.Named) types.Type { - // if the type was seen before, i is its index (>= 0) - i := p.tagOrIndex() - if i >= 0 { - return p.typList[i] - } - - // otherwise, i is the type tag (< 0) - switch i { - case namedTag: - // read type object - pos := p.pos() - parent, name := p.qualifiedName() - scope := parent.Scope() - obj := scope.Lookup(name) - - // if the object doesn't exist yet, create and insert it - if obj == nil { - obj = types.NewTypeName(pos, parent, name, nil) - scope.Insert(obj) - } - - if _, ok := obj.(*types.TypeName); !ok { - errorf("pkg = %s, name = %s => %s", parent, name, obj) - } - - // associate new named type with obj if it doesn't exist yet - t0 := types.NewNamed(obj.(*types.TypeName), nil, nil) - - // but record the existing type, if any - tname := obj.Type().(*types.Named) // tname is either t0 or the existing type - p.record(tname) - - // read underlying type - t0.SetUnderlying(p.typ(parent, t0)) - - // interfaces don't have associated methods - if types.IsInterface(t0) { - return tname - } - - // read associated methods - for i := p.int(); i > 0; i-- { - // TODO(gri) replace this with something closer to fieldName - pos := p.pos() - name := p.string() - if !exported(name) { - p.pkg() - } - - recv, _ := p.paramList() // TODO(gri) do we need a full param list for the receiver? - params, isddd := p.paramList() - result, _ := p.paramList() - p.int() // go:nointerface pragma - discarded - - sig := types.NewSignature(recv.At(0), params, result, isddd) - t0.AddMethod(types.NewFunc(pos, parent, name, sig)) - } - - return tname - - case arrayTag: - t := new(types.Array) - if p.trackAllTypes { - p.record(t) - } - - n := p.int64() - *t = *types.NewArray(p.typ(parent, nil), n) - return t - - case sliceTag: - t := new(types.Slice) - if p.trackAllTypes { - p.record(t) - } - - *t = *types.NewSlice(p.typ(parent, nil)) - return t - - case dddTag: - t := new(dddSlice) - if p.trackAllTypes { - p.record(t) - } - - t.elem = p.typ(parent, nil) - return t - - case structTag: - t := new(types.Struct) - if p.trackAllTypes { - p.record(t) - } - - *t = *types.NewStruct(p.fieldList(parent)) - return t - - case pointerTag: - t := new(types.Pointer) - if p.trackAllTypes { - p.record(t) - } - - *t = *types.NewPointer(p.typ(parent, nil)) - return t - - case signatureTag: - t := new(types.Signature) - if p.trackAllTypes { - p.record(t) - } - - params, isddd := p.paramList() - result, _ := p.paramList() - *t = *types.NewSignature(nil, params, result, isddd) - return t - - case interfaceTag: - // Create a dummy entry in the type list. This is safe because we - // cannot expect the interface type to appear in a cycle, as any - // such cycle must contain a named type which would have been - // first defined earlier. - // TODO(gri) Is this still true now that we have type aliases? - // See issue #23225. - n := len(p.typList) - if p.trackAllTypes { - p.record(nil) - } - - var embeddeds []types.Type - for n := p.int(); n > 0; n-- { - p.pos() - embeddeds = append(embeddeds, p.typ(parent, nil)) - } - - t := newInterface(p.methodList(parent, tname), embeddeds) - p.interfaceList = append(p.interfaceList, t) - if p.trackAllTypes { - p.typList[n] = t - } - return t - - case mapTag: - t := new(types.Map) - if p.trackAllTypes { - p.record(t) - } - - key := p.typ(parent, nil) - val := p.typ(parent, nil) - *t = *types.NewMap(key, val) - return t - - case chanTag: - t := new(types.Chan) - if p.trackAllTypes { - p.record(t) - } - - dir := chanDir(p.int()) - val := p.typ(parent, nil) - *t = *types.NewChan(dir, val) - return t - - default: - errorf("unexpected type tag %d", i) // panics - panic("unreachable") - } -} - func chanDir(d int) types.ChanDir { // tag values must match the constants in cmd/compile/internal/gc/go.go switch d { @@ -603,394 +88,6 @@ func chanDir(d int) types.ChanDir { } } -func (p *importer) fieldList(parent *types.Package) (fields []*types.Var, tags []string) { - if n := p.int(); n > 0 { - fields = make([]*types.Var, n) - tags = make([]string, n) - for i := range fields { - fields[i], tags[i] = p.field(parent) - } - } - return -} - -func (p *importer) field(parent *types.Package) (*types.Var, string) { - pos := p.pos() - pkg, name, alias := p.fieldName(parent) - typ := p.typ(parent, nil) - tag := p.string() - - anonymous := false - if name == "" { - // anonymous field - typ must be T or *T and T must be a type name - switch typ := deref(typ).(type) { - case *types.Basic: // basic types are named types - pkg = nil // // objects defined in Universe scope have no package - name = typ.Name() - case *types.Named: - name = typ.Obj().Name() - default: - errorf("named base type expected") - } - anonymous = true - } else if alias { - // anonymous field: we have an explicit name because it's an alias - anonymous = true - } - - return types.NewField(pos, pkg, name, typ, anonymous), tag -} - -func (p *importer) methodList(parent *types.Package, baseType *types.Named) (methods []*types.Func) { - if n := p.int(); n > 0 { - methods = make([]*types.Func, n) - for i := range methods { - methods[i] = p.method(parent, baseType) - } - } - return -} - -func (p *importer) method(parent *types.Package, baseType *types.Named) *types.Func { - pos := p.pos() - pkg, name, _ := p.fieldName(parent) - // If we don't have a baseType, use a nil receiver. - // A receiver using the actual interface type (which - // we don't know yet) will be filled in when we call - // types.Interface.Complete. - var recv *types.Var - if baseType != nil { - recv = types.NewVar(token.NoPos, parent, "", baseType) - } - params, isddd := p.paramList() - result, _ := p.paramList() - sig := types.NewSignature(recv, params, result, isddd) - return types.NewFunc(pos, pkg, name, sig) -} - -func (p *importer) fieldName(parent *types.Package) (pkg *types.Package, name string, alias bool) { - name = p.string() - pkg = parent - if pkg == nil { - // use the imported package instead - pkg = p.pkgList[0] - } - if p.version == 0 && name == "_" { - // version 0 didn't export a package for _ fields - return - } - switch name { - case "": - // 1) field name matches base type name and is exported: nothing to do - case "?": - // 2) field name matches base type name and is not exported: need package - name = "" - pkg = p.pkg() - case "@": - // 3) field name doesn't match type name (alias) - name = p.string() - alias = true - fallthrough - default: - if !exported(name) { - pkg = p.pkg() - } - } - return -} - -func (p *importer) paramList() (*types.Tuple, bool) { - n := p.int() - if n == 0 { - return nil, false - } - // negative length indicates unnamed parameters - named := true - if n < 0 { - n = -n - named = false - } - // n > 0 - params := make([]*types.Var, n) - isddd := false - for i := range params { - params[i], isddd = p.param(named) - } - return types.NewTuple(params...), isddd -} - -func (p *importer) param(named bool) (*types.Var, bool) { - t := p.typ(nil, nil) - td, isddd := t.(*dddSlice) - if isddd { - t = types.NewSlice(td.elem) - } - - var pkg *types.Package - var name string - if named { - name = p.string() - if name == "" { - errorf("expected named parameter") - } - if name != "_" { - pkg = p.pkg() - } - if i := strings.Index(name, "·"); i > 0 { - name = name[:i] // cut off gc-specific parameter numbering - } - } - - // read and discard compiler-specific info - p.string() - - return types.NewVar(token.NoPos, pkg, name, t), isddd -} - -func exported(name string) bool { - ch, _ := utf8.DecodeRuneInString(name) - return unicode.IsUpper(ch) -} - -func (p *importer) value() constant.Value { - switch tag := p.tagOrIndex(); tag { - case falseTag: - return constant.MakeBool(false) - case trueTag: - return constant.MakeBool(true) - case int64Tag: - return constant.MakeInt64(p.int64()) - case floatTag: - return p.float() - case complexTag: - re := p.float() - im := p.float() - return constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) - case stringTag: - return constant.MakeString(p.string()) - case unknownTag: - return constant.MakeUnknown() - default: - errorf("unexpected value tag %d", tag) // panics - panic("unreachable") - } -} - -func (p *importer) float() constant.Value { - sign := p.int() - if sign == 0 { - return constant.MakeInt64(0) - } - - exp := p.int() - mant := []byte(p.string()) // big endian - - // remove leading 0's if any - for len(mant) > 0 && mant[0] == 0 { - mant = mant[1:] - } - - // convert to little endian - // TODO(gri) go/constant should have a more direct conversion function - // (e.g., once it supports a big.Float based implementation) - for i, j := 0, len(mant)-1; i < j; i, j = i+1, j-1 { - mant[i], mant[j] = mant[j], mant[i] - } - - // adjust exponent (constant.MakeFromBytes creates an integer value, - // but mant represents the mantissa bits such that 0.5 <= mant < 1.0) - exp -= len(mant) << 3 - if len(mant) > 0 { - for msd := mant[len(mant)-1]; msd&0x80 == 0; msd <<= 1 { - exp++ - } - } - - x := constant.MakeFromBytes(mant) - switch { - case exp < 0: - d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp)) - x = constant.BinaryOp(x, token.QUO, d) - case exp > 0: - x = constant.Shift(x, token.SHL, uint(exp)) - } - - if sign < 0 { - x = constant.UnaryOp(token.SUB, x, 0) - } - return x -} - -// ---------------------------------------------------------------------------- -// Low-level decoders - -func (p *importer) tagOrIndex() int { - if p.debugFormat { - p.marker('t') - } - - return int(p.rawInt64()) -} - -func (p *importer) int() int { - x := p.int64() - if int64(int(x)) != x { - errorf("exported integer too large") - } - return int(x) -} - -func (p *importer) int64() int64 { - if p.debugFormat { - p.marker('i') - } - - return p.rawInt64() -} - -func (p *importer) path() string { - if p.debugFormat { - p.marker('p') - } - // if the path was seen before, i is its index (>= 0) - // (the empty string is at index 0) - i := p.rawInt64() - if i >= 0 { - return p.pathList[i] - } - // otherwise, i is the negative path length (< 0) - a := make([]string, -i) - for n := range a { - a[n] = p.string() - } - s := strings.Join(a, "/") - p.pathList = append(p.pathList, s) - return s -} - -func (p *importer) string() string { - if p.debugFormat { - p.marker('s') - } - // if the string was seen before, i is its index (>= 0) - // (the empty string is at index 0) - i := p.rawInt64() - if i >= 0 { - return p.strList[i] - } - // otherwise, i is the negative string length (< 0) - if n := int(-i); n <= cap(p.buf) { - p.buf = p.buf[:n] - } else { - p.buf = make([]byte, n) - } - for i := range p.buf { - p.buf[i] = p.rawByte() - } - s := string(p.buf) - p.strList = append(p.strList, s) - return s -} - -func (p *importer) marker(want byte) { - if got := p.rawByte(); got != want { - errorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read) - } - - pos := p.read - if n := int(p.rawInt64()); n != pos { - errorf("incorrect position: got %d; want %d", n, pos) - } -} - -// rawInt64 should only be used by low-level decoders. -func (p *importer) rawInt64() int64 { - i, err := binary.ReadVarint(p) - if err != nil { - errorf("read error: %v", err) - } - return i -} - -// rawStringln should only be used to read the initial version string. -func (p *importer) rawStringln(b byte) string { - p.buf = p.buf[:0] - for b != '\n' { - p.buf = append(p.buf, b) - b = p.rawByte() - } - return string(p.buf) -} - -// needed for binary.ReadVarint in rawInt64 -func (p *importer) ReadByte() (byte, error) { - return p.rawByte(), nil -} - -// byte is the bottleneck interface for reading p.data. -// It unescapes '|' 'S' to '$' and '|' '|' to '|'. -// rawByte should only be used by low-level decoders. -func (p *importer) rawByte() byte { - b := p.data[0] - r := 1 - if b == '|' { - b = p.data[1] - r = 2 - switch b { - case 'S': - b = '$' - case '|': - // nothing to do - default: - errorf("unexpected escape sequence in export data") - } - } - p.data = p.data[r:] - p.read += r - return b - -} - -// ---------------------------------------------------------------------------- -// Export format - -// Tags. Must be < 0. -const ( - // Objects - packageTag = -(iota + 1) - constTag - typeTag - varTag - funcTag - endTag - - // Types - namedTag - arrayTag - sliceTag - dddTag - structTag - pointerTag - signatureTag - interfaceTag - mapTag - chanTag - - // Values - falseTag - trueTag - int64Tag - floatTag - fractionTag // not used by gc - complexTag - stringTag - nilTag // only used by gc (appears in exported inlined function bodies) - unknownTag // not used by gc (only appears in packages with errors) - - // Type aliases - aliasTag -) - var predeclOnce sync.Once var predecl []types.Type // initialized lazily diff --git a/go/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go b/go/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go index 0372fb3a646..b1223713b94 100644 --- a/go/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go +++ b/go/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go @@ -7,6 +7,18 @@ // Package gcimporter provides various functions for reading // gc-generated object files that can be used to implement the // Importer interface defined by the Go 1.5 standard library package. +// +// The encoding is deterministic: if the encoder is applied twice to +// the same types.Package data structure, both encodings are equal. +// This property may be important to avoid spurious changes in +// applications such as build systems. +// +// However, the encoder is not necessarily idempotent. Importing an +// exported package may yield a types.Package that, while it +// represents the same set of Go types as the original, may differ in +// the details of its internal representation. Because of these +// differences, re-encoding the imported package may yield a +// different, but equally valid, encoding of the package. package gcimporter // import "golang.org/x/tools/internal/gcimporter" import ( @@ -218,20 +230,17 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func // Or, define a new standard go/types/gcexportdata package. fset := token.NewFileSet() - // The indexed export format starts with an 'i'; the older - // binary export format starts with a 'c', 'd', or 'v' - // (from "version"). Select appropriate importer. + // Select appropriate importer. if len(data) > 0 { switch data[0] { - case 'i': + case 'v', 'c', 'd': // binary, till go1.10 + return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0]) + + case 'i': // indexed, till go1.19 _, pkg, err := IImportData(fset, packages, data[1:], id) return pkg, err - case 'v', 'c', 'd': - _, pkg, err := BImportData(fset, packages, data, id) - return pkg, err - - case 'u': + case 'u': // unified, from go1.20 _, pkg, err := UImportData(fset, packages, data[1:size], id) return pkg, err diff --git a/go/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/go/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index ba53cdcdd10..3e3fce1731f 100644 --- a/go/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/go/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -22,17 +22,23 @@ import ( "strconv" "strings" + "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/internal/tokeninternal" "golang.org/x/tools/internal/typeparams" ) // IExportShallow encodes "shallow" export data for the specified package. // -// No promises are made about the encoding other than that it can be -// decoded by the same version of IIExportShallow. If you plan to save -// export data in the file system, be sure to include a cryptographic -// digest of the executable in the key to avoid version skew. -func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) { +// No promises are made about the encoding other than that it can be decoded by +// the same version of IIExportShallow. If you plan to save export data in the +// file system, be sure to include a cryptographic digest of the executable in +// the key to avoid version skew. +// +// If the provided reportf func is non-nil, it will be used for reporting bugs +// encountered during export. +// TODO(rfindley): remove reportf when we are confident enough in the new +// objectpath encoding. +func IExportShallow(fset *token.FileSet, pkg *types.Package, reportf ReportFunc) ([]byte, error) { // In principle this operation can only fail if out.Write fails, // but that's impossible for bytes.Buffer---and as a matter of // fact iexportCommon doesn't even check for I/O errors. @@ -40,26 +46,34 @@ func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) { // TODO(adonovan): use byte slices throughout, avoiding copying. const bundle, shallow = false, true var out bytes.Buffer - err := iexportCommon(&out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg}) + err := iexportCommon(&out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg}, reportf) return out.Bytes(), err } -// IImportShallow decodes "shallow" types.Package data encoded by IExportShallow -// in the same executable. This function cannot import data from +// IImportShallow decodes "shallow" types.Package data encoded by +// IExportShallow in the same executable. This function cannot import data from // cmd/compile or gcexportdata.Write. -func IImportShallow(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string, insert InsertType) (*types.Package, error) { +// +// The importer calls getPackages to obtain package symbols for all +// packages mentioned in the export data, including the one being +// decoded. +// +// If the provided reportf func is non-nil, it will be used for reporting bugs +// encountered during import. +// TODO(rfindley): remove reportf when we are confident enough in the new +// objectpath encoding. +func IImportShallow(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, path string, reportf ReportFunc) (*types.Package, error) { const bundle = false - pkgs, err := iimportCommon(fset, imports, data, bundle, path, insert) + const shallow = true + pkgs, err := iimportCommon(fset, getPackages, data, bundle, path, shallow, reportf) if err != nil { return nil, err } return pkgs[0], nil } -// InsertType is the type of a function that creates a types.TypeName -// object for a named type and inserts it into the scope of the -// specified Package. -type InsertType = func(pkg *types.Package, name string) +// ReportFunc is the type of a function used to report formatted bugs. +type ReportFunc = func(string, ...interface{}) // Current bundled export format version. Increase with each format change. // 0: initial implementation @@ -72,16 +86,16 @@ const bundleVersion = 0 // so that calls to IImportData can override with a provided package path. func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) error { const bundle, shallow = false, false - return iexportCommon(out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg}) + return iexportCommon(out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg}, nil) } // IExportBundle writes an indexed export bundle for pkgs to out. func IExportBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error { const bundle, shallow = true, false - return iexportCommon(out, fset, bundle, shallow, iexportVersion, pkgs) + return iexportCommon(out, fset, bundle, shallow, iexportVersion, pkgs, nil) } -func iexportCommon(out io.Writer, fset *token.FileSet, bundle, shallow bool, version int, pkgs []*types.Package) (err error) { +func iexportCommon(out io.Writer, fset *token.FileSet, bundle, shallow bool, version int, pkgs []*types.Package, reportf ReportFunc) (err error) { if !debug { defer func() { if e := recover(); e != nil { @@ -99,6 +113,7 @@ func iexportCommon(out io.Writer, fset *token.FileSet, bundle, shallow bool, ver fset: fset, version: version, shallow: shallow, + reportf: reportf, allPkgs: map[*types.Package]bool{}, stringIndex: map[string]uint64{}, declIndex: map[types.Object]uint64{}, @@ -313,8 +328,10 @@ type iexporter struct { out *bytes.Buffer version int - shallow bool // don't put types from other packages in the index - localpkg *types.Package // (nil in bundle mode) + shallow bool // don't put types from other packages in the index + objEncoder *objectpath.Encoder // encodes objects from other packages in shallow mode; lazily allocated + reportf ReportFunc // if non-nil, used to report bugs + localpkg *types.Package // (nil in bundle mode) // allPkgs tracks all packages that have been referenced by // the export data, so we can ensure to include them in the @@ -354,6 +371,17 @@ func (p *iexporter) trace(format string, args ...interface{}) { fmt.Printf(strings.Repeat("..", p.indent)+format+"\n", args...) } +// objectpathEncoder returns the lazily allocated objectpath.Encoder to use +// when encoding objects in other packages during shallow export. +// +// Using a shared Encoder amortizes some of cost of objectpath search. +func (p *iexporter) objectpathEncoder() *objectpath.Encoder { + if p.objEncoder == nil { + p.objEncoder = new(objectpath.Encoder) + } + return p.objEncoder +} + // stringOff returns the offset of s within the string section. // If not already present, it's added to the end. func (p *iexporter) stringOff(s string) uint64 { @@ -413,7 +441,6 @@ type exportWriter struct { p *iexporter data intWriter - currPkg *types.Package prevFile string prevLine int64 prevColumn int64 @@ -436,7 +463,6 @@ func (p *iexporter) doDecl(obj types.Object) { }() } w := p.newWriter() - w.setPkg(obj.Pkg(), false) switch obj := obj.(type) { case *types.Var: @@ -673,6 +699,9 @@ func (w *exportWriter) qualifiedType(obj *types.TypeName) { w.pkg(obj.Pkg()) } +// TODO(rfindley): what does 'pkg' even mean here? It would be better to pass +// it in explicitly into signatures and structs that may use it for +// constructing fields. func (w *exportWriter) typ(t types.Type, pkg *types.Package) { w.data.uint64(w.p.typOff(t, pkg)) } @@ -764,30 +793,53 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { case *types.Signature: w.startType(signatureType) - w.setPkg(pkg, true) + w.pkg(pkg) w.signature(t) case *types.Struct: w.startType(structType) n := t.NumFields() + // Even for struct{} we must emit some qualifying package, because that's + // what the compiler does, and thus that's what the importer expects. + fieldPkg := pkg if n > 0 { - w.setPkg(t.Field(0).Pkg(), true) // qualifying package for field objects - } else { - w.setPkg(pkg, true) + fieldPkg = t.Field(0).Pkg() } + if fieldPkg == nil { + // TODO(rfindley): improve this very hacky logic. + // + // The importer expects a package to be set for all struct types, even + // those with no fields. A better encoding might be to set NumFields + // before pkg. setPkg panics with a nil package, which may be possible + // to reach with invalid packages (and perhaps valid packages, too?), so + // (arbitrarily) set the localpkg if available. + // + // Alternatively, we may be able to simply guarantee that pkg != nil, by + // reconsidering the encoding of constant values. + if w.p.shallow { + fieldPkg = w.p.localpkg + } else { + panic(internalErrorf("no package to set for empty struct")) + } + } + w.pkg(fieldPkg) w.uint64(uint64(n)) + for i := 0; i < n; i++ { f := t.Field(i) + if w.p.shallow { + w.objectPath(f) + } w.pos(f.Pos()) w.string(f.Name()) // unexported fields implicitly qualified by prior setPkg - w.typ(f.Type(), pkg) + w.typ(f.Type(), fieldPkg) w.bool(f.Anonymous()) w.string(t.Tag(i)) // note (or tag) } case *types.Interface: w.startType(interfaceType) - w.setPkg(pkg, true) + w.pkg(pkg) n := t.NumEmbeddeds() w.uint64(uint64(n)) @@ -802,10 +854,16 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { w.typ(ft, tPkg) } + // See comment for struct fields. In shallow mode we change the encoding + // for interface methods that are promoted from other packages. + n = t.NumExplicitMethods() w.uint64(uint64(n)) for i := 0; i < n; i++ { m := t.ExplicitMethod(i) + if w.p.shallow { + w.objectPath(m) + } w.pos(m.Pos()) w.string(m.Name()) sig, _ := m.Type().(*types.Signature) @@ -827,12 +885,57 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { } } -func (w *exportWriter) setPkg(pkg *types.Package, write bool) { - if write { - w.pkg(pkg) +// objectPath writes the package and objectPath to use to look up obj in a +// different package, when encoding in "shallow" mode. +// +// When doing a shallow import, the importer creates only the local package, +// and requests package symbols for dependencies from the client. +// However, certain types defined in the local package may hold objects defined +// (perhaps deeply) within another package. +// +// For example, consider the following: +// +// package a +// func F() chan * map[string] struct { X int } +// +// package b +// import "a" +// var B = a.F() +// +// In this example, the type of b.B holds fields defined in package a. +// In order to have the correct canonical objects for the field defined in the +// type of B, they are encoded as objectPaths and later looked up in the +// importer. The same problem applies to interface methods. +func (w *exportWriter) objectPath(obj types.Object) { + if obj.Pkg() == nil || obj.Pkg() == w.p.localpkg { + // obj.Pkg() may be nil for the builtin error.Error. + // In this case, or if obj is declared in the local package, no need to + // encode. + w.string("") + return } - - w.currPkg = pkg + objectPath, err := w.p.objectpathEncoder().For(obj) + if err != nil { + // Fall back to the empty string, which will cause the importer to create a + // new object. + // + // This is incorrect in shallow mode (golang/go#60819), but matches + // the previous behavior. This code is defensive, as it is hard to + // prove that the objectpath algorithm will succeed in all cases, and + // creating a new object sort of works. + // (we didn't notice the bug during months of gopls@v0.12.0 testing) + // + // However, report a bug so that we can eventually have confidence + // that export/import is producing a correct package. + // + // TODO: remove reportf once we have such confidence. + objectPath = "" + if w.p.reportf != nil { + w.p.reportf("unable to encode object %q in package %q: %v", obj.Name(), obj.Pkg().Path(), err) + } + } + w.string(string(objectPath)) + w.pkg(obj.Pkg()) } func (w *exportWriter) signature(sig *types.Signature) { @@ -913,6 +1016,17 @@ func (w *exportWriter) value(typ types.Type, v constant.Value) { w.int64(int64(v.Kind())) } + if v.Kind() == constant.Unknown { + // golang/go#60605: treat unknown constant values as if they have invalid type + // + // This loses some fidelity over the package type-checked from source, but that + // is acceptable. + // + // TODO(rfindley): we should switch on the recorded constant kind rather + // than the constant type + return + } + switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType { case types.IsBoolean: w.bool(constant.BoolVal(v)) @@ -969,6 +1083,16 @@ func constantToFloat(x constant.Value) *big.Float { return &f } +func valueToRat(x constant.Value) *big.Rat { + // Convert little-endian to big-endian. + // I can't believe this is necessary. + bytes := constant.Bytes(x) + for i := 0; i < len(bytes)/2; i++ { + bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] + } + return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) +} + // mpint exports a multi-precision integer. // // For unsigned types, small values are written out as a single @@ -1178,3 +1302,19 @@ func (q *objQueue) popHead() types.Object { q.head++ return obj } + +// internalError represents an error generated inside this package. +type internalError string + +func (e internalError) Error() string { return "gcimporter: " + string(e) } + +// TODO(adonovan): make this call panic, so that it's symmetric with errorf. +// Otherwise it's easy to forget to do anything with the error. +// +// TODO(adonovan): also, consider switching the names "errorf" and +// "internalErrorf" as the former is used for bugs, whose cause is +// internal inconsistency, whereas the latter is used for ordinary +// situations like bad input, whose cause is external. +func internalErrorf(format string, args ...interface{}) error { + return internalError(fmt.Sprintf(format, args...)) +} diff --git a/go/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/go/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 448f903e86a..8e64cf644fc 100644 --- a/go/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/go/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -21,6 +21,7 @@ import ( "sort" "strings" + "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/internal/typeparams" ) @@ -85,7 +86,7 @@ const ( // If the export data version is not recognized or the format is otherwise // compromised, an error is returned. func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) { - pkgs, err := iimportCommon(fset, imports, data, false, path, nil) + pkgs, err := iimportCommon(fset, GetPackagesFromMap(imports), data, false, path, false, nil) if err != nil { return 0, nil, err } @@ -94,10 +95,49 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] // IImportBundle imports a set of packages from the serialized package bundle. func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) { - return iimportCommon(fset, imports, data, true, "", nil) + return iimportCommon(fset, GetPackagesFromMap(imports), data, true, "", false, nil) } -func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data []byte, bundle bool, path string, insert InsertType) (pkgs []*types.Package, err error) { +// A GetPackagesFunc function obtains the non-nil symbols for a set of +// packages, creating and recursively importing them as needed. An +// implementation should store each package symbol is in the Pkg +// field of the items array. +// +// Any error causes importing to fail. This can be used to quickly read +// the import manifest of an export data file without fully decoding it. +type GetPackagesFunc = func(items []GetPackagesItem) error + +// A GetPackagesItem is a request from the importer for the package +// symbol of the specified name and path. +type GetPackagesItem struct { + Name, Path string + Pkg *types.Package // to be filled in by GetPackagesFunc call + + // private importer state + pathOffset uint64 + nameIndex map[string]uint64 +} + +// GetPackagesFromMap returns a GetPackagesFunc that retrieves +// packages from the given map of package path to package. +// +// The returned function may mutate m: each requested package that is not +// found is created with types.NewPackage and inserted into m. +func GetPackagesFromMap(m map[string]*types.Package) GetPackagesFunc { + return func(items []GetPackagesItem) error { + for i, item := range items { + pkg, ok := m[item.Path] + if !ok { + pkg = types.NewPackage(item.Path, item.Name) + m[item.Path] = pkg + } + items[i].Pkg = pkg + } + return nil + } +} + +func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, bundle bool, path string, shallow bool, reportf ReportFunc) (pkgs []*types.Package, err error) { const currentVersion = iexportVersionCurrent version := int64(-1) if !debug { @@ -108,7 +148,7 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data } else if version > currentVersion { err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) } else { - err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) + err = fmt.Errorf("internal error while importing %q (%v); please report an issue", path, e) } } }() @@ -117,11 +157,8 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data r := &intReader{bytes.NewReader(data), path} if bundle { - bundleVersion := r.uint64() - switch bundleVersion { - case bundleVersion: - default: - errorf("unknown bundle format version %d", bundleVersion) + if v := r.uint64(); v != bundleVersion { + errorf("unknown bundle format version %d", v) } } @@ -139,7 +176,7 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data sLen := int64(r.uint64()) var fLen int64 var fileOffset []uint64 - if insert != nil { + if shallow { // Shallow mode uses a different position encoding. fLen = int64(r.uint64()) fileOffset = make([]uint64, r.uint64()) @@ -158,7 +195,8 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data p := iimporter{ version: int(version), ipath: path, - insert: insert, + shallow: shallow, + reportf: reportf, stringData: stringData, stringCache: make(map[uint64]string), @@ -185,8 +223,9 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data p.typCache[uint64(i)] = pt } - pkgList := make([]*types.Package, r.uint64()) - for i := range pkgList { + // Gather the relevant packages from the manifest. + items := make([]GetPackagesItem, r.uint64()) + for i := range items { pkgPathOff := r.uint64() pkgPath := p.stringAt(pkgPathOff) pkgName := p.stringAt(r.uint64()) @@ -195,30 +234,42 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data if pkgPath == "" { pkgPath = path } - pkg := imports[pkgPath] - if pkg == nil { - pkg = types.NewPackage(pkgPath, pkgName) - imports[pkgPath] = pkg - } else if pkg.Name() != pkgName { - errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path) - } - if i == 0 && !bundle { - p.localpkg = pkg - } - - p.pkgCache[pkgPathOff] = pkg + items[i].Name = pkgName + items[i].Path = pkgPath + items[i].pathOffset = pkgPathOff // Read index for package. nameIndex := make(map[string]uint64) nSyms := r.uint64() - // In shallow mode we don't expect an index for other packages. - assert(nSyms == 0 || p.localpkg == pkg || p.insert == nil) + // In shallow mode, only the current package (i=0) has an index. + assert(!(shallow && i > 0 && nSyms != 0)) for ; nSyms > 0; nSyms-- { name := p.stringAt(r.uint64()) nameIndex[name] = r.uint64() } - p.pkgIndex[pkg] = nameIndex + items[i].nameIndex = nameIndex + } + + // Request packages all at once from the client, + // enabling a parallel implementation. + if err := getPackages(items); err != nil { + return nil, err // don't wrap this error + } + + // Check the results and complete the index. + pkgList := make([]*types.Package, len(items)) + for i, item := range items { + pkg := item.Pkg + if pkg == nil { + errorf("internal error: getPackages returned nil package for %q", item.Path) + } else if pkg.Path() != item.Path { + errorf("internal error: getPackages returned wrong path %q, want %q", pkg.Path(), item.Path) + } else if pkg.Name() != item.Name { + errorf("internal error: getPackages returned wrong name %s for package %q, want %s", pkg.Name(), item.Path, item.Name) + } + p.pkgCache[item.pathOffset] = pkg + p.pkgIndex[pkg] = item.nameIndex pkgList[i] = pkg } @@ -277,6 +328,13 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data typ.Complete() } + // Workaround for golang/go#61561. See the doc for instanceList for details. + for _, typ := range p.instanceList { + if iface, _ := typ.Underlying().(*types.Interface); iface != nil { + iface.Complete() + } + } + return pkgs, nil } @@ -289,8 +347,8 @@ type iimporter struct { version int ipath string - localpkg *types.Package - insert func(pkg *types.Package, name string) // "shallow" mode only + shallow bool + reportf ReportFunc // if non-nil, used to report bugs stringData []byte stringCache map[uint64]string @@ -307,6 +365,12 @@ type iimporter struct { fake fakeFileSet interfaceList []*types.Interface + // Workaround for the go/types bug golang/go#61561: instances produced during + // instantiation may contain incomplete interfaces. Here we only complete the + // underlying type of the instance, which is the most common case but doesn't + // handle parameterized interface literals defined deeper in the type. + instanceList []types.Type // instances for later completion (see golang/go#61561) + // Arguments for calls to SetConstraint that are deferred due to recursive types later []setConstraintArgs @@ -338,13 +402,9 @@ func (p *iimporter) doDecl(pkg *types.Package, name string) { off, ok := p.pkgIndex[pkg][name] if !ok { - // In "shallow" mode, call back to the application to - // find the object and insert it into the package scope. - if p.insert != nil { - assert(pkg != p.localpkg) - p.insert(pkg, name) // "can't fail" - return - } + // In deep mode, the index should be complete. In shallow + // mode, we should have already recursively loaded necessary + // dependencies so the above Lookup succeeds. errorf("%v.%v not in index", pkg, name) } @@ -711,7 +771,8 @@ func (r *importReader) qualifiedIdent() (*types.Package, string) { } func (r *importReader) pos() token.Pos { - if r.p.insert != nil { // shallow mode + if r.p.shallow { + // precise offsets are encoded only in shallow mode return r.posv2() } if r.p.version >= iexportVersionPosCol { @@ -812,13 +873,28 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { fields := make([]*types.Var, r.uint64()) tags := make([]string, len(fields)) for i := range fields { + var field *types.Var + if r.p.shallow { + field, _ = r.objectPathObject().(*types.Var) + } + fpos := r.pos() fname := r.ident() ftyp := r.typ() emb := r.bool() tag := r.string() - fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + // Either this is not a shallow import, the field is local, or the + // encoded objectPath failed to produce an object (a bug). + // + // Even in this last, buggy case, fall back on creating a new field. As + // discussed in iexport.go, this is not correct, but mostly works and is + // preferable to failing (for now at least). + if field == nil { + field = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + } + + fields[i] = field tags[i] = tag } return types.NewStruct(fields, tags) @@ -834,6 +910,11 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { methods := make([]*types.Func, r.uint64()) for i := range methods { + var method *types.Func + if r.p.shallow { + method, _ = r.objectPathObject().(*types.Func) + } + mpos := r.pos() mname := r.ident() @@ -843,9 +924,12 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { if base != nil { recv = types.NewVar(token.NoPos, r.currPkg, "", base) } - msig := r.signature(recv, nil, nil) - methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig) + + if method == nil { + method = types.NewFunc(mpos, r.currPkg, mname, msig) + } + methods[i] = method } typ := newInterface(methods, embeddeds) @@ -883,6 +967,9 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { // we must always use the methods of the base (orig) type. // TODO provide a non-nil *Environment t, _ := typeparams.Instantiate(nil, baseType, targs, false) + + // Workaround for golang/go#61561. See the doc for instanceList for details. + r.p.instanceList = append(r.p.instanceList, t) return t case unionType: @@ -901,6 +988,26 @@ func (r *importReader) kind() itag { return itag(r.uint64()) } +// objectPathObject is the inverse of exportWriter.objectPath. +// +// In shallow mode, certain fields and methods may need to be looked up in an +// imported package. See the doc for exportWriter.objectPath for a full +// explanation. +func (r *importReader) objectPathObject() types.Object { + objPath := objectpath.Path(r.string()) + if objPath == "" { + return nil + } + pkg := r.pkg() + obj, err := objectpath.Object(pkg, objPath) + if err != nil { + if r.p.reportf != nil { + r.p.reportf("failed to find object for objectPath %q: %v", objPath, err) + } + } + return obj +} + func (r *importReader) signature(recv *types.Var, rparams []*typeparams.TypeParam, tparams []*typeparams.TypeParam) *types.Signature { params := r.paramList() results := r.paramList() diff --git a/go/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go b/go/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go index b285a11ce25..b977435f626 100644 --- a/go/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go +++ b/go/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go @@ -10,8 +10,10 @@ package gcimporter import ( + "fmt" "go/token" "go/types" + "sort" "strings" "golang.org/x/tools/internal/pkgbits" @@ -62,6 +64,14 @@ type typeInfo struct { } func UImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { + if !debug { + defer func() { + if x := recover(); x != nil { + err = fmt.Errorf("internal error in importing %q (%v); please report an issue", path, x) + } + }() + } + s := string(data) s = s[:strings.LastIndex(s, "\n$$\n")] input := pkgbits.NewPkgDecoder(path, s) @@ -121,6 +131,16 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st iface.Complete() } + // Imports() of pkg are all of the transitive packages that were loaded. + var imps []*types.Package + for _, imp := range pr.pkgs { + if imp != nil && imp != pkg { + imps = append(imps, imp) + } + } + sort.Sort(byPath(imps)) + pkg.SetImports(imps) + pkg.MarkComplete() return pkg } @@ -260,39 +280,9 @@ func (r *reader) doPkg() *types.Package { pkg := types.NewPackage(path, name) r.p.imports[path] = pkg - imports := make([]*types.Package, r.Len()) - for i := range imports { - imports[i] = r.pkg() - } - pkg.SetImports(flattenImports(imports)) - return pkg } -// flattenImports returns the transitive closure of all imported -// packages rooted from pkgs. -func flattenImports(pkgs []*types.Package) []*types.Package { - var res []*types.Package - seen := make(map[*types.Package]struct{}) - for _, pkg := range pkgs { - if _, ok := seen[pkg]; ok { - continue - } - seen[pkg] = struct{}{} - res = append(res, pkg) - - // pkg.Imports() is already flattened. - for _, pkg := range pkg.Imports() { - if _, ok := seen[pkg]; ok { - continue - } - seen[pkg] = struct{}{} - res = append(res, pkg) - } - } - return res -} - // @@@ Types func (r *reader) typ() types.Type { diff --git a/go/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/go/vendor/golang.org/x/tools/internal/gocommand/invoke.go index d50551693f3..53cf66da019 100644 --- a/go/vendor/golang.org/x/tools/internal/gocommand/invoke.go +++ b/go/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -8,10 +8,12 @@ package gocommand import ( "bytes" "context" + "errors" "fmt" "io" "log" "os" + "reflect" "regexp" "runtime" "strconv" @@ -22,6 +24,9 @@ import ( exec "golang.org/x/sys/execabs" "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/event/label" + "golang.org/x/tools/internal/event/tag" ) // An Runner will run go command invocations and serialize @@ -51,9 +56,19 @@ func (runner *Runner) initialize() { // 1.14: go: updating go.mod: existing contents have changed since last read var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) +// verb is an event label for the go command verb. +var verb = keys.NewString("verb", "go command verb") + +func invLabels(inv Invocation) []label.Label { + return []label.Label{verb.Of(inv.Verb), tag.Directory.Of(inv.WorkingDir)} +} + // Run is a convenience wrapper around RunRaw. // It returns only stdout and a "friendly" error. func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { + ctx, done := event.Start(ctx, "gocommand.Runner.Run", invLabels(inv)...) + defer done() + stdout, _, friendly, _ := runner.RunRaw(ctx, inv) return stdout, friendly } @@ -61,6 +76,9 @@ func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, e // RunPiped runs the invocation serially, always waiting for any concurrent // invocations to complete first. func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error { + ctx, done := event.Start(ctx, "gocommand.Runner.RunPiped", invLabels(inv)...) + defer done() + _, err := runner.runPiped(ctx, inv, stdout, stderr) return err } @@ -68,6 +86,8 @@ func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stde // RunRaw runs the invocation, serializing requests only if they fight over // go.mod changes. func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { + ctx, done := event.Start(ctx, "gocommand.Runner.RunRaw", invLabels(inv)...) + defer done() // Make sure the runner is always initialized. runner.initialize() @@ -215,6 +235,18 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { cmd := exec.Command("go", goArgs...) cmd.Stdout = stdout cmd.Stderr = stderr + + // cmd.WaitDelay was added only in go1.20 (see #50436). + if waitDelay := reflect.ValueOf(cmd).Elem().FieldByName("WaitDelay"); waitDelay.IsValid() { + // https://go.dev/issue/59541: don't wait forever copying stderr + // after the command has exited. + // After CL 484741 we copy stdout manually, so we we'll stop reading that as + // soon as ctx is done. However, we also don't want to wait around forever + // for stderr. Give a much-longer-than-reasonable delay and then assume that + // something has wedged in the kernel or runtime. + waitDelay.Set(reflect.ValueOf(30 * time.Second)) + } + // On darwin the cwd gets resolved to the real path, which breaks anything that // expects the working directory to keep the original path, including the // go command when dealing with modules. @@ -229,6 +261,7 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) cmd.Dir = i.WorkingDir } + defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) return runCmdContext(ctx, cmd) @@ -242,10 +275,85 @@ var DebugHangingGoCommands = false // runCmdContext is like exec.CommandContext except it sends os.Interrupt // before os.Kill. -func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { - if err := cmd.Start(); err != nil { +func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) { + // If cmd.Stdout is not an *os.File, the exec package will create a pipe and + // copy it to the Writer in a goroutine until the process has finished and + // either the pipe reaches EOF or command's WaitDelay expires. + // + // However, the output from 'go list' can be quite large, and we don't want to + // keep reading (and allocating buffers) if we've already decided we don't + // care about the output. We don't want to wait for the process to finish, and + // we don't wait to wait for the WaitDelay to expire either. + // + // Instead, if cmd.Stdout requires a copying goroutine we explicitly replace + // it with a pipe (which is an *os.File), which we can close in order to stop + // copying output as soon as we realize we don't care about it. + var stdoutW *os.File + if cmd.Stdout != nil { + if _, ok := cmd.Stdout.(*os.File); !ok { + var stdoutR *os.File + stdoutR, stdoutW, err = os.Pipe() + if err != nil { + return err + } + prevStdout := cmd.Stdout + cmd.Stdout = stdoutW + + stdoutErr := make(chan error, 1) + go func() { + _, err := io.Copy(prevStdout, stdoutR) + if err != nil { + err = fmt.Errorf("copying stdout: %w", err) + } + stdoutErr <- err + }() + defer func() { + // We started a goroutine to copy a stdout pipe. + // Wait for it to finish, or terminate it if need be. + var err2 error + select { + case err2 = <-stdoutErr: + stdoutR.Close() + case <-ctx.Done(): + stdoutR.Close() + // Per https://pkg.go.dev/os#File.Close, the call to stdoutR.Close + // should cause the Read call in io.Copy to unblock and return + // immediately, but we still need to receive from stdoutErr to confirm + // that it has happened. + <-stdoutErr + err2 = ctx.Err() + } + if err == nil { + err = err2 + } + }() + + // Per https://pkg.go.dev/os/exec#Cmd, “If Stdout and Stderr are the + // same writer, and have a type that can be compared with ==, at most + // one goroutine at a time will call Write.” + // + // Since we're starting a goroutine that writes to cmd.Stdout, we must + // also update cmd.Stderr so that it still holds. + func() { + defer func() { recover() }() + if cmd.Stderr == prevStdout { + cmd.Stderr = cmd.Stdout + } + }() + } + } + + err = cmd.Start() + if stdoutW != nil { + // The child process has inherited the pipe file, + // so close the copy held in this process. + stdoutW.Close() + stdoutW = nil + } + if err != nil { return err } + resChan := make(chan error, 1) go func() { resChan <- cmd.Wait() @@ -253,11 +361,14 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { // If we're interested in debugging hanging Go commands, stop waiting after a // minute and panic with interesting information. - if DebugHangingGoCommands { + debug := DebugHangingGoCommands + if debug { + timer := time.NewTimer(1 * time.Minute) + defer timer.Stop() select { case err := <-resChan: return err - case <-time.After(1 * time.Minute): + case <-timer.C: HandleHangingGoCommand(cmd.Process) case <-ctx.Done(): } @@ -270,30 +381,25 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { } // Cancelled. Interrupt and see if it ends voluntarily. - cmd.Process.Signal(os.Interrupt) - select { - case err := <-resChan: - return err - case <-time.After(time.Second): + if err := cmd.Process.Signal(os.Interrupt); err == nil { + // (We used to wait only 1s but this proved + // fragile on loaded builder machines.) + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + select { + case err := <-resChan: + return err + case <-timer.C: + } } // Didn't shut down in response to interrupt. Kill it hard. // TODO(rfindley): per advice from bcmills@, it may be better to send SIGQUIT // on certain platforms, such as unix. - if err := cmd.Process.Kill(); err != nil && DebugHangingGoCommands { - // Don't panic here as this reliably fails on windows with EINVAL. + if err := cmd.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) && debug { log.Printf("error killing the Go command: %v", err) } - // See above: don't wait indefinitely if we're debugging hanging Go commands. - if DebugHangingGoCommands { - select { - case err := <-resChan: - return err - case <-time.After(10 * time.Second): // a shorter wait as resChan should return quickly following Kill - HandleHangingGoCommand(cmd.Process) - } - } return <-resChan } diff --git a/go/vendor/golang.org/x/tools/internal/gocommand/version.go b/go/vendor/golang.org/x/tools/internal/gocommand/version.go index 307a76d474a..446c5846a60 100644 --- a/go/vendor/golang.org/x/tools/internal/gocommand/version.go +++ b/go/vendor/golang.org/x/tools/internal/gocommand/version.go @@ -23,21 +23,11 @@ import ( func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { inv.Verb = "list" inv.Args = []string{"-e", "-f", `{{context.ReleaseTags}}`, `--`, `unsafe`} - inv.Env = append(append([]string{}, inv.Env...), "GO111MODULE=off") - // Unset any unneeded flags, and remove them from BuildFlags, if they're - // present. - inv.ModFile = "" + inv.BuildFlags = nil // This is not a build command. inv.ModFlag = "" - var buildFlags []string - for _, flag := range inv.BuildFlags { - // Flags can be prefixed by one or two dashes. - f := strings.TrimPrefix(strings.TrimPrefix(flag, "-"), "-") - if strings.HasPrefix(f, "mod=") || strings.HasPrefix(f, "modfile=") { - continue - } - buildFlags = append(buildFlags, flag) - } - inv.BuildFlags = buildFlags + inv.ModFile = "" + inv.Env = append(inv.Env[:len(inv.Env):len(inv.Env)], "GO111MODULE=off") + stdoutBytes, err := r.Run(ctx, inv) if err != nil { return 0, err diff --git a/go/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go b/go/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go index a3fb2d4f29d..7e638ec24fc 100644 --- a/go/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go +++ b/go/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go @@ -7,7 +7,9 @@ package tokeninternal import ( + "fmt" "go/token" + "sort" "sync" "unsafe" ) @@ -57,3 +59,93 @@ func GetLines(file *token.File) []int { panic("unexpected token.File size") } } + +// AddExistingFiles adds the specified files to the FileSet if they +// are not already present. It panics if any pair of files in the +// resulting FileSet would overlap. +func AddExistingFiles(fset *token.FileSet, files []*token.File) { + // Punch through the FileSet encapsulation. + type tokenFileSet struct { + // This type remained essentially consistent from go1.16 to go1.21. + mutex sync.RWMutex + base int + files []*token.File + _ *token.File // changed to atomic.Pointer[token.File] in go1.19 + } + + // If the size of token.FileSet changes, this will fail to compile. + const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) + var _ [-delta * delta]int + + type uP = unsafe.Pointer + var ptr *tokenFileSet + *(*uP)(uP(&ptr)) = uP(fset) + ptr.mutex.Lock() + defer ptr.mutex.Unlock() + + // Merge and sort. + newFiles := append(ptr.files, files...) + sort.Slice(newFiles, func(i, j int) bool { + return newFiles[i].Base() < newFiles[j].Base() + }) + + // Reject overlapping files. + // Discard adjacent identical files. + out := newFiles[:0] + for i, file := range newFiles { + if i > 0 { + prev := newFiles[i-1] + if file == prev { + continue + } + if prev.Base()+prev.Size()+1 > file.Base() { + panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)", + prev.Name(), prev.Base(), prev.Base()+prev.Size(), + file.Name(), file.Base(), file.Base()+file.Size())) + } + } + out = append(out, file) + } + newFiles = out + + ptr.files = newFiles + + // Advance FileSet.Base(). + if len(newFiles) > 0 { + last := newFiles[len(newFiles)-1] + newBase := last.Base() + last.Size() + 1 + if ptr.base < newBase { + ptr.base = newBase + } + } +} + +// FileSetFor returns a new FileSet containing a sequence of new Files with +// the same base, size, and line as the input files, for use in APIs that +// require a FileSet. +// +// Precondition: the input files must be non-overlapping, and sorted in order +// of their Base. +func FileSetFor(files ...*token.File) *token.FileSet { + fset := token.NewFileSet() + for _, f := range files { + f2 := fset.AddFile(f.Name(), f.Base(), f.Size()) + lines := GetLines(f) + f2.SetLines(lines) + } + return fset +} + +// CloneFileSet creates a new FileSet holding all files in fset. It does not +// create copies of the token.Files in fset: they are added to the resulting +// FileSet unmodified. +func CloneFileSet(fset *token.FileSet) *token.FileSet { + var files []*token.File + fset.Iterate(func(f *token.File) bool { + files = append(files, f) + return true + }) + newFileSet := token.NewFileSet() + AddExistingFiles(newFileSet, files) + return newFileSet +} diff --git a/go/vendor/golang.org/x/tools/internal/typeparams/common.go b/go/vendor/golang.org/x/tools/internal/typeparams/common.go index 25a1426d30e..d0d0649fe2a 100644 --- a/go/vendor/golang.org/x/tools/internal/typeparams/common.go +++ b/go/vendor/golang.org/x/tools/internal/typeparams/common.go @@ -23,6 +23,7 @@ package typeparams import ( + "fmt" "go/ast" "go/token" "go/types" @@ -87,7 +88,6 @@ func IsTypeParam(t types.Type) bool { func OriginMethod(fn *types.Func) *types.Func { recv := fn.Type().(*types.Signature).Recv() if recv == nil { - return fn } base := recv.Type() @@ -106,6 +106,31 @@ func OriginMethod(fn *types.Func) *types.Func { } orig := NamedTypeOrigin(named) gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name()) + + // This is a fix for a gopls crash (#60628) due to a go/types bug (#60634). In: + // package p + // type T *int + // func (*T) f() {} + // LookupFieldOrMethod(T, true, p, f)=nil, but NewMethodSet(*T)={(*T).f}. + // Here we make them consistent by force. + // (The go/types bug is general, but this workaround is reached only + // for generic T thanks to the early return above.) + if gfn == nil { + mset := types.NewMethodSet(types.NewPointer(orig)) + for i := 0; i < mset.Len(); i++ { + m := mset.At(i) + if m.Obj().Id() == fn.Id() { + gfn = m.Obj() + break + } + } + } + + // In golang/go#61196, we observe another crash, this time inexplicable. + if gfn == nil { + panic(fmt.Sprintf("missing origin method for %s.%s; named == origin: %t, named.NumMethods(): %d, origin.NumMethods(): %d", named, fn, named == orig, named.NumMethods(), orig.NumMethods())) + } + return gfn.(*types.Func) } diff --git a/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go b/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go index b4788978ff4..7ed86e1711b 100644 --- a/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go +++ b/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go @@ -129,7 +129,7 @@ func NamedTypeArgs(*types.Named) *TypeList { } // NamedTypeOrigin is the identity method at this Go version. -func NamedTypeOrigin(named *types.Named) types.Type { +func NamedTypeOrigin(named *types.Named) *types.Named { return named } diff --git a/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go b/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go index 114a36b866b..cf301af1dbe 100644 --- a/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go +++ b/go/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go @@ -103,7 +103,7 @@ func NamedTypeArgs(named *types.Named) *TypeList { } // NamedTypeOrigin returns named.Orig(). -func NamedTypeOrigin(named *types.Named) types.Type { +func NamedTypeOrigin(named *types.Named) *types.Named { return named.Origin() } diff --git a/go/vendor/golang.org/x/tools/internal/typesinternal/types.go b/go/vendor/golang.org/x/tools/internal/typesinternal/types.go index ce7d4351b22..66e8b099bd6 100644 --- a/go/vendor/golang.org/x/tools/internal/typesinternal/types.go +++ b/go/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -11,6 +11,8 @@ import ( "go/types" "reflect" "unsafe" + + "golang.org/x/tools/go/types/objectpath" ) func SetUsesCgo(conf *types.Config) bool { @@ -50,3 +52,17 @@ func ReadGo116ErrorData(err types.Error) (code ErrorCode, start, end token.Pos, } var SetGoVersion = func(conf *types.Config, version string) bool { return false } + +// SkipEncoderMethodSorting marks the encoder as not requiring sorted methods, +// as an optimization for gopls (which guarantees the order of parsed source files). +// +// TODO(golang/go#61443): eliminate this parameter one way or the other. +// +//go:linkname SkipEncoderMethodSorting golang.org/x/tools/go/types/objectpath.skipMethodSorting +func SkipEncoderMethodSorting(enc *objectpath.Encoder) + +// ObjectpathObject is like objectpath.Object, but allows suppressing method +// sorting (which is not necessary for gopls). +// +//go:linkname ObjectpathObject golang.org/x/tools/go/types/objectpath.object +func ObjectpathObject(pkg *types.Package, p objectpath.Path, skipMethodSorting bool) (types.Object, error) diff --git a/go/vendor/modules.txt b/go/vendor/modules.txt index 596bd8e8ae9..1f1e5a29f25 100644 --- a/go/vendor/modules.txt +++ b/go/vendor/modules.txt @@ -1,21 +1,23 @@ -# golang.org/x/mod v0.8.0 +# golang.org/x/mod v0.12.0 ## explicit; go 1.17 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/sys v0.5.0 +# golang.org/x/sys v0.10.0 ## explicit; go 1.17 golang.org/x/sys/execabs -# golang.org/x/tools v0.6.0 +# golang.org/x/tools v0.11.1 ## explicit; go 1.18 golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/go/packages +golang.org/x/tools/go/types/objectpath golang.org/x/tools/internal/event golang.org/x/tools/internal/event/core golang.org/x/tools/internal/event/keys golang.org/x/tools/internal/event/label +golang.org/x/tools/internal/event/tag golang.org/x/tools/internal/gcimporter golang.org/x/tools/internal/gocommand golang.org/x/tools/internal/packagesinternal @@ -23,5 +25,5 @@ golang.org/x/tools/internal/pkgbits golang.org/x/tools/internal/tokeninternal golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal -# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 -## explicit; go 1.11 +# golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 +## explicit; go 1.17 diff --git a/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt b/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt index a426c7bd622..cfdf9895782 100644 --- a/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt @@ -88,12 +88,10 @@ class ExternalDeclExtractor(val logger: FileLogger, val invocationTrapFile: Stri nextBatch.forEach { workPair -> val (irDecl, possiblyLongSignature) = workPair extractElement(irDecl, possiblyLongSignature, false) { trapFileBW, signature, manager -> - val containingClass = getContainingClassOrSelf(irDecl) - if (containingClass == null) { - logger.errorElement("Unable to get containing class", irDecl) + val binaryPath = getIrDeclarationBinaryPath(irDecl) + if (binaryPath == null) { + logger.errorElement("Unable to get binary path", irDecl) } else { - val binaryPath = getIrClassBinaryPath(containingClass) - // We want our comments to be the first thing in the file, // so start off with a PlainTrapWriter val tw = PlainTrapWriter(logger.loggerBase, TrapLabelManager(), trapFileBW, diagnosticTrapWriter) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 06a5b4bb48c..55ec8c44dbb 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -191,7 +191,7 @@ open class KotlinFileExtractor( } } is IrFunction -> { - val parentId = useDeclarationParent(declaration.parent, false)?.cast() + val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { extractFunction(declaration, parentId, extractBody = extractFunctionBodies, extractMethodAndParameterTypeAccesses = extractFunctionBodies, extractAnnotations = extractAnnotations, null, listOf()) } @@ -201,21 +201,21 @@ open class KotlinFileExtractor( // Leaving this intentionally empty. init blocks are extracted during class extraction. } is IrProperty -> { - val parentId = useDeclarationParent(declaration.parent, false)?.cast() + val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { extractProperty(declaration, parentId, extractBackingField = true, extractFunctionBodies = extractFunctionBodies, extractPrivateMembers = extractPrivateMembers, extractAnnotations = extractAnnotations, null, listOf()) } Unit } is IrEnumEntry -> { - val parentId = useDeclarationParent(declaration.parent, false)?.cast() + val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { extractEnumEntry(declaration, parentId, extractPrivateMembers, extractFunctionBodies) } Unit } is IrField -> { - val parentId = useDeclarationParent(getFieldParent(declaration), false)?.cast() + val parentId = useDeclarationParentOf(declaration, false)?.cast() if (parentId != null) { // For consistency with the Java extractor, enum entries get type accesses only if we're extracting from .kt source (i.e., when `extractFunctionBodies` is set) extractField(declaration, parentId, extractAnnotationEnumTypeAccesses = extractFunctionBodies) @@ -408,11 +408,10 @@ open class KotlinFileExtractor( private fun getLocation(decl: IrDeclaration, typeArgs: List?): Label { return if (typeArgs != null && typeArgs.isNotEmpty()) { - val c = getContainingClassOrSelf(decl) - if (c == null) { + val binaryPath = getIrDeclarationBinaryPath(decl) + if (binaryPath == null) { tw.getLocation(decl) } else { - val binaryPath = getIrClassBinaryPath(c) val newTrapWriter = tw.makeFileTrapWriter(binaryPath, true) newTrapWriter.getWholeFileLocation() } @@ -472,7 +471,7 @@ open class KotlinFileExtractor( private fun extractObinitFunction(c: IrClass, parentId: Label) { // add method: - val obinitLabel = getObinitLabel(c) + val obinitLabel = getObinitLabel(c, parentId) val obinitId = tw.getLabelFor(obinitLabel) val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN) tw.writeMethods(obinitId, "", "()", returnType.javaResult.id, parentId, obinitId) @@ -552,9 +551,13 @@ open class KotlinFileExtractor( logger.warnElement("Expected annotation property to define a getter", prop) } else { val getterId = useFunction(getter) - val exprId = extractAnnotationValueExpression(v, id, i, "{$getterId}", getter.returnType, extractEnumTypeAccesses) - if (exprId != null) { - tw.writeAnnotValue(id, getterId, exprId) + if (getterId == null) { + logger.errorElement("Couldn't get ID for getter", getter) + } else { + val exprId = extractAnnotationValueExpression(v, id, i, "{$getterId}", getter.returnType, extractEnumTypeAccesses) + if (exprId != null) { + tw.writeAnnotValue(id, getterId, exprId) + } } } } @@ -979,6 +982,10 @@ open class KotlinFileExtractor( private fun extractInstanceInitializerBlock(parent: StmtParent, enclosingConstructor: IrConstructor) { with("object initializer block", enclosingConstructor) { val constructorId = useFunction(enclosingConstructor) + if (constructorId == null) { + logger.errorElement("Cannot get ID for constructor", enclosingConstructor) + return + } val enclosingClass = enclosingConstructor.parentClassOrNull if (enclosingClass == null) { logger.errorElement("Constructor's parent is not a class", enclosingConstructor) @@ -1152,6 +1159,10 @@ open class KotlinFileExtractor( return val id = getDefaultsMethodLabel(f) + if (id == null) { + logger.errorElement("Cannot get defaults method label for function", f) + return + } val locId = getLocation(f, null) val extReceiver = f.extensionReceiverParameter val dispatchReceiver = if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter @@ -1273,9 +1284,13 @@ open class KotlinFileExtractor( val sourceParentId = maybeSourceParentId ?: if (typeSubstitution != null) - useDeclarationParent(f.parent, false) + useDeclarationParentOf(f, false) else parentId + if (sourceParentId == null) { + logger.errorElement("Cannot get source parent ID for function", f) + return + } val sourceDeclId = tw.getLabelFor(getFunctionLabel(f, sourceParentId, listOf(), overloadParameters)) val overriddenAttributes = OverriddenFunctionAttributes(id = overloadId, sourceDeclarationId = sourceDeclId, valueParameters = overloadParameters) forceExtractFunction(f, parentId, extractBody = false, extractMethodAndParameterTypeAccesses, extractAnnotations = false, typeSubstitution, classTypeArgsIncludingOuterClasses, overriddenAttributes = overriddenAttributes) @@ -1293,7 +1308,7 @@ open class KotlinFileExtractor( val constructorCallId = tw.getFreshIdLabel() tw.writeStmts_constructorinvocationstmt(constructorCallId, blockId, 0, overloadId) tw.writeHasLocation(constructorCallId, realFunctionLocId) - tw.writeCallableBinding(constructorCallId, getDefaultsMethodLabel(f)) + tw.writeCallableBinding(constructorCallId, getDefaultsMethodLabel(f, parentId)) extractDefaultsCallArguments(constructorCallId, f, overloadId, constructorCallId, regularArgs, null, null) } else { @@ -1410,10 +1425,17 @@ open class KotlinFileExtractor( val sourceDeclaration = overriddenAttributes?.sourceDeclarationId ?: - if (typeSubstitution != null && overriddenAttributes?.id == null) - useFunction(f) - else + if (typeSubstitution != null && overriddenAttributes?.id == null) { + val sourceFunId = useFunction(f) + if (sourceFunId == null) { + logger.errorElement("Cannot get source ID for function", f) + id // TODO: This is wrong; we ought to just fail in this case + } else { + sourceFunId + } + } else { id + } val extReceiver = f.extensionReceiverParameter // The following parameter order is correct, because member $default methods (where the order would be [dispatchParam], [extensionParam], normalParams) are not extracted here @@ -1594,7 +1616,7 @@ open class KotlinFileExtractor( } if (bf != null && extractBackingField) { - val fieldParentId = useDeclarationParent(getFieldParent(bf), false) + val fieldParentId = useDeclarationParentOf(bf, false) if (fieldParentId != null) { val fieldId = extractField(bf, fieldParentId.cast(), extractFunctionBodies) tw.writeKtPropertyBackingFields(id, fieldId) @@ -2066,13 +2088,23 @@ open class KotlinFileExtractor( getFunctionShortName(f).nameInDB + "\$default" } - private fun getDefaultsMethodLabel(f: IrFunction): Label { + private fun getDefaultsMethodLabel(f: IrFunction): Label? { + val classTypeArgsIncludingOuterClasses = null + val parentId = useDeclarationParentOf(f, false, classTypeArgsIncludingOuterClasses, true) + if (parentId == null) { + logger.errorElement("Couldn't get parent ID for defaults method", f) + return null + } + return getDefaultsMethodLabel(f, parentId) + } + + private fun getDefaultsMethodLabel(f: IrFunction, parentId: Label): Label { val defaultsMethodName = if (f is IrConstructor) "" else getDefaultsMethodName(f) val argTypes = getDefaultsMethodArgTypes(f) val defaultMethodLabelStr = getFunctionLabel( f.parent, - maybeParentId = null, + parentId, defaultsMethodName, argTypes, erase(f.returnType), @@ -2121,7 +2153,7 @@ open class KotlinFileExtractor( if (overriddenCallTarget.isLocalFunction()) { extractTypeAccess(getLocallyVisibleFunctionLabels(overriddenCallTarget).type, locId, id, -1, enclosingCallable, enclosingStmt) } else { - extractStaticTypeAccessQualifierUnchecked(overriddenCallTarget.parent, id, locId, enclosingCallable, enclosingStmt) + extractStaticTypeAccessQualifierUnchecked(overriddenCallTarget, id, locId, enclosingCallable, enclosingStmt) } extractDefaultsCallArguments(id, overriddenCallTarget, enclosingCallable, enclosingStmt, valueArguments, dispatchReceiver, extensionReceiver) @@ -2347,8 +2379,17 @@ open class KotlinFileExtractor( extractValueArguments(argParent, idxOffset) } - private fun extractStaticTypeAccessQualifierUnchecked(parent: IrDeclarationParent, parentExpr: Label, locId: Label, enclosingCallable: Label?, enclosingStmt: Label?) { - if (parent is IrClass) { + private fun extractStaticTypeAccessQualifierUnchecked(target: IrDeclaration, parentExpr: Label, locId: Label, enclosingCallable: Label?, enclosingStmt: Label?) { + val parent = target.parent + if (parent is IrExternalPackageFragment) { + // This is in a file class. + val fqName = getFileClassFqName(target) + if (fqName == null) { + logger.error("Can't get FqName for element in external package fragment ${target.javaClass}") + } else { + extractTypeAccess(useFileClassType(fqName), locId, parentExpr, -1, enclosingCallable, enclosingStmt) + } + } else if (parent is IrClass) { extractTypeAccessRecursive(parent.toRawType(), locId, parentExpr, -1, enclosingCallable, enclosingStmt) } else if (parent is IrFile) { extractTypeAccess(useFileClassType(parent), locId, parentExpr, -1, enclosingCallable, enclosingStmt) @@ -2359,7 +2400,7 @@ open class KotlinFileExtractor( private fun extractStaticTypeAccessQualifier(target: IrDeclaration, parentExpr: Label, locId: Label, enclosingCallable: Label?, enclosingStmt: Label?) { if (target.shouldExtractAsStatic) { - extractStaticTypeAccessQualifierUnchecked(target.parent, parentExpr, locId, enclosingCallable, enclosingStmt) + extractStaticTypeAccessQualifierUnchecked(target, parentExpr, locId, enclosingCallable, enclosingStmt) } } @@ -2926,7 +2967,11 @@ open class KotlinFileExtractor( tw.writeStmts_throwstmt(throwId, stmtParent.parent, stmtParent.idx, callable) tw.writeHasLocation(throwId, locId) val newExprId = extractNewExpr(it, null, thrownType, locId, throwId, 0, callable, throwId) - extractTypeAccess(thrownType, locId, newExprId, -3, callable, throwId) + if (newExprId == null) { + logger.errorElement("No ID for newExpr in noWhenBranchMatchedException", c) + } else { + extractTypeAccess(thrownType, locId, newExprId, -3, callable, throwId) + } } } isBuiltinCallInternal(c, "illegalArgumentException") -> { @@ -3270,13 +3315,20 @@ open class KotlinFileExtractor( idx: Int, callable: Label, enclosingStmt: Label - ): Label = extractNewExpr(useFunction(calledConstructor, constructorTypeArgs), constructedType, locId, parent, idx, callable, enclosingStmt) + ): Label? { + val funId = useFunction(calledConstructor, constructorTypeArgs) + if (funId == null) { + logger.error("Cannot get ID for newExpr function") + return null + } + return extractNewExpr(funId, constructedType, locId, parent, idx, callable, enclosingStmt) + } private fun needsObinitFunction(c: IrClass) = c.primaryConstructor == null && c.constructors.count() > 1 - private fun getObinitLabel(c: IrClass) = getFunctionLabel( + private fun getObinitLabel(c: IrClass, parentId: Label): String = getFunctionLabel( c, - null, + parentId, "", listOf(), pluginContext.irBuiltIns.unitType, @@ -3306,30 +3358,40 @@ open class KotlinFileExtractor( val valueArgs = (0 until e.valueArgumentsCount).map { e.getValueArgument(it) } val id = if (e !is IrEnumConstructorCall && callUsesDefaultArguments(e.symbol.owner, valueArgs)) { - extractNewExpr(getDefaultsMethodLabel(e.symbol.owner).cast(), type, locId, parent, idx, callable, enclosingStmt).also { + val defaultsMethodId = getDefaultsMethodLabel(e.symbol.owner) + if (defaultsMethodId == null) { + logger.errorElement("Cannot get defaults method ID", e) + return + } + extractNewExpr(defaultsMethodId.cast(), type, locId, parent, idx, callable, enclosingStmt).also { extractDefaultsCallArguments(it, e.symbol.owner, callable, enclosingStmt, valueArgs, null, null) } } else { - extractNewExpr(e.symbol.owner, eType.arguments, type, locId, parent, idx, callable, enclosingStmt).also { - - val realCallTarget = e.symbol.owner.realOverrideTarget - // Generated constructor calls to kotlin.Enum have no arguments in IR, but the constructor takes two parameters. - if (e is IrEnumConstructorCall && - realCallTarget is IrConstructor && - realCallTarget.parentClassOrNull?.fqNameWhenAvailable?.asString() == "kotlin.Enum" && - realCallTarget.valueParameters.size == 2 && - realCallTarget.valueParameters[0].type == pluginContext.irBuiltIns.stringType && - realCallTarget.valueParameters[1].type == pluginContext.irBuiltIns.intType) { - - val id0 = extractNull(pluginContext.irBuiltIns.stringType, locId, it, 0, callable, enclosingStmt) - tw.writeCompiler_generated(id0, CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind) - - val id1 = extractConstantInteger(0, locId, it, 1, callable, enclosingStmt) - tw.writeCompiler_generated(id1, CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind) - } else { - extractCallValueArguments(it, e, enclosingStmt, callable, 0) - } + val newExprId = extractNewExpr(e.symbol.owner, eType.arguments, type, locId, parent, idx, callable, enclosingStmt) + if (newExprId == null) { + logger.errorElement("Cannot get newExpr ID", e) + return } + + val realCallTarget = e.symbol.owner.realOverrideTarget + // Generated constructor calls to kotlin.Enum have no arguments in IR, but the constructor takes two parameters. + if (e is IrEnumConstructorCall && + realCallTarget is IrConstructor && + realCallTarget.parentClassOrNull?.fqNameWhenAvailable?.asString() == "kotlin.Enum" && + realCallTarget.valueParameters.size == 2 && + realCallTarget.valueParameters[0].type == pluginContext.irBuiltIns.stringType && + realCallTarget.valueParameters[1].type == pluginContext.irBuiltIns.intType) { + + val id0 = extractNull(pluginContext.irBuiltIns.stringType, locId, newExprId, 0, callable, enclosingStmt) + tw.writeCompiler_generated(id0, CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind) + + val id1 = extractConstantInteger(0, locId, newExprId, 1, callable, enclosingStmt) + tw.writeCompiler_generated(id1, CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind) + } else { + extractCallValueArguments(newExprId, e, enclosingStmt, callable, 0) + } + + newExprId } if (isAnonymous) { @@ -3698,9 +3760,13 @@ open class KotlinFileExtractor( val locId = tw.getLocation(e) val methodId = useFunction(e.symbol.owner) + if (methodId == null) { + logger.errorElement("Cannot get ID for delegating constructor", e) + } else { + tw.writeCallableBinding(id.cast(), methodId) + } tw.writeHasLocation(id, locId) - tw.writeCallableBinding(id.cast(), methodId) extractCallValueArguments(id, e, id, callable, 0) val dr = e.dispatchReceiver if (dr != null) { @@ -3782,7 +3848,13 @@ open class KotlinFileExtractor( val id = tw.getFreshIdLabel() val type = useType(pluginContext.irBuiltIns.unitType) val locId = tw.getLocation(e) - val methodLabel = getObinitLabel(irConstructor.parentAsClass) + val parentClass = irConstructor.parentAsClass + val parentId = useDeclarationParentOf(irConstructor, false, null, true) + if (parentId == null) { + logger.errorElement("Cannot get parent ID for obinit", e) + return + } + val methodLabel = getObinitLabel(parentClass, parentId) val methodId = tw.getLabelFor(methodLabel) tw.writeExprs_methodaccess(id, type.javaResult.id, exprParent.parent, exprParent.idx) tw.writeExprsKotlinType(id, type.kotlinResult.id) @@ -4636,7 +4708,11 @@ open class KotlinFileExtractor( extractExprContext(callId, locId, labels.methodId, retId) val callableId = useFunction(target.owner.realOverrideTarget, classTypeArgsIncludingOuterClasses) - tw.writeCallableBinding(callId.cast(), callableId) + if (callableId == null) { + logger.error("Cannot get ID for reflection target") + } else { + tw.writeCallableBinding(callId.cast(), callableId) + } val useFirstArgAsDispatch: Boolean if (dispatchReceiverInfo != null) { @@ -4818,20 +4894,24 @@ open class KotlinFileExtractor( val getterReturnType = parameterTypes.last() if (getter != null) { - val getLabels = addFunctionManual(tw.getFreshIdLabel(), OperatorNameConventions.GET.asString(), getterParameterTypes, getterReturnType, classId, locId) val getterCallableId = useFunction(getter.owner.realOverrideTarget, classTypeArguments) + if (getterCallableId == null) { + logger.errorElement("Cannot get ID for getter", propertyReferenceExpr) + } else { + val getLabels = addFunctionManual(tw.getFreshIdLabel(), OperatorNameConventions.GET.asString(), getterParameterTypes, getterReturnType, classId, locId) - helper.extractCallToReflectionTarget( - getLabels, - getter, - getterReturnType, - expressionTypeArguments, - classTypeArguments - ) + helper.extractCallToReflectionTarget( + getLabels, + getter, + getterReturnType, + expressionTypeArguments, + classTypeArguments + ) - tw.writePropertyRefGetBinding(idPropertyRef, getterCallableId) + tw.writePropertyRefGetBinding(idPropertyRef, getterCallableId) - helper.extractPropertyReferenceInvoke(getLabels.methodId, getterParameterTypes, getterReturnType) + helper.extractPropertyReferenceInvoke(getLabels.methodId, getterParameterTypes, getterReturnType) + } } else { // Property without a getter. if (backingField == null) { @@ -4852,19 +4932,22 @@ open class KotlinFileExtractor( } if (setter != null) { - val setLabels = addFunctionManual(tw.getFreshIdLabel(), OperatorNameConventions.SET.asString(), parameterTypes, pluginContext.irBuiltIns.unitType, classId, locId) - val setterCallableId = useFunction(setter.owner.realOverrideTarget, classTypeArguments) + if (setterCallableId == null) { + logger.errorElement("Cannot get ID for setter", propertyReferenceExpr) + } else { + val setLabels = addFunctionManual(tw.getFreshIdLabel(), OperatorNameConventions.SET.asString(), parameterTypes, pluginContext.irBuiltIns.unitType, classId, locId) - helper.extractCallToReflectionTarget( - setLabels, - setter, - pluginContext.irBuiltIns.unitType, - expressionTypeArguments, - classTypeArguments - ) + helper.extractCallToReflectionTarget( + setLabels, + setter, + pluginContext.irBuiltIns.unitType, + expressionTypeArguments, + classTypeArguments + ) - tw.writePropertyRefSetBinding(idPropertyRef, setterCallableId) + tw.writePropertyRefSetBinding(idPropertyRef, setterCallableId) + } } else { if (backingField != null && !backingField.owner.isFinal) { val setLabels = addFunctionManual(tw.getFreshIdLabel(), OperatorNameConventions.SET.asString(), parameterTypes, pluginContext.irBuiltIns.unitType, classId, locId) @@ -4999,7 +5082,11 @@ open class KotlinFileExtractor( tw.writeCallableBinding(idMemberRef, ids.constructor) val targetCallableId = useFunction(target.owner.realOverrideTarget, classTypeArguments) - tw.writeMemberRefBinding(idMemberRef, targetCallableId) + if (targetCallableId == null) { + logger.errorElement("Cannot get ID for function reference callable", functionReferenceExpr) + } else { + tw.writeMemberRefBinding(idMemberRef, targetCallableId) + } val helper = CallableReferenceHelper(functionReferenceExpr, locId, ids) @@ -5145,7 +5232,11 @@ open class KotlinFileExtractor( tw.writeExprsKotlinType(callId, callType.kotlinResult.id) extractExprContext(callId, locId, funLabels.methodId, retId) val calledMethodId = useFunction(lambda) - tw.writeCallableBinding(callId, calledMethodId) + if (calledMethodId == null) { + logger.errorElement("Cannot get ID for called lambda", lambda) + } else { + tw.writeCallableBinding(callId, calledMethodId) + } // this access extractThisAccess(ids.type, funLabels.methodId, callId, -1, retId, locId) @@ -5614,7 +5705,11 @@ open class KotlinFileExtractor( tw.writeExprsKotlinType(callId, callType.kotlinResult.id) extractExprContext(callId, locId, ids.function, returnId) val calledMethodId = useFunction(invokeMethod, functionType.arguments) - tw.writeCallableBinding(callId, calledMethodId) + if (calledMethodId == null) { + logger.errorElement("Cannot get ID for called method", invokeMethod) + } else { + tw.writeCallableBinding(callId, calledMethodId) + } // access val lhsId = tw.getFreshIdLabel() @@ -5737,14 +5832,17 @@ open class KotlinFileExtractor( if (baseConstructor == null) { logger.warnElement("Cannot find base constructor", elementToReportOn) } else { - val superCallId = tw.getFreshIdLabel() - tw.writeStmts_superconstructorinvocationstmt(superCallId, constructorBlockId, 0, ids.constructor) - val baseConstructorId = useFunction(baseConstructor) + if (baseConstructorId == null) { + logger.errorElement("Cannot find base constructor ID", elementToReportOn) + } else { + val superCallId = tw.getFreshIdLabel() + tw.writeStmts_superconstructorinvocationstmt(superCallId, constructorBlockId, 0, ids.constructor) - tw.writeHasLocation(superCallId, locId) - tw.writeCallableBinding(superCallId.cast(), baseConstructorId) - extractSuperconstructorArgs(superCallId) + tw.writeHasLocation(superCallId, locId) + tw.writeCallableBinding(superCallId.cast(), baseConstructorId) + extractSuperconstructorArgs(superCallId) + } } } diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt index 95b7dd6d28d..9c8f289eb0a 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt @@ -2,6 +2,7 @@ package com.github.codeql import com.github.codeql.utils.* import com.github.codeql.utils.versions.codeQlWithHasQuestionMark +import com.github.codeql.utils.versions.getFileClassFqName import com.github.codeql.utils.versions.getKotlinType import com.github.codeql.utils.versions.isRawType import com.semmle.extractor.java.OdasaOutput @@ -71,18 +72,41 @@ open class KotlinUsesExtractor( TypeResult(fakeKotlinType(), "", "") ) + fun useFileClassType(fqName: FqName) = TypeResults( + TypeResult(extractFileClass(fqName), "", ""), + TypeResult(fakeKotlinType(), "", "") + ) + + private fun useFileClassType(pkg: String, jvmName: String) = TypeResults( + TypeResult(extractFileClass(pkg, jvmName), "", ""), + TypeResult(fakeKotlinType(), "", "") + ) + fun extractFileClass(f: IrFile): Label { val pkg = f.fqName.asString() val jvmName = getFileClassName(f) + val id = extractFileClass(pkg, jvmName) + if (tw.lm.fileClassLocationsExtracted.add(f)) { + val fileId = tw.mkFileId(f.path, false) + val locId = tw.getWholeFileLocation(fileId) + tw.writeHasLocation(id, locId) + } + return id + } + + private fun extractFileClass(fqName: FqName): Label { + val pkg = if (fqName.isRoot()) "" else fqName.parent().asString() + val jvmName = fqName.shortName().asString() + return extractFileClass(pkg, jvmName) + } + + private fun extractFileClass(pkg: String, jvmName: String): Label { val qualClassName = if (pkg.isEmpty()) jvmName else "$pkg.$jvmName" val label = "@\"class;$qualClassName\"" val id: Label = tw.getLabelFor(label) { - val fileId = tw.mkFileId(f.path, false) - val locId = tw.getWholeFileLocation(fileId) val pkgId = extractPackage(pkg) tw.writeClasses_or_interfaces(it, jvmName, pkgId, it) tw.writeFile_class(it) - tw.writeHasLocation(it, locId) addModifiers(it, "public", "final") } @@ -250,7 +274,11 @@ open class KotlinUsesExtractor( is IrClass -> extractExternalClassLater(parent) is IrFunction -> extractExternalEnclosingClassLater(parent) is IrFile -> logger.error("extractExternalEnclosingClassLater but no enclosing class.") - else -> logger.error("Unrecognised extractExternalEnclosingClassLater: " + d.javaClass) + is IrExternalPackageFragment -> { + // The parent is a (multi)file class. We don't need + // extract it separately. + } + else -> logger.error("Unrecognised extractExternalEnclosingClassLater ${parent.javaClass} for ${d.javaClass}") } } @@ -293,7 +321,17 @@ open class KotlinUsesExtractor( private fun extractFunctionLaterIfExternalFileMember(f: IrFunction) { if (isExternalFileClassMember(f)) { - extractExternalClassLater(f.parentAsClass) + val p = f.parent + when (p) { + is IrClass -> extractExternalClassLater(p) + is IrExternalPackageFragment -> { + // The parent is a (multi)file class. We don't need + // extract it separately. + } + else -> { + logger.warn("Unexpected parent type ${p.javaClass} for external file class member") + } + } (f as? IrSimpleFunction)?.correspondingPropertySymbol?.let { extractPropertyLaterIfExternalFileMember(it.owner) // No need to extract the function specifically, as the property's @@ -761,6 +799,41 @@ open class KotlinUsesExtractor( } } + private fun parentOf(d: IrDeclaration): IrDeclarationParent { + if (d is IrField) { + return getFieldParent(d) + } + return d.parent + } + + fun useDeclarationParentOf( + // The declaration + d: IrDeclaration, + // Whether the type of entity whose parent this is can be a + // top-level entity in the JVM's eyes. If so, then its parent may + // be a file; otherwise, if dp is a file foo.kt, then the parent + // is really the JVM class FooKt. + canBeTopLevel: Boolean, + classTypeArguments: List? = null, + inReceiverContext: Boolean = false): + Label? { + + val parent = parentOf(d) + if (parent is IrExternalPackageFragment) { + // This is in a file class. + val fqName = getFileClassFqName(d) + if (fqName == null) { + logger.error("Can't get FqName for element in external package fragment ${d.javaClass}") + return null + } + return extractFileClass(fqName) + } + return useDeclarationParent(parent, canBeTopLevel, classTypeArguments, inReceiverContext) + } + + // Generally, useDeclarationParentOf should be used instead of + // calling this directly, as this cannot handle + // IrExternalPackageFragment fun useDeclarationParent( // The declaration parent according to Kotlin dp: IrDeclarationParent, @@ -792,8 +865,7 @@ open class KotlinUsesExtractor( } is IrFunction -> useFunction(dp) is IrExternalPackageFragment -> { - // TODO - logger.error("Unhandled IrExternalPackageFragment") + logger.error("Unable to handle IrExternalPackageFragment as an IrDeclarationParent") null } else -> { @@ -1034,8 +1106,13 @@ open class KotlinUsesExtractor( * enclosing classes to get the instantiation that this function is * in. */ - fun getFunctionLabel(f: IrFunction, classTypeArgsIncludingOuterClasses: List?) : String { - return getFunctionLabel(f, null, classTypeArgsIncludingOuterClasses) + fun getFunctionLabel(f: IrFunction, classTypeArgsIncludingOuterClasses: List?): String? { + val parentId = useDeclarationParentOf(f, false, classTypeArgsIncludingOuterClasses, true) + if (parentId == null) { + logger.error("Couldn't get parent ID for function label") + return null + } + return getFunctionLabel(f, parentId, classTypeArgsIncludingOuterClasses) } /* @@ -1052,10 +1129,10 @@ open class KotlinUsesExtractor( * that omit one or more parameters that has a default value specified. */ @OptIn(ObsoleteDescriptorBasedAPI::class) - fun getFunctionLabel(f: IrFunction, maybeParentId: Label?, classTypeArgsIncludingOuterClasses: List?, maybeParameterList: List? = null) = + fun getFunctionLabel(f: IrFunction, parentId: Label, classTypeArgsIncludingOuterClasses: List?, maybeParameterList: List? = null): String = getFunctionLabel( f.parent, - maybeParentId, + parentId, getFunctionShortName(f).nameInDB, (maybeParameterList ?: f.valueParameters).map { it.type }, getAdjustedReturnType(f), @@ -1078,7 +1155,7 @@ open class KotlinUsesExtractor( // The parent of the function; normally f.parent. parent: IrDeclarationParent, // The ID of the function's parent, or null if we should work it out ourselves. - maybeParentId: Label?, + parentId: Label, // The name of the function; normally f.name.asString(). name: String, // The types of the value parameters that the functions takes; normally f.valueParameters.map { it.type }. @@ -1102,7 +1179,6 @@ open class KotlinUsesExtractor( // The prefix used in the label. "callable", unless a property label is created, then it's "property". prefix: String = "callable" ): String { - val parentId = maybeParentId ?: useDeclarationParent(parent, false, classTypeArgsIncludingOuterClasses, true) val allParamTypes = if (extensionParamType == null) parameterTypes else listOf(extensionParamType) + parameterTypes val substitutionMap = classTypeArgsIncludingOuterClasses?.let { notNullArgs -> @@ -1322,16 +1398,30 @@ open class KotlinUsesExtractor( else -> false } - fun useFunction(f: IrFunction, classTypeArgsIncludingOuterClasses: List? = null, noReplace: Boolean = false): Label { - return useFunction(f, null, classTypeArgsIncludingOuterClasses, noReplace) - } - - fun useFunction(f: IrFunction, parentId: Label?, classTypeArgsIncludingOuterClasses: List?, noReplace: Boolean = false): Label { + fun useFunction(f: IrFunction, classTypeArgsIncludingOuterClasses: List? = null, noReplace: Boolean = false): Label? { if (f.isLocalFunction()) { val ids = getLocallyVisibleFunctionLabels(f) return ids.function.cast() } val javaFun = kotlinFunctionToJavaEquivalent(f, noReplace) + val parentId = useDeclarationParentOf(javaFun, false, classTypeArgsIncludingOuterClasses, true) + if (parentId == null) { + logger.error("Couldn't find parent ID for function ${f.name.asString()}") + return null + } + return useFunction(f, javaFun, parentId, classTypeArgsIncludingOuterClasses) + } + + fun useFunction(f: IrFunction, parentId: Label, classTypeArgsIncludingOuterClasses: List?, noReplace: Boolean = false): Label { + if (f.isLocalFunction()) { + val ids = getLocallyVisibleFunctionLabels(f) + return ids.function.cast() + } + val javaFun = kotlinFunctionToJavaEquivalent(f, noReplace) + return useFunction(f, javaFun, parentId, classTypeArgsIncludingOuterClasses) + } + + private fun useFunction(f: IrFunction, javaFun: IrFunction, parentId: Label, classTypeArgsIncludingOuterClasses: List?): Label { val label = getFunctionLabel(javaFun, parentId, classTypeArgsIncludingOuterClasses) val id: Label = tw.getLabelFor(label) { extractPrivateSpecialisedDeclaration(f, classTypeArgsIncludingOuterClasses) @@ -1621,7 +1711,7 @@ open class KotlinUsesExtractor( val overriddenParentAttributes = (declarationParent as? IrFunction)?.let { (this as? KotlinFileExtractor)?.declarationStack?.findOverriddenAttributes(it) } - val parentId = parent ?: overriddenParentAttributes?.id ?: useDeclarationParent(declarationParent, false) + val parentId = parent ?: overriddenParentAttributes?.id ?: useDeclarationParentOf(vp, false) val idxBase = overriddenParentAttributes?.valueParameters?.indexOf(vp) ?: vp.index val idxOffset = if (declarationParent is IrFunction && declarationParent.extensionReceiverParameter != null) @@ -1649,7 +1739,7 @@ open class KotlinUsesExtractor( it.isConst || it.isLateinit } ?: false - fun getFieldParent(f: IrField) = + private fun getFieldParent(f: IrField) = f.parentClassOrNull?.let { if (it.isCompanion && isDirectlyExposableCompanionObjectField(f)) it.parent @@ -1666,7 +1756,7 @@ open class KotlinUsesExtractor( } fun getFieldLabel(f: IrField): String { - val parentId = useDeclarationParent(getFieldParent(f), false) + val parentId = useDeclarationParentOf(f, false) // Distinguish backing fields of properties based on their extension receiver type; // otherwise two extension properties declared in the same enclosing context will get // clashing trap labels. These are always private, so we can just make up a label without @@ -1679,7 +1769,7 @@ open class KotlinUsesExtractor( tw.getLabelFor(getFieldLabel(f)).also { extractFieldLaterIfExternalFileMember(f) } fun getPropertyLabel(p: IrProperty): String? { - val parentId = useDeclarationParent(p.parent, false) + val parentId = useDeclarationParentOf(p, false) if (parentId == null) { return null } else { @@ -1711,7 +1801,7 @@ open class KotlinUsesExtractor( } fun getEnumEntryLabel(ee: IrEnumEntry): String { - val parentId = useDeclarationParent(ee.parent, false) + val parentId = useDeclarationParentOf(ee, false) return "@\"field;{$parentId};${ee.name.asString()}\"" } @@ -1719,7 +1809,7 @@ open class KotlinUsesExtractor( tw.getLabelFor(getEnumEntryLabel(ee)) fun getTypeAliasLabel(ta: IrTypeAlias): String { - val parentId = useDeclarationParent(ta.parent, true) + val parentId = useDeclarationParentOf(ta, true) return "@\"type_alias;{$parentId};${ta.name.asString()}\"" } diff --git a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt index b27aec9cc52..af3e06599d7 100644 --- a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt +++ b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt @@ -48,6 +48,15 @@ class TrapLabelManager { * duplication. */ val genericSpecialisationsExtracted = HashSet() + + /** + * Sometimes, when we extract a file class we don't have the IrFile + * for it, so we are not able to give it a location. This means that + * the location is written outside of the label creation. + * This allows us to keep track of whether we've written the location + * already in this TRAP file, to avoid duplication. + */ + val fileClassLocationsExtracted = HashSet() } /** diff --git a/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt b/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt index ab4010242a7..927d579ec34 100644 --- a/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt +++ b/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt @@ -1,6 +1,7 @@ package com.github.codeql import com.github.codeql.utils.getJvmName +import com.github.codeql.utils.versions.getFileClassFqName import com.intellij.openapi.vfs.StandardFileSystems import org.jetbrains.kotlin.load.java.sources.JavaSourceElement import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass @@ -108,14 +109,29 @@ private fun getRawIrClassBinaryPath(irClass: IrClass) = fun getIrClassBinaryPath(irClass: IrClass): String { return getRawIrClassBinaryPath(irClass) // Otherwise, make up a fake location: - ?: "/!unknown-binary-location/${getIrElementBinaryName(irClass).replace(".", "/")}.class" + ?: getUnknownBinaryLocation(getIrElementBinaryName(irClass)) } -fun getContainingClassOrSelf(decl: IrDeclaration): IrClass? { - return when(decl) { - is IrClass -> decl - else -> decl.parentClassOrNull +fun getIrDeclarationBinaryPath(d: IrDeclaration): String? { + if (d is IrClass) { + return getIrClassBinaryPath(d) } + val parentClass = d.parentClassOrNull + if (parentClass != null) { + return getIrClassBinaryPath(parentClass) + } + if (d.parent is IrExternalPackageFragment) { + // This is in a file class. + val fqName = getFileClassFqName(d) + if (fqName != null) { + return getUnknownBinaryLocation(fqName.asString()) + } + } + return null +} + +private fun getUnknownBinaryLocation(s: String): String { + return "/!unknown-binary-location/${s.replace(".", "/")}.class" } fun getJavaEquivalentClassId(c: IrClass) = diff --git a/java/kotlin-extractor/src/main/kotlin/utils/ExternalDecls.kt b/java/kotlin-extractor/src/main/kotlin/utils/ExternalDecls.kt index 527f28dd165..8c7aa94c654 100644 --- a/java/kotlin-extractor/src/main/kotlin/utils/ExternalDecls.kt +++ b/java/kotlin-extractor/src/main/kotlin/utils/ExternalDecls.kt @@ -3,17 +3,54 @@ package com.github.codeql.utils import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrExternalPackageFragment import org.jetbrains.kotlin.ir.util.isFileClass import org.jetbrains.kotlin.ir.util.parentClassOrNull fun isExternalDeclaration(d: IrDeclaration): Boolean { - return d.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB || - d.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB || - d.origin.toString() == "FUNCTION_INTERFACE_CLASS" // Treat kotlin.coroutines.* like ordinary library classes + /* + With Kotlin 1 we get things like (from .dump()): + PROPERTY IR_EXTERNAL_JAVA_DECLARATION_STUB name:MIN_VALUE visibility:public modality:FINAL [const,val] + FIELD IR_EXTERNAL_JAVA_DECLARATION_STUB name:MIN_VALUE type:kotlin.Int visibility:public [final,static] + EXPRESSION_BODY + CONST Int type=kotlin.Int value=-2147483648 + */ + if (d.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB || + d.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB || + d.origin.toString() == "FUNCTION_INTERFACE_CLASS") { // Treat kotlin.coroutines.* like ordinary library classes + return true + } + /* + With Kotlin 2, the property itself is not marked as an external stub, but it parent is: + CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:OPEN visibility:public [companion] superTypes:[] + PROPERTY name:MIN_VALUE visibility:public modality:FINAL [const,val] + FIELD PROPERTY_BACKING_FIELD name:MIN_VALUE type:kotlin.Int visibility:public [final] + EXPRESSION_BODY + CONST Int type=kotlin.Int value=-2147483648 + */ + val p = d.parent + if (p is IrExternalPackageFragment) { + // This is an external declaration in a (multi)file class + return true + } + if (p is IrDeclaration) { + return isExternalDeclaration(p) + } + return false } /** * Returns true if `d` is not itself a class, but is a member of an external file class. */ -fun isExternalFileClassMember(d: IrDeclaration) = d !is IrClass && (d.parentClassOrNull?.let { it.isFileClass } ?: false) +fun isExternalFileClassMember(d: IrDeclaration): Boolean { + if (d is IrClass) { return false } + val p = d.parent + when (p) { + is IrClass -> return p.isFileClass + is IrExternalPackageFragment -> + // This is an external declaration in a (multi)file class + return true + } + return false +} diff --git a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_4_32/getFileClassFqName.kt b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_4_32/getFileClassFqName.kt new file mode 100644 index 00000000000..33e1a676348 --- /dev/null +++ b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_4_32/getFileClassFqName.kt @@ -0,0 +1,8 @@ +package com.github.codeql.utils.versions + +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.ir.declarations.IrDeclaration + +fun getFileClassFqName(@Suppress("UNUSED_PARAMETER") d: IrDeclaration): FqName? { + return null +} diff --git a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_7_0/getFileClassFqName.kt b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_7_0/getFileClassFqName.kt new file mode 100644 index 00000000000..687271534b4 --- /dev/null +++ b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_7_0/getFileClassFqName.kt @@ -0,0 +1,29 @@ +package com.github.codeql.utils.versions + +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrMemberWithContainerSource +import org.jetbrains.kotlin.load.kotlin.FacadeClassSource + +fun getFileClassFqName(d: IrDeclaration): FqName? { + // d is in a file class. + // Get the name in a similar way to the compiler's ExternalPackageParentPatcherLowering + // visitMemberAccess/generateOrGetFacadeClass. + if (d is IrMemberWithContainerSource) { + val containerSource = d.containerSource + if (containerSource is FacadeClassSource) { + val facadeClassName = containerSource.facadeClassName + if (facadeClassName != null) { + // TODO: This is really a multifile-class rather than a file-class, + // but for now we treat them the same. + return facadeClassName.fqNameForTopLevelClassMaybeWithDollars + } else { + return containerSource.className.fqNameForTopLevelClassMaybeWithDollars + } + } else { + return null + } + } else { + return null + } +} diff --git a/java/ql/lib/CHANGELOG.md b/java/ql/lib/CHANGELOG.md index b6315cf80e4..d2f6119868f 100644 --- a/java/ql/lib/CHANGELOG.md +++ b/java/ql/lib/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.7.2 + +### New Features + +* A `Diagnostic.getCompilationInfo()` predicate has been added. + +### Minor Analysis Improvements + +* Fixed a typo in the `StdlibRandomSource` class in `RandomDataSource.qll`, which caused the class to improperly model calls to the `nextBytes` method. Queries relying on `StdlibRandomSource` may see an increase in results. +* Improved the precision of virtual dispatch of `java.io.InputStream` methods. Now, calls to these methods will not dispatch to arbitrary implementations of `InputStream` if there is a high-confidence alternative (like a models-as-data summary). +* Added more dataflow steps for `java.io.InputStream`s that wrap other `java.io.InputStream`s. +* Added models for the Struts 2 framework. +* Improved the modeling of Struts 2 sources of untrusted data by tainting the whole object graph of the objects unmarshaled from an HTTP request. + ## 0.7.1 ### New Features diff --git a/java/ql/lib/change-notes/2023-06-16-initial-version.md b/java/ql/lib/change-notes/2023-06-16-initial-version.md new file mode 100644 index 00000000000..1f9ea943244 --- /dev/null +++ b/java/ql/lib/change-notes/2023-06-16-initial-version.md @@ -0,0 +1,4 @@ +--- +category: majorAnalysis +--- +* Improved support for flow through captured variables that properly adheres to inter-procedural control flow. diff --git a/java/ql/lib/change-notes/2023-07-12-add-models-for-struts2-framework.md b/java/ql/lib/change-notes/2023-07-12-add-models-for-struts2-framework.md deleted file mode 100644 index 6b7843357dd..00000000000 --- a/java/ql/lib/change-notes/2023-07-12-add-models-for-struts2-framework.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -category: minorAnalysis ---- -* Added models for the Struts 2 framework. - diff --git a/java/ql/lib/change-notes/2023-07-12-improve-sources-for-the-struts2-framework.md b/java/ql/lib/change-notes/2023-07-12-improve-sources-for-the-struts2-framework.md deleted file mode 100644 index 6e81383751f..00000000000 --- a/java/ql/lib/change-notes/2023-07-12-improve-sources-for-the-struts2-framework.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -category: minorAnalysis ---- -* Improved the modeling of Struts 2 sources of untrusted data by tainting the whole object graph of the objects unmarshaled from an HTTP request. - diff --git a/java/ql/lib/change-notes/2023-07-14-getCompilationInfo.md b/java/ql/lib/change-notes/2023-07-14-getCompilationInfo.md deleted file mode 100644 index bb86f479b3d..00000000000 --- a/java/ql/lib/change-notes/2023-07-14-getCompilationInfo.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: feature ---- -* A `Diagnostic.getCompilationInfo()` predicate has been added. diff --git a/java/ql/lib/change-notes/2023-07-19-inputstream-dispatch.md b/java/ql/lib/change-notes/2023-07-19-inputstream-dispatch.md deleted file mode 100644 index d093c771d51..00000000000 --- a/java/ql/lib/change-notes/2023-07-19-inputstream-dispatch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -category: minorAnalysis ---- -* Improved the precision of virtual dispatch of `java.io.InputStream` methods. Now, calls to these methods will not dispatch to arbitrary implementations of `InputStream` if there is a high-confidence alternative (like a models-as-data summary). - \ No newline at end of file diff --git a/java/ql/lib/change-notes/2023-07-19-inputstream-wrapper-steps.md b/java/ql/lib/change-notes/2023-07-19-inputstream-wrapper-steps.md deleted file mode 100644 index aaeacf93e34..00000000000 --- a/java/ql/lib/change-notes/2023-07-19-inputstream-wrapper-steps.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: minorAnalysis ---- -* Added more dataflow steps for `java.io.InputStream`s that wrap other `java.io.InputStream`s. diff --git a/java/ql/lib/change-notes/2023-08-04-mad-withoutelement.md b/java/ql/lib/change-notes/2023-08-04-mad-withoutelement.md new file mode 100644 index 00000000000..f5e5cda8896 --- /dev/null +++ b/java/ql/lib/change-notes/2023-08-04-mad-withoutelement.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Add support for `WithElement` and `WithoutElement` for MaD access paths. \ No newline at end of file diff --git a/java/ql/lib/change-notes/2023-08-07-randomdatasource-typo-fix.md b/java/ql/lib/change-notes/2023-08-07-randomdatasource-typo-fix.md deleted file mode 100644 index 199cd8c1238..00000000000 --- a/java/ql/lib/change-notes/2023-08-07-randomdatasource-typo-fix.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: minorAnalysis ---- -* Fixed a typo in the `StdlibRandomSource` class in `RandomDataSource.qll`, which caused the class to improperly model calls to the `nextBytes` method. Queries relying on `StdlibRandomSource` may see an increase in results. diff --git a/java/ql/lib/change-notes/released/0.7.2.md b/java/ql/lib/change-notes/released/0.7.2.md new file mode 100644 index 00000000000..1a19854d503 --- /dev/null +++ b/java/ql/lib/change-notes/released/0.7.2.md @@ -0,0 +1,13 @@ +## 0.7.2 + +### New Features + +* A `Diagnostic.getCompilationInfo()` predicate has been added. + +### Minor Analysis Improvements + +* Fixed a typo in the `StdlibRandomSource` class in `RandomDataSource.qll`, which caused the class to improperly model calls to the `nextBytes` method. Queries relying on `StdlibRandomSource` may see an increase in results. +* Improved the precision of virtual dispatch of `java.io.InputStream` methods. Now, calls to these methods will not dispatch to arbitrary implementations of `InputStream` if there is a high-confidence alternative (like a models-as-data summary). +* Added more dataflow steps for `java.io.InputStream`s that wrap other `java.io.InputStream`s. +* Added models for the Struts 2 framework. +* Improved the modeling of Struts 2 sources of untrusted data by tainting the whole object graph of the objects unmarshaled from an HTTP request. diff --git a/java/ql/lib/codeql-pack.release.yml b/java/ql/lib/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/java/ql/lib/codeql-pack.release.yml +++ b/java/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/java/ql/lib/ext/java.lang.model.yml b/java/ql/lib/ext/java.lang.model.yml index 817683836fb..4b7353b0062 100644 --- a/java/ql/lib/ext/java.lang.model.yml +++ b/java/ql/lib/ext/java.lang.model.yml @@ -176,7 +176,6 @@ extensions: - ["java.lang", "Object", "getClass", "()", "summary", "manual"] - ["java.lang", "Object", "hashCode", "()", "summary", "manual"] - ["java.lang", "Object", "toString", "()", "summary", "manual"] - - ["java.lang", "Runnable", "run", "()", "summary", "manual"] - ["java.lang", "Runtime", "getRuntime", "()", "summary", "manual"] - ["java.lang", "String", "compareTo", "(String)", "summary", "manual"] - ["java.lang", "String", "contains", "(CharSequence)", "summary", "manual"] diff --git a/java/ql/lib/ext/java.util.model.yml b/java/ql/lib/ext/java.util.model.yml index 15ab5e0c0be..feb7ff473bd 100644 --- a/java/ql/lib/ext/java.util.model.yml +++ b/java/ql/lib/ext/java.util.model.yml @@ -140,7 +140,8 @@ extensions: - ["java.util", "LinkedHashSet", False, "LinkedHashSet", "(Collection)", "", "Argument[0].Element", "Argument[this].Element", "value", "manual"] - ["java.util", "LinkedList", False, "LinkedList", "(Collection)", "", "Argument[0].Element", "Argument[this].Element", "value", "manual"] - ["java.util", "List", True, "add", "(int,Object)", "", "Argument[1]", "Argument[this].Element", "value", "manual"] - - ["java.util", "List", True, "addAll", "(int,Collection)", "", "Argument[1].Element", "Argument[this].Element", "value", "manual"] + - ["java.util", "List", True, "addAll", "(int,Collection)", "", "Argument[1].WithElement", "Argument[this]", "value", "manual"] + - ["java.util", "List", True, "clear", "()", "", "Argument[this].WithoutElement", "Argument[this]", "value", "manual"] - ["java.util", "List", False, "copyOf", "(Collection)", "", "Argument[0].Element", "ReturnValue.Element", "value", "manual"] - ["java.util", "List", True, "get", "(int)", "", "Argument[this].Element", "ReturnValue", "value", "manual"] - ["java.util", "List", True, "listIterator", "", "", "Argument[this].Element", "ReturnValue.Element", "value", "manual"] @@ -313,6 +314,7 @@ extensions: - ["java.util", "Scanner", True, "useLocale", "", "", "Argument[this]", "ReturnValue", "value", "manual"] - ["java.util", "Scanner", True, "useRadix", "", "", "Argument[this]", "ReturnValue", "value", "manual"] - ["java.util", "Set", False, "copyOf", "(Collection)", "", "Argument[0].Element", "ReturnValue.Element", "value", "manual"] + - ["java.util", "Set", False, "clear", "()", "", "Argument[this].WithoutElement", "Argument[this]", "value", "manual"] - ["java.util", "Set", False, "of", "(Object)", "", "Argument[0]", "ReturnValue.Element", "value", "manual"] - ["java.util", "Set", False, "of", "(Object,Object)", "", "Argument[0..1]", "ReturnValue.Element", "value", "manual"] - ["java.util", "Set", False, "of", "(Object,Object,Object)", "", "Argument[0..2]", "ReturnValue.Element", "value", "manual"] @@ -424,10 +426,8 @@ extensions: # When `WithoutElement` is implemented, these should be changed to summary models of the form `Argument[this].WithoutElement -> Argument[this]`. - ["java.util", "Collection", "removeIf", "(Predicate)", "summary", "manual"] - ["java.util", "Iterator", "remove", "()", "summary", "manual"] - - ["java.util", "List", "clear", "()", "summary", "manual"] - ["java.util", "List", "remove", "(Object)", "summary", "manual"] - ["java.util", "Map", "clear", "()", "summary", "manual"] - - ["java.util", "Set", "clear", "()", "summary", "manual"] - ["java.util", "Set", "remove", "(Object)", "summary", "manual"] - ["java.util", "Set", "removeAll", "(Collection)", "summary", "manual"] diff --git a/java/ql/lib/qlpack.yml b/java/ql/lib/qlpack.yml index 8b5bd6697c2..02e9780249f 100644 --- a/java/ql/lib/qlpack.yml +++ b/java/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/java-all -version: 0.7.2-dev +version: 0.7.3-dev groups: java dbscheme: config/semmlecode.dbscheme extractor: java diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll index c04126d5f7f..b7ef1410bfa 100644 --- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll @@ -35,8 +35,9 @@ * or method, or a parameter. * 7. The `input` column specifies how data enters the element selected by the * first 6 columns, and the `output` column specifies how data leaves the - * element selected by the first 6 columns. An `input` can be either "", - * "Argument[n]", "Argument[n1..n2]", "ReturnValue": + * element selected by the first 6 columns. An `input` can be a dot separated + * path consisting of either "", "Argument[n]", "Argument[n1..n2]", + * "ReturnValue", "Element", "WithoutElement", or "WithElement": * - "": Selects a write to the selected element in case this is a field. * - "Argument[n]": Selects an argument in a call to the selected element. * The arguments are zero-indexed, and `this` specifies the qualifier. @@ -44,9 +45,15 @@ * the given range. The range is inclusive at both ends. * - "ReturnValue": Selects a value being returned by the selected element. * This requires that the selected element is a method with a body. + * - "Element": Selects the collection elements of the selected element. + * - "WithoutElement": Selects the selected element but without + * its collection elements. + * - "WithElement": Selects the collection elements of the selected element, but + * points to the selected element. * - * An `output` can be either "", "Argument[n]", "Argument[n1..n2]", "Parameter", - * "Parameter[n]", "Parameter[n1..n2]", or "ReturnValue": + * An `output` can be can be a dot separated path consisting of either "", + * "Argument[n]", "Argument[n1..n2]", "Parameter", "Parameter[n]", + * "Parameter[n1..n2]", "ReturnValue", or "Element": * - "": Selects a read of a selected field, or a selected parameter. * - "Argument[n]": Selects the post-update value of an argument in a call to the * selected element. That is, the value of the argument after the call returns. @@ -61,6 +68,7 @@ * - "Parameter[n1..n2]": Similar to "Parameter[n]" but selects any parameter * in the given range. The range is inclusive at both ends. * - "ReturnValue": Selects the return value of a call to the selected element. + * - "Element": Selects the collection elements of the selected element. * 8. The `kind` column is a tag that can be referenced from QL to determine to * which classes the interpreted elements should be added. For example, for * sources "remote" indicates a default remote flow source, and for summaries diff --git a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll index e45ba0be27e..b0e342ab320 100644 --- a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll +++ b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll @@ -101,6 +101,7 @@ abstract class SyntheticCallable extends string { * A module for importing frameworks that define synthetic callables. */ private module SyntheticCallables { + private import semmle.code.java.dispatch.WrappedInvocation private import semmle.code.java.frameworks.android.Intent private import semmle.code.java.frameworks.Stream } @@ -170,6 +171,8 @@ class SummarizedCallableBase extends TSummarizedCallableBase { } } +class Provenance = Impl::Public::Provenance; + class SummarizedCallable = Impl::Public::SummarizedCallable; class NeutralCallable = Impl::Public::NeutralCallable; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll index 8c44e7df5b0..7ee703808e2 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll @@ -55,7 +55,8 @@ private module Cached { ) } or TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or - TFieldValueNode(Field f) + TFieldValueNode(Field f) or + TCaptureNode(CaptureFlow::SynthesizedCaptureNode cn) cached newtype TContent = @@ -64,6 +65,7 @@ private module Cached { TCollectionContent() or TMapKeyContent() or TMapValueContent() or + TCapturedVariableContent(CapturedVariable v) or TSyntheticFieldContent(SyntheticField s) cached @@ -73,6 +75,7 @@ private module Cached { TCollectionContentApprox() or TMapKeyContentApprox() or TMapValueContentApprox() or + TCapturedVariableContentApprox(CapturedVariable v) or TSyntheticFieldApproxContent() } @@ -127,6 +130,8 @@ module Public { or result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType() or + result = this.(CaptureNode).getTypeImpl() + or result = this.(FieldValueNode).getField().getType() } @@ -372,6 +377,7 @@ module Private { result.asCallable() = n.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or result = nodeGetEnclosingCallable(n.(ImplicitPostUpdateNode).getPreUpdateNode()) or result.asSummarizedCallable() = n.(FlowSummaryNode).getSummarizedCallable() or + result.asCallable() = n.(CaptureNode).getSynthesizedCaptureNode().getEnclosingCallable() or result.asFieldScope() = n.(FieldValueNode).getField() } @@ -491,6 +497,28 @@ module Private { c.asSummarizedCallable() = this.getSummarizedCallable() and pos = this.getPosition() } } + + /** + * A synthesized data flow node representing a closure object that tracks + * captured variables. + */ + class CaptureNode extends Node, TCaptureNode { + private CaptureFlow::SynthesizedCaptureNode cn; + + CaptureNode() { this = TCaptureNode(cn) } + + CaptureFlow::SynthesizedCaptureNode getSynthesizedCaptureNode() { result = cn } + + override Location getLocation() { result = cn.getLocation() } + + override string toString() { result = cn.toString() } + + Type getTypeImpl() { + exists(Variable v | cn.isVariableAccess(v) and result = v.getType()) + or + cn.isInstanceAccess() and result = cn.getEnclosingCallable().getDeclaringType() + } + } } private import Private @@ -520,3 +548,14 @@ private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode { override Node getPreUpdateNode() { result = pre } } + +private class CapturePostUpdateNode extends PostUpdateNode, CaptureNode { + private CaptureNode pre; + + CapturePostUpdateNode() { + CaptureFlow::capturePostUpdateNode(this.getSynthesizedCaptureNode(), + pre.getSynthesizedCaptureNode()) + } + + override Node getPreUpdateNode() { result = pre } +} diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index ed4598c8bb6..212232e077a 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -10,6 +10,7 @@ private import semmle.code.java.dataflow.FlowSummary private import FlowSummaryImpl as FlowSummaryImpl private import DataFlowImplConsistency private import DataFlowNodes +private import codeql.dataflow.VariableCapture as VariableCapture import DataFlowNodes::Private private newtype TReturnKind = TNormalReturnKind() @@ -51,26 +52,131 @@ private predicate fieldStep(Node node1, Node node2) { ) } -/** - * Holds if data can flow from `node1` to `node2` through variable capture. - */ -private predicate variableCaptureStep(Node node1, ExprNode node2) { - exists(SsaImplicitInit closure, SsaVariable captured | - closure.captures(captured) and - node2.getExpr() = closure.getAFirstUse() - | - node1.asExpr() = captured.getAUse() - or - not exists(captured.getAUse()) and - exists(SsaVariable capturedDef | capturedDef = captured.getAnUltimateDefinition() | - capturedDef.(SsaImplicitInit).isParameterDefinition(node1.asParameter()) or - capturedDef.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = - node1.asExpr() or - capturedDef.(SsaExplicitUpdate).getDefiningExpr().(AssignOp) = node1.asExpr() - ) +private predicate closureFlowStep(Expr e1, Expr e2) { + simpleAstFlowStep(e1, e2) + or + exists(SsaVariable v | + v.getAUse() = e2 and + v.getAnUltimateDefinition().(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = + e1 ) } +private module CaptureInput implements VariableCapture::InputSig { + private import java as J + + class Location = J::Location; + + class BasicBlock instanceof J::BasicBlock { + string toString() { result = super.toString() } + + Callable getEnclosingCallable() { result = super.getEnclosingCallable() } + + Location getLocation() { result = super.getLocation() } + } + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { bbIDominates(result, bb) } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { + result = bb.(J::BasicBlock).getABBSuccessor() + } + + //TODO: support capture of `this` in lambdas + class CapturedVariable instanceof LocalScopeVariable { + CapturedVariable() { + 2 <= + strictcount(J::Callable c | + c = this.getCallable() or c = this.getAnAccess().getEnclosingCallable() + ) + } + + string toString() { result = super.toString() } + + Callable getCallable() { result = super.getCallable() } + + Location getLocation() { result = super.getLocation() } + } + + class CapturedParameter extends CapturedVariable instanceof Parameter { } + + class Expr instanceof J::Expr { + string toString() { result = super.toString() } + + Location getLocation() { result = super.getLocation() } + + predicate hasCfgNode(BasicBlock bb, int i) { this = bb.(J::BasicBlock).getNode(i) } + } + + class VariableWrite extends Expr instanceof VariableUpdate { + CapturedVariable v; + + VariableWrite() { super.getDestVar() = v } + + CapturedVariable getVariable() { result = v } + + Expr getSource() { + result = this.(VariableAssign).getSource() or + result = this.(AssignOp) + } + } + + class VariableRead extends Expr instanceof RValue { + CapturedVariable v; + + VariableRead() { super.getVariable() = v } + + CapturedVariable getVariable() { result = v } + } + + class ClosureExpr extends Expr instanceof ClassInstanceExpr { + NestedClass nc; + + ClosureExpr() { + nc.(AnonymousClass).getClassInstanceExpr() = this + or + nc instanceof LocalClass and + super.getConstructedType().getASourceSupertype*().getSourceDeclaration() = nc + } + + predicate hasBody(Callable body) { nc.getACallable() = body } + + predicate hasAliasedAccess(Expr f) { closureFlowStep+(this, f) and not closureFlowStep(f, _) } + } + + class Callable extends J::Callable { + predicate isConstructor() { this instanceof Constructor } + } +} + +class CapturedVariable = CaptureInput::CapturedVariable; + +class CapturedParameter = CaptureInput::CapturedParameter; + +module CaptureFlow = VariableCapture::Flow; + +private CaptureFlow::ClosureNode asClosureNode(Node n) { + result = n.(CaptureNode).getSynthesizedCaptureNode() or + result.(CaptureFlow::ExprNode).getExpr() = n.asExpr() or + result.(CaptureFlow::ExprPostUpdateNode).getExpr() = + n.(PostUpdateNode).getPreUpdateNode().asExpr() or + result.(CaptureFlow::ParameterNode).getParameter() = n.asParameter() or + result.(CaptureFlow::ThisParameterNode).getCallable() = n.(InstanceParameterNode).getCallable() or + exprNode(result.(CaptureFlow::MallocNode).getClosureExpr()).(PostUpdateNode).getPreUpdateNode() = + n +} + +private predicate captureStoreStep(Node node1, CapturedVariableContent c, Node node2) { + CaptureFlow::storeStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2)) +} + +private predicate captureReadStep(Node node1, CapturedVariableContent c, Node node2) { + CaptureFlow::readStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2)) +} + +predicate captureValueStep(Node node1, Node node2) { + CaptureFlow::localFlowStep(asClosureNode(node1), asClosureNode(node2)) +} + /** * Holds if data can flow from `node1` to `node2` through a field or * variable capture. @@ -78,10 +184,6 @@ private predicate variableCaptureStep(Node node1, ExprNode node2) { predicate jumpStep(Node node1, Node node2) { fieldStep(node1, node2) or - variableCaptureStep(node1, node2) - or - variableCaptureStep(node1.(PostUpdateNode).getPreUpdateNode(), node2) - or any(AdditionalValueStep a).step(node1, node2) and node1.getEnclosingCallable() != node2.getEnclosingCallable() or @@ -117,6 +219,8 @@ predicate storeStep(Node node1, ContentSet f, Node node2) { or FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), f, node2.(FlowSummaryNode).getSummaryNode()) + or + captureStoreStep(node1, f, node2) } /** @@ -149,6 +253,8 @@ predicate readStep(Node node1, ContentSet f, Node node2) { or FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), f, node2.(FlowSummaryNode).getSummaryNode()) + or + captureReadStep(node1, f, node2) } /** @@ -231,19 +337,29 @@ private newtype TDataFlowCallable = TSummarizedCallable(SummarizedCallable c) or TFieldScope(Field f) +/** + * A callable or scope enclosing some number of data flow nodes. This can either + * be a source callable, a synthesized callable for which we have a summary + * model, or a synthetic scope for a field value node. + */ class DataFlowCallable extends TDataFlowCallable { + /** Gets the source callable corresponding to this callable, if any. */ Callable asCallable() { this = TSrcCallable(result) } + /** Gets the summary model callable corresponding to this callable, if any. */ SummarizedCallable asSummarizedCallable() { this = TSummarizedCallable(result) } + /** Gets the field corresponding to this callable, if it is a field value scope. */ Field asFieldScope() { this = TFieldScope(result) } + /** Gets a textual representation of this callable. */ string toString() { result = this.asCallable().toString() or result = "Synthetic: " + this.asSummarizedCallable().toString() or result = "Field scope: " + this.asFieldScope().toString() } + /** Gets the location of this callable. */ Location getLocation() { result = this.asCallable().getLocation() or result = this.asSummarizedCallable().getLocation() or @@ -406,6 +522,8 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves */ predicate allowParameterReturnInSelf(ParameterNode p) { FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p) + or + CaptureFlow::heuristicAllowInstanceParameterReturnInSelf(p.(InstanceParameterNode).getCallable()) } /** An approximated `Content`. */ @@ -447,6 +565,10 @@ ContentApprox getContentApprox(Content c) { or c instanceof MapValueContent and result = TMapValueContentApprox() or + exists(CapturedVariable v | + c = TCapturedVariableContent(v) and result = TCapturedVariableContentApprox(v) + ) + or c instanceof SyntheticFieldContent and result = TSyntheticFieldApproxContent() } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 29758d4b972..080724d8f0c 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -135,6 +135,30 @@ private module Cached { import Cached +private predicate capturedVariableRead(Node n) { + n.asExpr().(RValue).getVariable() instanceof CapturedVariable +} + +/** + * Holds if there is a data flow step from `e1` to `e2` that only steps from + * child to parent in the AST. + */ +predicate simpleAstFlowStep(Expr e1, Expr e2) { + e2.(CastingExpr).getExpr() = e1 + or + e2.(ChooseExpr).getAResultExpr() = e1 + or + e2.(AssignExpr).getSource() = e1 + or + e2.(ArrayCreationExpr).getInit() = e1 + or + e2 = any(StmtExpr stmtExpr | e1 = stmtExpr.getResultExpr()) + or + e2 = any(NotNullExpr nne | e1 = nne.getExpr()) + or + e2.(WhenExpr).getBranch(_).getAResult() = e1 +} + private predicate simpleLocalFlowStep0(Node node1, Node node2) { TaintTrackingUtil::forceCachingInSameStage() and // Variable flow steps through adjacent def-use and use-use pairs. @@ -142,39 +166,31 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or upd.getDefiningExpr().(AssignOp) = node1.asExpr() | - node2.asExpr() = upd.getAFirstUse() + node2.asExpr() = upd.getAFirstUse() and + not capturedVariableRead(node2) ) or exists(SsaImplicitInit init | init.isParameterDefinition(node1.asParameter()) and - node2.asExpr() = init.getAFirstUse() + node2.asExpr() = init.getAFirstUse() and + not capturedVariableRead(node2) ) or adjacentUseUse(node1.asExpr(), node2.asExpr()) and not exists(FieldRead fr | hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr() ) and - not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(node1, _) + not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(node1, _) and + not capturedVariableRead(node2) or ThisFlow::adjacentThisRefs(node1, node2) or - adjacentUseUse(node1.(PostUpdateNode).getPreUpdateNode().asExpr(), node2.asExpr()) + adjacentUseUse(node1.(PostUpdateNode).getPreUpdateNode().asExpr(), node2.asExpr()) and + not capturedVariableRead(node2) or ThisFlow::adjacentThisRefs(node1.(PostUpdateNode).getPreUpdateNode(), node2) or - node2.asExpr().(CastingExpr).getExpr() = node1.asExpr() - or - node2.asExpr().(ChooseExpr).getAResultExpr() = node1.asExpr() - or - node2.asExpr().(AssignExpr).getSource() = node1.asExpr() - or - node2.asExpr().(ArrayCreationExpr).getInit() = node1.asExpr() - or - node2.asExpr() = any(StmtExpr stmtExpr | node1.asExpr() = stmtExpr.getResultExpr()) - or - node2.asExpr() = any(NotNullExpr nne | node1.asExpr() = nne.getExpr()) - or - node2.asExpr().(WhenExpr).getBranch(_).getAResult() = node1.asExpr() + simpleAstFlowStep(node1.asExpr(), node2.asExpr()) or exists(MethodAccess ma, ValuePreservingMethod m, int argNo | ma.getCallee().getSourceDeclaration() = m and m.returnsValue(argNo) @@ -185,6 +201,8 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) { or FlowSummaryImpl::Private::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(), node2.(FlowSummaryNode).getSummaryNode(), true) + or + captureValueStep(node1, node2) } /** @@ -256,6 +274,19 @@ class MapValueContent extends Content, TMapValueContent { override string toString() { result = "" } } +/** A captured variable. */ +class CapturedVariableContent extends Content, TCapturedVariableContent { + CapturedVariable v; + + CapturedVariableContent() { this = TCapturedVariableContent(v) } + + CapturedVariable getVariable() { result = v } + + override DataFlowType getType() { result = getErasedRepr(v.(Variable).getType()) } + + override string toString() { result = v.toString() } +} + /** A reference through a synthetic instance field. */ class SyntheticFieldContent extends Content, TSyntheticFieldContent { SyntheticField s; diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll index d9782c2eecf..b99c96c8085 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll @@ -170,6 +170,10 @@ predicate neutralSummaryElement(SummarizedCallableBase c, string provenance) { bindingset[c] SummaryComponent interpretComponentSpecific(AccessPathToken c) { exists(Content content | parseContent(c, content) and result = SummaryComponent::content(content)) + or + c = "WithoutElement" and result = SummaryComponent::withoutContent(any(CollectionContent cc)) + or + c = "WithElement" and result = SummaryComponent::withContent(any(CollectionContent cc)) } /** Gets the summary component for specification component `c`, if any. */ @@ -196,6 +200,10 @@ private string getContentSpecific(Content c) { /** Gets the textual representation of the content in the format used for MaD models. */ string getMadRepresentationSpecific(SummaryComponent sc) { exists(Content c | sc = TContentSummaryComponent(c) and result = getContentSpecific(c)) + or + sc = TWithoutContentSummaryComponent(_) and result = "WithoutElement" + or + sc = TWithContentSummaryComponent(_) and result = "WithElement" } bindingset[pos] diff --git a/java/ql/lib/semmle/code/java/dispatch/WrappedInvocation.qll b/java/ql/lib/semmle/code/java/dispatch/WrappedInvocation.qll index ec1eea5f93c..77f3ff11291 100644 --- a/java/ql/lib/semmle/code/java/dispatch/WrappedInvocation.qll +++ b/java/ql/lib/semmle/code/java/dispatch/WrappedInvocation.qll @@ -58,3 +58,37 @@ Method getRunnerTarget(MethodAccess ma) { result.overridesOrInstantiates*(runmethod) ) } + +import semmle.code.java.dataflow.FlowSummary +import semmle.code.java.dataflow.internal.FlowSummaryImplSpecific as ImplSpecific + +private predicate mayInvokeCallback(SrcMethod m, int n) { + m.getParameterType(n).(RefType).getSourceDeclaration() instanceof FunctionalInterface and + (not m.fromSource() or m.isNative() or m.getFile().getAbsolutePath().matches("%/test/stubs/%")) +} + +private class SummarizedCallableWithCallback extends SummarizedCallable { + private int pos; + + SummarizedCallableWithCallback() { mayInvokeCallback(this.asCallable(), pos) } + + override predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + input = SummaryComponentStack::argument(pos) and + output = SummaryComponentStack::push(SummaryComponent::parameter(-1), input) and + preservesValue = true + } + + override predicate hasProvenance(Provenance provenance) { provenance = "hq-generated" } +} + +private class RequiredComponentStackForCallback extends RequiredSummaryComponentStack { + override predicate required(SummaryComponent head, SummaryComponentStack tail) { + exists(int pos | + mayInvokeCallback(_, pos) and + head = SummaryComponent::parameter(-1) and + tail = SummaryComponentStack::argument(pos) + ) + } +} diff --git a/java/ql/src/CHANGELOG.md b/java/ql/src/CHANGELOG.md index d2517609d01..9f6f4717c1f 100644 --- a/java/ql/src/CHANGELOG.md +++ b/java/ql/src/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.7.2 + +### Minor Analysis Improvements + +* The sanitizer in `java/potentially-weak-cryptographic-algorithm` has been improved, so the query may yield additional results. + ## 0.7.1 ### Minor Analysis Improvements diff --git a/java/ql/src/Metrics/Summaries/TopJdkApis.qll b/java/ql/src/Metrics/Summaries/TopJdkApis.qll index 420c231c003..20540f4f619 100644 --- a/java/ql/src/Metrics/Summaries/TopJdkApis.qll +++ b/java/ql/src/Metrics/Summaries/TopJdkApis.qll @@ -307,6 +307,7 @@ class TopJdkApi extends SummarizedCallableBase { predicate hasManualMadModel() { this.hasManualSummary() or this.hasManualNeutral() } /* * Note: the following top JDK APIs are not modeled with MaD: + * `java.lang.Runnable#run()`: specialised lambda flow * `java.lang.String#valueOf(Object)`: a complex case; an alias for `Object.toString`, except the dispatch is hidden * `java.lang.System#getProperty(String)`: needs to be modeled by regular CodeQL matching the get and set keys to reduce FPs * `java.lang.System#setProperty(String,String)`: needs to be modeled by regular CodeQL matching the get and set keys to reduce FPs diff --git a/java/ql/src/Telemetry/AutomodelApplicationModeCharacteristics.qll b/java/ql/src/Telemetry/AutomodelApplicationModeCharacteristics.qll index e14c18ace32..90ec5e93f8b 100644 --- a/java/ql/src/Telemetry/AutomodelApplicationModeCharacteristics.qll +++ b/java/ql/src/Telemetry/AutomodelApplicationModeCharacteristics.qll @@ -21,21 +21,97 @@ import AutomodelEndpointTypes as AutomodelEndpointTypes newtype JavaRelatedLocationType = CallContext() +newtype TApplicationModeEndpoint = + TExplicitArgument(Call call, DataFlow::Node arg) { + exists(Argument argExpr | + arg.asExpr() = argExpr and call = argExpr.getCall() and not argExpr.isVararg() + ) + } or + TInstanceArgument(Call call, DataFlow::Node arg) { arg = DataFlow::getInstanceArgument(call) } or + TImplicitVarargsArray(Call call, DataFlow::Node arg, int idx) { + exists(Argument argExpr | + arg.asExpr() = argExpr and + call.getArgument(idx) = argExpr and + argExpr.isVararg() and + not exists(int i | i < idx and call.getArgument(i).(Argument).isVararg()) + ) + } + +/** + * An endpoint is a node that is a candidate for modeling. + */ +abstract private class ApplicationModeEndpoint extends TApplicationModeEndpoint { + abstract predicate isArgOf(Call c, int idx); + + Call getCall() { this.isArgOf(result, _) } + + int getArgIndex() { this.isArgOf(_, result) } + + abstract Top asTop(); + + abstract DataFlow::Node asNode(); + + abstract string toString(); +} + /** * A class representing nodes that are arguments to calls. */ -private class ArgumentNode extends DataFlow::Node { - Call c; +class ExplicitArgument extends ApplicationModeEndpoint, TExplicitArgument { + Call call; + DataFlow::Node arg; - ArgumentNode() { - exists(Argument arg | this.asExpr() = arg and not arg.isVararg() and c = arg.getCall()) - or - this.(DataFlow::ImplicitVarargsArray).getCall() = c - or - this = DataFlow::getInstanceArgument(c) + ExplicitArgument() { this = TExplicitArgument(call, arg) } + + override predicate isArgOf(Call c, int idx) { c = call and this.asTop() = c.getArgument(idx) } + + override Top asTop() { result = arg.asExpr() } + + override DataFlow::Node asNode() { result = arg } + + override string toString() { result = arg.toString() } +} + +class InstanceArgument extends ApplicationModeEndpoint, TInstanceArgument { + Call call; + DataFlow::Node arg; + + InstanceArgument() { this = TInstanceArgument(call, arg) } + + override predicate isArgOf(Call c, int idx) { + c = call and this.asTop() = c.getQualifier() and idx = -1 } - Call getCall() { result = c } + override Top asTop() { if exists(arg.asExpr()) then result = arg.asExpr() else result = call } + + override DataFlow::Node asNode() { result = arg } + + override string toString() { result = arg.toString() } +} + +/** + * An endpoint that represents an implicit varargs array. + * We choose to represent the varargs array as a single endpoint, rather than as multiple endpoints. + * + * This avoids the problem of having to deal with redundant endpoints downstream. + * + * In order to be able to distinguish between varargs endpoints and regular endpoints, we export the `isVarargsArray` + * meta data field in the extraction queries. + */ +class ImplicitVarargsArray extends ApplicationModeEndpoint, TImplicitVarargsArray { + Call call; + DataFlow::Node vararg; + int idx; + + ImplicitVarargsArray() { this = TImplicitVarargsArray(call, vararg, idx) } + + override predicate isArgOf(Call c, int i) { c = call and i = idx } + + override Top asTop() { result = this.getCall() } + + override DataFlow::Node asNode() { result = vararg } + + override string toString() { result = vararg.toString() } } /** @@ -47,7 +123,7 @@ private class ArgumentNode extends DataFlow::Node { */ module ApplicationCandidatesImpl implements SharedCharacteristics::CandidateSig { // for documentation of the implementations here, see the QLDoc in the CandidateSig signature module. - class Endpoint = ArgumentNode; + class Endpoint = ApplicationModeEndpoint; class EndpointType = AutomodelEndpointTypes::EndpointType; @@ -61,18 +137,18 @@ module ApplicationCandidatesImpl implements SharedCharacteristics::CandidateSig predicate isSanitizer(Endpoint e, EndpointType t) { exists(t) and ( - e.getType() instanceof BoxedType + e.asNode().getType() instanceof BoxedType or - e.getType() instanceof PrimitiveType + e.asNode().getType() instanceof PrimitiveType or - e.getType() instanceof NumberType + e.asNode().getType() instanceof NumberType ) or t instanceof AutomodelEndpointTypes::PathInjectionSinkType and - e instanceof PathSanitizer::PathInjectionSanitizer + e.asNode() instanceof PathSanitizer::PathInjectionSanitizer } - RelatedLocation asLocation(Endpoint e) { result = e.asExpr() } + RelatedLocation asLocation(Endpoint e) { result = e.asTop() } predicate isKnownKind = AutomodelJavaUtil::isKnownKind/2; @@ -98,16 +174,7 @@ module ApplicationCandidatesImpl implements SharedCharacteristics::CandidateSig ApplicationModeGetCallable::getCallable(e).hasQualifiedName(package, type, name) and signature = ExternalFlow::paramsString(ApplicationModeGetCallable::getCallable(e)) and ext = "" and - ( - exists(Call c, int argIdx | - e.asExpr() = c.getArgument(argIdx) and - input = AutomodelJavaUtil::getArgumentForIndex(argIdx) - ) - or - exists(Call c | - e.asExpr() = c.getQualifier() and input = AutomodelJavaUtil::getArgumentForIndex(-1) - ) - ) + input = AutomodelJavaUtil::getArgumentForIndex(e.getArgIndex()) } /** @@ -118,7 +185,7 @@ module ApplicationCandidatesImpl implements SharedCharacteristics::CandidateSig */ RelatedLocation getRelatedLocation(Endpoint e, RelatedLocationType type) { type = CallContext() and - result = any(Call c | e.asExpr() = [c.getAnArgument(), c.getQualifier()]) + result = e.getCall() } } @@ -132,12 +199,7 @@ private module ApplicationModeGetCallable implements AutomodelSharedGetCallable: /** * Returns the API callable being modeled. */ - Callable getCallable(Endpoint e) { - exists(Call c | - e.asExpr() = [c.getAnArgument(), c.getQualifier()] and - result = c.getCallee() - ) - } + Callable getCallable(Endpoint e) { result = e.getCall().getCallee() } } /** @@ -145,7 +207,7 @@ private module ApplicationModeGetCallable implements AutomodelSharedGetCallable: * should be empty. */ private predicate isCustomSink(Endpoint e, string kind) { - e instanceof QueryInjectionSink and kind = "sql" + e.asNode() instanceof QueryInjectionSink and kind = "sql" } module CharacteristicsImpl = @@ -167,23 +229,21 @@ class ApplicationModeMetadataExtractor extends string { predicate hasMetadata( Endpoint e, string package, string type, string subtypes, string name, string signature, - string input + string input, string isVarargsArray ) { - exists(Call call, Callable callable, int argIdx | - call.getCallee() = callable and - ( - e.asExpr() = call.getArgument(argIdx) - or - e.asExpr() = call.getQualifier() and argIdx = -1 - ) and - input = AutomodelJavaUtil::getArgumentForIndex(argIdx) and + exists(Callable callable | + e.getCall().getCallee() = callable and + input = AutomodelJavaUtil::getArgumentForIndex(e.getArgIndex()) and package = callable.getDeclaringType().getPackage().getName() and // we're using the erased types because the MaD convention is to not specify type parameters. // Whether something is or isn't a sink doesn't usually depend on the type parameters. type = callable.getDeclaringType().getErasure().(RefType).nestedName() and subtypes = AutomodelJavaUtil::considerSubtypes(callable).toString() and name = callable.getName() and - signature = ExternalFlow::paramsString(callable) + signature = ExternalFlow::paramsString(callable) and + if e instanceof ImplicitVarargsArray + then isVarargsArray = "true" + else isVarargsArray = "false" ) } } @@ -253,28 +313,10 @@ private class IsMaDTaintStepCharacteristic extends CharacteristicsImpl::NotASink IsMaDTaintStepCharacteristic() { this = "taint step" } override predicate appliesToEndpoint(Endpoint e) { - FlowSummaryImpl::Private::Steps::summaryThroughStepValue(e, _, _) or - FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(e, _, _) or - FlowSummaryImpl::Private::Steps::summaryGetterStep(e, _, _, _) or - FlowSummaryImpl::Private::Steps::summarySetterStep(e, _, _, _) - } -} - -/** - * A negative characteristic that filters out qualifiers that are classes (i.e. static calls). These - * are unlikely to have any non-trivial flow going into them. - * - * Technically, an accessed type _could_ come from outside of the source code, but there's not - * much likelihood of that being user-controlled. - */ -private class ClassQualifierCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic { - ClassQualifierCharacteristic() { this = "class qualifier" } - - override predicate appliesToEndpoint(Endpoint e) { - exists(Call c | - e.asExpr() = c.getQualifier() and - c.getQualifier() instanceof TypeAccess - ) + FlowSummaryImpl::Private::Steps::summaryThroughStepValue(e.asNode(), _, _) or + FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(e.asNode(), _, _) or + FlowSummaryImpl::Private::Steps::summaryGetterStep(e.asNode(), _, _, _) or + FlowSummaryImpl::Private::Steps::summarySetterStep(e.asNode(), _, _, _) } } @@ -351,7 +393,7 @@ private class OtherArgumentToModeledMethodCharacteristic extends Characteristics private class FunctionValueCharacteristic extends CharacteristicsImpl::LikelyNotASinkCharacteristic { FunctionValueCharacteristic() { this = "function value" } - override predicate appliesToEndpoint(Endpoint e) { e.asExpr() instanceof FunctionalExpr } + override predicate appliesToEndpoint(Endpoint e) { e.asNode().asExpr() instanceof FunctionalExpr } } /** @@ -371,12 +413,12 @@ private class CannotBeTaintedCharacteristic extends CharacteristicsImpl::LikelyN * Holds if the node `n` is known as the predecessor in a modeled flow step. */ private predicate isKnownOutNodeForStep(Endpoint e) { - e.asExpr() instanceof Call or // we just assume flow in that case - TaintTracking::localTaintStep(_, e) or - FlowSummaryImpl::Private::Steps::summaryThroughStepValue(_, e, _) or - FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(_, e, _) or - FlowSummaryImpl::Private::Steps::summaryGetterStep(_, _, e, _) or - FlowSummaryImpl::Private::Steps::summarySetterStep(_, _, e, _) + e.asNode().asExpr() instanceof Call or // we just assume flow in that case + TaintTracking::localTaintStep(_, e.asNode()) or + FlowSummaryImpl::Private::Steps::summaryThroughStepValue(_, e.asNode(), _) or + FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(_, e.asNode(), _) or + FlowSummaryImpl::Private::Steps::summaryGetterStep(_, _, e.asNode(), _) or + FlowSummaryImpl::Private::Steps::summarySetterStep(_, _, e.asNode(), _) } } diff --git a/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql b/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql index 4940b4a741f..d58af008d87 100644 --- a/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql +++ b/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql @@ -25,16 +25,18 @@ private import AutomodelJavaUtil bindingset[limit] private Endpoint getSampleForSignature( int limit, string package, string type, string subtypes, string name, string signature, - string input + string input, string isVarargs ) { exists(int n, int num_endpoints, ApplicationModeMetadataExtractor meta | num_endpoints = - count(Endpoint e | meta.hasMetadata(e, package, type, subtypes, name, signature, input)) + count(Endpoint e | + meta.hasMetadata(e, package, type, subtypes, name, signature, input, isVarargs) + ) | result = rank[n](Endpoint e, Location loc | - loc = e.getLocation() and - meta.hasMetadata(e, package, type, subtypes, name, signature, input) + loc = e.asTop().getLocation() and + meta.hasMetadata(e, package, type, subtypes, name, signature, input, isVarargs) | e order by @@ -53,19 +55,21 @@ private Endpoint getSampleForSignature( from Endpoint endpoint, string message, ApplicationModeMetadataExtractor meta, DollarAtString package, DollarAtString type, DollarAtString subtypes, DollarAtString name, DollarAtString signature, - DollarAtString input + DollarAtString input, DollarAtString isVarargsArray where not exists(CharacteristicsImpl::UninterestingToModelCharacteristic u | u.appliesToEndpoint(endpoint) ) and - endpoint = getSampleForSignature(9, package, type, subtypes, name, signature, input) and + endpoint = + getSampleForSignature(9, package, type, subtypes, name, signature, input, isVarargsArray) and // If a node is already a known sink for any of our existing ATM queries and is already modeled as a MaD sink, we // don't include it as a candidate. Otherwise, we might include it as a candidate for query A, but the model will // label it as a sink for one of the sink types of query B, for which it's already a known sink. This would result in // overlap between our detected sinks and the pre-existing modeling. We assume that, if a sink has already been // modeled in a MaD model, then it doesn't belong to any additional sink types, and we don't need to reexamine it. not CharacteristicsImpl::isSink(endpoint, _, _) and - meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and + meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, isVarargsArray) and + includeAutomodelCandidate(package, type, name, signature) and // The message is the concatenation of all sink types for which this endpoint is known neither to be a sink nor to be // a non-sink, and we surface only endpoints that have at least one such sink type. message = @@ -75,11 +79,13 @@ where | sinkType, ", " ) -select endpoint, message + "\nrelated locations: $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", // +select endpoint.asNode(), + message + "\nrelated locations: $@." + "\nmetadata: $@, $@, $@, $@, $@, $@, $@.", // CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, CallContext()), "CallContext", // package, "package", // type, "type", // subtypes, "subtypes", // name, "name", // method name signature, "signature", // - input, "input" // + input, "input", // + isVarargsArray, "isVarargsArray" diff --git a/java/ql/src/Telemetry/AutomodelApplicationModeExtractNegativeExamples.ql b/java/ql/src/Telemetry/AutomodelApplicationModeExtractNegativeExamples.ql index e8a284dd6c0..a1cb9c8961a 100644 --- a/java/ql/src/Telemetry/AutomodelApplicationModeExtractNegativeExamples.ql +++ b/java/ql/src/Telemetry/AutomodelApplicationModeExtractNegativeExamples.ql @@ -24,7 +24,7 @@ Endpoint getSampleForCharacteristic(EndpointCharacteristic c, int limit) { exists(int n, int num_endpoints | num_endpoints = count(Endpoint e | c.appliesToEndpoint(e)) | result = rank[n](Endpoint e, Location loc | - loc = e.getLocation() and c.appliesToEndpoint(e) + loc = e.asTop().getLocation() and c.appliesToEndpoint(e) | e order by @@ -43,7 +43,8 @@ Endpoint getSampleForCharacteristic(EndpointCharacteristic c, int limit) { from Endpoint endpoint, EndpointCharacteristic characteristic, float confidence, string message, ApplicationModeMetadataExtractor meta, DollarAtString package, DollarAtString type, - DollarAtString subtypes, DollarAtString name, DollarAtString signature, DollarAtString input + DollarAtString subtypes, DollarAtString name, DollarAtString signature, DollarAtString input, + DollarAtString isVarargsArray where endpoint = getSampleForCharacteristic(characteristic, 100) and confidence >= SharedCharacteristics::highConfidence() and @@ -51,7 +52,7 @@ where // Exclude endpoints that have contradictory endpoint characteristics, because we only want examples we're highly // certain about in the prompt. not erroneousEndpoints(endpoint, _, _, _, _, false) and - meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and + meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, isVarargsArray) and // It's valid for a node to satisfy the logic for both `isSink` and `isSanitizer`, but in that case it will be // treated by the actual query as a sanitizer, since the final logic is something like // `isSink(n) and not isSanitizer(n)`. We don't want to include such nodes as negative examples in the prompt, because @@ -63,11 +64,13 @@ where characteristic2.hasImplications(positiveType, true, confidence2) ) and message = characteristic -select endpoint, message + "\nrelated locations: $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", // +select endpoint.asNode(), + message + "\nrelated locations: $@." + "\nmetadata: $@, $@, $@, $@, $@, $@, $@.", // CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, CallContext()), "CallContext", // package, "package", // type, "type", // subtypes, "subtypes", // name, "name", // signature, "signature", // - input, "input" // + input, "input", // + isVarargsArray, "isVarargsArray" // diff --git a/java/ql/src/Telemetry/AutomodelApplicationModeExtractPositiveExamples.ql b/java/ql/src/Telemetry/AutomodelApplicationModeExtractPositiveExamples.ql index c62476377db..dac9bef0728 100644 --- a/java/ql/src/Telemetry/AutomodelApplicationModeExtractPositiveExamples.ql +++ b/java/ql/src/Telemetry/AutomodelApplicationModeExtractPositiveExamples.ql @@ -15,19 +15,22 @@ private import AutomodelJavaUtil from Endpoint endpoint, SinkType sinkType, ApplicationModeMetadataExtractor meta, DollarAtString package, DollarAtString type, DollarAtString subtypes, DollarAtString name, - DollarAtString signature, DollarAtString input + DollarAtString signature, DollarAtString input, DollarAtString isVarargsArray where // Exclude endpoints that have contradictory endpoint characteristics, because we only want examples we're highly // certain about in the prompt. not erroneousEndpoints(endpoint, _, _, _, _, false) and - meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and + meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, isVarargsArray) and // Extract positive examples of sinks belonging to the existing ATM query configurations. - CharacteristicsImpl::isKnownSink(endpoint, sinkType) -select endpoint, sinkType + "\nrelated locations: $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", // + CharacteristicsImpl::isKnownSink(endpoint, sinkType) and + exists(CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, CallContext())) +select endpoint.asNode(), + sinkType + "\nrelated locations: $@." + "\nmetadata: $@, $@, $@, $@, $@, $@, $@.", // CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, CallContext()), "CallContext", // package, "package", // type, "type", // subtypes, "subtypes", // name, "name", // signature, "signature", // - input, "input" // + input, "input", // + isVarargsArray, "isVarargsArray" diff --git a/java/ql/src/Telemetry/AutomodelCandidateFilter.yml b/java/ql/src/Telemetry/AutomodelCandidateFilter.yml new file mode 100644 index 00000000000..52e64d54446 --- /dev/null +++ b/java/ql/src/Telemetry/AutomodelCandidateFilter.yml @@ -0,0 +1,5 @@ +extensions: + - addsTo: + pack: codeql/java-queries + extensible: automodelCandidateFilter + data: [] diff --git a/java/ql/src/Telemetry/AutomodelFrameworkModeExtractCandidates.ql b/java/ql/src/Telemetry/AutomodelFrameworkModeExtractCandidates.ql index e66af08707c..028a27a9bdc 100644 --- a/java/ql/src/Telemetry/AutomodelFrameworkModeExtractCandidates.ql +++ b/java/ql/src/Telemetry/AutomodelFrameworkModeExtractCandidates.ql @@ -30,6 +30,7 @@ where // modeled in a MaD model, then it doesn't belong to any additional sink types, and we don't need to reexamine it. not CharacteristicsImpl::isSink(endpoint, _, _) and meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, parameterName) and + includeAutomodelCandidate(package, type, name, signature) and // The message is the concatenation of all sink types for which this endpoint is known neither to be a sink nor to be // a non-sink, and we surface only endpoints that have at least one such sink type. message = diff --git a/java/ql/src/Telemetry/AutomodelJavaUtil.qll b/java/ql/src/Telemetry/AutomodelJavaUtil.qll index 65be12ce1f9..a224fc291a2 100644 --- a/java/ql/src/Telemetry/AutomodelJavaUtil.qll +++ b/java/ql/src/Telemetry/AutomodelJavaUtil.qll @@ -66,3 +66,24 @@ boolean considerSubtypes(Callable callable) { then result = false else result = true } + +/** + * Holds if the given package, type, name and signature is a candidate for automodeling. + * + * This predicate is extensible, so that different endpoints can be selected at runtime. + */ +extensible predicate automodelCandidateFilter( + string package, string type, string name, string signature +); + +/** + * Holds if the given package, type, name and signature is a candidate for automodeling. + * + * This relies on an extensible predicate, and if that is not supplied then + * all endpoints are considered candidates. + */ +bindingset[package, type, name, signature] +predicate includeAutomodelCandidate(string package, string type, string name, string signature) { + not automodelCandidateFilter(_, _, _, _) or + automodelCandidateFilter(package, type, name, signature) +} diff --git a/java/ql/src/change-notes/2023-08-01-maybebrokencrypto-barrier.md b/java/ql/src/change-notes/released/0.7.2.md similarity index 75% rename from java/ql/src/change-notes/2023-08-01-maybebrokencrypto-barrier.md rename to java/ql/src/change-notes/released/0.7.2.md index ac24d88d359..98e5f74f27d 100644 --- a/java/ql/src/change-notes/2023-08-01-maybebrokencrypto-barrier.md +++ b/java/ql/src/change-notes/released/0.7.2.md @@ -1,4 +1,5 @@ ---- -category: minorAnalysis ---- +## 0.7.2 + +### Minor Analysis Improvements + * The sanitizer in `java/potentially-weak-cryptographic-algorithm` has been improved, so the query may yield additional results. diff --git a/java/ql/src/codeql-pack.release.yml b/java/ql/src/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/java/ql/src/codeql-pack.release.yml +++ b/java/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/java/ql/src/qlpack.yml b/java/ql/src/qlpack.yml index 76f4fcc7797..037fe540607 100644 --- a/java/ql/src/qlpack.yml +++ b/java/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/java-queries -version: 0.7.2-dev +version: 0.7.3-dev groups: - java - queries @@ -12,4 +12,5 @@ dependencies: codeql/util: ${workspace} dataExtensions: - Telemetry/ExtractorInformation.yml + - Telemetry/AutomodelCandidateFilter.yml warnOnImplicitThis: true diff --git a/java/ql/test/ext/TopJdkApis/TopJdkApisTest.expected b/java/ql/test/ext/TopJdkApis/TopJdkApisTest.expected index 2e0ace91209..7bdac8e59ce 100644 --- a/java/ql/test/ext/TopJdkApis/TopJdkApisTest.expected +++ b/java/ql/test/ext/TopJdkApis/TopJdkApisTest.expected @@ -1,3 +1,4 @@ +| java.lang.Runnable#run() | no manual model | | java.lang.String#valueOf(Object) | no manual model | | java.lang.System#getProperty(String) | no manual model | | java.lang.System#setProperty(String,String) | no manual model | diff --git a/java/ql/test/library-tests/dataflow/capture/B.java b/java/ql/test/library-tests/dataflow/capture/B.java new file mode 100644 index 00000000000..0d43a0dba17 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/capture/B.java @@ -0,0 +1,251 @@ +import java.util.*; +import java.util.function.*; + +public class B { + static String source(String label) { return null; } + + static void sink(String s) { } + + static void test1() { + List l1 = new ArrayList<>(); + l1.add(source("L")); + List l2 = new ArrayList<>(); + l1.forEach(e -> l2.add(e)); + sink(l2.get(0)); // $ hasValueFlow=L + } + + String bf1; + String bf2; + + void test2() { + B other = new B(); + Consumer f = x -> { this.bf1 = x; bf2 = x; other.bf1 = x; }; + + // no flow + sink(bf1); + sink(this.bf2); + sink(other.bf1); + sink(other.bf2); + + f.accept(source("T")); + + sink(bf1); // $ MISSING: hasValueFlow=T + sink(this.bf2); // $ MISSING: hasValueFlow=T + sink(other.bf1); // $ hasValueFlow=T + sink(other.bf2); + } + + static void convert(Map inp, Map out) { + inp.forEach((key, value) -> { out.put(key, value); }); + } + + void test3() { + HashMap m1 = new HashMap<>(); + HashMap m2 = new HashMap<>(); + m1.put(source("Key"), source("Value")); + convert(m1, m2); + m2.forEach((k, v) -> { + sink(k); // $ hasValueFlow=Key + sink(v); // $ hasValueFlow=Value + }); + } + + String elem; + + void testParamIn1() { + elem = source("pin.This.elem"); + testParamIn2(source("pin.Arg")); + } + + void testParamIn2(String param) { + Runnable r = () -> { + sink(elem); // $ MISSING: hasValueFlow=pin.This.elem + sink(this.elem); // $ MISSING: hasValueFlow=pin.This.elem + sink(param); // $ hasValueFlow=pin.Arg + }; + r.run(); + } + + void testParamOut1() { + B other = new B(); + testParamOut2(other); + sink(elem); // $ MISSING: hasValueFlow=pout.This.elem + sink(this.elem); // $ MISSING: hasValueFlow=pout.This.elem + sink(other.elem); // $ hasValueFlow=pout.param + } + + void testParamOut2(B param) { + Runnable r = () -> { + this.elem = source("pout.This.elem"); + param.elem = source("pout.param"); + }; + r.run(); + } + + void testCrossLambda() { + B b = new B(); + Runnable sink1 = () -> { sink(b.elem); }; + Runnable sink2 = () -> { sink(b.elem); }; // $ hasValueFlow=src + Runnable src = () -> { b.elem = source("src"); }; + doRun(sink1); + doRun(src); + doRun(sink2); + } + + void doRun(Runnable r) { + r.run(); + } + + void testNested() { + List l1 = new ArrayList<>(); + List> l2 = new ArrayList<>(); + l1.add(source("nest.out")); + l2.add(l1); + String s = source("nest.in"); + List out1 = new ArrayList<>(); + List out2 = new ArrayList<>(); + l2.forEach(l -> l.forEach(x -> { + sink(s); // $ hasValueFlow=nest.in + out1.add(x); + out2.add(s); + })); + sink(out1.get(0)); // $ hasValueFlow=nest.out + sink(out2.get(0)); // $ hasValueFlow=nest.in + } + + static interface TwoRuns { + void run1(); + void run2(); + } + + void testAnonymousClass() { + List l1 = new ArrayList<>(); + List l2 = new ArrayList<>(); + TwoRuns r = new TwoRuns() { + @Override + public void run1() { + l1.add(source("run1")); + } + @Override + public void run2() { + l2.add(l1.get(0)); + } + }; + r.run2(); + sink(l2.get(0)); + r.run1(); + r.run2(); + sink(l2.get(0)); // $ hasValueFlow=run1 + } + + void testLocalClass1() { + String s = source("local1"); + class MyLocal { + String f; + MyLocal() { this.f = s; } + String getF() { return this.f; } + } + MyLocal m = new MyLocal(); + sink(m.getF()); // $ hasValueFlow=local1 + } + + void testLocalClass2() { + String s1 = source("s1"); + String s2 = source("s2"); + List l = new ArrayList<>(); + class MyLocal { + String f; + MyLocal() { + this.f = s1; + sink(s2); // $ hasValueFlow=s2 + } + void test() { + sink(f); // $ hasValueFlow=s1 + sink(s2); // $ hasValueFlow=s2 + } + void add(String s) { + l.add(s); + } + String get() { + return l.get(0); + } + } + MyLocal m1 = new MyLocal(); + MyLocal m2 = new MyLocal(); + m1.test(); + sink(m1.get()); + m1.add(source("m1.add")); + sink(m2.get()); // $ hasValueFlow=m1.add + } + + void testComplex() { + String s = source("complex"); + class LocalComplex { + Supplier getBoxSupplier() { + return new Supplier() { + StringBox b = new StringBox(); + @Override + public StringBox get() { return b; } + }; + } + class StringBox { + String get() { + // capture through regular nested class inside local nested class + return s; + } + } + } + LocalComplex lc = new LocalComplex(); + sink(lc.getBoxSupplier().get().get()); // $ MISSING: hasValueFlow=complex + } + + void testCapturedLambda() { + String s = source("double.capture.in"); + List out = new ArrayList<>(); + Runnable r1 = () -> { + sink(s); // $ hasValueFlow=double.capture.in + out.add(source("double.capture.out")); + }; + Runnable r2 = () -> { + r1.run(); + }; + r2.run(); + sink(out.get(0)); // $ MISSING: hasValueFlow=double.capture.out + } + + void testEnhancedForStmtCapture() { + List l = new ArrayList<>(); + l.add(source("list")); + String[] a = new String[] { source("array") }; + for (String x : l) { + Runnable r = () -> sink(x); // $ MISSING: hasValueFlow=list + r.run(); + } + for (String x : a) { + Runnable r = () -> sink(x); // $ MISSING: hasValueFlow=array + r.run(); + } + } + + void testDoubleCall() { + String s = source("src"); + List l = new ArrayList<>(); + List l2 = new ArrayList<>(); + class MyLocal2 { + MyLocal2() { + sink(l.get(0)); // no flow + sink(l2.get(0)); // no flow + l.add(s); + } + void run() { + l2.add(l.get(0)); + } + } + // The ClassInstanceExpr has two calls in the same cfg node: + // First the constructor call for which it is the postupdate, + // and then as instance argument to the run call. + new MyLocal2().run(); + sink(l.get(0)); // $ hasValueFlow=src + sink(l2.get(0)); // $ hasValueFlow=src + } +} diff --git a/java/ql/test/library-tests/dataflow/capture/inlinetest.expected b/java/ql/test/library-tests/dataflow/capture/inlinetest.expected new file mode 100644 index 00000000000..48de9172b36 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/capture/inlinetest.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/java/ql/test/library-tests/dataflow/capture/inlinetest.ql b/java/ql/test/library-tests/dataflow/capture/inlinetest.ql new file mode 100644 index 00000000000..50e3f8d2f7d --- /dev/null +++ b/java/ql/test/library-tests/dataflow/capture/inlinetest.ql @@ -0,0 +1,2 @@ +import TestUtilities.InlineFlowTest +import DefaultFlowTest diff --git a/java/ql/test/library-tests/dataflow/capture/test.expected b/java/ql/test/library-tests/dataflow/capture/test.expected index aca9fe129a9..6a04b82f223 100644 --- a/java/ql/test/library-tests/dataflow/capture/test.expected +++ b/java/ql/test/library-tests/dataflow/capture/test.expected @@ -1,26 +1,93 @@ -| A.java:14:14:14:16 | "A" | A.java:14:14:14:16 | "A" | -| A.java:14:14:14:16 | "A" | A.java:15:16:15:22 | get(...) | -| A.java:14:14:14:16 | "A" | A.java:18:8:18:15 | p | -| A.java:14:14:14:16 | "A" | A.java:32:26:32:26 | p | -| A.java:21:11:21:13 | "B" | A.java:15:16:15:22 | get(...) | -| A.java:21:11:21:13 | "B" | A.java:21:7:21:13 | ...=... | -| A.java:21:11:21:13 | "B" | A.java:21:11:21:13 | "B" | -| A.java:21:11:21:13 | "B" | A.java:33:26:33:26 | s | -| A.java:23:11:23:13 | "C" | A.java:15:16:15:22 | get(...) | -| A.java:23:11:23:13 | "C" | A.java:23:7:23:13 | ...=... | -| A.java:23:11:23:13 | "C" | A.java:23:11:23:13 | "C" | -| A.java:23:11:23:13 | "C" | A.java:33:26:33:26 | s | -| A.java:25:22:25:24 | "D" | A.java:4:9:4:16 | e | -| A.java:25:22:25:24 | "D" | A.java:4:21:4:28 | ...=... | -| A.java:25:22:25:24 | "D" | A.java:4:28:4:28 | e | -| A.java:25:22:25:24 | "D" | A.java:6:31:6:34 | elem | -| A.java:25:22:25:24 | "D" | A.java:15:16:15:22 | get(...) | -| A.java:25:22:25:24 | "D" | A.java:25:22:25:24 | "D" | -| A.java:25:22:25:24 | "D" | A.java:34:26:34:37 | getElem(...) | -| A.java:27:16:27:18 | "E" | A.java:5:18:5:25 | e | -| A.java:27:16:27:18 | "E" | A.java:5:30:5:37 | ...=... | -| A.java:27:16:27:18 | "E" | A.java:5:37:5:37 | e | -| A.java:27:16:27:18 | "E" | A.java:6:31:6:34 | elem | -| A.java:27:16:27:18 | "E" | A.java:15:16:15:22 | get(...) | -| A.java:27:16:27:18 | "E" | A.java:27:16:27:18 | "E" | -| A.java:27:16:27:18 | "E" | A.java:35:26:35:37 | getElem(...) | +| A.java:14:14:14:16 | "A" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:15:16:15:16 | a : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:15:16:15:22 | get(...) : String | +| A.java:14:14:14:16 | "A" : String | A.java:18:8:18:15 | p : String | +| A.java:14:14:14:16 | "A" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:28:11:38:5 | p : String | +| A.java:14:14:14:16 | "A" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:31:17:31:17 | this : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:32:26:32:26 | p : String | +| A.java:14:14:14:16 | "A" : String | A.java:32:26:32:26 | this : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:33:26:33:26 | this : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:34:26:34:27 | this : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:35:26:35:27 | this : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:39:12:39:12 | a : new A(...) { ... } [p] | +| A.java:14:14:14:16 | "A" : String | A.java:39:12:39:12 | p : String | +| A.java:21:11:21:13 | "B" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:15:16:15:16 | a : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:15:16:15:22 | get(...) : String | +| A.java:21:11:21:13 | "B" : String | A.java:21:7:21:13 | ...=... : String | +| A.java:21:11:21:13 | "B" : String | A.java:25:5:25:26 | phi(String s) : String | +| A.java:21:11:21:13 | "B" : String | A.java:28:11:38:5 | String s : String | +| A.java:21:11:21:13 | "B" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:31:17:31:17 | this : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:32:26:32:26 | this : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:33:26:33:26 | s : String | +| A.java:21:11:21:13 | "B" : String | A.java:33:26:33:26 | this : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:34:26:34:27 | this : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:35:26:35:27 | this : new A(...) { ... } [String s] | +| A.java:21:11:21:13 | "B" : String | A.java:39:12:39:12 | String s : String | +| A.java:21:11:21:13 | "B" : String | A.java:39:12:39:12 | a : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:15:16:15:16 | a : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:15:16:15:22 | get(...) : String | +| A.java:23:11:23:13 | "C" : String | A.java:23:7:23:13 | ...=... : String | +| A.java:23:11:23:13 | "C" : String | A.java:25:5:25:26 | phi(String s) : String | +| A.java:23:11:23:13 | "C" : String | A.java:28:11:38:5 | String s : String | +| A.java:23:11:23:13 | "C" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:31:17:31:17 | this : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:32:26:32:26 | this : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:33:26:33:26 | s : String | +| A.java:23:11:23:13 | "C" : String | A.java:33:26:33:26 | this : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:34:26:34:27 | this : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:35:26:35:27 | this : new A(...) { ... } [String s] | +| A.java:23:11:23:13 | "C" : String | A.java:39:12:39:12 | String s : String | +| A.java:23:11:23:13 | "C" : String | A.java:39:12:39:12 | a : new A(...) { ... } [String s] | +| A.java:25:22:25:24 | "D" : String | A.java:4:9:4:16 | e : String | +| A.java:25:22:25:24 | "D" : String | A.java:4:21:4:24 | this <.field> [post update] : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:4:21:4:28 | ...=... : String | +| A.java:25:22:25:24 | "D" : String | A.java:4:28:4:28 | e : String | +| A.java:25:22:25:24 | "D" : String | A.java:6:12:6:18 | parameter this : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:6:31:6:34 | elem : String | +| A.java:25:22:25:24 | "D" : String | A.java:6:31:6:34 | this <.field> : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:15:16:15:16 | a : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:15:16:15:22 | get(...) : String | +| A.java:25:22:25:24 | "D" : String | A.java:25:14:25:25 | new Box(...) : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:28:11:38:5 | Box b1 : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:31:17:31:17 | this : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:32:26:32:26 | this : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:33:26:33:26 | this : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:34:26:34:27 | b1 : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:34:26:34:27 | this : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:34:26:34:37 | getElem(...) : String | +| A.java:25:22:25:24 | "D" : String | A.java:35:26:35:27 | this : new A(...) { ... } [Box b1, ... (2)] | +| A.java:25:22:25:24 | "D" : String | A.java:39:12:39:12 | Box b1 : Box [elem] | +| A.java:25:22:25:24 | "D" : String | A.java:39:12:39:12 | a : new A(...) { ... } [Box b1, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:5:18:5:25 | e : String | +| A.java:27:16:27:18 | "E" : String | A.java:5:30:5:33 | this <.field> [post update] : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:5:30:5:37 | ...=... : String | +| A.java:27:16:27:18 | "E" : String | A.java:5:37:5:37 | e : String | +| A.java:27:16:27:18 | "E" : String | A.java:6:12:6:18 | parameter this : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:6:31:6:34 | elem : String | +| A.java:27:16:27:18 | "E" : String | A.java:6:31:6:34 | this <.field> : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:14:11:14:20 | f2(...) : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:15:16:15:16 | a : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:15:16:15:22 | get(...) : String | +| A.java:27:16:27:18 | "E" : String | A.java:27:5:27:6 | b2 [post update] : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:28:11:38:5 | Box b2 : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:28:11:38:5 | new (...) : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:30:14:30:16 | parameter this : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:31:17:31:17 | this : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:32:26:32:26 | this : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:33:26:33:26 | this : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:34:26:34:27 | this : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:35:26:35:27 | b2 : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:35:26:35:27 | this : new A(...) { ... } [Box b2, ... (2)] | +| A.java:27:16:27:18 | "E" : String | A.java:35:26:35:37 | getElem(...) : String | +| A.java:27:16:27:18 | "E" : String | A.java:39:12:39:12 | Box b2 : Box [elem] | +| A.java:27:16:27:18 | "E" : String | A.java:39:12:39:12 | a : new A(...) { ... } [Box b2, ... (2)] | diff --git a/java/ql/test/library-tests/dataflow/capture/test.ql b/java/ql/test/library-tests/dataflow/capture/test.ql index 448bb030583..da94ff27848 100644 --- a/java/ql/test/library-tests/dataflow/capture/test.ql +++ b/java/ql/test/library-tests/dataflow/capture/test.ql @@ -1,16 +1,23 @@ import java import semmle.code.java.dataflow.DataFlow -StringLiteral src() { result.getCompilationUnit().fromSource() } +StringLiteral src() { + result.getCompilationUnit().fromSource() and + result.getFile().toString() = "A" +} module Config implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node n) { n.asExpr() = src() } - predicate isSink(DataFlow::Node n) { any() } + predicate isSink(DataFlow::Node n) { none() } } module Flow = DataFlow::Global; -from DataFlow::Node src, DataFlow::Node sink -where Flow::flow(src, sink) +int explorationLimit() { result = 100 } + +module PartialFlow = Flow::FlowExploration; + +from PartialFlow::PartialPathNode src, PartialFlow::PartialPathNode sink +where PartialFlow::partialFlow(src, sink, _) select src, sink diff --git a/java/ql/test/library-tests/dataflow/collections/B.java b/java/ql/test/library-tests/dataflow/collections/B.java index 0bb2b74d749..7d9c418c162 100644 --- a/java/ql/test/library-tests/dataflow/collections/B.java +++ b/java/ql/test/library-tests/dataflow/collections/B.java @@ -9,6 +9,7 @@ public class B { static void sink(Object obj) { } static Object[] storeArrayElement(Object obj) { return new Object[] {obj}; } + static Set storeSetElement(Object obj) { return Set.of(obj); } static Object readArrayElement(Object[] obj) { return obj[0]; } static boolean readArrayElement(boolean[] obj) { return obj[0]; } @@ -49,96 +50,96 @@ public class B { void foo() throws InterruptedException { { - // "java.util;Map$Entry;true;getKey;;;MapKey of Argument[this];ReturnValue;value;manual", + // "java.util;Map$Entry;true;getKey;;;Argument[this].MapKey;ReturnValue;value;manual", Object out = null; Object in = storeMapKeyEntry(source()); out = ((Map.Entry)in).getKey(); sink(out); // $ hasValueFlow } { - // "java.util;Map$Entry;true;getValue;;;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map$Entry;true;getValue;;;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValueEntry(source()); out = ((Map.Entry)in).getValue(); sink(out); // $ hasValueFlow } { - // "java.util;Map$Entry;true;setValue;;;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map$Entry;true;setValue;;;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValueEntry(source()); out = ((Map.Entry)in).setValue(null); sink(out); // $ hasValueFlow } { - // "java.util;Map$Entry;true;setValue;;;Argument[0];MapValue of Argument[this];value;manual", + // "java.util;Map$Entry;true;setValue;;;Argument[0];Argument[this].MapValue;value;manual", Map.Entry out = null; Object in = source(); out.setValue(in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.lang;Iterable;true;iterator;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.lang;Iterable;true;iterator;();;Argument[this].Element;ReturnValue.Element;value;manual", Iterator out = null; Iterable in = storeElementList(source()); out = in.iterator(); sink(readElement(out)); // $ hasValueFlow } { - // "java.lang;Iterable;true;spliterator;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.lang;Iterable;true;spliterator;();;Argument[this].Element;ReturnValue.Element;value;manual", Spliterator out = null; Iterable in = storeElementList(source()); out = in.spliterator(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Iterator;true;next;;;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Iterator;true;next;;;Argument[this].Element;ReturnValue;value;manual", Object out = null; Iterator in = storeElementListIterator(source()); out = in.next(); sink(out); // $ hasValueFlow } { - // "java.util;ListIterator;true;previous;;;Element of Argument[this];ReturnValue;value;manual", + // "java.util;ListIterator;true;previous;;;Argument[this].Element;ReturnValue;value;manual", Object out = null; ListIterator in = storeElementListIterator(source()); out = in.previous(); sink(out); // $ hasValueFlow } { - // "java.util;ListIterator;true;add;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;ListIterator;true;add;(Object);;Argument[0];Argument[this].Element;value;manual", ListIterator out = null; Object in = source(); out.add(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;ListIterator;true;set;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;ListIterator;true;set;(Object);;Argument[0];Argument[this].Element;value;manual", ListIterator out = null; Object in = source(); out.set(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Enumeration;true;asIterator;;;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Enumeration;true;asIterator;;;Argument[this].Element;ReturnValue.Element;value;manual", Iterator out = null; Enumeration in = storeElementEnumeration(source()); out = in.asIterator(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Enumeration;true;nextElement;;;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Enumeration;true;nextElement;;;Argument[this].Element;ReturnValue;value;manual", Object out = null; Enumeration in = storeElementEnumeration(source()); out = in.nextElement(); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;computeIfAbsent;;;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map;true;computeIfAbsent;;;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Map)in).computeIfAbsent(null,null); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;computeIfAbsent;;;ReturnValue of Argument[1];ReturnValue;value;manual", + // "java.util;Map;true;computeIfAbsent;;;Argument[1].ReturnValue;ReturnValue;value;manual", Object out = ((Map)null).computeIfAbsent(null,k -> source()); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;computeIfAbsent;;;ReturnValue of Argument[1];MapValue of Argument[this];value;manual", + // "java.util;Map;true;computeIfAbsent;;;Argument[1].ReturnValue;Argument[this].MapValue;value;manual", Map out = null; out.computeIfAbsent(null,k -> source()); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;true;entrySet;;;MapValue of Argument[this];MapValue of Element of ReturnValue;value;manual", + // "java.util;Map;true;entrySet;;;Argument[this].MapValue;ReturnValue.Element.MapValue;value;manual", Set out = null; Object in = storeMapValue(source()); out = ((Map)in).entrySet(); sink(readMapValue(readElement(out))); // $ hasValueFlow } { - // "java.util;Map;true;entrySet;;;MapKey of Argument[this];MapKey of Element of ReturnValue;value;manual", + // "java.util;Map;true;entrySet;;;Argument[this].MapKey;ReturnValue.Element.MapKey;value;manual", Set out = null; Object in = storeMapKey(source()); out = ((Map)in).entrySet(); sink(readMapKey(readElement(out))); // $ hasValueFlow } { - // "java.util;Map;true;get;;;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map;true;get;;;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Map)in).get(null); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;getOrDefault;;;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map;true;getOrDefault;;;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Map)in).getOrDefault(null,null); sink(out); // $ hasValueFlow } @@ -148,1707 +149,1727 @@ public class B { Object in = source(); out = ((Map)null).getOrDefault(null,in); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;put;;;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map;true;put;;;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Map)in).put(null,null); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;put;;;Argument[0];MapKey of Argument[this];value;manual", + // "java.util;Map;true;put;;;Argument[0];Argument[this].MapKey;value;manual", Map out = null; Object in = source(); out.put(in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;true;put;;;Argument[1];MapValue of Argument[this];value;manual", + // "java.util;Map;true;put;;;Argument[1];Argument[this].MapValue;value;manual", Map out = null; Object in = source(); out.put(null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;true;putIfAbsent;;;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map;true;putIfAbsent;;;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Map)in).putIfAbsent(null,null); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;putIfAbsent;;;Argument[0];MapKey of Argument[this];value;manual", + // "java.util;Map;true;putIfAbsent;;;Argument[0];Argument[this].MapKey;value;manual", Map out = null; Object in = source(); out.putIfAbsent(in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;true;putIfAbsent;;;Argument[1];MapValue of Argument[this];value;manual", + // "java.util;Map;true;putIfAbsent;;;Argument[1];Argument[this].MapValue;value;manual", Map out = null; Object in = source(); out.putIfAbsent(null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;true;remove;(Object);;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map;true;remove;(Object);;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Map)in).remove(null); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;replace;(Object,Object);;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Map;true;replace;(Object,Object);;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Map)in).replace(null,null); sink(out); // $ hasValueFlow } { - // "java.util;Map;true;replace;(Object,Object);;Argument[0];MapKey of Argument[this];value;manual", + // "java.util;Map;true;replace;(Object,Object);;Argument[0];Argument[this].MapKey;value;manual", Map out = null; Object in = source(); out.replace(in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;true;replace;(Object,Object);;Argument[1];MapValue of Argument[this];value;manual", + // "java.util;Map;true;replace;(Object,Object);;Argument[1];Argument[this].MapValue;value;manual", Map out = null; Object in = source(); out.replace(null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;true;replace;(Object,Object,Object);;Argument[0];MapKey of Argument[this];value;manual", + // "java.util;Map;true;replace;(Object,Object,Object);;Argument[0];Argument[this].MapKey;value;manual", Map out = null; Object in = source(); out.replace(in,null,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;true;replace;(Object,Object,Object);;Argument[2];MapValue of Argument[this];value;manual", + // "java.util;Map;true;replace;(Object,Object,Object);;Argument[2];Argument[this].MapValue;value;manual", Map out = null; Object in = source(); out.replace(null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;true;keySet;();;MapKey of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Map;true;keySet;();;Argument[this].MapKey;ReturnValue.Element;value;manual", Set out = null; Object in = storeMapKey(source()); out = ((Map)in).keySet(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Map;true;values;();;MapValue of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Map;true;values;();;Argument[this].MapValue;ReturnValue.Element;value;manual", Iterable out = null; Object in = storeMapValue(source()); out = ((Map)in).values(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Map;true;merge;(Object,Object,BiFunction);;Argument[1];MapValue of Argument[this];value;manual", + // "java.util;Map;true;merge;(Object,Object,BiFunction);;Argument[1];Argument[this].MapValue;value;manual", Map out = null; Object in = source(); out.merge(null,in,null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;true;putAll;(Map);;MapKey of Argument[0];MapKey of Argument[this];value;manual", + // "java.util;Map;true;putAll;(Map);;Argument[0].MapKey;Argument[this].MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out.putAll((Map)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;true;putAll;(Map);;MapValue of Argument[0];MapValue of Argument[this];value;manual", + // "java.util;Map;true;putAll;(Map);;Argument[0].MapValue;Argument[this].MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out.putAll((Map)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collection;true;parallelStream;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Collection;true;parallelStream;();;Argument[this].Element;ReturnValue.Element;value;manual", Stream out = null; Collection in = storeElementList(source()); out = in.parallelStream(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collection;true;stream;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Collection;true;stream;();;Argument[this].Element;ReturnValue.Element;value;manual", Stream out = null; Collection in = storeElementList(source()); out = in.stream(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collection;true;toArray;;;Element of Argument[this];ArrayElement of ReturnValue;value;manual", + // "java.util;Collection;true;toArray;;;Argument[this].Element;ReturnValue.ArrayElement;value;manual", Object[] out = null; Collection in = storeElementList(source()); out = in.toArray(); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Collection;true;toArray;;;Element of Argument[this];ArrayElement of Argument[0];value;manual", + // "java.util;Collection;true;toArray;;;Argument[this].Element;Argument[0].ArrayElement;value;manual", Object[] out = null; Collection in = storeElementList(source()); in.toArray(out); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Collection;true;add;;;Argument[0];Element of Argument[this];value;manual", + // "java.util;Collection;true;add;;;Argument[0];Argument[this].Element;value;manual", Collection out = null; Object in = source(); out.add(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collection;true;addAll;;;Element of Argument[0];Element of Argument[this];value;manual", + // "java.util;Collection;true;addAll;;;Argument[0].Element;Argument[this].Element;value;manual", Collection out = null; Collection in = storeElementList(source()); out.addAll(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;true;get;(int);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;List;true;get;(int);;Argument[this].Element;ReturnValue;value;manual", Object out = null; List in = storeElementList(source()); out = in.get(0); sink(out); // $ hasValueFlow } { - // "java.util;List;true;listIterator;;;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;List;true;listIterator;;;Argument[this].Element;ReturnValue.Element;value;manual", ListIterator out = null; List in = storeElementList(source()); out = in.listIterator(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;true;remove;(int);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;List;true;remove;(int);;Argument[this].Element;ReturnValue;value;manual", Object out = null; List in = storeElementList(source()); out = in.remove(0); sink(out); // $ hasValueFlow } { - // "java.util;List;true;set;(int,Object);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;List;true;set;(int,Object);;Argument[this].Element;ReturnValue;value;manual", Object out = null; List in = storeElementList(source()); out = in.set(0,null); sink(out); // $ hasValueFlow } { - // "java.util;List;true;set;(int,Object);;Argument[1];Element of Argument[this];value;manual", + // "java.util;List;true;set;(int,Object);;Argument[1];Argument[this].Element;value;manual", List out = null; Object in = source(); out.set(0,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;true;subList;;;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;List;true;subList;;;Argument[this].Element;ReturnValue.Element;value;manual", List out = null; List in = storeElementList(source()); out = in.subList(0,0); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;true;add;(int,Object);;Argument[1];Element of Argument[this];value;manual", + // "java.util;List;true;add;(int,Object);;Argument[1];Argument[this].Element;value;manual", List out = null; Object in = source(); out.add(0,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;true;addAll;(int,Collection);;Element of Argument[1];Element of Argument[this];value;manual", + // "java.util;List;true;addAll;(int,Collection);;Argument[1].Element;Argument[this].Element;value;manual", List out = null; Collection in = storeElementList(source()); out.addAll(0,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Vector;true;elementAt;(int);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;List;true;clear;;;Argument[this].WithoutElement;Argument[this];value;manual" + List out = null; + List in = storeElementList(source()); out = in; out.clear(); sink(readElement(out)); // No flow + } + { + // "java.util;List;true;clear;;;Argument[this].WithoutElement;Argument[this];value;manual" + List out = null; + List in = (List)source(); out = in; out.clear(); sink(out); // $ hasValueFlow + } + { + // "java.util;Vector;true;elementAt;(int);;Argument[this].Element;ReturnValue;value;manual", Object out = null; Vector in = storeElementStack(source()); out = in.elementAt(0); sink(out); // $ hasValueFlow } { - // "java.util;Vector;true;elements;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Vector;true;elements;();;Argument[this].Element;ReturnValue.Element;value;manual", Enumeration out = null; Vector in = storeElementStack(source()); out = in.elements(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Vector;true;firstElement;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Vector;true;firstElement;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Vector in = storeElementStack(source()); out = in.firstElement(); sink(out); // $ hasValueFlow } { - // "java.util;Vector;true;lastElement;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Vector;true;lastElement;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Vector in = storeElementStack(source()); out = in.lastElement(); sink(out); // $ hasValueFlow } { - // "java.util;Vector;true;addElement;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Vector;true;addElement;(Object);;Argument[0];Argument[this].Element;value;manual", Vector out = null; Object in = source(); out.addElement(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Vector;true;insertElementAt;(Object,int);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Vector;true;insertElementAt;(Object,int);;Argument[0];Argument[this].Element;value;manual", Vector out = null; Object in = source(); out.insertElementAt(in,0); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Vector;true;setElementAt;(Object,int);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Vector;true;setElementAt;(Object,int);;Argument[0];Argument[this].Element;value;manual", Vector out = null; Object in = source(); out.setElementAt(in,0); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Vector;true;copyInto;(Object[]);;Element of Argument[this];ArrayElement of Argument[0];value;manual", + // "java.util;Vector;true;copyInto;(Object[]);;Argument[this].Element;Argument[0].ArrayElement;value;manual", Object[] out = null; Vector in = storeElementStack(source()); in.copyInto(out); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Stack;true;peek;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Stack;true;peek;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Stack in = storeElementStack(source()); out = in.peek(); sink(out); // $ hasValueFlow } { - // "java.util;Stack;true;pop;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Stack;true;pop;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Stack in = storeElementStack(source()); out = in.pop(); sink(out); // $ hasValueFlow } { - // "java.util;Stack;true;push;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Stack;true;push;(Object);;Argument[0];Argument[this].Element;value;manual", Stack out = null; Object in = source(); out.push(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Queue;true;element;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Queue;true;element;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Queue in = storeElementBlockingDeque(source()); out = in.element(); sink(out); // $ hasValueFlow } { - // "java.util;Queue;true;peek;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Queue;true;peek;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Queue in = storeElementBlockingDeque(source()); out = in.peek(); sink(out); // $ hasValueFlow } { - // "java.util;Queue;true;poll;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Queue;true;poll;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Queue in = storeElementBlockingDeque(source()); out = in.poll(); sink(out); // $ hasValueFlow } { - // "java.util;Queue;true;remove;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Queue;true;remove;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Queue in = storeElementBlockingDeque(source()); out = in.remove(); sink(out); // $ hasValueFlow } { - // "java.util;Queue;true;offer;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Queue;true;offer;(Object);;Argument[0];Argument[this].Element;value;manual", Queue out = null; Object in = source(); out.offer(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Deque;true;descendingIterator;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Deque;true;descendingIterator;();;Argument[this].Element;ReturnValue.Element;value;manual", Iterator out = null; Deque in = storeElementBlockingDeque(source()); out = in.descendingIterator(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Deque;true;getFirst;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;getFirst;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.getFirst(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;getLast;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;getLast;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.getLast(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;peekFirst;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;peekFirst;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.peekFirst(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;peekLast;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;peekLast;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.peekLast(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;pollFirst;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;pollFirst;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.pollFirst(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;pollLast;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;pollLast;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.pollLast(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;pop;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;pop;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.pop(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;removeFirst;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;removeFirst;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.removeFirst(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;removeLast;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;Deque;true;removeLast;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; Deque in = storeElementBlockingDeque(source()); out = in.removeLast(); sink(out); // $ hasValueFlow } { - // "java.util;Deque;true;push;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Deque;true;push;(Object);;Argument[0];Argument[this].Element;value;manual", Deque out = null; Object in = source(); out.push(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Deque;true;offerLast;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Deque;true;offerLast;(Object);;Argument[0];Argument[this].Element;value;manual", Deque out = null; Object in = source(); out.offerLast(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Deque;true;offerFirst;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Deque;true;offerFirst;(Object);;Argument[0];Argument[this].Element;value;manual", Deque out = null; Object in = source(); out.offerFirst(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Deque;true;addLast;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Deque;true;addLast;(Object);;Argument[0];Argument[this].Element;value;manual", Deque out = null; Object in = source(); out.addLast(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Deque;true;addFirst;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util;Deque;true;addFirst;(Object);;Argument[0];Argument[this].Element;value;manual", Deque out = null; Object in = source(); out.addFirst(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;pollFirst;(long,TimeUnit);;Element of Argument[this];ReturnValue;value;manual", + // "java.util.concurrent;BlockingDeque;true;pollFirst;(long,TimeUnit);;Argument[this].Element;ReturnValue;value;manual", Object out = null; BlockingDeque in = storeElementBlockingDeque(source()); out = in.pollFirst(0,null); sink(out); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;pollLast;(long,TimeUnit);;Element of Argument[this];ReturnValue;value;manual", + // "java.util.concurrent;BlockingDeque;true;pollLast;(long,TimeUnit);;Argument[this].Element;ReturnValue;value;manual", Object out = null; BlockingDeque in = storeElementBlockingDeque(source()); out = in.pollLast(0,null); sink(out); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;takeFirst;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util.concurrent;BlockingDeque;true;takeFirst;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; BlockingDeque in = storeElementBlockingDeque(source()); out = in.takeFirst(); sink(out); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;takeLast;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util.concurrent;BlockingDeque;true;takeLast;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; BlockingDeque in = storeElementBlockingDeque(source()); out = in.takeLast(); sink(out); // $ hasValueFlow } { - // "java.util.concurrent;BlockingQueue;true;poll;(long,TimeUnit);;Element of Argument[this];ReturnValue;value;manual", + // "java.util.concurrent;BlockingQueue;true;poll;(long,TimeUnit);;Argument[this].Element;ReturnValue;value;manual", Object out = null; BlockingQueue in = storeElementBlockingDeque(source()); out = in.poll(0,null); sink(out); // $ hasValueFlow } { - // "java.util.concurrent;BlockingQueue;true;take;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util.concurrent;BlockingQueue;true;take;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; BlockingQueue in = storeElementBlockingDeque(source()); out = in.take(); sink(out); // $ hasValueFlow } { - // "java.util.concurrent;BlockingQueue;true;offer;(Object,long,TimeUnit);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;BlockingQueue;true;offer;(Object,long,TimeUnit);;Argument[0];Argument[this].Element;value;manual", BlockingQueue out = null; Object in = source(); out.offer(in,0,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingQueue;true;put;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;BlockingQueue;true;put;(Object);;Argument[0];Argument[this].Element;value;manual", BlockingQueue out = null; Object in = source(); out.put(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;offerLast;(Object,long,TimeUnit);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;BlockingDeque;true;offerLast;(Object,long,TimeUnit);;Argument[0];Argument[this].Element;value;manual", BlockingDeque out = null; Object in = source(); out.offerLast(in,0,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;offerFirst;(Object,long,TimeUnit);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;BlockingDeque;true;offerFirst;(Object,long,TimeUnit);;Argument[0];Argument[this].Element;value;manual", BlockingDeque out = null; Object in = source(); out.offerFirst(in,0,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;putLast;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;BlockingDeque;true;putLast;(Object);;Argument[0];Argument[this].Element;value;manual", BlockingDeque out = null; Object in = source(); out.putLast(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingDeque;true;putFirst;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;BlockingDeque;true;putFirst;(Object);;Argument[0];Argument[this].Element;value;manual", BlockingDeque out = null; Object in = source(); out.putFirst(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingQueue;true;drainTo;(Collection,int);;Element of Argument[this];Element of Argument[0];value;manual", + // "java.util.concurrent;BlockingQueue;true;drainTo;(Collection,int);;Argument[this].Element;Argument[0].Element;value;manual", Collection out = null; BlockingQueue in = storeElementBlockingDeque(source()); in.drainTo(out,0); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;BlockingQueue;true;drainTo;(Collection);;Element of Argument[this];Element of Argument[0];value;manual", + // "java.util.concurrent;BlockingQueue;true;drainTo;(Collection);;Argument[this].Element;Argument[0].Element;value;manual", Collection out = null; BlockingQueue in = storeElementBlockingDeque(source()); in.drainTo(out); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;ConcurrentHashMap;true;elements;();;MapValue of Argument[this];Element of ReturnValue;value;manual", + // "java.util.concurrent;ConcurrentHashMap;true;elements;();;Argument[this].MapValue;ReturnValue.Element;value;manual", Enumeration out = null; Object in = storeMapValue(source()); out = ((ConcurrentHashMap)in).elements(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Dictionary;true;elements;();;MapValue of Argument[this];Element of ReturnValue;value;manual", + // "java.util;Dictionary;true;elements;();;Argument[this].MapValue;ReturnValue.Element;value;manual", Enumeration out = null; Object in = storeMapValue(source()); out = ((Dictionary)in).elements(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Dictionary;true;get;(Object);;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Dictionary;true;get;(Object);;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Dictionary)in).get(null); sink(out); // $ hasValueFlow } { - // "java.util;Dictionary;true;put;(Object,Object);;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Dictionary;true;put;(Object,Object);;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Dictionary)in).put(null,null); sink(out); // $ hasValueFlow } { - // "java.util;Dictionary;true;put;(Object,Object);;Argument[0];MapKey of Argument[this];value;manual", + // "java.util;Dictionary;true;put;(Object,Object);;Argument[0];Argument[this].MapKey;value;manual", Dictionary out = null; Object in = source(); out.put(in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Dictionary;true;put;(Object,Object);;Argument[1];MapValue of Argument[this];value;manual", + // "java.util;Dictionary;true;put;(Object,Object);;Argument[1];Argument[this].MapValue;value;manual", Dictionary out = null; Object in = source(); out.put(null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Dictionary;true;remove;(Object);;MapValue of Argument[this];ReturnValue;value;manual", + // "java.util;Dictionary;true;remove;(Object);;Argument[this].MapValue;ReturnValue;value;manual", Object out = null; Object in = storeMapValue(source()); out = ((Dictionary)in).remove(null); sink(out); // $ hasValueFlow } { - // "java.util;NavigableMap;true;ceilingEntry;(Object);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;ceilingEntry;(Object);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).ceilingEntry(null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;ceilingEntry;(Object);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;ceilingEntry;(Object);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).ceilingEntry(null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;descendingMap;();;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;descendingMap;();;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).descendingMap(); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;descendingMap;();;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;descendingMap;();;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).descendingMap(); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;firstEntry;();;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;firstEntry;();;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).firstEntry(); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;firstEntry;();;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;firstEntry;();;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).firstEntry(); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;floorEntry;(Object);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;floorEntry;(Object);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).floorEntry(null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;floorEntry;(Object);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;floorEntry;(Object);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).floorEntry(null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;headMap;(Object,boolean);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;headMap;(Object,boolean);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).headMap(null,true); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;headMap;(Object,boolean);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;headMap;(Object,boolean);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).headMap(null,true); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;higherEntry;(Object);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;higherEntry;(Object);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).higherEntry(null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;higherEntry;(Object);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;higherEntry;(Object);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).higherEntry(null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;lastEntry;();;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;lastEntry;();;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).lastEntry(); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;lastEntry;();;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;lastEntry;();;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).lastEntry(); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;lowerEntry;(Object);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;lowerEntry;(Object);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).lowerEntry(null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;lowerEntry;(Object);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;lowerEntry;(Object);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).lowerEntry(null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;pollFirstEntry;();;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;pollFirstEntry;();;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).pollFirstEntry(); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;pollFirstEntry;();;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;pollFirstEntry;();;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).pollFirstEntry(); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;pollLastEntry;();;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;pollLastEntry;();;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).pollLastEntry(); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;pollLastEntry;();;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;pollLastEntry;();;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).pollLastEntry(); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;subMap;(Object,boolean,Object,boolean);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;subMap;(Object,boolean,Object,boolean);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).subMap(null,true,null,true); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;subMap;(Object,boolean,Object,boolean);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;subMap;(Object,boolean,Object,boolean);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).subMap(null,true,null,true); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;tailMap;(Object,boolean);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;NavigableMap;true;tailMap;(Object,boolean);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((NavigableMap)in).tailMap(null,true); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;NavigableMap;true;tailMap;(Object,boolean);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;NavigableMap;true;tailMap;(Object,boolean);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((NavigableMap)in).tailMap(null,true); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;NavigableSet;true;ceiling;(Object);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;NavigableSet;true;ceiling;(Object);;Argument[this].Element;ReturnValue;value;manual", Object out = null; NavigableSet in = storeElementNavSet(source()); out = in.ceiling(null); sink(out); // $ hasValueFlow } { - // "java.util;NavigableSet;true;descendingIterator;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;NavigableSet;true;descendingIterator;();;Argument[this].Element;ReturnValue.Element;value;manual", Iterator out = null; NavigableSet in = storeElementNavSet(source()); out = in.descendingIterator(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;NavigableSet;true;descendingSet;();;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;NavigableSet;true;descendingSet;();;Argument[this].Element;ReturnValue.Element;value;manual", Set out = null; NavigableSet in = storeElementNavSet(source()); out = in.descendingSet(); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;NavigableSet;true;floor;(Object);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;NavigableSet;true;floor;(Object);;Argument[this].Element;ReturnValue;value;manual", Object out = null; NavigableSet in = storeElementNavSet(source()); out = in.floor(null); sink(out); // $ hasValueFlow } { - // "java.util;NavigableSet;true;headSet;(Object,boolean);;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;NavigableSet;true;headSet;(Object,boolean);;Argument[this].Element;ReturnValue.Element;value;manual", Set out = null; NavigableSet in = storeElementNavSet(source()); out = in.headSet(null,true); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;NavigableSet;true;higher;(Object);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;NavigableSet;true;higher;(Object);;Argument[this].Element;ReturnValue;value;manual", Object out = null; NavigableSet in = storeElementNavSet(source()); out = in.higher(null); sink(out); // $ hasValueFlow } { - // "java.util;NavigableSet;true;lower;(Object);;Element of Argument[this];ReturnValue;value;manual", + // "java.util;NavigableSet;true;lower;(Object);;Argument[this].Element;ReturnValue;value;manual", Object out = null; NavigableSet in = storeElementNavSet(source()); out = in.lower(null); sink(out); // $ hasValueFlow } { - // "java.util;NavigableSet;true;pollFirst;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;NavigableSet;true;pollFirst;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; NavigableSet in = storeElementNavSet(source()); out = in.pollFirst(); sink(out); // $ hasValueFlow } { - // "java.util;NavigableSet;true;pollLast;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;NavigableSet;true;pollLast;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; NavigableSet in = storeElementNavSet(source()); out = in.pollLast(); sink(out); // $ hasValueFlow } { - // "java.util;NavigableSet;true;subSet;(Object,boolean,Object,boolean);;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;NavigableSet;true;subSet;(Object,boolean,Object,boolean);;Argument[this].Element;ReturnValue.Element;value;manual", Set out = null; NavigableSet in = storeElementNavSet(source()); out = in.subSet(null,true,null,true); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;NavigableSet;true;tailSet;(Object,boolean);;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;NavigableSet;true;tailSet;(Object,boolean);;Argument[this].Element;ReturnValue.Element;value;manual", Set out = null; NavigableSet in = storeElementNavSet(source()); out = in.tailSet(null,true); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;SortedMap;true;headMap;(Object);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;SortedMap;true;headMap;(Object);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((SortedMap)in).headMap(null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;SortedMap;true;headMap;(Object);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;SortedMap;true;headMap;(Object);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((SortedMap)in).headMap(null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;SortedMap;true;subMap;(Object,Object);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;SortedMap;true;subMap;(Object,Object);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((SortedMap)in).subMap(null,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;SortedMap;true;subMap;(Object,Object);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;SortedMap;true;subMap;(Object,Object);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((SortedMap)in).subMap(null,null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;SortedMap;true;tailMap;(Object);;MapKey of Argument[this];MapKey of ReturnValue;value;manual", + // "java.util;SortedMap;true;tailMap;(Object);;Argument[this].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((SortedMap)in).tailMap(null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;SortedMap;true;tailMap;(Object);;MapValue of Argument[this];MapValue of ReturnValue;value;manual", + // "java.util;SortedMap;true;tailMap;(Object);;Argument[this].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((SortedMap)in).tailMap(null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;SortedSet;true;first;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;SortedSet;true;first;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; SortedSet in = storeElementNavSet(source()); out = in.first(); sink(out); // $ hasValueFlow } { - // "java.util;SortedSet;true;headSet;(Object);;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;SortedSet;true;headSet;(Object);;Argument[this].Element;ReturnValue.Element;value;manual", Set out = null; SortedSet in = storeElementNavSet(source()); out = in.headSet(null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;SortedSet;true;last;();;Element of Argument[this];ReturnValue;value;manual", + // "java.util;SortedSet;true;last;();;Argument[this].Element;ReturnValue;value;manual", Object out = null; SortedSet in = storeElementNavSet(source()); out = in.last(); sink(out); // $ hasValueFlow } { - // "java.util;SortedSet;true;subSet;(Object,Object);;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;SortedSet;true;subSet;(Object,Object);;Argument[this].Element;ReturnValue.Element;value;manual", Set out = null; SortedSet in = storeElementNavSet(source()); out = in.subSet(null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;SortedSet;true;tailSet;(Object);;Element of Argument[this];Element of ReturnValue;value;manual", + // "java.util;SortedSet;true;tailSet;(Object);;Argument[this].Element;ReturnValue.Element;value;manual", Set out = null; SortedSet in = storeElementNavSet(source()); out = in.tailSet(null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;TransferQueue;true;tryTransfer;(Object,long,TimeUnit);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;TransferQueue;true;tryTransfer;(Object,long,TimeUnit);;Argument[0];Argument[this].Element;value;manual", TransferQueue out = null; Object in = source(); out.tryTransfer(in,0,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;TransferQueue;true;transfer;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;TransferQueue;true;transfer;(Object);;Argument[0];Argument[this].Element;value;manual", TransferQueue out = null; Object in = source(); out.transfer(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util.concurrent;TransferQueue;true;tryTransfer;(Object);;Argument[0];Element of Argument[this];value;manual", + // "java.util.concurrent;TransferQueue;true;tryTransfer;(Object);;Argument[0];Argument[this].Element;value;manual", TransferQueue out = null; Object in = source(); out.tryTransfer(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;copyOf;(Collection);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;copyOf;(Collection);;Argument[0].Element;ReturnValue.Element;value;manual", List out = null; Collection in = storeElementList(source()); out = List.copyOf(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object[]);;ArrayElement of Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object[]);;Argument[0].ArrayElement;ReturnValue.Element;value;manual", List out = null; Object[] in = storeArrayElement(source()); out = List.of(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(in,null,null,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,in,null,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,in,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[9];Element of ReturnValue;value;manual", + // "java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[9];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = List.of(null,null,null,null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Map;false;copyOf;(Map);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;copyOf;(Map);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = Map.copyOf((Map)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;copyOf;(Map);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;copyOf;(Map);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = Map.copyOf((Map)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;entry;(Object,Object);;Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;entry;(Object,Object);;Argument[0];ReturnValue.MapKey;value;manual", Map.Entry out = null; Object in = source(); out = Map.entry(in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;entry;(Object,Object);;Argument[1];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;entry;(Object,Object);;Argument[1];ReturnValue.MapValue;value;manual", Map.Entry out = null; Object in = source(); out = Map.entry(null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[0];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[1];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[1];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[2];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[2];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[3];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[3];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[4];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[4];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[5];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[5];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[6];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[6];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[7];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[7];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[8];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[8];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[9];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[9];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[10];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[10];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[11];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[11];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[12];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[12];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[13];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[13];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[14];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[14];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[15];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[15];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[16];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[16];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[17];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[17];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[18];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[18];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;of;;;Argument[19];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;of;;;Argument[19];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = Map.of(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Map;false;ofEntries;;;MapKey of ArrayElement of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Map;false;ofEntries;;;Argument[0].MapKey.ArrayElement;ReturnValue.MapKey;value;manual", Map out = null; Object[] in = storeArrayElement(storeMapKey(source())); out = Map.ofEntries((Map.Entry[])in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Map;false;ofEntries;;;MapValue of ArrayElement of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Map;false;ofEntries;;;Argument[0].MapValue.ArrayElement;ReturnValue.MapValue;value;manual", Map out = null; Object[] in = storeArrayElement(storeMapValue(source())); out = Map.ofEntries((Map.Entry[])in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Set;false;copyOf;(Collection);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;true;clear;;;Argument[this].WithoutElement;Argument[this];value;manual" + Set out = null; + Set in = storeSetElement(source()); out = in; out.clear(); sink(readElement(out)); // No flow + } + { + // "java.util;Set;true;clear;;;Argument[this].WithoutElement;Argument[this];value;manual" + Set out = null; + Set in = (Set)source(); out = in; out.clear(); sink(out); // $ hasValueFlow + } + { + // "java.util;Set;false;copyOf;(Collection);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; Collection in = storeElementList(source()); out = Set.copyOf(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object[]);;ArrayElement of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object[]);;Argument[0].ArrayElement;ReturnValue.Element;value;manual", Set out = null; Object[] in = storeArrayElement(source()); out = Set.of(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(in,null,null,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,in,null,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,in,null,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[3];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,in,null,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[4];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,in,null,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[5];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,in,null,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[6];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,in,null,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[7];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,null,in,null,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[8];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,null,null,in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[9];Element of ReturnValue;value;manual", + // "java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[9];ReturnValue.Element;value;manual", Set out = null; Object in = source(); out = Set.of(null,null,null,null,null,null,null,null,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;stream;;;ArrayElement of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Arrays;false;stream;;;Argument[0].ArrayElement;ReturnValue.Element;value;manual", Stream out = null; Object[] in = storeArrayElement(source()); out = ((Arrays)null).stream(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;spliterator;;;ArrayElement of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Arrays;false;spliterator;;;Argument[0].ArrayElement;ReturnValue.Element;value;manual", Spliterator out = null; Object[] in = storeArrayElement(source()); out = ((Arrays)null).spliterator(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;copyOfRange;;;ArrayElement of Argument[0];ArrayElement of ReturnValue;value;manual", + // "java.util;Arrays;false;copyOfRange;;;Argument[0].ArrayElement;ReturnValue.ArrayElement;value;manual", Object[] out = null; Object[] in = storeArrayElement(source()); out = ((Arrays)null).copyOfRange(in,0,0); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;copyOf;;;ArrayElement of Argument[0];ArrayElement of ReturnValue;value;manual", + // "java.util;Arrays;false;copyOf;;;Argument[0].ArrayElement;ReturnValue.ArrayElement;value;manual", Object[] out = null; Object[] in = storeArrayElement(source()); out = ((Arrays)null).copyOf(in,0); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;list;(Enumeration);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;list;(Enumeration);;Argument[0].Element;ReturnValue.Element;value;manual", List out = null; Enumeration in = storeElementEnumeration(source()); out = ((Collections)null).list(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;enumeration;(Collection);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;enumeration;(Collection);;Argument[0].Element;ReturnValue.Element;value;manual", Enumeration out = null; Collection in = storeElementList(source()); out = ((Collections)null).enumeration(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;nCopies;(int,Object);;Argument[1];Element of ReturnValue;value;manual", + // "java.util;Collections;false;nCopies;(int,Object);;Argument[1];ReturnValue.Element;value;manual", Collection out = null; Object in = source(); out = ((Collections)null).nCopies(0,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;singletonMap;(Object,Object);;Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;singletonMap;(Object,Object);;Argument[0];ReturnValue.MapKey;value;manual", Map out = null; Object in = source(); out = ((Collections)null).singletonMap(in,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;singletonMap;(Object,Object);;Argument[1];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;singletonMap;(Object,Object);;Argument[1];ReturnValue.MapValue;value;manual", Map out = null; Object in = source(); out = ((Collections)null).singletonMap(null,in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;singletonList;(Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;singletonList;(Object);;Argument[0];ReturnValue.Element;value;manual", List out = null; Object in = source(); out = ((Collections)null).singletonList(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;singleton;(Object);;Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;singleton;(Object);;Argument[0];ReturnValue.Element;value;manual", Collection out = null; Object in = source(); out = ((Collections)null).singleton(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedNavigableMap;(NavigableMap,Class,Class);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;checkedNavigableMap;(NavigableMap,Class,Class);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).checkedNavigableMap((NavigableMap)in,null,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedNavigableMap;(NavigableMap,Class,Class);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;checkedNavigableMap;(NavigableMap,Class,Class);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).checkedNavigableMap((NavigableMap)in,null,null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedSortedMap;(SortedMap,Class,Class);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;checkedSortedMap;(SortedMap,Class,Class);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).checkedSortedMap((SortedMap)in,null,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedSortedMap;(SortedMap,Class,Class);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;checkedSortedMap;(SortedMap,Class,Class);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).checkedSortedMap((SortedMap)in,null,null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedMap;(Map,Class,Class);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;checkedMap;(Map,Class,Class);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).checkedMap((Map)in,null,null); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedMap;(Map,Class,Class);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;checkedMap;(Map,Class,Class);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).checkedMap((Map)in,null,null); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedList;(List,Class);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;checkedList;(List,Class);;Argument[0].Element;ReturnValue.Element;value;manual", List out = null; List in = storeElementList(source()); out = ((Collections)null).checkedList(in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedNavigableSet;(NavigableSet,Class);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;checkedNavigableSet;(NavigableSet,Class);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; NavigableSet in = storeElementNavSet(source()); out = ((Collections)null).checkedNavigableSet(in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedSortedSet;(SortedSet,Class);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;checkedSortedSet;(SortedSet,Class);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; SortedSet in = storeElementNavSet(source()); out = ((Collections)null).checkedSortedSet(in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedSet;(Set,Class);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;checkedSet;(Set,Class);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; Set in = storeElementNavSet(source()); out = ((Collections)null).checkedSet(in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;checkedCollection;(Collection,Class);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;checkedCollection;(Collection,Class);;Argument[0].Element;ReturnValue.Element;value;manual", Collection out = null; Collection in = storeElementList(source()); out = ((Collections)null).checkedCollection(in,null); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedNavigableMap;(NavigableMap);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedNavigableMap;(NavigableMap);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).synchronizedNavigableMap((NavigableMap)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedNavigableMap;(NavigableMap);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedNavigableMap;(NavigableMap);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).synchronizedNavigableMap((NavigableMap)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedSortedMap;(SortedMap);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedSortedMap;(SortedMap);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).synchronizedSortedMap((SortedMap)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedSortedMap;(SortedMap);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedSortedMap;(SortedMap);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).synchronizedSortedMap((SortedMap)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedMap;(Map);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedMap;(Map);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).synchronizedMap((Map)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedMap;(Map);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedMap;(Map);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).synchronizedMap((Map)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedList;(List);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedList;(List);;Argument[0].Element;ReturnValue.Element;value;manual", List out = null; List in = storeElementList(source()); out = ((Collections)null).synchronizedList(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedNavigableSet;(NavigableSet);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedNavigableSet;(NavigableSet);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; NavigableSet in = storeElementNavSet(source()); out = ((Collections)null).synchronizedNavigableSet(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedSortedSet;(SortedSet);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedSortedSet;(SortedSet);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; SortedSet in = storeElementNavSet(source()); out = ((Collections)null).synchronizedSortedSet(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedSet;(Set);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedSet;(Set);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; Set in = storeElementNavSet(source()); out = ((Collections)null).synchronizedSet(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;synchronizedCollection;(Collection);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;synchronizedCollection;(Collection);;Argument[0].Element;ReturnValue.Element;value;manual", Collection out = null; Collection in = storeElementList(source()); out = ((Collections)null).synchronizedCollection(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableNavigableMap;(NavigableMap);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableNavigableMap;(NavigableMap);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).unmodifiableNavigableMap((NavigableMap)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableNavigableMap;(NavigableMap);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableNavigableMap;(NavigableMap);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).unmodifiableNavigableMap((NavigableMap)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableSortedMap;(SortedMap);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableSortedMap;(SortedMap);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).unmodifiableSortedMap((SortedMap)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableSortedMap;(SortedMap);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableSortedMap;(SortedMap);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).unmodifiableSortedMap((SortedMap)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableMap;(Map);;MapKey of Argument[0];MapKey of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableMap;(Map);;Argument[0].MapKey;ReturnValue.MapKey;value;manual", Map out = null; Object in = storeMapKey(source()); out = ((Collections)null).unmodifiableMap((Map)in); sink(readMapKey(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableMap;(Map);;MapValue of Argument[0];MapValue of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableMap;(Map);;Argument[0].MapValue;ReturnValue.MapValue;value;manual", Map out = null; Object in = storeMapValue(source()); out = ((Collections)null).unmodifiableMap((Map)in); sink(readMapValue(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableList;(List);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableList;(List);;Argument[0].Element;ReturnValue.Element;value;manual", List out = null; List in = storeElementList(source()); out = ((Collections)null).unmodifiableList(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableNavigableSet;(NavigableSet);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableNavigableSet;(NavigableSet);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; NavigableSet in = storeElementNavSet(source()); out = ((Collections)null).unmodifiableNavigableSet(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableSortedSet;(SortedSet);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableSortedSet;(SortedSet);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; SortedSet in = storeElementNavSet(source()); out = ((Collections)null).unmodifiableSortedSet(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableSet;(Set);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableSet;(Set);;Argument[0].Element;ReturnValue.Element;value;manual", Set out = null; Set in = storeElementNavSet(source()); out = ((Collections)null).unmodifiableSet(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;unmodifiableCollection;(Collection);;Element of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Collections;false;unmodifiableCollection;(Collection);;Argument[0].Element;ReturnValue.Element;value;manual", Collection out = null; Collection in = storeElementList(source()); out = ((Collections)null).unmodifiableCollection(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;max;;;Element of Argument[0];ReturnValue;value;manual", + // "java.util;Collections;false;max;;;Argument[0].Element;ReturnValue;value;manual", Object out = null; Collection in = storeElementList(source()); out = ((Collections)null).max(in); sink(out); // $ hasValueFlow } { - // "java.util;Collections;false;min;;;Element of Argument[0];ReturnValue;value;manual", + // "java.util;Collections;false;min;;;Argument[0].Element;ReturnValue;value;manual", Object out = null; Collection in = storeElementList(source()); out = ((Collections)null).min(in); sink(out); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(Object[],int,int,Object);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(Object[],int,int,Object);;Argument[3];Argument[0].ArrayElement;value;manual", Object[] out = null; Object in = source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(Object[],Object);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(Object[],Object);;Argument[1];Argument[0].ArrayElement;value;manual", Object[] out = null; Object in = source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(float[],int,int,float);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(float[],int,int,float);;Argument[3];Argument[0].ArrayElement;value;manual", float[] out = null; float in = (Float)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(float[],float);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(float[],float);;Argument[1];Argument[0].ArrayElement;value;manual", float[] out = null; float in = (Float)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(double[],int,int,double);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(double[],int,int,double);;Argument[3];Argument[0].ArrayElement;value;manual", double[] out = null; double in = (Double)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(double[],double);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(double[],double);;Argument[1];Argument[0].ArrayElement;value;manual", double[] out = null; double in = (Double)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(boolean[],int,int,boolean);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(boolean[],int,int,boolean);;Argument[3];Argument[0].ArrayElement;value;manual", boolean[] out = null; boolean in = (Boolean)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(boolean[],boolean);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(boolean[],boolean);;Argument[1];Argument[0].ArrayElement;value;manual", boolean[] out = null; boolean in = (Boolean)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(byte[],int,int,byte);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(byte[],int,int,byte);;Argument[3];Argument[0].ArrayElement;value;manual", byte[] out = null; byte in = (Byte)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(byte[],byte);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(byte[],byte);;Argument[1];Argument[0].ArrayElement;value;manual", byte[] out = null; byte in = (Byte)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(char[],int,int,char);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(char[],int,int,char);;Argument[3];Argument[0].ArrayElement;value;manual", char[] out = null; char in = (Character)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(char[],char);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(char[],char);;Argument[1];Argument[0].ArrayElement;value;manual", char[] out = null; char in = (Character)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(short[],int,int,short);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(short[],int,int,short);;Argument[3];Argument[0].ArrayElement;value;manual", short[] out = null; short in = (Short)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(short[],short);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(short[],short);;Argument[1];Argument[0].ArrayElement;value;manual", short[] out = null; short in = (Short)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(int[],int,int,int);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(int[],int,int,int);;Argument[3];Argument[0].ArrayElement;value;manual", int[] out = null; int in = (Integer)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(int[],int);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(int[],int);;Argument[1];Argument[0].ArrayElement;value;manual", int[] out = null; int in = (Integer)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(long[],int,int,long);;Argument[3];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(long[],int,int,long);;Argument[3];Argument[0].ArrayElement;value;manual", long[] out = null; long in = (Long)source(); ((Arrays)null).fill(out,0,0,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;fill;(long[],long);;Argument[1];ArrayElement of Argument[0];value;manual", + // "java.util;Arrays;false;fill;(long[],long);;Argument[1];Argument[0].ArrayElement;value;manual", long[] out = null; long in = (Long)source(); ((Arrays)null).fill(out,in); sink(readArrayElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;replaceAll;(List,Object,Object);;Argument[2];Element of Argument[0];value;manual", + // "java.util;Collections;false;replaceAll;(List,Object,Object);;Argument[2];Argument[0].Element;value;manual", List out = null; Object in = source(); ((Collections)null).replaceAll(out,null,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;copy;(List,List);;Element of Argument[1];Element of Argument[0];value;manual", + // "java.util;Collections;false;copy;(List,List);;Argument[1].Element;Argument[0].Element;value;manual", List out = null; List in = storeElementList(source()); ((Collections)null).copy(out,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;fill;(List,Object);;Argument[1];Element of Argument[0];value;manual", + // "java.util;Collections;false;fill;(List,Object);;Argument[1];Argument[0].Element;value;manual", List out = null; Object in = source(); ((Collections)null).fill(out,in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Arrays;false;asList;;;ArrayElement of Argument[0];Element of ReturnValue;value;manual", + // "java.util;Arrays;false;asList;;;Argument[0].ArrayElement;ReturnValue.Element;value;manual", List out = null; Object[] in = storeArrayElement(source()); out = ((Arrays)null).asList(in); sink(readElement(out)); // $ hasValueFlow } { - // "java.util;Collections;false;addAll;(Collection,Object[]);;ArrayElement of Argument[1];Element of Argument[0];value;manual" + // "java.util;Collections;false;addAll;(Collection,Object[]);;Argument[1].ArrayElement;Argument[0].Element;value;manual" Collection out = null; Object[] in = storeArrayElement(source()); ((Collections)null).addAll(out,in); sink(readElement(out)); // $ hasValueFlow } diff --git a/java/ql/test/library-tests/dataflow/stream-read/A.java b/java/ql/test/library-tests/dataflow/stream-read/A.java index 779f95bcefa..705977757a1 100644 --- a/java/ql/test/library-tests/dataflow/stream-read/A.java +++ b/java/ql/test/library-tests/dataflow/stream-read/A.java @@ -99,7 +99,7 @@ public class A { } public static void testWrapCall() { - sink(wrapStream(null)); // $ SPURIOUS: hasTaintFlow + sink(wrapStream(null)); // no flow sink(wrapStream(source())); // $ hasTaintFlow } diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java index 83ca80fc37f..da87794eb56 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java @@ -107,13 +107,13 @@ class IntegrationTest { filterAndMerge_2(pojoForm, mergedParams, name -> false); return mergedParams; }).then(pojoMap -> { - sink(pojoMap.keySet().iterator().next()); //TODO:$hasTaintFlow - sink(pojoMap.get("value")); //TODO:$hasTaintFlow + sink(pojoMap.keySet().iterator().next()); //$hasTaintFlow + sink(pojoMap.get("value")); //$hasTaintFlow pojoMap.forEach((key, value) -> { - sink(key); //TODO:$hasTaintFlow - sink(value); //TODO:$hasTaintFlow + sink(key); //$hasTaintFlow + sink(value); //$hasTaintFlow List values = (List) value; - sink(values.get(0)); //TODO:$hasTaintFlow + sink(values.get(0)); //$hasTaintFlow }); }); } diff --git a/java/ql/test/query-tests/Metrics/GeneratedVsManualCoverage/TopJdkApisTest/TopJdkApisTest.expected b/java/ql/test/query-tests/Metrics/GeneratedVsManualCoverage/TopJdkApisTest/TopJdkApisTest.expected index 4fee1c07e06..a092100b6a7 100644 --- a/java/ql/test/query-tests/Metrics/GeneratedVsManualCoverage/TopJdkApisTest/TopJdkApisTest.expected +++ b/java/ql/test/query-tests/Metrics/GeneratedVsManualCoverage/TopJdkApisTest/TopJdkApisTest.expected @@ -13,7 +13,7 @@ | java.time | 0 | 0 | 0 | 17 | 17 | 0.0 | 0.0 | 0.0 | NaN | NaN | 1.0 | | java.time.chrono | 0 | 0 | 0 | 1 | 1 | 0.0 | 0.0 | 0.0 | NaN | NaN | 1.0 | | java.time.format | 0 | 0 | 0 | 2 | 2 | 0.0 | 0.0 | 0.0 | NaN | NaN | 1.0 | -| java.util | 0 | 0 | 84 | 68 | 152 | 0.5526315789473685 | 0.0 | 0.5526315789473685 | 0.0 | NaN | 0.4473684210526316 | +| java.util | 0 | 0 | 86 | 66 | 152 | 0.5657894736842105 | 0.0 | 0.5657894736842105 | 0.0 | NaN | 0.4342105263157895 | | java.util.concurrent | 0 | 0 | 9 | 9 | 18 | 0.5 | 0.0 | 0.5 | 0.0 | NaN | 0.5 | | java.util.concurrent.atomic | 0 | 0 | 2 | 11 | 13 | 0.15384615384615385 | 0.0 | 0.15384615384615385 | 0.0 | NaN | 0.8461538461538461 | | java.util.concurrent.locks | 0 | 0 | 0 | 2 | 2 | 0.0 | 0.0 | 0.0 | NaN | NaN | 1.0 | diff --git a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractCandidates.expected b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractCandidates.expected index 8b6f95795b0..5f946f49d19 100644 --- a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractCandidates.expected +++ b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractCandidates.expected @@ -1,2 +1,3 @@ -| Test.java:16:3:16:11 | reference | command-injection, path-injection, request-forgery, sql-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@. | Test.java:16:3:16:24 | set(...) | CallContext | file://java.util.concurrent.atomic:1:1:1:1 | java.util.concurrent.atomic | package | file://AtomicReference:1:1:1:1 | AtomicReference | type | file://false:1:1:1:1 | false | subtypes | file://set:1:1:1:1 | set | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | -| Test.java:21:3:21:10 | supplier | command-injection, path-injection, request-forgery, sql-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@. | Test.java:21:3:21:16 | get(...) | CallContext | file://java.util.function:1:1:1:1 | java.util.function | package | file://Supplier:1:1:1:1 | Supplier | type | file://true:1:1:1:1 | true | subtypes | file://get:1:1:1:1 | get | name | file://():1:1:1:1 | () | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | +| Test.java:16:3:16:11 | reference | command-injection, path-injection, request-forgery, sql-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:16:3:16:24 | set(...) | CallContext | file://java.util.concurrent.atomic:1:1:1:1 | java.util.concurrent.atomic | package | file://AtomicReference:1:1:1:1 | AtomicReference | type | file://false:1:1:1:1 | false | subtypes | file://set:1:1:1:1 | set | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://false:1:1:1:1 | false | isVarargsArray | +| Test.java:21:3:21:10 | supplier | command-injection, path-injection, request-forgery, sql-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:21:3:21:16 | get(...) | CallContext | file://java.util.function:1:1:1:1 | java.util.function | package | file://Supplier:1:1:1:1 | Supplier | type | file://true:1:1:1:1 | true | subtypes | file://get:1:1:1:1 | get | name | file://():1:1:1:1 | () | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://false:1:1:1:1 | false | isVarargsArray | +| Test.java:53:4:53:4 | o | command-injection, path-injection, request-forgery, sql-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:51:3:56:3 | walk(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://walk:1:1:1:1 | walk | name | file://(Path,FileVisitOption[]):1:1:1:1 | (Path,FileVisitOption[]) | signature | file://Argument[1]:1:1:1:1 | Argument[1] | input | file://true:1:1:1:1 | true | isVarargsArray | diff --git a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractNegativeExamples.expected b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractNegativeExamples.expected index 0c626ca2d0b..1ca68cb2281 100644 --- a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractNegativeExamples.expected +++ b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractNegativeExamples.expected @@ -1,2 +1,2 @@ -| Test.java:40:14:40:21 | openPath | taint step\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@. | Test.java:40:4:40:22 | get(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Paths:1:1:1:1 | Paths | type | file://false:1:1:1:1 | false | subtypes | file://get:1:1:1:1 | get | name | file://(String,String[]):1:1:1:1 | (String,String[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | -| Test.java:46:4:46:5 | f2 | known non-sink\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@. | Test.java:45:10:47:3 | compareTo(...) | CallContext | file://java.io:1:1:1:1 | java.io | package | file://File:1:1:1:1 | File | type | file://true:1:1:1:1 | true | subtypes | file://compareTo:1:1:1:1 | compareTo | name | file://(File):1:1:1:1 | (File) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | +| Test.java:46:4:46:5 | f2 | known non-sink\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:45:10:47:3 | compareTo(...) | CallContext | file://java.io:1:1:1:1 | java.io | package | file://File:1:1:1:1 | File | type | file://true:1:1:1:1 | true | subtypes | file://compareTo:1:1:1:1 | compareTo | name | file://(File):1:1:1:1 | (File) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://false:1:1:1:1 | false | isVarargsArray | +| Test.java:52:4:52:4 | p | taint step\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:51:3:56:3 | walk(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://walk:1:1:1:1 | walk | name | file://(Path,FileVisitOption[]):1:1:1:1 | (Path,FileVisitOption[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://false:1:1:1:1 | false | isVarargsArray | diff --git a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractPositiveExamples.expected b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractPositiveExamples.expected index 841599ce021..e07f92f4baf 100644 --- a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractPositiveExamples.expected +++ b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/AutomodelApplicationModeExtractPositiveExamples.expected @@ -1,3 +1,3 @@ -| Test.java:26:4:26:9 | source | path-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@. | Test.java:25:3:29:3 | copy(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://copy:1:1:1:1 | copy | name | file://(Path,Path,CopyOption[]):1:1:1:1 | (Path,Path,CopyOption[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | -| Test.java:27:4:27:9 | target | path-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@. | Test.java:25:3:29:3 | copy(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://copy:1:1:1:1 | copy | name | file://(Path,Path,CopyOption[]):1:1:1:1 | (Path,Path,CopyOption[]) | signature | file://Argument[1]:1:1:1:1 | Argument[1] | input | -| Test.java:34:4:34:11 | openPath | path-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@. | Test.java:33:10:35:3 | newInputStream(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://newInputStream:1:1:1:1 | newInputStream | name | file://(Path,OpenOption[]):1:1:1:1 | (Path,OpenOption[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | +| Test.java:26:4:26:9 | source | path-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:25:3:29:3 | copy(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://copy:1:1:1:1 | copy | name | file://(Path,Path,CopyOption[]):1:1:1:1 | (Path,Path,CopyOption[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://false:1:1:1:1 | false | isVarargsArray | +| Test.java:27:4:27:9 | target | path-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:25:3:29:3 | copy(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://copy:1:1:1:1 | copy | name | file://(Path,Path,CopyOption[]):1:1:1:1 | (Path,Path,CopyOption[]) | signature | file://Argument[1]:1:1:1:1 | Argument[1] | input | file://false:1:1:1:1 | false | isVarargsArray | +| Test.java:34:4:34:11 | openPath | path-injection\nrelated locations: $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@. | Test.java:33:10:35:3 | newInputStream(...) | CallContext | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://newInputStream:1:1:1:1 | newInputStream | name | file://(Path,OpenOption[]):1:1:1:1 | (Path,OpenOption[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://false:1:1:1:1 | false | isVarargsArray | diff --git a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/Test.java b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/Test.java index 67423855a8b..1b5cea4b907 100644 --- a/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/Test.java +++ b/java/ql/test/query-tests/Telemetry/AutomodelApplicationModeExtraction/Test.java @@ -8,7 +8,7 @@ import java.nio.file.Paths; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.io.File; - +import java.nio.file.FileVisitOption; class Test { public static void main(String[] args) throws Exception { @@ -46,5 +46,14 @@ class Test { f2 // negative example (modeled as not a sink) ); } + + public static void FilesWalkExample(Path p, FileVisitOption o) throws Exception { + Files.walk( + p, // negative example (modeled as a taint step) + o, // the implicit varargs array is a candidate + o // not a candidate (only the first arg corresponding to a varargs array + // is extracted) + ); + } } diff --git a/javascript/ql/lib/CHANGELOG.md b/javascript/ql/lib/CHANGELOG.md index ed2b926666e..fcd73315764 100644 --- a/javascript/ql/lib/CHANGELOG.md +++ b/javascript/ql/lib/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.7.2 + +### Minor Analysis Improvements + +* Added `log-injection` as a customizable sink kind for log injection. + ## 0.7.1 No user-facing changes. diff --git a/javascript/ql/lib/change-notes/2023-07-28-mad-log-injection.md b/javascript/ql/lib/change-notes/released/0.7.2.md similarity index 56% rename from javascript/ql/lib/change-notes/2023-07-28-mad-log-injection.md rename to javascript/ql/lib/change-notes/released/0.7.2.md index 63dba2e4539..3d9ca6e6141 100644 --- a/javascript/ql/lib/change-notes/2023-07-28-mad-log-injection.md +++ b/javascript/ql/lib/change-notes/released/0.7.2.md @@ -1,4 +1,5 @@ ---- -category: minorAnalysis ---- -* Added `log-injection` as a customizable sink kind for log injection. \ No newline at end of file +## 0.7.2 + +### Minor Analysis Improvements + +* Added `log-injection` as a customizable sink kind for log injection. diff --git a/javascript/ql/lib/codeql-pack.release.yml b/javascript/ql/lib/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/javascript/ql/lib/codeql-pack.release.yml +++ b/javascript/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/javascript/ql/lib/qlpack.yml b/javascript/ql/lib/qlpack.yml index 64ceca80726..a7f38195559 100644 --- a/javascript/ql/lib/qlpack.yml +++ b/javascript/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/javascript-all -version: 0.7.2-dev +version: 0.7.3-dev groups: javascript dbscheme: semmlecode.javascript.dbscheme extractor: javascript diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ServerLess.qll b/javascript/ql/lib/semmle/javascript/frameworks/ServerLess.qll index 6eace6700ae..a88a78fd033 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/ServerLess.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/ServerLess.qll @@ -1,120 +1,52 @@ /** * Provides classes and predicates for working with serverless handlers. * E.g. [AWS](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) or [serverless](https://npmjs.com/package/serverless) + * In particular a `RemoteFlowSource` is added for AWS, Alibaba, and serverless. */ import javascript +import codeql.serverless.ServerLess + +private module YamlImpl implements Input { + import semmle.javascript.Files + import semmle.javascript.YAML +} + +module SL = ServerLess; /** - * Provides classes and predicates for working with serverless handlers. - * In particular a `RemoteFlowSource` is added for AWS, Alibaba, and serverless. + * Gets a function that is a serverless request handler. + * + * For example: if an AWS serverless resource contains the following properties (in the "template.yml" file): + * ```yaml + * Handler: mylibrary.handler + * Runtime: nodejs12.x + * CodeUri: backend/src/ + * ``` + * + * And a file "mylibrary.js" exists in the folder "backend/src" (relative to the "template.yml" file). + * Then the result of this predicate is a function exported as "handler" from "mylibrary.js". + * The "mylibrary.js" file could for example look like: + * + * ```JavaScript + * module.exports.handler = function (event) { ... } + * ``` */ -private module ServerLess { - /** - * Holds if the `.yml` file `ymlFile` contains a serverless configuration with `handler` and `codeURI` properties. - * `codeURI` defaults to the empty string if no explicit value is set in the configuration. - */ - private predicate hasServerlessHandler(File ymlFile, string handler, string codeUri) { - exists(YamlMapping resource | ymlFile = resource.getFile() | - // There exists at least "AWS::Serverless::Function" and "Aliyun::Serverless::Function" - resource.lookup("Type").(YamlScalar).getValue().regexpMatch(".*::Serverless::Function") and - exists(YamlMapping properties | properties = resource.lookup("Properties") | - handler = properties.lookup("Handler").(YamlScalar).getValue() and - if exists(properties.lookup("CodeUri")) - then codeUri = properties.lookup("CodeUri").(YamlScalar).getValue() - else codeUri = "" - ) - or - // The `serverless` library, which specifies a top-level `functions` property - exists(YamlMapping functions | - functions = resource.lookup("functions") and - not exists(resource.getParentNode()) and - handler = functions.getValue(_).(YamlMapping).lookup("handler").(YamlScalar).getValue() and - codeUri = "" - ) - ) - } - - /** - * Gets a string where an ending "/." is simplified to "/" (if it exists). - */ - bindingset[base] - private string removeTrailingDot(string base) { - if base.regexpMatch(".*/\\.") - then result = base.substring(0, base.length() - 1) - else result = base - } - - /** - * Gets a string where a leading "./" is simplified to "" (if it exists). - */ - bindingset[base] - private string removeLeadingDotSlash(string base) { - if base.regexpMatch("\\./.*") then result = base.substring(2, base.length()) else result = base - } - - /** - * Gets a path to a file from a `codeURI` property and a file name from a serverless configuration. - * - * For example if `codeURI` is "function/." and `file` is "index", then the result becomes "function/index.js". - */ - bindingset[codeUri, file] - private string getPathFromHandlerProperties(string codeUri, string file) { - exists(string folder | folder = removeLeadingDotSlash(removeTrailingDot(codeUri)) | - result = folder + file + ".js" - ) - } - - /** - * Holds if `file` has a serverless handler function with name `func`. - */ - private predicate hasServerlessHandler(File file, string func) { - exists(File ymlFile, string handler, string codeUri, string fileName | - hasServerlessHandler(ymlFile, handler, codeUri) and - // Splits a `handler` into two components. The `fileName` to the left of the dot, and the `func` to the right. - // E.g. if `handler` is "index.foo", then `fileName` is "index" and `func` is "foo". - exists(string pattern | pattern = "(.*)\\.(.*)" | - fileName = handler.regexpCapture(pattern, 1) and - func = handler.regexpCapture(pattern, 2) - ) - | - file.getAbsolutePath() = - ymlFile.getParentContainer().getAbsolutePath() + "/" + - getPathFromHandlerProperties(codeUri, fileName) - ) - } - - /** - * Gets a function that is a serverless request handler. - * - * For example: if an AWS serverless resource contains the following properties (in the "template.yml" file): - * ```yaml - * Handler: mylibrary.handler - * Runtime: nodejs12.x - * CodeUri: backend/src/ - * ``` - * - * And a file "mylibrary.js" exists in the folder "backend/src" (relative to the "template.yml" file). - * Then the result of this predicate is a function exported as "handler" from "mylibrary.js". - * The "mylibrary.js" file could for example look like: - * - * ```JavaScript - * module.exports.handler = function (event) { ... } - * ``` - */ - private DataFlow::FunctionNode getAServerlessHandler() { - exists(File file, string handler, Module mod | hasServerlessHandler(file, handler) | - mod.getFile() = file and - result = mod.getAnExportedValue(handler).getAFunctionValue() - ) - } - - /** - * A serverless request handler event, seen as a RemoteFlowSource. - */ - private class ServerlessHandlerEventAsRemoteFlow extends RemoteFlowSource { - ServerlessHandlerEventAsRemoteFlow() { this = getAServerlessHandler().getParameter(0) } - - override string getSourceType() { result = "Serverless event" } - } +private DataFlow::FunctionNode getAServerlessHandler() { + exists(File file, string stem, string handler, Module mod | + SL::hasServerlessHandler(stem, handler, _, _) and + file.getAbsolutePath() = stem + ".js" + | + mod.getFile() = file and + result = mod.getAnExportedValue(handler).getAFunctionValue() + ) +} + +/** + * A serverless request handler event, seen as a RemoteFlowSource. + */ +private class ServerlessHandlerEventAsRemoteFlow extends RemoteFlowSource { + ServerlessHandlerEventAsRemoteFlow() { this = getAServerlessHandler().getParameter(0) } + + override string getSourceType() { result = "Serverless event" } } diff --git a/javascript/ql/src/CHANGELOG.md b/javascript/ql/src/CHANGELOG.md index 05fd164a7f1..5d62761a097 100644 --- a/javascript/ql/src/CHANGELOG.md +++ b/javascript/ql/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.2 + +No user-facing changes. + ## 0.7.1 ### Minor Analysis Improvements diff --git a/javascript/ql/src/Security/CWE-770/examples/MissingRateLimitingGood.js b/javascript/ql/src/Security/CWE-770/examples/MissingRateLimitingGood.js index 72bb25eab16..1adc08ae39c 100644 --- a/javascript/ql/src/Security/CWE-770/examples/MissingRateLimitingGood.js +++ b/javascript/ql/src/Security/CWE-770/examples/MissingRateLimitingGood.js @@ -4,8 +4,8 @@ var app = express(); // set up rate limiter: maximum of five requests per minute var RateLimit = require('express-rate-limit'); var limiter = RateLimit({ - windowMs: 1*60*1000, // 1 minute - max: 5 + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // max 100 requests per windowMs }); // apply rate limiter to all requests diff --git a/javascript/ql/src/change-notes/released/0.7.2.md b/javascript/ql/src/change-notes/released/0.7.2.md new file mode 100644 index 00000000000..8693d609ec7 --- /dev/null +++ b/javascript/ql/src/change-notes/released/0.7.2.md @@ -0,0 +1,3 @@ +## 0.7.2 + +No user-facing changes. diff --git a/javascript/ql/src/codeql-pack.release.yml b/javascript/ql/src/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/javascript/ql/src/codeql-pack.release.yml +++ b/javascript/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/javascript/ql/src/qlpack.yml b/javascript/ql/src/qlpack.yml index 52a16265fa7..8c950551140 100644 --- a/javascript/ql/src/qlpack.yml +++ b/javascript/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/javascript-queries -version: 0.7.2-dev +version: 0.7.3-dev groups: - javascript - queries diff --git a/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/PolynomialBackTracking.expected b/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/PolynomialBackTracking.expected index d8bac89a913..f5d780e3190 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/PolynomialBackTracking.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/PolynomialBackTracking.expected @@ -445,7 +445,7 @@ | tst.js:146:15:146:21 | (\\d\|5)* | Strings with many repetitions of '0' can start matching anywhere after the start of the preceeding ((\\d\|5)*)" | | tst.js:149:15:149:24 | (\\s\|[\\f])* | Strings with many repetitions of '\\t' can start matching anywhere after the start of the preceeding ((\\s\|[\\f])*)" | | tst.js:152:15:152:28 | (\\s\|[\\v]\|\\\\v)* | Strings with many repetitions of '\\t' can start matching anywhere after the start of the preceeding ((\\s\|[\\v]\|\\\\v)*)" | -| tst.js:155:15:155:24 | (\\f\|[\\f])* | Strings with many repetitions of '\u000c' can start matching anywhere after the start of the preceeding ((\\f\|[\\f])*)" | +| tst.js:155:15:155:24 | (\\f\|[\\f])* | Strings with many repetitions of '\\u000c' can start matching anywhere after the start of the preceeding ((\\f\|[\\f])*)" | | tst.js:158:15:158:22 | (\\W\|\\D)* | Strings with many repetitions of '/' can start matching anywhere after the start of the preceeding ((\\W\|\\D)*)" | | tst.js:161:15:161:22 | (\\S\|\\w)* | Strings with many repetitions of '!' can start matching anywhere after the start of the preceeding ((\\S\|\\w)*)" | | tst.js:164:15:164:24 | (\\S\|[\\w])* | Strings with many repetitions of '!' can start matching anywhere after the start of the preceeding ((\\S\|[\\w])*)" | diff --git a/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/ReDoS.expected b/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/ReDoS.expected index 674a0dcc29e..662dadaaa5b 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/ReDoS.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/ReDoS.expected @@ -123,9 +123,9 @@ | tst.js:137:15:137:21 | (\\w\|G)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'G'. | | tst.js:143:15:143:22 | (\\d\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | tst.js:146:15:146:21 | (\\d\|5)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '5'. | -| tst.js:149:15:149:24 | (\\s\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. | -| tst.js:152:15:152:28 | (\\s\|[\\v]\|\\\\v)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000b'. | -| tst.js:155:15:155:24 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. | +| tst.js:149:15:149:24 | (\\s\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000c'. | +| tst.js:152:15:152:28 | (\\s\|[\\v]\|\\\\v)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000b'. | +| tst.js:155:15:155:24 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000c'. | | tst.js:158:15:158:22 | (\\W\|\\D)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '/'. | | tst.js:161:15:161:22 | (\\S\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | tst.js:164:15:164:24 | (\\S\|[\\w])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | @@ -199,3 +199,5 @@ | tst.js:404:6:405:7 | (g\|gg)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'gg'. | | tst.js:407:125:407:127 | \\s* | This part of the regular expression may cause exponential backtracking on strings starting with '0/*' and containing many repetitions of ' ;0'. | | tst.js:411:15:411:19 | a{1,} | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.js:413:25:413:35 | (\\u0000\|.)+ | This part of the regular expression may cause exponential backtracking on strings starting with '\\n\\u0000' and containing many repetitions of '\\u0000'. | +| tst.js:415:44:415:57 | (\ud83d\ude80\|.)+ | This part of the regular expression may cause exponential backtracking on strings starting with '\\n\\u{1f680}' and containing many repetitions of '\\u{1f680}'. | diff --git a/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/tst.js b/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/tst.js index 19fdd42dcad..ef82076e702 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/tst.js +++ b/javascript/ql/test/query-tests/Security/CWE-400/ReDoS/tst.js @@ -408,4 +408,8 @@ var bad98 = /^(?:\*\/\*|[a-zA-Z0-9][a-zA-Z0-9!\#\$&\-\^_\.\+]{0,126}\/(?:\*|[a-z var good48 = /(\/(?:\/[\w.-]*)*){0,1}:([\w.-]+)/; -var bad99 = /(a{1,})*b/; \ No newline at end of file +var bad99 = /(a{1,})*b/; + +var unicode = /^\n\u0000(\u0000|.)+$/; + +var largeUnicode = new RegExp("^\n\u{1F680}(\u{1F680}|.)+X$"); \ No newline at end of file diff --git a/misc/bazel/cmake/cmake.bzl b/misc/bazel/cmake/cmake.bzl index 653725e5641..38bfe4305ea 100644 --- a/misc/bazel/cmake/cmake.bzl +++ b/misc/bazel/cmake/cmake.bzl @@ -51,6 +51,12 @@ def _get_includes(includes): def _cmake_aspect_impl(target, ctx): if not ctx.rule.kind.startswith("cc_"): return [CmakeInfo(name = None, transitive_deps = depset())] + if ctx.rule.kind == "cc_binary_add_features": + dep = ctx.rule.attr.dep[0][CmakeInfo] + return [CmakeInfo( + name = None, + transitive_deps = depset([dep], transitive = [dep.transitive_deps]), + )] name = _cmake_name(ctx.label) @@ -146,7 +152,7 @@ def _cmake_aspect_impl(target, ctx): cmake_aspect = aspect( implementation = _cmake_aspect_impl, - attr_aspects = ["deps"], + attr_aspects = ["deps", "dep"], fragments = ["cpp"], ) @@ -156,10 +162,10 @@ def _map_cmake_info(info, is_windows): "add_%s(%s)" % (info.kind, args), ] if info.imported_libs: - commands += [ + commands.append( "target_link_libraries(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.imported_libs)), - ] + ) if info.deps: libs = {} if info.modifier == "INTERFACE": @@ -168,46 +174,46 @@ def _map_cmake_info(info, is_windows): for lib in info.deps: libs.setdefault(lib.modifier, []).append(lib.name) for modifier, names in libs.items(): - commands += [ + commands.append( "target_link_libraries(%s %s %s)" % (info.name, modifier or "PUBLIC", " ".join(names)), - ] + ) if info.includes: - commands += [ + commands.append( "target_include_directories(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.includes)), - ] + ) if info.system_includes: - commands += [ + commands.append( "target_include_directories(%s SYSTEM %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.system_includes)), - ] + ) if info.quote_includes: if is_windows: - commands += [ + commands.append( "target_include_directories(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.quote_includes)), - ] + ) else: - commands += [ + commands.append( "target_compile_options(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(["-iquote%s" % i for i in info.quote_includes])), - ] + ) if info.copts and info.modifier != "INTERFACE": - commands += [ + commands.append( "target_compile_options(%s PRIVATE %s)" % (info.name, " ".join(info.copts)), - ] + ) if info.linkopts: - commands += [ + commands.append( "target_link_options(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.linkopts)), - ] + ) if info.force_cxx_compilation and any([f.endswith(".c") for f in info.srcs]): - commands += [ + commands.append( "set_source_files_properties(%s PROPERTIES LANGUAGE CXX)" % " ".join([f for f in info.srcs if f.endswith(".c")]), - ] + ) if info.defines: - commands += [ + commands.append( "target_compile_definitions(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.defines)), - ] + ) if info.local_defines: - commands += [ + commands.append( "target_compile_definitions(%s %s %s)" % (info.name, info.modifier or "PRIVATE", " ".join(info.local_defines)), - ] + ) return commands GeneratedCmakeFiles = provider( diff --git a/misc/scripts/accept-expected-changes-from-ci.py b/misc/scripts/accept-expected-changes-from-ci.py index 12c1bb21763..47a6bec76ab 100755 --- a/misc/scripts/accept-expected-changes-from-ci.py +++ b/misc/scripts/accept-expected-changes-from-ci.py @@ -336,7 +336,10 @@ def main(pr_number: Optional[int], sha_override: Optional[str] = None, force=Fal check_runs = json.loads(subprocess.check_output(["gh", "api", "--paginate", check_failure_url]).decode("utf-8")) for check_run in check_runs["check_runs"]: if check_run["conclusion"] == "failure": - m = re.fullmatch(r"^https://github\.com/([^/]+/[^/]+)/actions/runs/(\d+)(?:/jobs/(\d+))?$", check_run["details_url"]) + m = re.fullmatch(r"^https://github\.com/([^/]+/[^/]+)/actions/runs/(\d+)(?:/job/(\d+))?$", check_run["details_url"]) + if not m: + LOGGER.error(f"Could not parse details URL for {check_run['name']}: '{check_run['details_url']}'") + continue nwo = m.group(1) run_id = m.group(2) jobs_url = f"https://api.github.com/repos/{nwo}/actions/runs/{run_id}/jobs" diff --git a/misc/suite-helpers/CHANGELOG.md b/misc/suite-helpers/CHANGELOG.md index d1c4b5782a9..052852d7899 100644 --- a/misc/suite-helpers/CHANGELOG.md +++ b/misc/suite-helpers/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.2 + +No user-facing changes. + ## 0.6.1 No user-facing changes. diff --git a/misc/suite-helpers/change-notes/released/0.6.2.md b/misc/suite-helpers/change-notes/released/0.6.2.md new file mode 100644 index 00000000000..43f80640fc5 --- /dev/null +++ b/misc/suite-helpers/change-notes/released/0.6.2.md @@ -0,0 +1,3 @@ +## 0.6.2 + +No user-facing changes. diff --git a/misc/suite-helpers/codeql-pack.release.yml b/misc/suite-helpers/codeql-pack.release.yml index 80fb0899f64..5501a2a1cc5 100644 --- a/misc/suite-helpers/codeql-pack.release.yml +++ b/misc/suite-helpers/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.6.1 +lastReleaseVersion: 0.6.2 diff --git a/misc/suite-helpers/qlpack.yml b/misc/suite-helpers/qlpack.yml index c59cb02091c..34aba77ae75 100644 --- a/misc/suite-helpers/qlpack.yml +++ b/misc/suite-helpers/qlpack.yml @@ -1,4 +1,4 @@ name: codeql/suite-helpers -version: 0.6.2-dev +version: 0.6.3-dev groups: shared warnOnImplicitThis: true diff --git a/python/ql/lib/CHANGELOG.md b/python/ql/lib/CHANGELOG.md index 6c74cee16c1..09e3fb6aa19 100644 --- a/python/ql/lib/CHANGELOG.md +++ b/python/ql/lib/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.2 + +No user-facing changes. + ## 0.10.1 ### New Features diff --git a/python/ql/lib/analysis/DefinitionTracking.qll b/python/ql/lib/analysis/DefinitionTracking.qll index ddc373236f7..6cf9e118681 100644 --- a/python/ql/lib/analysis/DefinitionTracking.qll +++ b/python/ql/lib/analysis/DefinitionTracking.qll @@ -492,9 +492,14 @@ class NiceLocationExpr extends Expr { // for `import xxx` or for `import xxx as yyy`. this.(ImportExpr).getLocation().hasLocationInfo(f, bl, bc, el, ec) or - /* Show y for `y` in `from xxx import y` */ - exists(string name | - name = this.(ImportMember).getName() and + // Show y for `y` in `from xxx import y` + // and y for `yyy as y` in `from xxx import yyy as y`. + exists(string name, Alias alias | + // This alias will always exist, as `from xxx import y` + // is expanded to `from xxx imprt y as y`. + this = alias.getValue() and + name = alias.getAsname().(Name).getId() + | this.(ImportMember).getLocation().hasLocationInfo(f, _, _, el, ec) and bl = el and bc = ec - name.length() + 1 diff --git a/python/ql/lib/change-notes/2023-07-12-aiohttp-improvements.md b/python/ql/lib/change-notes/2023-07-12-aiohttp-improvements.md new file mode 100644 index 00000000000..5805ee1baa9 --- /dev/null +++ b/python/ql/lib/change-notes/2023-07-12-aiohttp-improvements.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improvements of the `aiohttp` models including remote-flow-sources from type annotations, new path manipulation, and SSRF sinks. diff --git a/python/ql/lib/change-notes/2023-08-07-serverless-sources.md b/python/ql/lib/change-notes/2023-08-07-serverless-sources.md new file mode 100644 index 00000000000..c184b2b7a3d --- /dev/null +++ b/python/ql/lib/change-notes/2023-08-07-serverless-sources.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added modeling of AWS Lambda handlers that can be identified with `AWS::Serverless::Function` in YAML files, where the event parameter is modeled as a remote-flow-source. diff --git a/python/ql/lib/change-notes/2023-08-08-relax-module-resolution.md b/python/ql/lib/change-notes/2023-08-08-relax-module-resolution.md new file mode 100644 index 00000000000..ad35d25d2ab --- /dev/null +++ b/python/ql/lib/change-notes/2023-08-08-relax-module-resolution.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Support analyzing packages (folders with python code) that do not have `__init__.py` files, although this is technically required, we see real world projects that don't have this. diff --git a/python/ql/lib/change-notes/2023-08-10-fix-alias-locations.md b/python/ql/lib/change-notes/2023-08-10-fix-alias-locations.md new file mode 100644 index 00000000000..44835fd3b9f --- /dev/null +++ b/python/ql/lib/change-notes/2023-08-10-fix-alias-locations.md @@ -0,0 +1,4 @@ +--- +category: fix +--- +* Fixed the computation of locations for imports with aliases in jump-to-definition. diff --git a/python/ql/lib/change-notes/released/0.10.2.md b/python/ql/lib/change-notes/released/0.10.2.md new file mode 100644 index 00000000000..7d66e65b13a --- /dev/null +++ b/python/ql/lib/change-notes/released/0.10.2.md @@ -0,0 +1,3 @@ +## 0.10.2 + +No user-facing changes. diff --git a/python/ql/lib/codeql-pack.release.yml b/python/ql/lib/codeql-pack.release.yml index af7510b3cd6..f0e1af76699 100644 --- a/python/ql/lib/codeql-pack.release.yml +++ b/python/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.10.1 +lastReleaseVersion: 0.10.2 diff --git a/python/ql/lib/qlpack.yml b/python/ql/lib/qlpack.yml index 8d8f85f53ac..6e571fcb203 100644 --- a/python/ql/lib/qlpack.yml +++ b/python/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/python-all -version: 0.10.2-dev +version: 0.10.3-dev groups: python dbscheme: semmlecode.python.dbscheme extractor: python diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index c0d3c5e0b23..82cb69679cb 100644 --- a/python/ql/lib/semmle/python/Frameworks.qll +++ b/python/ql/lib/semmle/python/Frameworks.qll @@ -49,6 +49,7 @@ private import semmle.python.frameworks.Requests private import semmle.python.frameworks.RestFramework private import semmle.python.frameworks.Rsa private import semmle.python.frameworks.RuamelYaml +private import semmle.python.frameworks.ServerLess private import semmle.python.frameworks.Simplejson private import semmle.python.frameworks.SqlAlchemy private import semmle.python.frameworks.Starlette diff --git a/python/ql/lib/semmle/python/Module.qll b/python/ql/lib/semmle/python/Module.qll index 87116a1d98b..56376155108 100644 --- a/python/ql/lib/semmle/python/Module.qll +++ b/python/ql/lib/semmle/python/Module.qll @@ -195,7 +195,22 @@ private predicate isPotentialPackage(Folder f) { } private string moduleNameFromBase(Container file) { - isPotentialPackage(file) and result = file.getBaseName() + // We used to also require `isPotentialPackage(f)` to hold in this case, + // but we saw modules not getting resolved because their folder did not + // contain an `__init__.py` file. + // + // This makes the folder not be a package but a namespace package instead. + // In most cases this is a mistake :| See following links for more details + // - https://dev.to/methane/don-t-omit-init-py-3hga + // - https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ + // - https://discuss.python.org/t/init-py-pep-420-and-iter-modules-confusion/9642 + // + // It is possible that we can keep the original requirement on + // `isPotentialPackage(f)` here, but relax `isPotentialPackage` itself to allow + // for this behavior of missing `__init__.py` files. However, doing so involves + // cascading changes (for example to `moduleNameFromFile`), and was a more involved + // task than we wanted to take on. + result = file.getBaseName() or file instanceof File and result = file.getStem() } diff --git a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll index 54decc5dcb8..546014c2dbf 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll @@ -468,6 +468,27 @@ module AiohttpWebModel { override string getSourceType() { result = "aiohttp.web.Request" } } + /** + * A parameter that has a type annotation of `aiohttp.web.Request`, so with all + * likelihood will receive an `aiohttp.web.Request` instance at some point when a + * request handler is invoked. + */ + class AiohttpRequestParamFromTypeAnnotation extends Request::InstanceSource, + DataFlow::ParameterNode, RemoteFlowSource::Range + { + AiohttpRequestParamFromTypeAnnotation() { + not this instanceof AiohttpRequestHandlerRequestParam and + this.getParameter().getAnnotation() = + API::moduleImport("aiohttp") + .getMember("web") + .getMember("Request") + .getAValueReachableFromSource() + .asExpr() + } + + override string getSourceType() { result = "aiohttp.web.Request from type-annotation" } + } + /** * A read of the `request` attribute on an instance of an aiohttp.web View class, * which is the request being processed currently. @@ -498,14 +519,17 @@ module AiohttpWebModel { * - https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions */ class AiohttpWebResponseInstantiation extends Http::Server::HttpResponse::Range, - Response::InstanceSource, DataFlow::CallCfgNode + Response::InstanceSource, API::CallNode { API::Node apiNode; AiohttpWebResponseInstantiation() { this = apiNode.getACall() and ( - apiNode = API::moduleImport("aiohttp").getMember("web").getMember("Response") + apiNode = + API::moduleImport("aiohttp") + .getMember("web") + .getMember(["FileResponse", "Response", "StreamResponse"]) or exists(string httpExceptionClassName | httpExceptionClassName in [ @@ -545,6 +569,10 @@ module AiohttpWebModel { override DataFlow::Node getMimetypeOrContentTypeArg() { result = this.getArgByName("content_type") + or + exists(string key | key.toLowerCase() = "content-type" | + result = this.getKeywordParameter("headers").getSubscript(key).getAValueReachingSink() + ) } override string getMimetypeDefault() { @@ -556,6 +584,37 @@ module AiohttpWebModel { } } + /** + * A call to the `aiohttp.web.FileResponse` constructor as a sink for Filesystem access. + */ + class FileResponseCall extends FileSystemAccess::Range, API::CallNode { + FileResponseCall() { + this = API::moduleImport("aiohttp").getMember("web").getMember("FileResponse").getACall() + } + + override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "path").asSink() } + } + + /** + * An instantiation of `aiohttp.web.StreamResponse`. + * + * See https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.StreamResponse + */ + class StreamResponse extends AiohttpWebResponseInstantiation { + StreamResponse() { + this = API::moduleImport("aiohttp").getMember("web").getMember("StreamResponse").getACall() + } + + override DataFlow::Node getBody() { + result = + this.getReturn() + .getMember(["write", "write_eof"]) + .getACall() + .getParameter(0, "data") + .asSink() + } + } + /** Gets an HTTP response instance. */ private API::Node aiohttpResponseInstance() { result = any(AiohttpWebResponseInstantiation call).getApiNode().getReturn() @@ -670,14 +729,14 @@ private module AiohttpClientModel { string methodName; OutgoingRequestCall() { - methodName in [Http::httpVerbLower(), "request"] and + methodName in [Http::httpVerbLower(), "request", "ws_connect"] and this = instance().getMember(methodName).getACall() } override DataFlow::Node getAUrlPart() { result = this.getArgByName("url") or - not methodName = "request" and + methodName in [Http::httpVerbLower(), "ws_connect"] and result = this.getArg(0) or methodName = "request" and diff --git a/python/ql/lib/semmle/python/frameworks/ServerLess.qll b/python/ql/lib/semmle/python/frameworks/ServerLess.qll new file mode 100644 index 00000000000..a7d2508b32b --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/ServerLess.qll @@ -0,0 +1,67 @@ +/** + * Provides classes and predicates for working with those serverless handlers, + * handled by the shared library. + * + * E.g. [AWS](https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html). + * + * In particular a `RemoteFlowSource` is added for each. + */ + +import python +import codeql.serverless.ServerLess +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.RemoteFlowSources + +private module YamlImpl implements Input { + import semmle.python.Files + import semmle.python.Yaml +} + +module SL = ServerLess; + +/** + * Gets a function that is a serverless request handler. + * + * For example: if an AWS serverless resource contains the following properties (in the "template.yml" file): + * ```yaml + * Handler: mylibrary.handler + * Runtime: pythonXXX + * CodeUri: backend/src/ + * ``` + * + * And a file "mylibrary.py" exists in the folder "backend/src" (relative to the "template.yml" file). + * Then the result of this predicate is a function exported as "handler" from "mylibrary.py". + * The "mylibrary.py" file could for example look like: + * + * ```python + * def handler(event): + * ... + * ``` + */ +private Function getAServerlessHandler() { + exists(File file, string stem, string handler, string runtime, Module mod | + SL::hasServerlessHandler(stem, handler, _, runtime) and + file.getAbsolutePath() = stem + ".py" and + // if runtime is specified, it should be python + (runtime = "" or runtime.matches("python%")) + | + mod.getFile() = file and + result.getScope() = mod and + result.getName() = handler + ) +} + +private DataFlow::ParameterNode getAHandlerEventParameter() { + exists(Function func | func = getAServerlessHandler() | + result.getParameter() in [func.getArg(0), func.getArgByName("event")] + ) +} + +/** + * A serverless request handler event, seen as a RemoteFlowSource. + */ +private class ServerlessHandlerEventAsRemoteFlow extends RemoteFlowSource::Range { + ServerlessHandlerEventAsRemoteFlow() { this = getAHandlerEventParameter() } + + override string getSourceType() { result = "Serverless event" } +} diff --git a/python/ql/src/CHANGELOG.md b/python/ql/src/CHANGELOG.md index 0d2fc2b6968..e09c8527f53 100644 --- a/python/ql/src/CHANGELOG.md +++ b/python/ql/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.2 + +No user-facing changes. + ## 0.8.1 ### Minor Analysis Improvements diff --git a/python/ql/src/change-notes/released/0.8.2.md b/python/ql/src/change-notes/released/0.8.2.md new file mode 100644 index 00000000000..11c1f6119a5 --- /dev/null +++ b/python/ql/src/change-notes/released/0.8.2.md @@ -0,0 +1,3 @@ +## 0.8.2 + +No user-facing changes. diff --git a/python/ql/src/codeql-pack.release.yml b/python/ql/src/codeql-pack.release.yml index 2f693f95ba6..404110129dc 100644 --- a/python/ql/src/codeql-pack.release.yml +++ b/python/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.8.1 +lastReleaseVersion: 0.8.2 diff --git a/python/ql/src/qlpack.yml b/python/ql/src/qlpack.yml index f54d0ebb998..3ba36f49464 100644 --- a/python/ql/src/qlpack.yml +++ b/python/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/python-queries -version: 0.8.2-dev +version: 0.8.3-dev groups: - python - queries diff --git a/python/ql/test/experimental/library-tests/CallGraph-implicit-init/example.py b/python/ql/test/experimental/library-tests/CallGraph-implicit-init/example.py index f14669948bc..12d3a013e8d 100644 --- a/python/ql/test/experimental/library-tests/CallGraph-implicit-init/example.py +++ b/python/ql/test/experimental/library-tests/CallGraph-implicit-init/example.py @@ -17,6 +17,6 @@ Since PEP 420 was accepted in Python 3, this test is Python 3 only. from foo.bar.a import afunc from foo_explicit.bar.a import explicit_afunc -afunc() # $ MISSING: pt,tt=afunc +afunc() # $ pt,tt="foo/bar/a.py:afunc" explicit_afunc() # $ pt,tt="foo_explicit/bar/a.py:explicit_afunc" diff --git a/python/ql/test/experimental/meta/ConceptsTest.qll b/python/ql/test/experimental/meta/ConceptsTest.qll index 13dac722c3b..255923d4e7f 100644 --- a/python/ql/test/experimental/meta/ConceptsTest.qll +++ b/python/ql/test/experimental/meta/ConceptsTest.qll @@ -292,10 +292,11 @@ module HttpServerHttpResponseTest implements TestSig { exists(DedicatedResponseTest d | d.isDedicatedFile(file)) ) and ( - exists(Http::Server::HttpResponse response | - location = response.getLocation() and - element = response.toString() and - value = prettyNodeForInlineTest(response.getBody()) and + exists(Http::Server::HttpResponse response, DataFlow::Node body | + body = response.getBody() and + location = body.getLocation() and + element = body.toString() and + value = prettyNodeForInlineTest(body) and tag = "responseBody" ) or diff --git a/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql index 8524da5fe7d..caaa22ef194 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql +++ b/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql @@ -1,2 +1,19 @@ import experimental.meta.InlineTaintTest -import MakeInlineTaintTest + +predicate isSafe(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { + g.(CallNode).getFunction().(NameNode).getId() = "is_safe" and + node = g.(CallNode).getArg(_) and + branch = true +} + +module CustomSanitizerOverridesConfig implements DataFlow::ConfigSig { + predicate isSource = TestTaintTrackingConfig::isSource/1; + + predicate isSink = TestTaintTrackingConfig::isSink/1; + + predicate isBarrier(DataFlow::Node node) { + node = DataFlow::BarrierGuard::getABarrierNode() + } +} + +import MakeInlineTaintTest diff --git a/python/ql/test/library-tests/frameworks/aiohttp/client_request.py b/python/ql/test/library-tests/frameworks/aiohttp/client_request.py index 1bafb4ef583..d7958cf660f 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/client_request.py +++ b/python/ql/test/library-tests/frameworks/aiohttp/client_request.py @@ -33,3 +33,5 @@ async def test(): assert context.verify_mode == ssl.VerifyMode.CERT_NONE s.get("url", ssl=context) # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled + + s.ws_connect("url") # $ clientRequestUrlPart="url" diff --git a/python/ql/test/library-tests/frameworks/aiohttp/response_test.py b/python/ql/test/library-tests/frameworks/aiohttp/response_test.py index e800c28234a..bc9bc8d3bda 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/response_test.py +++ b/python/ql/test/library-tests/frameworks/aiohttp/response_test.py @@ -23,6 +23,9 @@ async def html_text(request): # $ requestHandler async def html_body(request): # $ requestHandler return web.Response(body=b"foo", content_type="text/html") # $ HttpResponse mimetype=text/html responseBody=b"foo" +@routes.get("/html_body_header") # $ routeSetup="/html_body_header" +async def html_body_header(request): # $ requestHandler + return web.Response(headers={"content-type": "text/html"}, text="foo") # $ HttpResponse mimetype=text/html responseBody="foo" @routes.get("/html_body_set_later") # $ routeSetup="/html_body_set_later" async def html_body_set_later(request): # $ requestHandler @@ -65,6 +68,26 @@ async def redirect_302(request): # $ requestHandler else: raise web.HTTPFound(location="/logout") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/logout" + +@routes.get("/file_response") # $ routeSetup="/file_response" +async def file_response(request): # $ requestHandler + filename = "foo.txt" + resp = web.FileResponse(filename) # $ HttpResponse mimetype=application/octet-stream getAPathArgument=filename + resp = web.FileResponse(path=filename) # $ HttpResponse mimetype=application/octet-stream getAPathArgument=filename + return resp + + +@routes.get("/streaming_response") # $ routeSetup="/streaming_response" +async def streaming_response(request): # $ requestHandler + resp = web.StreamResponse() # $ HttpResponse mimetype=application/octet-stream + await resp.prepare(request) + + await resp.write(b"foo") # $ responseBody=b"foo" + await resp.write(data=b"bar") # $ responseBody=b"bar" + await resp.write_eof(b"baz") # $ responseBody=b"baz" + + return resp + ################################################################################ # Cookies ################################################################################ diff --git a/python/ql/test/library-tests/frameworks/aiohttp/taint_test.py b/python/ql/test/library-tests/frameworks/aiohttp/taint_test.py index 54da5726803..fb65a17554d 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/taint_test.py +++ b/python/ql/test/library-tests/frameworks/aiohttp/taint_test.py @@ -142,10 +142,36 @@ class TaintTestClass(web.View): self.request.url # $ tainted ) +# not a request handler, and not called, but since we have type-annotation, should be a +# remote-flow-source. +async def test_source_from_type_annotation(request: web.Request): + # picking out just a few of the tests from `test_taint` above, to show that we have + # the same taint-steps :) + ensure_tainted( + request, # $ tainted + request.url, # $ tainted + await request.content.read(), # $ tainted + ) + +# Test that since we can reach the `request` object in the helper function, we don't +# introduce a new remote-flow-source, but instead use the one from the caller. (which is +# checked to not be tainted) +async def test_sanitizer(request): # $ requestHandler + ensure_tainted(request, request.url, await request.content.read()) # $ tainted + + if (is_safe(request)): + ensure_not_tainted(request, request.url, await request.content.read()) + test_safe_helper_function_no_route_with_type(request) + + +async def test_safe_helper_function_no_route_with_type(request: web.Request): + ensure_not_tainted(request, request.url, await request.content.read()) # $ SPURIOUS: tainted + app = web.Application() app.router.add_get(r"/test_taint/{name}/{number:\d+}", test_taint) # $ routeSetup="/test_taint/{name}/{number:\d+}" app.router.add_view(r"/test_taint_class", TaintTestClass) # $ routeSetup="/test_taint_class" +app.router.add_view(r"/test_sanitizer", test_sanitizer) # $ routeSetup="/test_sanitizer" if __name__ == "__main__": diff --git a/python/ql/test/library-tests/frameworks/serverless/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/serverless/InlineTaintTest.expected new file mode 100644 index 00000000000..4a72c551661 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/serverless/InlineTaintTest.expected @@ -0,0 +1,4 @@ +failures +argumentToEnsureNotTaintedNotMarkedAsSpurious +untaintedArgumentToEnsureTaintedNotMarkedAsMissing +testFailures diff --git a/python/ql/test/library-tests/frameworks/serverless/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/serverless/InlineTaintTest.ql new file mode 100644 index 00000000000..8524da5fe7d --- /dev/null +++ b/python/ql/test/library-tests/frameworks/serverless/InlineTaintTest.ql @@ -0,0 +1,2 @@ +import experimental.meta.InlineTaintTest +import MakeInlineTaintTest diff --git a/python/ql/test/library-tests/frameworks/serverless/aws_lambda/function/extra_lambdas.py b/python/ql/test/library-tests/frameworks/serverless/aws_lambda/function/extra_lambdas.py new file mode 100644 index 00000000000..610a25770d8 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/serverless/aws_lambda/function/extra_lambdas.py @@ -0,0 +1,13 @@ +def handler1(event, context): + ensure_tainted(event) # $ tainted + return "Hello World!" + +def handler2(event, context): + ensure_tainted(event) # $ tainted + return "Hello World!" + +# This function is not mentioned in template.yml +# and so it is not receiving user input. +def non_handler(event, context): + ensure_not_tainted(event) + return "Hello World!" diff --git a/python/ql/test/library-tests/frameworks/serverless/aws_lambda/function/lambda_function.py b/python/ql/test/library-tests/frameworks/serverless/aws_lambda/function/lambda_function.py new file mode 100644 index 00000000000..3a482996bf3 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/serverless/aws_lambda/function/lambda_function.py @@ -0,0 +1,11 @@ +def lambda_handler(event, context): + ensure_tainted( + event, # $ tainted + # event is usually a dict, see https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html + event["key"], # $ tainted + event["key"]["key2"], # $ tainted + event["key"][0], # $ tainted + # but can also be a list + event[0], # $ tainted + ) + return "OK" diff --git a/python/ql/test/library-tests/frameworks/serverless/aws_lambda/template.yml b/python/ql/test/library-tests/frameworks/serverless/aws_lambda/template.yml new file mode 100644 index 00000000000..532514fa49e --- /dev/null +++ b/python/ql/test/library-tests/frameworks/serverless/aws_lambda/template.yml @@ -0,0 +1,62 @@ +# inspired by https://github.com/awsdocs/aws-lambda-developer-guide/blob/main/sample-apps/blank-python/template.yml +# but we have added extra handlers +AWSTemplateFormatVersion: '2010-09-09' +Transform: 'AWS::Serverless-2016-10-31' +Description: An AWS Lambda application that calls the Lambda API. +Resources: + function: + Type: AWS::Serverless::Function + Properties: + Handler: lambda_function.lambda_handler + Runtime: python3.8 + CodeUri: function/. + Description: Call the AWS Lambda API + Timeout: 10 + # Function's execution role + Policies: + - AWSLambdaBasicExecutionRole + - AWSLambda_ReadOnlyAccess + - AWSXrayWriteOnlyAccess + Tracing: Active + Layers: + - !Ref libs + function: + Type: AWS::Serverless::Function + Properties: + Handler: extra_lambdas.handler1 + Runtime: python3.8 + CodeUri: function/. + Description: Call the AWS Lambda API + Timeout: 10 + # Function's execution role + Policies: + - AWSLambdaBasicExecutionRole + - AWSLambda_ReadOnlyAccess + - AWSXrayWriteOnlyAccess + Tracing: Active + Layers: + - !Ref libs + function: + Type: AWS::Serverless::Function + Properties: + Handler: extra_lambdas.handler2 + Runtime: python3.8 + CodeUri: function/. + Description: Call the AWS Lambda API + Timeout: 10 + # Function's execution role + Policies: + - AWSLambdaBasicExecutionRole + - AWSLambda_ReadOnlyAccess + - AWSXrayWriteOnlyAccess + Tracing: Active + Layers: + - !Ref libs + libs: + Type: AWS::Serverless::LayerVersion + Properties: + LayerName: blank-python-lib + Description: Dependencies for the blank-python sample app. + ContentUri: package/. + CompatibleRuntimes: + - python3.8 diff --git a/python/ql/test/library-tests/frameworks/serverless/options b/python/ql/test/library-tests/frameworks/serverless/options new file mode 100644 index 00000000000..3819071b01c --- /dev/null +++ b/python/ql/test/library-tests/frameworks/serverless/options @@ -0,0 +1 @@ +semmle-extractor-options: -R . diff --git a/python/ql/test/library-tests/locations/imports/import_statements.py b/python/ql/test/library-tests/locations/imports/import_statements.py new file mode 100644 index 00000000000..488f1f4e2be --- /dev/null +++ b/python/ql/test/library-tests/locations/imports/import_statements.py @@ -0,0 +1,4 @@ +from nova.api.openstack.placement import microversion +from nova.api.openstack.placement.objects import resource_provider as rp_obj +from nova.api.openstack.placement.policies import allocation_candidate as \ + policies diff --git a/python/ql/test/library-tests/locations/imports/imports.expected b/python/ql/test/library-tests/locations/imports/imports.expected new file mode 100644 index 00000000000..8d2726f748e --- /dev/null +++ b/python/ql/test/library-tests/locations/imports/imports.expected @@ -0,0 +1,6 @@ +| import_statements.py | 1 | 6 | 1 | 33 | +| import_statements.py | 1 | 42 | 1 | 53 | +| import_statements.py | 2 | 6 | 2 | 41 | +| import_statements.py | 2 | 71 | 2 | 76 | +| import_statements.py | 3 | 6 | 3 | 42 | +| import_statements.py | 4 | 5 | 4 | 12 | diff --git a/python/ql/test/library-tests/locations/imports/imports.ql b/python/ql/test/library-tests/locations/imports/imports.ql new file mode 100644 index 00000000000..f19c52e8d0a --- /dev/null +++ b/python/ql/test/library-tests/locations/imports/imports.ql @@ -0,0 +1,6 @@ +import python +import analysis.DefinitionTracking + +from NiceLocationExpr expr, string f, int bl, int bc, int el, int ec +where expr.hasLocationInfo(f, bl, bc, el, ec) +select f, bl, bc, el, ec diff --git a/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected b/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected index 9a60667fd53..e6768bfaea0 100644 --- a/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected +++ b/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected @@ -35,9 +35,9 @@ | redos.py:139:25:139:31 | (\\w\|G)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'G'. | | redos.py:145:25:145:32 | (\\d\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | redos.py:148:25:148:31 | (\\d\|5)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '5'. | -| redos.py:151:25:151:34 | (\\s\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. | -| redos.py:154:25:154:38 | (\\s\|[\\v]\|\\\\v)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000b'. | -| redos.py:157:25:157:34 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. | +| redos.py:151:25:151:34 | (\\s\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000c'. | +| redos.py:154:25:154:38 | (\\s\|[\\v]\|\\\\v)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000b'. | +| redos.py:157:25:157:34 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000c'. | | redos.py:160:25:160:32 | (\\W\|\\D)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' '. | | redos.py:163:25:163:32 | (\\S\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | redos.py:166:25:166:34 | (\\S\|[\\w])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | @@ -67,8 +67,8 @@ | redos.py:259:24:259:126 | (.thisisagoddamnlongstringforstresstestingthequery\|\\sthisisagoddamnlongstringforstresstestingthequery)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\tthisisagoddamnlongstringforstresstestingthequery'. | | redos.py:262:24:262:87 | (thisisagoddamnlongstringforstresstestingthequery\|this\\w+query)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'thisisagoddamnlongstringforstresstestingthequery'. | | redos.py:262:78:262:80 | \\w+ | This part of the regular expression may cause exponential backtracking on strings starting with 'this' and containing many repetitions of '0querythis'. | -| redos.py:268:28:268:39 | ([\ufffd\ufffd]\|[\ufffd\ufffd])* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\ufffd'. | -| redos.py:271:28:271:41 | ((\ufffd\|\ufffd)\|(\ufffd\|\ufffd))* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\ufffd'. | +| redos.py:268:28:268:39 | ([\ufffd\ufffd]\|[\ufffd\ufffd])* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\\ufffd'. | +| redos.py:271:28:271:41 | ((\ufffd\|\ufffd)\|(\ufffd\|\ufffd))* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\\ufffd'. | | redos.py:274:31:274:32 | b+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'b'. | | redos.py:277:48:277:50 | \\s* | This part of the regular expression may cause exponential backtracking on strings starting with '<0\\t0=' and containing many repetitions of '""\\t0='. | | redos.py:283:26:283:27 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | @@ -103,5 +103,5 @@ | redos.py:385:24:385:30 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | redos.py:386:26:386:32 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | redos.py:391:15:391:25 | (\\u0061\|a)* | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of 'a'. | -| unittests.py:5:17:5:23 | (\u00c6\|\\\u00c6)+ | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of '\u00c6'. | +| unittests.py:5:17:5:23 | (\u00c6\|\\\u00c6)+ | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of '\\u00c6'. | | unittests.py:9:16:9:24 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | diff --git a/ruby/ql/lib/CHANGELOG.md b/ruby/ql/lib/CHANGELOG.md index 47221ac14e3..0a5357f8df1 100644 --- a/ruby/ql/lib/CHANGELOG.md +++ b/ruby/ql/lib/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.2 + +No user-facing changes. + ## 0.7.1 ### New Features diff --git a/ruby/ql/lib/change-notes/2023-08-08-splat-arguments.md b/ruby/ql/lib/change-notes/2023-08-08-splat-arguments.md new file mode 100644 index 00000000000..54786c50cfe --- /dev/null +++ b/ruby/ql/lib/change-notes/2023-08-08-splat-arguments.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Flow between splat arguments (`*args`) and positional parameters is now tracked more precisely. diff --git a/ruby/ql/lib/change-notes/released/0.7.2.md b/ruby/ql/lib/change-notes/released/0.7.2.md new file mode 100644 index 00000000000..8693d609ec7 --- /dev/null +++ b/ruby/ql/lib/change-notes/released/0.7.2.md @@ -0,0 +1,3 @@ +## 0.7.2 + +No user-facing changes. diff --git a/ruby/ql/lib/codeql-pack.release.yml b/ruby/ql/lib/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/ruby/ql/lib/codeql-pack.release.yml +++ b/ruby/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/ruby/ql/lib/codeql/ruby/ast/Operation.qll b/ruby/ql/lib/codeql/ruby/ast/Operation.qll index 1efab2b4515..e5c68b72c8f 100644 --- a/ruby/ql/lib/codeql/ruby/ast/Operation.qll +++ b/ruby/ql/lib/codeql/ruby/ast/Operation.qll @@ -48,7 +48,7 @@ class UnaryLogicalOperation extends UnaryOperation, TUnaryLogicalOperation { } * not params.empty? * ``` */ -class NotExpr extends UnaryLogicalOperation, TNotExpr { +class NotExpr extends UnaryLogicalOperation instanceof NotExprImpl { final override string getAPrimaryQlClass() { result = "NotExpr" } } @@ -118,7 +118,7 @@ class ComplementExpr extends UnaryBitwiseOperation, TComplementExpr { * defined? some_method * ``` */ -class DefinedExpr extends UnaryOperation, TDefinedExpr { +class DefinedExpr extends UnaryOperation instanceof DefinedExprImpl { final override string getAPrimaryQlClass() { result = "DefinedExpr" } } diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll b/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll index 4e2853e3a35..91f20773917 100644 --- a/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll +++ b/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll @@ -123,7 +123,8 @@ private module Cached { TConstantWriteAccessSynth(Ast::AstNode parent, int i, string value) { mkSynthChild(ConstantWriteAccessKind(value), parent, i) } or - TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or + TDefinedExprReal(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or + TDefinedExprSynth(Ast::AstNode parent, int i) { mkSynthChild(DefinedExprKind(), parent, i) } or TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) { not strictcount(int i | exists(g.getParent().(Ruby::LeftAssignmentList).getChild(i))) = 1 @@ -228,7 +229,8 @@ private module Cached { TNilLiteralReal(Ruby::Nil g) or TNilLiteralSynth(Ast::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or - TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or + TNotExprReal(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or + TNotExprSynth(Ast::AstNode parent, int i) { mkSynthChild(NotExprKind(), parent, i) } or TOptionalParameter(Ruby::OptionalParameter g) or TPair(Ruby::Pair g) or TParenthesizedExpr(Ruby::ParenthesizedStatements g) or @@ -354,21 +356,21 @@ private module Cached { TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or TBlockParameter or TBraceBlockReal or TBreakStmt or TCaseEqExpr or TCaseExpr or TCaseMatchReal or TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or - TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or - TDestructuredParameter or TDivExprReal or TDo or TDoBlock or TElementReference or - TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or TEnsure or TEqExpr or - TExponentExprReal or TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or - TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or - THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or - THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or - TIfReal or TIfModifierExpr or TInClauseReal or TInstanceVariableAccessReal or - TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or - TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or - TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TMatchPattern or - TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or - TNilLiteralReal or TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or - TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or - TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or + TComplexLiteral or TDefinedExprReal or TDelimitedSymbolLiteral or + TDestructuredLeftAssignment or TDestructuredParameter or TDivExprReal or TDo or TDoBlock or + TElementReference or TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or + TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFile or TFindPattern or + TFloatLiteral or TForExpr or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or + TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashPattern or + THashSplatExpr or THashSplatNilParameter or THashSplatParameter or THereDoc or + TIdentifierMethodCall or TIfReal or TIfModifierExpr or TInClauseReal or + TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or + TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLine or + TLocalVariableAccessReal or TLogicalAndExprReal or TLogicalOrExprReal or TMethod or + TMatchPattern or TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or + TNextStmt or TNilLiteralReal or TNoRegExpMatchExpr or TNotExprReal or TOptionalParameter or + TPair or TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or + TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or @@ -438,7 +440,7 @@ private module Cached { n = TClassVariableAccessReal(result, _) or n = TComplementExpr(result) or n = TComplexLiteral(result) or - n = TDefinedExpr(result) or + n = TDefinedExprReal(result) or n = TDelimitedSymbolLiteral(result) or n = TDestructuredLeftAssignment(result) or n = TDivExprReal(result) or @@ -495,7 +497,7 @@ private module Cached { n = TNextStmt(result) or n = TNilLiteralReal(result) or n = TNoRegExpMatchExpr(result) or - n = TNotExpr(result) or + n = TNotExprReal(result) or n = TOptionalParameter(result) or n = TPair(result) or n = TParenthesizedExpr(result) or @@ -585,6 +587,8 @@ private module Cached { or result = TConstantWriteAccessSynth(parent, i, _) or + result = TDefinedExprSynth(parent, i) + or result = TDivExprSynth(parent, i) or result = TElseSynth(parent, i) @@ -617,6 +621,8 @@ private module Cached { or result = TNilLiteralSynth(parent, i) or + result = TNotExprSynth(parent, i) + or result = TRangeLiteralSynth(parent, i, _) or result = TRShiftExprSynth(parent, i) @@ -789,10 +795,14 @@ class TNamespace = TClassDeclaration or TModuleDeclaration; class TOperation = TUnaryOperation or TBinaryOperation or TAssignment; +class TDefinedExpr = TDefinedExprReal or TDefinedExprSynth; + class TUnaryOperation = TUnaryLogicalOperation or TUnaryArithmeticOperation or TUnaryBitwiseOperation or TDefinedExpr or TSplatExpr or THashSplatExpr; +class TNotExpr = TNotExprReal or TNotExprSynth; + class TUnaryLogicalOperation = TNotExpr; class TUnaryArithmeticOperation = TUnaryPlusExpr or TUnaryMinusExpr; diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll index 3f08a8b7869..6fcf0bfc737 100644 --- a/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll @@ -35,6 +35,16 @@ class UnaryOperationGenerated extends UnaryOperationImpl { final override string getOperatorImpl() { result = g.getOperator() } } +abstract class NotExprImpl extends UnaryOperationImpl, TNotExpr { } + +class NotExprReal extends NotExprImpl, UnaryOperationGenerated, TNotExprReal { } + +class NotExprSynth extends NotExprImpl, TNotExprSynth { + final override string getOperatorImpl() { result = "!" } + + final override Expr getOperandImpl() { synthChild(this, 0, result) } +} + class SplatExprReal extends UnaryOperationImpl, TSplatExprReal { private Ruby::SplatArgument g; @@ -67,6 +77,16 @@ class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr { final override string getOperatorImpl() { result = "**" } } +abstract class DefinedExprImpl extends UnaryOperationImpl, TDefinedExpr { } + +class DefinedExprReal extends DefinedExprImpl, UnaryOperationGenerated, TDefinedExprReal { } + +class DefinedExprSynth extends DefinedExprImpl, TDefinedExprSynth { + final override string getOperatorImpl() { result = "defined?" } + + final override Expr getOperandImpl() { synthChild(this, 0, result) } +} + abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinaryOperation { abstract Stmt getLeftOperandImpl(); diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll index 47a119ab951..f33fb647bf3 100644 --- a/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll @@ -21,6 +21,7 @@ newtype SynthKind = BraceBlockKind() or CaseMatchKind() or ClassVariableAccessKind(ClassVariable v) or + DefinedExprKind() or DivExprKind() or ElseKind() or ExponentExprKind() or @@ -40,6 +41,7 @@ newtype SynthKind = ModuloExprKind() or MulExprKind() or NilLiteralKind() or + NotExprKind() or RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or RShiftExprKind() or SimpleParameterKind() or @@ -1258,6 +1260,7 @@ private module HashLiteralDesugar { * ``` * desugars to, roughly, * ```rb + * if not defined? x then x = nil end * xs.each { |__synth__0| x = __synth__0; } * ``` * @@ -1267,58 +1270,160 @@ private module HashLiteralDesugar { * scoped to the synthesized block. */ private module ForLoopDesugar { + private Ruby::AstNode getForLoopPatternChild(Ruby::For for) { + result = for.getPattern() + or + result.getParent() = getForLoopPatternChild(for) + } + + /** Holds if `n` is an access to variable `v` in the pattern of `for`. */ + pragma[nomagic] + private predicate forLoopVariableAccess(Ruby::For for, Ruby::AstNode n, VariableReal v) { + n = getForLoopPatternChild(for) and + access(n, v) + } + + /** Holds if `v` is the `i`th iteration variable of `for`. */ + private predicate forLoopVariable(Ruby::For for, VariableReal v, int i) { + v = + rank[i + 1](VariableReal v0, Ruby::AstNode n, Location l | + forLoopVariableAccess(for, n, v0) and + l = n.getLocation() + | + v0 order by l.getStartLine(), l.getStartColumn() + ) + } + + /** Gets the number of iteration variables of `for`. */ + private int forLoopVariableCount(Ruby::For for) { + result = count(int j | forLoopVariable(for, _, j)) + } + + private Ruby::For toTsFor(ForExpr for) { for = TForExpr(result) } + + /** + * Synthesizes an assignment + * ```rb + * if not defined? v then v = nil end + * ``` + * anchored at index `rootIndex` of `root`. + */ + bindingset[root, rootIndex, v] + private predicate nilAssignUndefined( + AstNode root, int rootIndex, AstNode parent, int i, Child child, VariableReal v + ) { + parent = root and + i = rootIndex and + child = SynthChild(IfKind()) + or + exists(AstNode if_ | if_ = TIfSynth(root, rootIndex) | + parent = if_ and + i = 0 and + child = SynthChild(NotExprKind()) + or + exists(AstNode not_ | not_ = TNotExprSynth(if_, 0) | + parent = not_ and + i = 0 and + child = SynthChild(DefinedExprKind()) + or + parent = TDefinedExprSynth(not_, 0) and + i = 0 and + child = SynthChild(LocalVariableAccessRealKind(v)) + ) + or + parent = if_ and + i = 1 and + child = SynthChild(AssignExprKind()) + or + parent = TAssignExprSynth(if_, 1) and + ( + i = 0 and + child = SynthChild(LocalVariableAccessRealKind(v)) + or + i = 1 and + child = SynthChild(NilLiteralKind()) + ) + ) + } + pragma[nomagic] private predicate forLoopSynthesis(AstNode parent, int i, Child child) { exists(ForExpr for | - // each call parent = for and i = -1 and - child = SynthChild(MethodCallKind("each", false, 0)) + child = SynthChild(StmtSequenceKind()) or - exists(MethodCall eachCall | eachCall = TMethodCallSynth(for, -1, "each", false, 0) | - // receiver - parent = eachCall and - i = 0 and - child = childRef(for.getValue()) // value is the Enumerable + exists(AstNode seq | seq = TStmtSequenceSynth(for, -1) | + exists(VariableReal v, int j | forLoopVariable(toTsFor(for), v, j) | + nilAssignUndefined(seq, j, parent, i, child, v) + ) or - parent = eachCall and - i = 1 and - child = SynthChild(BraceBlockKind()) - or - exists(Block block | block = TBraceBlockSynth(eachCall, 1) | - // block params - parent = block and - i = 0 and - child = SynthChild(SimpleParameterKind()) + exists(int numberOfVars | numberOfVars = forLoopVariableCount(toTsFor(for)) | + // each call + parent = seq and + i = numberOfVars and + child = SynthChild(MethodCallKind("each", false, 0)) or - exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) | - parent = param and + exists(MethodCall eachCall | + eachCall = TMethodCallSynth(seq, numberOfVars, "each", false, 0) + | + // receiver + parent = eachCall and i = 0 and - child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0))) + child = childRef(for.getValue()) // value is the Enumerable or - // assignment to pattern from for loop to synth parameter - parent = block and + parent = eachCall and i = 1 and - child = SynthChild(AssignExprKind()) + child = SynthChild(BraceBlockKind()) or - parent = TAssignExprSynth(block, 1) and - ( + exists(Block block | block = TBraceBlockSynth(eachCall, 1) | + // block params + parent = block and i = 0 and - child = childRef(for.getPattern()) + child = SynthChild(SimpleParameterKind()) or - i = 1 and - child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0))) + exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) | + parent = param and + i = 0 and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0))) + or + // assignment to pattern from for loop to synth parameter + parent = block and + i = 1 and + child = SynthChild(AssignExprKind()) + or + parent = TAssignExprSynth(block, 1) and + ( + i = 0 and + child = childRef(for.getPattern()) + or + i = 1 and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0))) + ) + ) + or + // rest of block body + parent = block and + child = childRef(for.getBody().(Do).getStmt(i - 2)) ) ) - or - // rest of block body - parent = block and - child = childRef(for.getBody().(Do).getStmt(i - 2)) ) ) ) } + pragma[nomagic] + private predicate isDesugaredInitNode(ForExpr for, Variable v, AstNode n) { + exists(StmtSequence seq, AssignExpr ae | + seq = for.getDesugared() and + n = seq.getStmt(_) and + ae = n.(IfExpr).getThen() and + v = ae.getLeftOperand().getAVariable() + ) + or + isDesugaredInitNode(for, v, n.getParent()) + } + private class ForLoopSynthesis extends Synthesis { final override predicate child(AstNode parent, int i, Child child) { forLoopSynthesis(parent, i, child) @@ -1338,6 +1443,14 @@ private module ForLoopDesugar { final override predicate excludeFromControlFlowTree(AstNode n) { n = any(ForExpr for).getBody() } + + final override predicate location(AstNode n, Location l) { + exists(ForExpr for, Ruby::AstNode access, Variable v | + forLoopVariableAccess(toTsFor(for), access, v) and + isDesugaredInitNode(for, v, n) and + l = access.getLocation() + ) + } } } diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll index 759c64c4abc..51e5d4b5630 100644 --- a/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll @@ -1,5 +1,6 @@ private import TreeSitter private import codeql.ruby.AST +private import codeql.ruby.CFG private import codeql.ruby.ast.internal.AST private import codeql.ruby.ast.internal.Parameter private import codeql.ruby.ast.internal.Pattern @@ -364,22 +365,11 @@ private module Cached { cached predicate isCapturedAccess(LocalVariableAccess access) { - exists(Scope scope1, Scope scope2 | + exists(Scope scope1, CfgScope scope2 | scope1 = access.getVariable().getDeclaringScope() and scope2 = access.getCfgScope() and - scope1 != scope2 - | - if access instanceof SelfVariableAccess - then - // ``` - // class C - // def self.m // not a captured access - // end - // end - // ``` - not scope2 instanceof Toplevel or - not access = any(SingletonMethod m).getObject() - else any() + scope1 != scope2 and + not scope2 instanceof Toplevel ) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll index 2521311c814..6052b462b27 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -439,6 +439,7 @@ private module Cached { } or THashSplatArgumentPosition() or TSplatAllArgumentPosition() or + TSplatArgumentPosition(int pos) { exists(Call c | c.getArgument(pos) instanceof SplatExpr) } or TAnyArgumentPosition() or TAnyKeywordArgumentPosition() @@ -468,6 +469,10 @@ private module Cached { // synthetic parameter position. TSynthHashSplatParameterPosition() or TSplatAllParameterPosition() or + TSplatParameterPosition(int pos) { + exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter) + } or + TSynthSplatParameterPosition() or TAnyParameterPosition() or TAnyKeywordParameterPosition() } @@ -1288,8 +1293,12 @@ class ParameterPosition extends TParameterPosition { predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() } + predicate isSynthSplat() { this = TSynthSplatParameterPosition() } + predicate isSplatAll() { this = TSplatAllParameterPosition() } + predicate isSplat(int n) { this = TSplatParameterPosition(n) } + /** * Holds if this position represents any parameter, except `self` parameters. This * includes both positional, named, and block parameters. @@ -1320,6 +1329,10 @@ class ParameterPosition extends TParameterPosition { this.isAny() and result = "any" or this.isAnyNamed() and result = "any-named" + or + this.isSynthSplat() and result = "synthetic *" + or + exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")") } } @@ -1354,6 +1367,8 @@ class ArgumentPosition extends TArgumentPosition { predicate isSplatAll() { this = TSplatAllArgumentPosition() } + predicate isSplat(int n) { this = TSplatArgumentPosition(n) } + /** Gets a textual representation of this position. */ string toString() { this.isSelf() and result = "self" @@ -1371,6 +1386,8 @@ class ArgumentPosition extends TArgumentPosition { this.isHashSplat() and result = "**" or this.isSplatAll() and result = "*" + or + exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")") } } @@ -1401,6 +1418,11 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { or ppos.isSplatAll() and apos.isSplatAll() or + ppos.isSynthSplat() and apos.isSplatAll() + or + // Exact splat match + exists(int n | apos.isSplat(n) and ppos.isSplat(n)) + or ppos.isAny() and argumentPositionIsNotSelf(apos) or apos.isAny() and parameterPositionIsNotSelf(ppos) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 70599e9a593..13f1d31a7d2 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -137,22 +137,6 @@ module LocalFlow { nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod()) } - /** - * Holds if `nodeFrom -> nodeTo` is a step from a parameter to a capture entry node for - * that parameter. - * - * This is intended to recover from flow not currently recognised by ordinary capture flow. - */ - predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) { - exists(Ssa::CapturedEntryDefinition def | - nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def - | - nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable() - or - nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable() - ) - } - /** * Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo` * involving SSA definition `def`. @@ -245,6 +229,7 @@ private class Argument extends CfgNodes::ExprCfgNode { not this.getExpr() instanceof BlockArgument and not this.getExpr().(Pair).getKey().getConstantValue().isSymbol(_) and not this.getExpr() instanceof HashSplatExpr and + not this.getExpr() instanceof SplatExpr and arg.isPositional(i) ) or @@ -261,8 +246,15 @@ private class Argument extends CfgNodes::ExprCfgNode { arg.isHashSplat() or this = call.getArgument(0) and + not exists(call.getArgument(1)) and this.getExpr() instanceof SplatExpr and arg.isSplatAll() + or + exists(int pos | pos > 0 or exists(call.getArgument(pos + 1)) | + this = call.getArgument(pos) and + this.getExpr() instanceof SplatExpr and + arg.isSplat(pos) + ) } /** Holds if this expression is the `i`th argument of `c`. */ @@ -277,6 +269,53 @@ predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) { not n.getExpr() instanceof ConstantAccess } +/** Provides logic related to captured variables. */ +module VariableCapture { + class CapturedVariable extends LocalVariable { + CapturedVariable() { this.isCaptured() } + + CfgScope getCfgScope() { + exists(Scope scope | scope = this.getDeclaringScope() | + result = scope + or + result = scope.(ModuleBase).getCfgScope() + ) + } + } + + class CapturedSsaDefinitionExt extends SsaImpl::DefinitionExt { + CapturedSsaDefinitionExt() { this.getSourceVariable() instanceof CapturedVariable } + } + + /** + * Holds if there is control-flow insensitive data-flow from `node1` to `node2` + * involving a captured variable. Only used in type tracking. + */ + predicate flowInsensitiveStep(Node node1, Node node2) { + exists(CapturedSsaDefinitionExt def, CapturedVariable v | + // From an assignment or implicit initialization of a captured variable to its flow-insensitive node + def = node1.(SsaDefinitionExtNode).getDefinitionExt() and + def.getSourceVariable() = v and + ( + def instanceof Ssa::WriteDefinition + or + def instanceof Ssa::SelfDefinition + ) and + node2.(CapturedVariableNode).getVariable() = v + or + // From a captured variable node to its flow-sensitive capture nodes + node1.(CapturedVariableNode).getVariable() = v and + def = node2.(SsaDefinitionExtNode).getDefinitionExt() and + def.getSourceVariable() = v and + ( + def instanceof Ssa::CapturedCallDefinition + or + def instanceof Ssa::CapturedEntryDefinition + ) + ) + } +} + /** A collection of cached types and predicates to be evaluated in the same stage. */ cached private module Cached { @@ -288,6 +327,7 @@ private module Cached { TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or TReturningNode(CfgNodes::ReturningCfgNode n) or TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or + TCapturedVariableNode(VariableCapture::CapturedVariable v) or TNormalParameterNode(Parameter p) { p instanceof SimpleParameter or p instanceof OptionalParameter or @@ -300,6 +340,10 @@ private module Cached { TSynthHashSplatParameterNode(DataFlowCallable c) { isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_))) } or + TSynthSplatParameterNode(DataFlowCallable c) { + exists(c.asCallable()) and // exclude library callables + isParameterNode(_, c, any(ParameterPosition p | p.isPositional(_))) + } or TExprPostUpdateNode(CfgNodes::ExprCfgNode n) { // filter out nodes that clearly don't need post-update nodes isNonConstantExpr(n) and @@ -318,7 +362,7 @@ private module Cached { class TSourceParameterNode = TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or - TSynthHashSplatParameterNode; + TSynthHashSplatParameterNode or TSynthSplatParameterNode; cached Location getLocation(NodeImpl n) { result = n.getLocationImpl() } @@ -377,6 +421,8 @@ private module Cached { LocalFlow::localFlowSsaInputFromRead(exprFrom, _, nodeTo) and exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()] ) + or + VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo) } private predicate entrySsaDefinition(SsaDefinitionExtNode n) { @@ -514,6 +560,8 @@ predicate nodeIsHidden(Node n) { n instanceof SynthHashSplatParameterNode or n instanceof SynthHashSplatArgumentNode + or + n instanceof SynthSplatParameterNode } /** An SSA definition, viewed as a node in a data flow graph. */ @@ -536,7 +584,7 @@ class SsaDefinitionExtNode extends NodeImpl, TSsaDefinitionExtNode { } /** An SSA definition for a `self` variable. */ -class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode { +class SsaSelfDefinitionNode extends SsaDefinitionExtNode { private SelfVariable self; SsaSelfDefinitionNode() { self = def.getSourceVariable() } @@ -545,6 +593,22 @@ class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode { Scope getSelfScope() { result = self.getDeclaringScope() } } +/** A data flow node representing a captured variable. Only used in type tracking. */ +class CapturedVariableNode extends NodeImpl, TCapturedVariableNode { + private VariableCapture::CapturedVariable variable; + + CapturedVariableNode() { this = TCapturedVariableNode(variable) } + + /** Gets the captured variable represented by this node. */ + VariableCapture::CapturedVariable getVariable() { result = variable } + + override CfgScope getCfgScope() { result = variable.getCfgScope() } + + override Location getLocationImpl() { result = variable.getLocation() } + + override string toStringImpl() { result = "captured " + variable.getName() } +} + /** * A value returning statement, viewed as a node in a data flow graph. * @@ -610,7 +674,15 @@ private module ParameterNodes { pos.isHashSplat() or parameter = callable.getParameter(0).(SplatParameter) and + not exists(callable.getParameter(1)) and pos.isSplatAll() + or + exists(int n | n > 0 | + parameter = callable.getParameter(n).(SplatParameter) and + pos.isSplat(n) and + // There are no positional parameters after the splat + not exists(SimpleParameter p, int m | m > n | p = callable.getParameter(m)) + ) ) } @@ -749,6 +821,70 @@ private module ParameterNodes { final override string toStringImpl() { result = "**kwargs" } } + /** + * A synthetic data-flow node to allow flow to positional parameters from a splat argument. + * + * For example, in the following code: + * + * ```rb + * def foo(x, y); end + * + * foo(*[a, b]) + * ``` + * + * We want `a` to flow to `x` and `b` to flow to `y`. We do this by constructing + * a `SynthSplatParameterNode` for the method `foo`, and matching the splat argument to this + * parameter node via `parameterMatch/2`. We then add read steps from this node to parameters + * `x` and `y`, for content at indices 0 and 1 respectively (see `readStep`). + * + * We don't yet correctly handle cases where the splat argument is not the first argument, e.g. in + * ```rb + * foo(a, *[b]) + * ``` + */ + class SynthSplatParameterNode extends ParameterNodeImpl, TSynthSplatParameterNode { + private DataFlowCallable callable; + + SynthSplatParameterNode() { this = TSynthSplatParameterNode(callable) } + + /** + * Gets a parameter which will contain the value given by `c`, assuming + * that the method was called with a single splat argument. + * For example, if the synth splat parameter is for the following method + * + * ```rb + * def foo(x, y, a:, *rest) + * end + * ``` + * + * Then `getAParameter(element 0) = x` and `getAParameter(element 1) = y`. + */ + ParameterNode getAParameter(ContentSet c) { + exists(int n | + isParameterNode(result, callable, (any(ParameterPosition p | p.isPositional(n)))) and + ( + c = getPositionalContent(n) + or + c.isSingleton(TUnknownElementContent()) + ) + ) + } + + final override Parameter getParameter() { none() } + + final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { + c = callable and pos.isSynthSplat() + } + + final override CfgScope getCfgScope() { result = callable.asCallable() } + + final override DataFlowCallable getEnclosingCallable() { result = callable } + + final override Location getLocationImpl() { result = callable.getLocation() } + + final override string toStringImpl() { result = "synthetic *args" } + } + /** A parameter for a library callable with a flow summary. */ class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode { private ParameterPosition pos_; @@ -1077,13 +1213,7 @@ private module OutNodes { import OutNodes -predicate jumpStep(Node pred, Node succ) { - SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(), - succ.(SsaDefinitionExtNode).getDefinitionExt()) - or - SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(), - succ.(SsaDefinitionExtNode).getDefinitionExt()) - or +predicate jumpStepTypeTracker(Node pred, Node succ) { succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr() or FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(), @@ -1092,6 +1222,16 @@ predicate jumpStep(Node pred, Node succ) { any(AdditionalJumpStep s).step(pred, succ) } +predicate jumpStep(Node pred, Node succ) { + jumpStepTypeTracker(pred, succ) + or + SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(), + succ.(SsaDefinitionExtNode).getDefinitionExt()) + or + SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(), + succ.(SsaDefinitionExtNode).getDefinitionExt()) +} + private ContentSet getKeywordContent(string name) { exists(ConstantValue::ConstantSymbolValue key | result.isSingleton(TKnownElementContent(key)) and @@ -1099,6 +1239,13 @@ private ContentSet getKeywordContent(string name) { ) } +private ContentSet getPositionalContent(int n) { + exists(ConstantValue::ConstantIntegerValue i | + result.isSingleton(TKnownElementContent(i)) and + i.isInt(n) + ) +} + /** * Subset of `storeStep` that should be shared with type-tracking. */ @@ -1187,6 +1334,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) { or node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c) or + node2 = node1.(SynthSplatParameterNode).getAParameter(c) + or FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c, node2.(FlowSummaryNode).getSummaryNode()) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index 7772e5235d7..269087b7a07 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -363,10 +363,9 @@ private module Cached { source = sink and source instanceof LocalSourceNode or - exists(Node mid | hasLocalSource(mid, source) | + exists(Node mid | + hasLocalSource(mid, source) and localFlowStepTypeTracker(mid, sink) - or - LocalFlow::localFlowSsaParamCaptureInput(mid, sink) ) } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 256d15212d0..e89260c9302 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -74,7 +74,7 @@ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; /** * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ -predicate jumpStep = DataFlowPrivate::jumpStep/2; +predicate jumpStep = DataFlowPrivate::jumpStepTypeTracker/2; /** Holds if there is direct flow from `param` to a return. */ pragma[nomagic] diff --git a/ruby/ql/lib/ide-contextual-queries/printCfg.ql b/ruby/ql/lib/ide-contextual-queries/printCfg.ql index 22681eb48f9..c902474a55c 100644 --- a/ruby/ql/lib/ide-contextual-queries/printCfg.ql +++ b/ruby/ql/lib/ide-contextual-queries/printCfg.ql @@ -9,14 +9,45 @@ private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::TestOutput private import codeql.IDEContextual +private import codeql.Locations +private import codeql.ruby.controlflow.ControlFlowGraph /** * Gets the source file to generate a CFG from. */ external string selectedSourceFile(); +external string selectedSourceLine(); + +external string selectedSourceColumn(); + +bindingset[file, line, column] +private CfgScope smallestEnclosingScope(File file, int line, int column) { + result = + min(Location loc, CfgScope scope | + loc = scope.getLocation() and + ( + loc.getStartLine() < line + or + loc.getStartLine() = line and loc.getStartColumn() <= column + ) and + ( + loc.getEndLine() > line + or + loc.getEndLine() = line and loc.getEndColumn() >= column + ) and + loc.getFile() = file + | + scope + order by + loc.getStartLine() desc, loc.getStartColumn() desc, loc.getEndLine(), loc.getEndColumn() + ) +} + class MyRelevantNode extends RelevantNode { MyRelevantNode() { - this.getScope().getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile()) + this.getScope() = + smallestEnclosingScope(getFileBySourceArchiveName(selectedSourceFile()), + selectedSourceLine().toInt(), selectedSourceColumn().toInt()) } } diff --git a/ruby/ql/lib/qlpack.yml b/ruby/ql/lib/qlpack.yml index 35b4d48f9da..1afd480424b 100644 --- a/ruby/ql/lib/qlpack.yml +++ b/ruby/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ruby-all -version: 0.7.2-dev +version: 0.7.3-dev groups: ruby extractor: ruby dbscheme: ruby.dbscheme diff --git a/ruby/ql/src/CHANGELOG.md b/ruby/ql/src/CHANGELOG.md index f7af0802f0a..4d7100e2c1d 100644 --- a/ruby/ql/src/CHANGELOG.md +++ b/ruby/ql/src/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.7.2 + +### New Queries + +* Added a new experimental query, `rb/ldap-injection`, to detect cases where user input is incorporated into LDAP queries without proper validation or sanitization, potentially leading to LDAP injection vulnerabilities. + ## 0.7.1 ### New Queries diff --git a/ruby/ql/src/change-notes/2023-05-28-ldap-injection-query.md b/ruby/ql/src/change-notes/released/0.7.2.md similarity index 77% rename from ruby/ql/src/change-notes/2023-05-28-ldap-injection-query.md rename to ruby/ql/src/change-notes/released/0.7.2.md index 1fb43a09fda..8741b19d2c5 100644 --- a/ruby/ql/src/change-notes/2023-05-28-ldap-injection-query.md +++ b/ruby/ql/src/change-notes/released/0.7.2.md @@ -1,4 +1,5 @@ ---- -category: newQuery ---- -* Added a new experimental query, `rb/ldap-injection`, to detect cases where user input is incorporated into LDAP queries without proper validation or sanitization, potentially leading to LDAP injection vulnerabilities. \ No newline at end of file +## 0.7.2 + +### New Queries + +* Added a new experimental query, `rb/ldap-injection`, to detect cases where user input is incorporated into LDAP queries without proper validation or sanitization, potentially leading to LDAP injection vulnerabilities. diff --git a/ruby/ql/src/codeql-pack.release.yml b/ruby/ql/src/codeql-pack.release.yml index e007a9aec3e..fee171e9685 100644 --- a/ruby/ql/src/codeql-pack.release.yml +++ b/ruby/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.1 +lastReleaseVersion: 0.7.2 diff --git a/ruby/ql/src/qlpack.yml b/ruby/ql/src/qlpack.yml index ffd639564b5..768b3c6c710 100644 --- a/ruby/ql/src/qlpack.yml +++ b/ruby/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ruby-queries -version: 0.7.2-dev +version: 0.7.3-dev groups: - ruby - queries diff --git a/ruby/ql/test/library-tests/ast/AstDesugar.expected b/ruby/ql/test/library-tests/ast/AstDesugar.expected index 3625cb83574..5d2a321922f 100644 --- a/ruby/ql/test/library-tests/ast/AstDesugar.expected +++ b/ruby/ql/test/library-tests/ast/AstDesugar.expected @@ -24,29 +24,45 @@ calls/calls.rb: # 67| getAnOperand/getArgument/getRightOperand: [MethodCall] call to bar # 67| getReceiver: [ConstantReadAccess] X # 226| [ForExpr] for ... in ... -# 226| getDesugared: [MethodCall] call to each -# 226| getReceiver: [MethodCall] call to bar -# 226| getReceiver: [SelfVariableAccess] self -# 226| getBlock: [BraceBlock] { ... } -# 226| getParameter: [SimpleParameter] __synth__0__1 -# 226| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 226| getStmt: [AssignExpr] ... = ... +# 226| getDesugared: [StmtSequence] ... +# 226| getStmt: [IfExpr] if ... +# 226| getCondition: [NotExpr] ! ... +# 226| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 226| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] x +# 226| getBranch/getThen: [AssignExpr] ... = ... # 226| getAnOperand/getLeftOperand: [LocalVariableAccess] x -# 226| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 -# 227| getStmt: [MethodCall] call to baz -# 227| getReceiver: [SelfVariableAccess] self +# 226| getAnOperand/getRightOperand: [NilLiteral] nil +# 226| getStmt: [MethodCall] call to each +# 226| getReceiver: [MethodCall] call to bar +# 226| getReceiver: [SelfVariableAccess] self +# 226| getBlock: [BraceBlock] { ... } +# 226| getParameter: [SimpleParameter] __synth__0__1 +# 226| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 226| getStmt: [AssignExpr] ... = ... +# 226| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 226| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 +# 227| getStmt: [MethodCall] call to baz +# 227| getReceiver: [SelfVariableAccess] self # 229| [ForExpr] for ... in ... -# 229| getDesugared: [MethodCall] call to each -# 229| getReceiver: [MethodCall] call to bar -# 229| getReceiver: [ConstantReadAccess] X -# 229| getBlock: [BraceBlock] { ... } -# 229| getParameter: [SimpleParameter] __synth__0__1 -# 229| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 229| getStmt: [AssignExpr] ... = ... +# 229| getDesugared: [StmtSequence] ... +# 229| getStmt: [IfExpr] if ... +# 229| getCondition: [NotExpr] ! ... +# 229| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 229| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] x +# 229| getBranch/getThen: [AssignExpr] ... = ... # 229| getAnOperand/getLeftOperand: [LocalVariableAccess] x -# 229| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 -# 230| getStmt: [MethodCall] call to baz -# 230| getReceiver: [ConstantReadAccess] X +# 229| getAnOperand/getRightOperand: [NilLiteral] nil +# 229| getStmt: [MethodCall] call to each +# 229| getReceiver: [MethodCall] call to bar +# 229| getReceiver: [ConstantReadAccess] X +# 229| getBlock: [BraceBlock] { ... } +# 229| getParameter: [SimpleParameter] __synth__0__1 +# 229| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 229| getStmt: [AssignExpr] ... = ... +# 229| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 229| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 +# 230| getStmt: [MethodCall] call to baz +# 230| getReceiver: [ConstantReadAccess] X # 249| [HashLiteral] {...} # 249| getDesugared: [MethodCall] call to [] # 249| getReceiver: [ConstantReadAccess] Hash @@ -245,52 +261,74 @@ calls/calls.rb: # 322| getArgument: [LocalVariableAccess] __synth__4 # 322| getStmt: [LocalVariableAccess] __synth__4 # 342| [ForExpr] for ... in ... -# 342| getDesugared: [MethodCall] call to each -# 342| getReceiver: [ArrayLiteral] [...] -# 342| getDesugared: [MethodCall] call to [] -# 342| getReceiver: [ConstantReadAccess] Array -# 342| getArgument: [ArrayLiteral] [...] -# 342| getDesugared: [MethodCall] call to [] -# 342| getReceiver: [ConstantReadAccess] Array -# 342| getArgument: [IntegerLiteral] 1 -# 342| getArgument: [IntegerLiteral] 2 -# 342| getArgument: [IntegerLiteral] 3 -# 342| getArgument: [ArrayLiteral] [...] -# 342| getDesugared: [MethodCall] call to [] -# 342| getReceiver: [ConstantReadAccess] Array -# 342| getArgument: [IntegerLiteral] 4 -# 342| getArgument: [IntegerLiteral] 5 -# 342| getArgument: [IntegerLiteral] 6 -# 342| getBlock: [BraceBlock] { ... } -# 342| getParameter: [SimpleParameter] __synth__0__1 -# 342| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 342| getStmt: [AssignExpr] ... = ... -# 342| getDesugared: [StmtSequence] ... -# 342| getStmt: [AssignExpr] ... = ... -# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__3__1 -# 342| getAnOperand/getRightOperand: [SplatExpr] * ... -# 342| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1 -# 342| getStmt: [AssignExpr] ... = ... -# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] x -# 342| getAnOperand/getRightOperand: [MethodCall] call to [] -# 342| getReceiver: [LocalVariableAccess] __synth__3__1 -# 342| getArgument: [IntegerLiteral] 0 -# 342| getStmt: [AssignExpr] ... = ... -# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] y -# 342| getAnOperand/getRightOperand: [MethodCall] call to [] -# 342| getReceiver: [LocalVariableAccess] __synth__3__1 +# 342| getDesugared: [StmtSequence] ... +# 342| getStmt: [IfExpr] if ... +# 342| getCondition: [NotExpr] ! ... +# 342| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 342| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] x +# 342| getBranch/getThen: [AssignExpr] ... = ... +# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 342| getAnOperand/getRightOperand: [NilLiteral] nil +# 342| getStmt: [IfExpr] if ... +# 342| getCondition: [NotExpr] ! ... +# 342| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 342| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] y +# 342| getBranch/getThen: [AssignExpr] ... = ... +# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] y +# 342| getAnOperand/getRightOperand: [NilLiteral] nil +# 342| getStmt: [IfExpr] if ... +# 342| getCondition: [NotExpr] ! ... +# 342| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 342| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] z +# 342| getBranch/getThen: [AssignExpr] ... = ... +# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 342| getAnOperand/getRightOperand: [NilLiteral] nil +# 342| getStmt: [MethodCall] call to each +# 342| getReceiver: [ArrayLiteral] [...] +# 342| getDesugared: [MethodCall] call to [] +# 342| getReceiver: [ConstantReadAccess] Array +# 342| getArgument: [ArrayLiteral] [...] +# 342| getDesugared: [MethodCall] call to [] +# 342| getReceiver: [ConstantReadAccess] Array # 342| getArgument: [IntegerLiteral] 1 -# 342| getStmt: [AssignExpr] ... = ... -# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] z -# 342| getAnOperand/getRightOperand: [MethodCall] call to [] -# 342| getReceiver: [LocalVariableAccess] __synth__3__1 # 342| getArgument: [IntegerLiteral] 2 -# 342| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...) -# 343| getStmt: [MethodCall] call to foo -# 343| getReceiver: [SelfVariableAccess] self -# 343| getArgument: [LocalVariableAccess] x -# 343| getArgument: [LocalVariableAccess] y -# 343| getArgument: [LocalVariableAccess] z +# 342| getArgument: [IntegerLiteral] 3 +# 342| getArgument: [ArrayLiteral] [...] +# 342| getDesugared: [MethodCall] call to [] +# 342| getReceiver: [ConstantReadAccess] Array +# 342| getArgument: [IntegerLiteral] 4 +# 342| getArgument: [IntegerLiteral] 5 +# 342| getArgument: [IntegerLiteral] 6 +# 342| getBlock: [BraceBlock] { ... } +# 342| getParameter: [SimpleParameter] __synth__0__1 +# 342| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 342| getStmt: [AssignExpr] ... = ... +# 342| getDesugared: [StmtSequence] ... +# 342| getStmt: [AssignExpr] ... = ... +# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__3__1 +# 342| getAnOperand/getRightOperand: [SplatExpr] * ... +# 342| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1 +# 342| getStmt: [AssignExpr] ... = ... +# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 342| getAnOperand/getRightOperand: [MethodCall] call to [] +# 342| getReceiver: [LocalVariableAccess] __synth__3__1 +# 342| getArgument: [IntegerLiteral] 0 +# 342| getStmt: [AssignExpr] ... = ... +# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] y +# 342| getAnOperand/getRightOperand: [MethodCall] call to [] +# 342| getReceiver: [LocalVariableAccess] __synth__3__1 +# 342| getArgument: [IntegerLiteral] 1 +# 342| getStmt: [AssignExpr] ... = ... +# 342| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 342| getAnOperand/getRightOperand: [MethodCall] call to [] +# 342| getReceiver: [LocalVariableAccess] __synth__3__1 +# 342| getArgument: [IntegerLiteral] 2 +# 342| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...) +# 343| getStmt: [MethodCall] call to foo +# 343| getReceiver: [SelfVariableAccess] self +# 343| getArgument: [LocalVariableAccess] x +# 343| getArgument: [LocalVariableAccess] y +# 343| getArgument: [LocalVariableAccess] z # 364| [MethodCall] call to empty? # 364| getDesugared: [StmtSequence] ... # 364| getStmt: [AssignExpr] ... = ... @@ -594,139 +632,185 @@ literals/literals.rb: # 199| getValue: [ConstantReadAccess] Z control/loops.rb: # 9| [ForExpr] for ... in ... -# 9| getDesugared: [MethodCall] call to each -# 9| getReceiver: [RangeLiteral] _ .. _ -# 9| getBegin: [IntegerLiteral] 1 -# 9| getEnd: [IntegerLiteral] 10 -# 9| getBlock: [BraceBlock] { ... } -# 9| getParameter: [SimpleParameter] __synth__0__1 -# 9| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 9| getStmt: [AssignExpr] ... = ... +# 9| getDesugared: [StmtSequence] ... +# 9| getStmt: [IfExpr] if ... +# 9| getCondition: [NotExpr] ! ... +# 9| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 9| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] n +# 9| getBranch/getThen: [AssignExpr] ... = ... # 9| getAnOperand/getLeftOperand: [LocalVariableAccess] n -# 9| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 -# 10| getStmt: [AssignAddExpr] ... += ... -# 10| getDesugared: [AssignExpr] ... = ... -# 10| getAnOperand/getLeftOperand: [LocalVariableAccess] sum -# 10| getAnOperand/getRightOperand: [AddExpr] ... + ... -# 10| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum -# 10| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n -# 11| getStmt: [AssignExpr] ... = ... -# 11| getAnOperand/getLeftOperand: [LocalVariableAccess] foo -# 11| getAnOperand/getRightOperand: [LocalVariableAccess] n +# 9| getAnOperand/getRightOperand: [NilLiteral] nil +# 9| getStmt: [MethodCall] call to each +# 9| getReceiver: [RangeLiteral] _ .. _ +# 9| getBegin: [IntegerLiteral] 1 +# 9| getEnd: [IntegerLiteral] 10 +# 9| getBlock: [BraceBlock] { ... } +# 9| getParameter: [SimpleParameter] __synth__0__1 +# 9| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 9| getStmt: [AssignExpr] ... = ... +# 9| getAnOperand/getLeftOperand: [LocalVariableAccess] n +# 9| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 +# 10| getStmt: [AssignAddExpr] ... += ... +# 10| getDesugared: [AssignExpr] ... = ... +# 10| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 10| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 10| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 10| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n +# 11| getStmt: [AssignExpr] ... = ... +# 11| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 11| getAnOperand/getRightOperand: [LocalVariableAccess] n # 16| [ForExpr] for ... in ... -# 16| getDesugared: [MethodCall] call to each -# 16| getReceiver: [RangeLiteral] _ .. _ -# 16| getBegin: [IntegerLiteral] 1 -# 16| getEnd: [IntegerLiteral] 10 -# 16| getBlock: [BraceBlock] { ... } -# 16| getParameter: [SimpleParameter] __synth__0__1 -# 16| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 16| getStmt: [AssignExpr] ... = ... +# 16| getDesugared: [StmtSequence] ... +# 16| getStmt: [IfExpr] if ... +# 16| getCondition: [NotExpr] ! ... +# 16| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 16| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] n +# 16| getBranch/getThen: [AssignExpr] ... = ... # 16| getAnOperand/getLeftOperand: [LocalVariableAccess] n -# 16| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 -# 17| getStmt: [AssignAddExpr] ... += ... -# 17| getDesugared: [AssignExpr] ... = ... -# 17| getAnOperand/getLeftOperand: [LocalVariableAccess] sum -# 17| getAnOperand/getRightOperand: [AddExpr] ... + ... -# 17| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum -# 17| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n -# 18| getStmt: [AssignSubExpr] ... -= ... -# 18| getDesugared: [AssignExpr] ... = ... -# 18| getAnOperand/getLeftOperand: [LocalVariableAccess] foo -# 18| getAnOperand/getRightOperand: [SubExpr] ... - ... -# 18| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo -# 18| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n +# 16| getAnOperand/getRightOperand: [NilLiteral] nil +# 16| getStmt: [MethodCall] call to each +# 16| getReceiver: [RangeLiteral] _ .. _ +# 16| getBegin: [IntegerLiteral] 1 +# 16| getEnd: [IntegerLiteral] 10 +# 16| getBlock: [BraceBlock] { ... } +# 16| getParameter: [SimpleParameter] __synth__0__1 +# 16| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 16| getStmt: [AssignExpr] ... = ... +# 16| getAnOperand/getLeftOperand: [LocalVariableAccess] n +# 16| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 +# 17| getStmt: [AssignAddExpr] ... += ... +# 17| getDesugared: [AssignExpr] ... = ... +# 17| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 17| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 17| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 17| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n +# 18| getStmt: [AssignSubExpr] ... -= ... +# 18| getDesugared: [AssignExpr] ... = ... +# 18| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 18| getAnOperand/getRightOperand: [SubExpr] ... - ... +# 18| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 18| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n # 22| [ForExpr] for ... in ... -# 22| getDesugared: [MethodCall] call to each -# 22| getReceiver: [HashLiteral] {...} -# 22| getDesugared: [MethodCall] call to [] -# 22| getReceiver: [ConstantReadAccess] Hash -# 22| getArgument: [Pair] Pair -# 22| getKey: [SymbolLiteral] :foo -# 22| getComponent: [StringTextComponent] foo -# 22| getValue: [IntegerLiteral] 0 -# 22| getArgument: [Pair] Pair -# 22| getKey: [SymbolLiteral] :bar -# 22| getComponent: [StringTextComponent] bar -# 22| getValue: [IntegerLiteral] 1 -# 22| getBlock: [BraceBlock] { ... } -# 22| getParameter: [SimpleParameter] __synth__0__1 -# 22| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 22| getStmt: [AssignExpr] ... = ... -# 22| getDesugared: [StmtSequence] ... -# 22| getStmt: [AssignExpr] ... = ... -# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2__1 -# 22| getAnOperand/getRightOperand: [SplatExpr] * ... -# 22| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1 -# 22| getStmt: [AssignExpr] ... = ... -# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] key -# 22| getAnOperand/getRightOperand: [MethodCall] call to [] -# 22| getReceiver: [LocalVariableAccess] __synth__2__1 -# 22| getArgument: [IntegerLiteral] 0 -# 22| getStmt: [AssignExpr] ... = ... -# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] value -# 22| getAnOperand/getRightOperand: [MethodCall] call to [] -# 22| getReceiver: [LocalVariableAccess] __synth__2__1 -# 22| getArgument: [IntegerLiteral] 1 -# 22| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...) -# 23| getStmt: [AssignAddExpr] ... += ... -# 23| getDesugared: [AssignExpr] ... = ... -# 23| getAnOperand/getLeftOperand: [LocalVariableAccess] sum -# 23| getAnOperand/getRightOperand: [AddExpr] ... + ... -# 23| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum -# 23| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value -# 24| getStmt: [AssignMulExpr] ... *= ... -# 24| getDesugared: [AssignExpr] ... = ... -# 24| getAnOperand/getLeftOperand: [LocalVariableAccess] foo -# 24| getAnOperand/getRightOperand: [MulExpr] ... * ... -# 24| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo -# 24| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 22| getDesugared: [StmtSequence] ... +# 22| getStmt: [IfExpr] if ... +# 22| getCondition: [NotExpr] ! ... +# 22| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 22| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] key +# 22| getBranch/getThen: [AssignExpr] ... = ... +# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] key +# 22| getAnOperand/getRightOperand: [NilLiteral] nil +# 22| getStmt: [IfExpr] if ... +# 22| getCondition: [NotExpr] ! ... +# 22| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 22| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] value +# 22| getBranch/getThen: [AssignExpr] ... = ... +# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] value +# 22| getAnOperand/getRightOperand: [NilLiteral] nil +# 22| getStmt: [MethodCall] call to each +# 22| getReceiver: [HashLiteral] {...} +# 22| getDesugared: [MethodCall] call to [] +# 22| getReceiver: [ConstantReadAccess] Hash +# 22| getArgument: [Pair] Pair +# 22| getKey: [SymbolLiteral] :foo +# 22| getComponent: [StringTextComponent] foo +# 22| getValue: [IntegerLiteral] 0 +# 22| getArgument: [Pair] Pair +# 22| getKey: [SymbolLiteral] :bar +# 22| getComponent: [StringTextComponent] bar +# 22| getValue: [IntegerLiteral] 1 +# 22| getBlock: [BraceBlock] { ... } +# 22| getParameter: [SimpleParameter] __synth__0__1 +# 22| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 22| getStmt: [AssignExpr] ... = ... +# 22| getDesugared: [StmtSequence] ... +# 22| getStmt: [AssignExpr] ... = ... +# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2__1 +# 22| getAnOperand/getRightOperand: [SplatExpr] * ... +# 22| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1 +# 22| getStmt: [AssignExpr] ... = ... +# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] key +# 22| getAnOperand/getRightOperand: [MethodCall] call to [] +# 22| getReceiver: [LocalVariableAccess] __synth__2__1 +# 22| getArgument: [IntegerLiteral] 0 +# 22| getStmt: [AssignExpr] ... = ... +# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] value +# 22| getAnOperand/getRightOperand: [MethodCall] call to [] +# 22| getReceiver: [LocalVariableAccess] __synth__2__1 +# 22| getArgument: [IntegerLiteral] 1 +# 22| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...) +# 23| getStmt: [AssignAddExpr] ... += ... +# 23| getDesugared: [AssignExpr] ... = ... +# 23| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 23| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 23| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 23| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 24| getStmt: [AssignMulExpr] ... *= ... +# 24| getDesugared: [AssignExpr] ... = ... +# 24| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 24| getAnOperand/getRightOperand: [MulExpr] ... * ... +# 24| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 24| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value # 28| [ForExpr] for ... in ... -# 28| getDesugared: [MethodCall] call to each -# 28| getReceiver: [HashLiteral] {...} -# 28| getDesugared: [MethodCall] call to [] -# 28| getReceiver: [ConstantReadAccess] Hash -# 28| getArgument: [Pair] Pair -# 28| getKey: [SymbolLiteral] :foo -# 28| getComponent: [StringTextComponent] foo -# 28| getValue: [IntegerLiteral] 0 -# 28| getArgument: [Pair] Pair -# 28| getKey: [SymbolLiteral] :bar -# 28| getComponent: [StringTextComponent] bar -# 28| getValue: [IntegerLiteral] 1 -# 28| getBlock: [BraceBlock] { ... } -# 28| getParameter: [SimpleParameter] __synth__0__1 -# 28| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 28| getStmt: [AssignExpr] ... = ... -# 28| getDesugared: [StmtSequence] ... -# 28| getStmt: [AssignExpr] ... = ... -# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2__1 -# 28| getAnOperand/getRightOperand: [SplatExpr] * ... -# 28| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1 -# 28| getStmt: [AssignExpr] ... = ... -# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] key -# 28| getAnOperand/getRightOperand: [MethodCall] call to [] -# 28| getReceiver: [LocalVariableAccess] __synth__2__1 -# 28| getArgument: [IntegerLiteral] 0 -# 28| getStmt: [AssignExpr] ... = ... -# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] value -# 28| getAnOperand/getRightOperand: [MethodCall] call to [] -# 28| getReceiver: [LocalVariableAccess] __synth__2__1 -# 28| getArgument: [IntegerLiteral] 1 -# 28| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...) -# 29| getStmt: [AssignAddExpr] ... += ... -# 29| getDesugared: [AssignExpr] ... = ... -# 29| getAnOperand/getLeftOperand: [LocalVariableAccess] sum -# 29| getAnOperand/getRightOperand: [AddExpr] ... + ... -# 29| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum -# 29| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value -# 30| getStmt: [AssignDivExpr] ... /= ... -# 30| getDesugared: [AssignExpr] ... = ... -# 30| getAnOperand/getLeftOperand: [LocalVariableAccess] foo -# 30| getAnOperand/getRightOperand: [DivExpr] ... / ... -# 30| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo -# 30| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value -# 31| getStmt: [BreakStmt] break +# 28| getDesugared: [StmtSequence] ... +# 28| getStmt: [IfExpr] if ... +# 28| getCondition: [NotExpr] ! ... +# 28| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 28| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] key +# 28| getBranch/getThen: [AssignExpr] ... = ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] key +# 28| getAnOperand/getRightOperand: [NilLiteral] nil +# 28| getStmt: [IfExpr] if ... +# 28| getCondition: [NotExpr] ! ... +# 28| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 28| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] value +# 28| getBranch/getThen: [AssignExpr] ... = ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] value +# 28| getAnOperand/getRightOperand: [NilLiteral] nil +# 28| getStmt: [MethodCall] call to each +# 28| getReceiver: [HashLiteral] {...} +# 28| getDesugared: [MethodCall] call to [] +# 28| getReceiver: [ConstantReadAccess] Hash +# 28| getArgument: [Pair] Pair +# 28| getKey: [SymbolLiteral] :foo +# 28| getComponent: [StringTextComponent] foo +# 28| getValue: [IntegerLiteral] 0 +# 28| getArgument: [Pair] Pair +# 28| getKey: [SymbolLiteral] :bar +# 28| getComponent: [StringTextComponent] bar +# 28| getValue: [IntegerLiteral] 1 +# 28| getBlock: [BraceBlock] { ... } +# 28| getParameter: [SimpleParameter] __synth__0__1 +# 28| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 28| getStmt: [AssignExpr] ... = ... +# 28| getDesugared: [StmtSequence] ... +# 28| getStmt: [AssignExpr] ... = ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2__1 +# 28| getAnOperand/getRightOperand: [SplatExpr] * ... +# 28| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1 +# 28| getStmt: [AssignExpr] ... = ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] key +# 28| getAnOperand/getRightOperand: [MethodCall] call to [] +# 28| getReceiver: [LocalVariableAccess] __synth__2__1 +# 28| getArgument: [IntegerLiteral] 0 +# 28| getStmt: [AssignExpr] ... = ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] value +# 28| getAnOperand/getRightOperand: [MethodCall] call to [] +# 28| getReceiver: [LocalVariableAccess] __synth__2__1 +# 28| getArgument: [IntegerLiteral] 1 +# 28| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...) +# 29| getStmt: [AssignAddExpr] ... += ... +# 29| getDesugared: [AssignExpr] ... = ... +# 29| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 29| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 29| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 29| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 30| getStmt: [AssignDivExpr] ... /= ... +# 30| getDesugared: [AssignExpr] ... = ... +# 30| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 30| getAnOperand/getRightOperand: [DivExpr] ... / ... +# 30| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 30| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 31| getStmt: [BreakStmt] break # 36| [AssignAddExpr] ... += ... # 36| getDesugared: [AssignExpr] ... = ... # 36| getAnOperand/getLeftOperand: [LocalVariableAccess] x @@ -985,29 +1069,37 @@ params/params.rb: # 21| getReceiver: [ConstantReadAccess] Array erb/template.html.erb: # 27| [ForExpr] for ... in ... -# 27| getDesugared: [MethodCall] call to each -# 27| getReceiver: [ArrayLiteral] [...] -# 27| getDesugared: [MethodCall] call to [] -# 27| getReceiver: [ConstantReadAccess] Array -# 27| getArgument: [StringLiteral] "foo" -# 27| getComponent: [StringTextComponent] foo -# 27| getArgument: [StringLiteral] "bar" -# 27| getComponent: [StringTextComponent] bar -# 27| getArgument: [StringLiteral] "baz" -# 27| getComponent: [StringTextComponent] baz -# 27| getBlock: [BraceBlock] { ... } -# 27| getParameter: [SimpleParameter] __synth__0__1 -# 27| getDefiningAccess: [LocalVariableAccess] __synth__0__1 -# 27| getStmt: [AssignExpr] ... = ... +# 27| getDesugared: [StmtSequence] ... +# 27| getStmt: [IfExpr] if ... +# 27| getCondition: [NotExpr] ! ... +# 27| getAnOperand/getOperand/getReceiver: [DefinedExpr] defined? ... +# 27| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] x +# 27| getBranch/getThen: [AssignExpr] ... = ... # 27| getAnOperand/getLeftOperand: [LocalVariableAccess] x -# 27| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 -# 28| getStmt: [AssignAddExpr] ... += ... -# 28| getDesugared: [AssignExpr] ... = ... -# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] xs -# 28| getAnOperand/getRightOperand: [AddExpr] ... + ... -# 28| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] xs -# 28| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] x -# 29| getStmt: [LocalVariableAccess] xs +# 27| getAnOperand/getRightOperand: [NilLiteral] nil +# 27| getStmt: [MethodCall] call to each +# 27| getReceiver: [ArrayLiteral] [...] +# 27| getDesugared: [MethodCall] call to [] +# 27| getReceiver: [ConstantReadAccess] Array +# 27| getArgument: [StringLiteral] "foo" +# 27| getComponent: [StringTextComponent] foo +# 27| getArgument: [StringLiteral] "bar" +# 27| getComponent: [StringTextComponent] bar +# 27| getArgument: [StringLiteral] "baz" +# 27| getComponent: [StringTextComponent] baz +# 27| getBlock: [BraceBlock] { ... } +# 27| getParameter: [SimpleParameter] __synth__0__1 +# 27| getDefiningAccess: [LocalVariableAccess] __synth__0__1 +# 27| getStmt: [AssignExpr] ... = ... +# 27| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 27| getAnOperand/getRightOperand: [LocalVariableAccess] __synth__0__1 +# 28| getStmt: [AssignAddExpr] ... += ... +# 28| getDesugared: [AssignExpr] ... = ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] xs +# 28| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 28| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] xs +# 28| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] x +# 29| getStmt: [LocalVariableAccess] xs gems/test.gemspec: # 2| [AssignExpr] ... = ... # 2| getDesugared: [StmtSequence] ... diff --git a/ruby/ql/test/library-tests/ast/ValueText.expected b/ruby/ql/test/library-tests/ast/ValueText.expected index 612b58e41e3..ff087d7d72d 100644 --- a/ruby/ql/test/library-tests/ast/ValueText.expected +++ b/ruby/ql/test/library-tests/ast/ValueText.expected @@ -10,6 +10,8 @@ exprValue | calls/calls.rb:26:7:26:7 | 1 | 1 | int | | calls/calls.rb:36:9:36:11 | 100 | 100 | int | | calls/calls.rb:36:14:36:16 | 200 | 200 | int | +| calls/calls.rb:226:5:226:5 | nil | nil | nil | +| calls/calls.rb:229:5:229:5 | nil | nil | nil | | calls/calls.rb:280:5:280:8 | :blah | :blah | symbol | | calls/calls.rb:281:5:281:8 | :blah | :blah | symbol | | calls/calls.rb:290:11:290:16 | "blah" | blah | string | @@ -58,8 +60,11 @@ exprValue | calls/calls.rb:322:37:322:37 | 2 | 2 | int | | calls/calls.rb:330:31:330:37 | "error" | error | string | | calls/calls.rb:342:5:342:5 | 0 | 0 | int | +| calls/calls.rb:342:5:342:5 | nil | nil | nil | | calls/calls.rb:342:8:342:8 | 1 | 1 | int | +| calls/calls.rb:342:8:342:8 | nil | nil | nil | | calls/calls.rb:342:11:342:11 | 2 | 2 | int | +| calls/calls.rb:342:11:342:11 | nil | nil | nil | | calls/calls.rb:342:18:342:18 | 1 | 1 | int | | calls/calls.rb:342:20:342:20 | 2 | 2 | int | | calls/calls.rb:342:22:342:22 | 3 | 3 | int | @@ -330,18 +335,24 @@ exprValue | control/loops.rb:4:5:4:5 | 0 | 0 | int | | control/loops.rb:5:5:5:5 | 0 | 0 | int | | control/loops.rb:6:5:6:5 | 0 | 0 | int | +| control/loops.rb:9:5:9:5 | nil | nil | nil | | control/loops.rb:9:10:9:10 | 1 | 1 | int | | control/loops.rb:9:13:9:14 | 10 | 10 | int | +| control/loops.rb:16:5:16:5 | nil | nil | nil | | control/loops.rb:16:10:16:10 | 1 | 1 | int | | control/loops.rb:16:13:16:14 | 10 | 10 | int | | control/loops.rb:22:5:22:7 | 0 | 0 | int | +| control/loops.rb:22:5:22:7 | nil | nil | nil | | control/loops.rb:22:10:22:14 | 1 | 1 | int | +| control/loops.rb:22:10:22:14 | nil | nil | nil | | control/loops.rb:22:20:22:22 | :foo | :foo | symbol | | control/loops.rb:22:25:22:25 | 0 | 0 | int | | control/loops.rb:22:28:22:30 | :bar | :bar | symbol | | control/loops.rb:22:33:22:33 | 1 | 1 | int | | control/loops.rb:28:6:28:8 | 0 | 0 | int | +| control/loops.rb:28:6:28:8 | nil | nil | nil | | control/loops.rb:28:11:28:15 | 1 | 1 | int | +| control/loops.rb:28:11:28:15 | nil | nil | nil | | control/loops.rb:28:22:28:24 | :foo | :foo | symbol | | control/loops.rb:28:27:28:27 | 0 | 0 | int | | control/loops.rb:28:30:28:32 | :bar | :bar | symbol | @@ -365,6 +376,7 @@ exprValue | control/loops.rb:66:11:66:11 | y | 0 | int | | erb/template.html.erb:19:5:19:17 | "hello world" | hello world | string | | erb/template.html.erb:25:9:25:10 | "" | | string | +| erb/template.html.erb:27:10:27:10 | nil | nil | nil | | erb/template.html.erb:27:16:27:20 | "foo" | foo | string | | erb/template.html.erb:27:23:27:27 | "bar" | bar | string | | erb/template.html.erb:27:30:27:34 | "baz" | baz | string | @@ -934,6 +946,8 @@ exprCfgNodeValue | calls/calls.rb:26:7:26:7 | 1 | 1 | int | | calls/calls.rb:36:9:36:11 | 100 | 100 | int | | calls/calls.rb:36:14:36:16 | 200 | 200 | int | +| calls/calls.rb:226:5:226:5 | nil | nil | nil | +| calls/calls.rb:229:5:229:5 | nil | nil | nil | | calls/calls.rb:280:5:280:8 | :blah | :blah | symbol | | calls/calls.rb:281:5:281:8 | :blah | :blah | symbol | | calls/calls.rb:290:11:290:16 | "blah" | blah | string | @@ -982,8 +996,11 @@ exprCfgNodeValue | calls/calls.rb:322:37:322:37 | 2 | 2 | int | | calls/calls.rb:330:31:330:37 | "error" | error | string | | calls/calls.rb:342:5:342:5 | 0 | 0 | int | +| calls/calls.rb:342:5:342:5 | nil | nil | nil | | calls/calls.rb:342:8:342:8 | 1 | 1 | int | +| calls/calls.rb:342:8:342:8 | nil | nil | nil | | calls/calls.rb:342:11:342:11 | 2 | 2 | int | +| calls/calls.rb:342:11:342:11 | nil | nil | nil | | calls/calls.rb:342:18:342:18 | 1 | 1 | int | | calls/calls.rb:342:20:342:20 | 2 | 2 | int | | calls/calls.rb:342:22:342:22 | 3 | 3 | int | @@ -1226,18 +1243,24 @@ exprCfgNodeValue | control/loops.rb:4:5:4:5 | 0 | 0 | int | | control/loops.rb:5:5:5:5 | 0 | 0 | int | | control/loops.rb:6:5:6:5 | 0 | 0 | int | +| control/loops.rb:9:5:9:5 | nil | nil | nil | | control/loops.rb:9:10:9:10 | 1 | 1 | int | | control/loops.rb:9:13:9:14 | 10 | 10 | int | +| control/loops.rb:16:5:16:5 | nil | nil | nil | | control/loops.rb:16:10:16:10 | 1 | 1 | int | | control/loops.rb:16:13:16:14 | 10 | 10 | int | | control/loops.rb:22:5:22:7 | 0 | 0 | int | +| control/loops.rb:22:5:22:7 | nil | nil | nil | | control/loops.rb:22:10:22:14 | 1 | 1 | int | +| control/loops.rb:22:10:22:14 | nil | nil | nil | | control/loops.rb:22:20:22:22 | :foo | :foo | symbol | | control/loops.rb:22:25:22:25 | 0 | 0 | int | | control/loops.rb:22:28:22:30 | :bar | :bar | symbol | | control/loops.rb:22:33:22:33 | 1 | 1 | int | | control/loops.rb:28:6:28:8 | 0 | 0 | int | +| control/loops.rb:28:6:28:8 | nil | nil | nil | | control/loops.rb:28:11:28:15 | 1 | 1 | int | +| control/loops.rb:28:11:28:15 | nil | nil | nil | | control/loops.rb:28:22:28:24 | :foo | :foo | symbol | | control/loops.rb:28:27:28:27 | 0 | 0 | int | | control/loops.rb:28:30:28:32 | :bar | :bar | symbol | @@ -1261,6 +1284,7 @@ exprCfgNodeValue | control/loops.rb:66:11:66:11 | y | 0 | int | | erb/template.html.erb:19:5:19:17 | "hello world" | hello world | string | | erb/template.html.erb:25:9:25:10 | "" | | string | +| erb/template.html.erb:27:10:27:10 | nil | nil | nil | | erb/template.html.erb:27:16:27:20 | "foo" | foo | string | | erb/template.html.erb:27:23:27:27 | "bar" | bar | string | | erb/template.html.erb:27:30:27:34 | "baz" | baz | string | diff --git a/ruby/ql/test/library-tests/ast/calls/calls.expected b/ruby/ql/test/library-tests/ast/calls/calls.expected index 628b3578994..da4849487f0 100644 --- a/ruby/ql/test/library-tests/ast/calls/calls.expected +++ b/ruby/ql/test/library-tests/ast/calls/calls.expected @@ -251,9 +251,13 @@ callsWithReceiver | calls.rb:223:1:223:6 | call to bar | calls.rb:223:1:223:1 | X | | calls.rb:223:14:223:19 | call to foo | calls.rb:223:14:223:14 | X | | calls.rb:226:1:228:3 | call to each | calls.rb:226:10:226:12 | call to bar | +| calls.rb:226:5:226:5 | ! ... | calls.rb:226:5:226:5 | defined? ... | +| calls.rb:226:5:226:5 | defined? ... | calls.rb:226:5:226:5 | x | | calls.rb:226:10:226:12 | call to bar | calls.rb:226:10:226:12 | self | | calls.rb:227:3:227:5 | call to baz | calls.rb:227:3:227:5 | self | | calls.rb:229:1:231:3 | call to each | calls.rb:229:10:229:15 | call to bar | +| calls.rb:229:5:229:5 | ! ... | calls.rb:229:5:229:5 | defined? ... | +| calls.rb:229:5:229:5 | defined? ... | calls.rb:229:5:229:5 | x | | calls.rb:229:10:229:15 | call to bar | calls.rb:229:10:229:10 | X | | calls.rb:230:3:230:8 | call to baz | calls.rb:230:3:230:3 | X | | calls.rb:234:1:234:3 | call to foo | calls.rb:234:1:234:3 | self | @@ -376,9 +380,15 @@ callsWithReceiver | calls.rb:338:3:338:13 | call to bar | calls.rb:338:3:338:13 | self | | calls.rb:342:1:344:3 | * ... | calls.rb:342:1:344:3 | __synth__0__1 | | calls.rb:342:1:344:3 | call to each | calls.rb:342:16:342:33 | [...] | +| calls.rb:342:5:342:5 | ! ... | calls.rb:342:5:342:5 | defined? ... | | calls.rb:342:5:342:5 | call to [] | calls.rb:342:5:342:5 | __synth__3__1 | +| calls.rb:342:5:342:5 | defined? ... | calls.rb:342:5:342:5 | x | +| calls.rb:342:8:342:8 | ! ... | calls.rb:342:8:342:8 | defined? ... | | calls.rb:342:8:342:8 | call to [] | calls.rb:342:8:342:8 | __synth__3__1 | +| calls.rb:342:8:342:8 | defined? ... | calls.rb:342:8:342:8 | y | +| calls.rb:342:11:342:11 | ! ... | calls.rb:342:11:342:11 | defined? ... | | calls.rb:342:11:342:11 | call to [] | calls.rb:342:11:342:11 | __synth__3__1 | +| calls.rb:342:11:342:11 | defined? ... | calls.rb:342:11:342:11 | z | | calls.rb:342:16:342:33 | call to [] | calls.rb:342:16:342:33 | Array | | calls.rb:342:17:342:23 | call to [] | calls.rb:342:17:342:23 | Array | | calls.rb:342:26:342:32 | call to [] | calls.rb:342:26:342:32 | Array | diff --git a/ruby/ql/test/library-tests/ast/control/ConditionalExpr.expected b/ruby/ql/test/library-tests/ast/control/ConditionalExpr.expected index 3d78655dbbb..db7cd1957fb 100644 --- a/ruby/ql/test/library-tests/ast/control/ConditionalExpr.expected +++ b/ruby/ql/test/library-tests/ast/control/ConditionalExpr.expected @@ -22,6 +22,12 @@ conditionalExprs | conditionals.rb:61:1:64:3 | if ... | IfExpr | conditionals.rb:61:4:61:8 | ... > ... | conditionals.rb:63:1:63:4 | else ... | false | | conditionals.rb:67:1:70:3 | if ... | IfExpr | conditionals.rb:67:4:67:8 | ... > ... | conditionals.rb:67:10:67:13 | then ... | true | | conditionals.rb:67:1:70:3 | if ... | IfExpr | conditionals.rb:67:4:67:8 | ... > ... | conditionals.rb:68:1:69:5 | else ... | false | +| loops.rb:9:5:9:5 | if ... | IfExpr | loops.rb:9:5:9:5 | ! ... | loops.rb:9:5:9:5 | ... = ... | true | +| loops.rb:16:5:16:5 | if ... | IfExpr | loops.rb:16:5:16:5 | ! ... | loops.rb:16:5:16:5 | ... = ... | true | +| loops.rb:22:5:22:7 | if ... | IfExpr | loops.rb:22:5:22:7 | ! ... | loops.rb:22:5:22:7 | ... = ... | true | +| loops.rb:22:10:22:14 | if ... | IfExpr | loops.rb:22:10:22:14 | ! ... | loops.rb:22:10:22:14 | ... = ... | true | +| loops.rb:28:6:28:8 | if ... | IfExpr | loops.rb:28:6:28:8 | ! ... | loops.rb:28:6:28:8 | ... = ... | true | +| loops.rb:28:11:28:15 | if ... | IfExpr | loops.rb:28:11:28:15 | ! ... | loops.rb:28:11:28:15 | ... = ... | true | ifExprs | conditionals.rb:10:1:12:3 | if ... | IfExpr | conditionals.rb:10:4:10:8 | ... > ... | conditionals.rb:10:10:11:5 | then ... | (none) | false | | conditionals.rb:15:1:19:3 | if ... | IfExpr | conditionals.rb:15:4:15:9 | ... == ... | conditionals.rb:15:10:16:5 | then ... | else ... | false | diff --git a/ruby/ql/test/library-tests/ast/control/ControlExpr.expected b/ruby/ql/test/library-tests/ast/control/ControlExpr.expected index 933b4561162..c4d09535ccf 100644 --- a/ruby/ql/test/library-tests/ast/control/ControlExpr.expected +++ b/ruby/ql/test/library-tests/ast/control/ControlExpr.expected @@ -27,9 +27,15 @@ | conditionals.rb:61:1:64:3 | if ... | IfExpr | | conditionals.rb:67:1:70:3 | if ... | IfExpr | | loops.rb:9:1:12:3 | for ... in ... | ForExpr | +| loops.rb:9:5:9:5 | if ... | IfExpr | | loops.rb:16:1:19:3 | for ... in ... | ForExpr | +| loops.rb:16:5:16:5 | if ... | IfExpr | | loops.rb:22:1:25:3 | for ... in ... | ForExpr | +| loops.rb:22:5:22:7 | if ... | IfExpr | +| loops.rb:22:10:22:14 | if ... | IfExpr | | loops.rb:28:1:32:3 | for ... in ... | ForExpr | +| loops.rb:28:6:28:8 | if ... | IfExpr | +| loops.rb:28:11:28:15 | if ... | IfExpr | | loops.rb:35:1:39:3 | while ... | WhileExpr | | loops.rb:42:1:45:3 | while ... | WhileExpr | | loops.rb:48:1:48:19 | ... while ... | WhileModifierExpr | diff --git a/ruby/ql/test/library-tests/controlflow/graph/Cfg.expected b/ruby/ql/test/library-tests/controlflow/graph/Cfg.expected index 071f118db84..e3b0e01adfe 100644 --- a/ruby/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ruby/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -2377,7 +2377,7 @@ cfg.rb: #-----| -> \u1234 # 88| ... = ... -#-----| -> Array +#-----| -> x # 88| "\u1234#{...}\n" #-----| -> ... = ... @@ -2394,9 +2394,21 @@ cfg.rb: # 88| \n #-----| -> "\u1234#{...}\n" +# 90| ... +#-----| -> $global + +# 90| ... = ... +#-----| -> if ... + # 90| ... = ... #-----| -> x +# 90| [false] ! ... +#-----| false -> if ... + +# 90| [true] ! ... +#-----| true -> x + # 90| __synth__0__1 #-----| -> x @@ -2404,7 +2416,11 @@ cfg.rb: #-----| -> ... = ... # 90| call to each -#-----| -> $global +#-----| -> ... + +# 90| defined? ... +#-----| false -> [true] ! ... +#-----| true -> [false] ! ... # 90| enter { ... } #-----| -> __synth__0__1 @@ -2414,6 +2430,18 @@ cfg.rb: # 90| exit { ... } (normal) #-----| -> exit { ... } +# 90| if ... +#-----| -> Array + +# 90| nil +#-----| -> ... = ... + +# 90| x +#-----| -> nil + +# 90| x +#-----| -> defined? ... + # 90| { ... } #-----| -> call to each diff --git a/ruby/ql/test/library-tests/controlflow/graph/Nodes.expected b/ruby/ql/test/library-tests/controlflow/graph/Nodes.expected index 0e39e2924bd..f7bc8a1b1e7 100644 --- a/ruby/ql/test/library-tests/controlflow/graph/Nodes.expected +++ b/ruby/ql/test/library-tests/controlflow/graph/Nodes.expected @@ -26,6 +26,9 @@ callsWithNoArguments | cfg.rb:62:7:62:12 | * ... | | cfg.rb:62:17:62:27 | * ... | | cfg.rb:90:1:93:3 | call to each | +| cfg.rb:90:5:90:5 | [false] ! ... | +| cfg.rb:90:5:90:5 | [true] ! ... | +| cfg.rb:90:5:90:5 | defined? ... | | cfg.rb:98:10:98:15 | ** ... | | cfg.rb:98:30:98:35 | ** ... | | cfg.rb:138:17:138:23 | * ... | diff --git a/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected b/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected index 5374f468c3e..d4416fc3e56 100644 --- a/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected +++ b/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected @@ -15,7 +15,6 @@ testFailures | array_flow.rb:376:10:376:13 | ...[...] | Unexpected result: hasValueFlow=42.3 | | array_flow.rb:377:10:377:13 | ...[...] | Unexpected result: hasValueFlow=42.3 | | array_flow.rb:378:10:378:13 | ...[...] | Unexpected result: hasValueFlow=42.3 | -| array_flow.rb:407:12:407:30 | # $ hasValueFlow=45 | Missing result:hasValueFlow=45 | | array_flow.rb:484:10:484:13 | ...[...] | Unexpected result: hasValueFlow=54.3 | | array_flow.rb:484:10:484:13 | ...[...] | Unexpected result: hasValueFlow=54.4 | | array_flow.rb:484:10:484:13 | ...[...] | Unexpected result: hasValueFlow=54.5 | diff --git a/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected index 60f62362f75..cbba246c74b 100644 --- a/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected +++ b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected @@ -2399,6 +2399,7 @@ | UseUseExplosion.rb:24:5:25:7 | use | UseUseExplosion.rb:1:1:26:3 | C | | local_dataflow.rb:1:1:7:3 | self (foo) | local_dataflow.rb:3:8:3:10 | self | | local_dataflow.rb:1:1:7:3 | self in foo | local_dataflow.rb:1:1:7:3 | self (foo) | +| local_dataflow.rb:1:1:150:3 | | local_dataflow.rb:10:9:10:9 | x | | local_dataflow.rb:1:1:150:3 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self | | local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:1:9:1:9 | a | | local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:2:7:2:7 | a | @@ -2424,21 +2425,33 @@ | local_dataflow.rb:9:1:9:5 | array | local_dataflow.rb:10:14:10:18 | array | | local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:5 | array | | local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... | +| local_dataflow.rb:10:5:13:3 | ... | local_dataflow.rb:10:1:13:3 | ... = ... | | local_dataflow.rb:10:5:13:3 | self | local_dataflow.rb:11:1:11:2 | self | +| local_dataflow.rb:10:5:13:3 | x | local_dataflow.rb:15:5:15:5 | x | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | ... = ... | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | __synth__0__1 | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | __synth__0__1 | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:9:10:9 | x | -| local_dataflow.rb:10:5:13:3 | call to each | local_dataflow.rb:10:1:13:3 | ... = ... | +| local_dataflow.rb:10:5:13:3 | call to each | local_dataflow.rb:10:5:13:3 | ... | +| local_dataflow.rb:10:9:10:9 | ... = ... | local_dataflow.rb:10:9:10:9 | if ... | +| local_dataflow.rb:10:9:10:9 | nil | local_dataflow.rb:10:9:10:9 | ... = ... | +| local_dataflow.rb:10:9:10:9 | nil | local_dataflow.rb:10:9:10:9 | x | +| local_dataflow.rb:10:9:10:9 | x | local_dataflow.rb:10:9:10:9 | phi | | local_dataflow.rb:10:9:10:9 | x | local_dataflow.rb:12:5:12:5 | x | | local_dataflow.rb:10:14:10:18 | [post] array | local_dataflow.rb:15:10:15:14 | array | | local_dataflow.rb:10:14:10:18 | array | local_dataflow.rb:15:10:15:14 | array | | local_dataflow.rb:11:1:11:2 | [post] self | local_dataflow.rb:12:3:12:5 | self | | local_dataflow.rb:11:1:11:2 | self | local_dataflow.rb:12:3:12:5 | self | +| local_dataflow.rb:15:1:17:3 | x | local_dataflow.rb:19:5:19:5 | x | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:1:17:3 | ... = ... | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:1:17:3 | __synth__0__1 | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:1:17:3 | __synth__0__1 | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:5:15:5 | x | +| local_dataflow.rb:15:1:17:3 | call to each | local_dataflow.rb:15:1:17:3 | ... | +| local_dataflow.rb:15:5:15:5 | ... = ... | local_dataflow.rb:15:5:15:5 | if ... | +| local_dataflow.rb:15:5:15:5 | nil | local_dataflow.rb:15:5:15:5 | ... = ... | +| local_dataflow.rb:15:5:15:5 | nil | local_dataflow.rb:15:5:15:5 | x | +| local_dataflow.rb:15:5:15:5 | x | local_dataflow.rb:15:5:15:5 | phi | | local_dataflow.rb:15:10:15:14 | [post] array | local_dataflow.rb:19:10:19:14 | array | | local_dataflow.rb:15:10:15:14 | array | local_dataflow.rb:19:10:19:14 | array | | local_dataflow.rb:16:9:16:10 | 10 | local_dataflow.rb:16:3:16:10 | break | @@ -2446,6 +2459,11 @@ | local_dataflow.rb:19:1:21:3 | __synth__0__1 | local_dataflow.rb:19:1:21:3 | __synth__0__1 | | local_dataflow.rb:19:1:21:3 | __synth__0__1 | local_dataflow.rb:19:1:21:3 | __synth__0__1 | | local_dataflow.rb:19:1:21:3 | __synth__0__1 | local_dataflow.rb:19:5:19:5 | x | +| local_dataflow.rb:19:1:21:3 | call to each | local_dataflow.rb:19:1:21:3 | ... | +| local_dataflow.rb:19:5:19:5 | ... = ... | local_dataflow.rb:19:5:19:5 | if ... | +| local_dataflow.rb:19:5:19:5 | nil | local_dataflow.rb:19:5:19:5 | ... = ... | +| local_dataflow.rb:19:5:19:5 | nil | local_dataflow.rb:19:5:19:5 | x | +| local_dataflow.rb:19:5:19:5 | x | local_dataflow.rb:19:5:19:5 | phi | | local_dataflow.rb:19:5:19:5 | x | local_dataflow.rb:20:6:20:6 | x | | local_dataflow.rb:24:2:24:8 | break | local_dataflow.rb:23:1:25:3 | while ... | | local_dataflow.rb:24:8:24:8 | 5 | local_dataflow.rb:24:2:24:8 | break | diff --git a/ruby/ql/test/library-tests/dataflow/local/Nodes.expected b/ruby/ql/test/library-tests/dataflow/local/Nodes.expected index 2ac17e470d7..3b7dd05902d 100644 --- a/ruby/ql/test/library-tests/dataflow/local/Nodes.expected +++ b/ruby/ql/test/library-tests/dataflow/local/Nodes.expected @@ -832,13 +832,22 @@ arg | local_dataflow.rb:9:12:9:12 | 2 | local_dataflow.rb:9:9:9:15 | call to [] | position 1 | | local_dataflow.rb:9:14:9:14 | 3 | local_dataflow.rb:9:9:9:15 | call to [] | position 2 | | local_dataflow.rb:10:5:13:3 | { ... } | local_dataflow.rb:10:5:13:3 | call to each | block | +| local_dataflow.rb:10:9:10:9 | defined? ... | local_dataflow.rb:10:9:10:9 | [false] ! ... | self | +| local_dataflow.rb:10:9:10:9 | defined? ... | local_dataflow.rb:10:9:10:9 | [true] ! ... | self | +| local_dataflow.rb:10:9:10:9 | x | local_dataflow.rb:10:9:10:9 | defined? ... | self | | local_dataflow.rb:10:14:10:18 | array | local_dataflow.rb:10:5:13:3 | call to each | self | | local_dataflow.rb:11:1:11:2 | self | local_dataflow.rb:11:1:11:2 | call to do | self | | local_dataflow.rb:12:3:12:5 | self | local_dataflow.rb:12:3:12:5 | call to p | self | | local_dataflow.rb:12:5:12:5 | x | local_dataflow.rb:12:3:12:5 | call to p | position 0 | | local_dataflow.rb:15:1:17:3 | { ... } | local_dataflow.rb:15:1:17:3 | call to each | block | +| local_dataflow.rb:15:5:15:5 | defined? ... | local_dataflow.rb:15:5:15:5 | [false] ! ... | self | +| local_dataflow.rb:15:5:15:5 | defined? ... | local_dataflow.rb:15:5:15:5 | [true] ! ... | self | +| local_dataflow.rb:15:5:15:5 | x | local_dataflow.rb:15:5:15:5 | defined? ... | self | | local_dataflow.rb:15:10:15:14 | array | local_dataflow.rb:15:1:17:3 | call to each | self | | local_dataflow.rb:19:1:21:3 | { ... } | local_dataflow.rb:19:1:21:3 | call to each | block | +| local_dataflow.rb:19:5:19:5 | defined? ... | local_dataflow.rb:19:5:19:5 | [false] ! ... | self | +| local_dataflow.rb:19:5:19:5 | defined? ... | local_dataflow.rb:19:5:19:5 | [true] ! ... | self | +| local_dataflow.rb:19:5:19:5 | x | local_dataflow.rb:19:5:19:5 | defined? ... | self | | local_dataflow.rb:19:10:19:14 | array | local_dataflow.rb:19:1:21:3 | call to each | self | | local_dataflow.rb:20:6:20:6 | x | local_dataflow.rb:20:6:20:10 | ... > ... | self | | local_dataflow.rb:20:10:20:10 | 1 | local_dataflow.rb:20:6:20:10 | ... > ... | position 0 | diff --git a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected index 525470654b6..237c067be1b 100644 --- a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected +++ b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected @@ -2796,6 +2796,7 @@ | UseUseExplosion.rb:21:3675:21:3680 | call to use | UseUseExplosion.rb:21:3670:21:3680 | else ... | | UseUseExplosion.rb:21:3686:21:3696 | else ... | UseUseExplosion.rb:21:9:21:3700 | if ... | | UseUseExplosion.rb:21:3691:21:3696 | call to use | UseUseExplosion.rb:21:3686:21:3696 | else ... | +| UseUseExplosion.rb:24:5:25:7 | synthetic *args | UseUseExplosion.rb:24:13:24:13 | i | | UseUseExplosion.rb:24:5:25:7 | use | UseUseExplosion.rb:1:1:26:3 | C | | file://:0:0:0:0 | [summary param] position 0 in & | file://:0:0:0:0 | [summary] read: Argument[0].Element[any] in & | | file://:0:0:0:0 | [summary param] position 0 in + | file://:0:0:0:0 | [summary] read: Argument[0].Element[any] in + | @@ -2840,6 +2841,8 @@ | file://:0:0:0:0 | [summary] read: Argument[0].Element[any] in Hash[] | file://:0:0:0:0 | [summary] read: Argument[0].Element[any].Element[1] in Hash[] | | local_dataflow.rb:1:1:7:3 | self (foo) | local_dataflow.rb:3:8:3:10 | self | | local_dataflow.rb:1:1:7:3 | self in foo | local_dataflow.rb:1:1:7:3 | self (foo) | +| local_dataflow.rb:1:1:7:3 | synthetic *args | local_dataflow.rb:1:9:1:9 | a | +| local_dataflow.rb:1:1:150:3 | | local_dataflow.rb:10:9:10:9 | x | | local_dataflow.rb:1:1:150:3 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self | | local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:1:9:1:9 | a | | local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:2:7:2:7 | a | @@ -2868,21 +2871,41 @@ | local_dataflow.rb:9:9:9:15 | Array | local_dataflow.rb:9:9:9:15 | call to [] | | local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:5 | array | | local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... | +| local_dataflow.rb:10:5:13:3 | ... | local_dataflow.rb:10:1:13:3 | ... = ... | | local_dataflow.rb:10:5:13:3 | self | local_dataflow.rb:11:1:11:2 | self | +| local_dataflow.rb:10:5:13:3 | x | local_dataflow.rb:15:5:15:5 | x | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | ... = ... | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | __synth__0__1 | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | __synth__0__1 | | local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:9:10:9 | x | -| local_dataflow.rb:10:5:13:3 | call to each | local_dataflow.rb:10:1:13:3 | ... = ... | +| local_dataflow.rb:10:5:13:3 | call to each | local_dataflow.rb:10:5:13:3 | ... | +| local_dataflow.rb:10:5:13:3 | synthetic *args | local_dataflow.rb:10:5:13:3 | __synth__0__1 | +| local_dataflow.rb:10:9:10:9 | ... = ... | local_dataflow.rb:10:9:10:9 | if ... | +| local_dataflow.rb:10:9:10:9 | defined? ... | local_dataflow.rb:10:9:10:9 | [false] ! ... | +| local_dataflow.rb:10:9:10:9 | defined? ... | local_dataflow.rb:10:9:10:9 | [true] ! ... | +| local_dataflow.rb:10:9:10:9 | nil | local_dataflow.rb:10:9:10:9 | ... = ... | +| local_dataflow.rb:10:9:10:9 | nil | local_dataflow.rb:10:9:10:9 | x | +| local_dataflow.rb:10:9:10:9 | x | local_dataflow.rb:10:9:10:9 | defined? ... | +| local_dataflow.rb:10:9:10:9 | x | local_dataflow.rb:10:9:10:9 | phi | | local_dataflow.rb:10:9:10:9 | x | local_dataflow.rb:12:5:12:5 | x | | local_dataflow.rb:10:14:10:18 | [post] array | local_dataflow.rb:15:10:15:14 | array | | local_dataflow.rb:10:14:10:18 | array | local_dataflow.rb:15:10:15:14 | array | | local_dataflow.rb:11:1:11:2 | [post] self | local_dataflow.rb:12:3:12:5 | self | | local_dataflow.rb:11:1:11:2 | self | local_dataflow.rb:12:3:12:5 | self | +| local_dataflow.rb:15:1:17:3 | x | local_dataflow.rb:19:5:19:5 | x | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:1:17:3 | ... = ... | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:1:17:3 | __synth__0__1 | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:1:17:3 | __synth__0__1 | | local_dataflow.rb:15:1:17:3 | __synth__0__1 | local_dataflow.rb:15:5:15:5 | x | +| local_dataflow.rb:15:1:17:3 | call to each | local_dataflow.rb:15:1:17:3 | ... | +| local_dataflow.rb:15:1:17:3 | synthetic *args | local_dataflow.rb:15:1:17:3 | __synth__0__1 | +| local_dataflow.rb:15:5:15:5 | ... = ... | local_dataflow.rb:15:5:15:5 | if ... | +| local_dataflow.rb:15:5:15:5 | defined? ... | local_dataflow.rb:15:5:15:5 | [false] ! ... | +| local_dataflow.rb:15:5:15:5 | defined? ... | local_dataflow.rb:15:5:15:5 | [true] ! ... | +| local_dataflow.rb:15:5:15:5 | nil | local_dataflow.rb:15:5:15:5 | ... = ... | +| local_dataflow.rb:15:5:15:5 | nil | local_dataflow.rb:15:5:15:5 | x | +| local_dataflow.rb:15:5:15:5 | x | local_dataflow.rb:15:5:15:5 | defined? ... | +| local_dataflow.rb:15:5:15:5 | x | local_dataflow.rb:15:5:15:5 | phi | | local_dataflow.rb:15:10:15:14 | [post] array | local_dataflow.rb:19:10:19:14 | array | | local_dataflow.rb:15:10:15:14 | array | local_dataflow.rb:19:10:19:14 | array | | local_dataflow.rb:16:9:16:10 | 10 | local_dataflow.rb:16:3:16:10 | break | @@ -2890,6 +2913,15 @@ | local_dataflow.rb:19:1:21:3 | __synth__0__1 | local_dataflow.rb:19:1:21:3 | __synth__0__1 | | local_dataflow.rb:19:1:21:3 | __synth__0__1 | local_dataflow.rb:19:1:21:3 | __synth__0__1 | | local_dataflow.rb:19:1:21:3 | __synth__0__1 | local_dataflow.rb:19:5:19:5 | x | +| local_dataflow.rb:19:1:21:3 | call to each | local_dataflow.rb:19:1:21:3 | ... | +| local_dataflow.rb:19:1:21:3 | synthetic *args | local_dataflow.rb:19:1:21:3 | __synth__0__1 | +| local_dataflow.rb:19:5:19:5 | ... = ... | local_dataflow.rb:19:5:19:5 | if ... | +| local_dataflow.rb:19:5:19:5 | defined? ... | local_dataflow.rb:19:5:19:5 | [false] ! ... | +| local_dataflow.rb:19:5:19:5 | defined? ... | local_dataflow.rb:19:5:19:5 | [true] ! ... | +| local_dataflow.rb:19:5:19:5 | nil | local_dataflow.rb:19:5:19:5 | ... = ... | +| local_dataflow.rb:19:5:19:5 | nil | local_dataflow.rb:19:5:19:5 | x | +| local_dataflow.rb:19:5:19:5 | x | local_dataflow.rb:19:5:19:5 | defined? ... | +| local_dataflow.rb:19:5:19:5 | x | local_dataflow.rb:19:5:19:5 | phi | | local_dataflow.rb:19:5:19:5 | x | local_dataflow.rb:20:6:20:6 | x | | local_dataflow.rb:20:6:20:6 | x | local_dataflow.rb:20:6:20:10 | ... > ... | | local_dataflow.rb:20:10:20:10 | 1 | local_dataflow.rb:20:6:20:10 | ... > ... | @@ -2901,11 +2933,13 @@ | local_dataflow.rb:30:14:30:20 | "class" | local_dataflow.rb:30:5:30:24 | C | | local_dataflow.rb:32:5:32:25 | bar | local_dataflow.rb:32:1:32:1 | x | | local_dataflow.rb:32:5:32:25 | bar | local_dataflow.rb:32:1:32:25 | ... = ... | +| local_dataflow.rb:34:1:39:3 | synthetic *args | local_dataflow.rb:34:7:34:7 | x | | local_dataflow.rb:34:7:34:7 | x | local_dataflow.rb:34:7:34:7 | x | | local_dataflow.rb:34:7:34:7 | x | local_dataflow.rb:35:6:35:6 | x | | local_dataflow.rb:35:6:35:6 | x | local_dataflow.rb:35:6:35:11 | ... == ... | | local_dataflow.rb:35:11:35:11 | 4 | local_dataflow.rb:35:6:35:11 | ... == ... | | local_dataflow.rb:36:13:36:13 | 7 | local_dataflow.rb:36:6:36:13 | return | +| local_dataflow.rb:41:1:47:3 | synthetic *args | local_dataflow.rb:41:7:41:7 | x | | local_dataflow.rb:41:7:41:7 | x | local_dataflow.rb:41:7:41:7 | x | | local_dataflow.rb:41:7:41:7 | x | local_dataflow.rb:42:6:42:6 | x | | local_dataflow.rb:42:6:42:6 | x | local_dataflow.rb:42:6:42:11 | ... == ... | @@ -2924,8 +2958,10 @@ | local_dataflow.rb:51:20:51:20 | x | local_dataflow.rb:51:20:51:24 | ... < ... | | local_dataflow.rb:51:24:51:24 | 9 | local_dataflow.rb:51:20:51:24 | ... < ... | | local_dataflow.rb:55:5:55:13 | Array | local_dataflow.rb:55:5:55:13 | call to [] | +| local_dataflow.rb:57:1:58:3 | synthetic *args | local_dataflow.rb:57:9:57:9 | x | | local_dataflow.rb:60:1:90:3 | self (test_case) | local_dataflow.rb:78:12:78:20 | self | | local_dataflow.rb:60:1:90:3 | self in test_case | local_dataflow.rb:60:1:90:3 | self (test_case) | +| local_dataflow.rb:60:1:90:3 | synthetic *args | local_dataflow.rb:60:15:60:15 | x | | local_dataflow.rb:60:15:60:15 | x | local_dataflow.rb:60:15:60:15 | x | | local_dataflow.rb:60:15:60:15 | x | local_dataflow.rb:61:12:61:12 | x | | local_dataflow.rb:61:7:68:5 | SSA phi read(x) | local_dataflow.rb:69:12:69:12 | x | @@ -3098,6 +3134,7 @@ | local_dataflow.rb:118:3:118:11 | call to source | local_dataflow.rb:118:3:118:31 | call to tap | | local_dataflow.rb:118:3:118:11 | self | local_dataflow.rb:119:3:119:31 | self | | local_dataflow.rb:118:17:118:31 | self | local_dataflow.rb:118:23:118:29 | self | +| local_dataflow.rb:118:17:118:31 | synthetic *args | local_dataflow.rb:118:20:118:20 | x | | local_dataflow.rb:118:20:118:20 | x | local_dataflow.rb:118:20:118:20 | x | | local_dataflow.rb:118:20:118:20 | x | local_dataflow.rb:118:28:118:28 | x | | local_dataflow.rb:119:3:119:31 | [post] self | local_dataflow.rb:119:8:119:16 | self | @@ -3112,8 +3149,10 @@ | local_dataflow.rb:123:8:123:20 | call to dup | local_dataflow.rb:123:8:123:45 | call to tap | | local_dataflow.rb:123:8:123:45 | call to tap | local_dataflow.rb:123:8:123:49 | call to dup | | local_dataflow.rb:123:26:123:45 | self | local_dataflow.rb:123:32:123:43 | self | +| local_dataflow.rb:123:26:123:45 | synthetic *args | local_dataflow.rb:123:29:123:29 | x | | local_dataflow.rb:126:1:128:3 | self (use) | local_dataflow.rb:127:3:127:8 | self | | local_dataflow.rb:126:1:128:3 | self in use | local_dataflow.rb:126:1:128:3 | self (use) | +| local_dataflow.rb:126:1:128:3 | synthetic *args | local_dataflow.rb:126:9:126:9 | x | | local_dataflow.rb:130:1:150:3 | self (use_use_madness) | local_dataflow.rb:132:6:132:11 | self | | local_dataflow.rb:130:1:150:3 | self in use_use_madness | local_dataflow.rb:130:1:150:3 | self (use_use_madness) | | local_dataflow.rb:131:3:131:3 | x | local_dataflow.rb:132:10:132:10 | x | diff --git a/ruby/ql/test/library-tests/dataflow/params/params-flow.expected b/ruby/ql/test/library-tests/dataflow/params/params-flow.expected index b54948d17a4..6ce6869c8d8 100644 --- a/ruby/ql/test/library-tests/dataflow/params/params-flow.expected +++ b/ruby/ql/test/library-tests/dataflow/params/params-flow.expected @@ -42,15 +42,67 @@ edges | params_flow.rb:41:24:41:29 | ** ... [element :p1] | params_flow.rb:16:13:16:14 | p1 | | params_flow.rb:41:26:41:29 | args [element :p1] | params_flow.rb:41:24:41:29 | ** ... [element :p1] | | params_flow.rb:44:12:44:20 | call to taint | params_flow.rb:9:16:9:17 | p1 | +| params_flow.rb:46:1:46:4 | args [element 0] | params_flow.rb:47:13:47:16 | args [element 0] | +| params_flow.rb:46:1:46:4 | args [element 1] | params_flow.rb:47:13:47:16 | args [element 1] | +| params_flow.rb:46:9:46:17 | call to taint | params_flow.rb:46:1:46:4 | args [element 0] | +| params_flow.rb:46:20:46:28 | call to taint | params_flow.rb:46:1:46:4 | args [element 1] | +| params_flow.rb:47:12:47:16 | * ... [element 0] | params_flow.rb:9:16:9:17 | p1 | +| params_flow.rb:47:12:47:16 | * ... [element 1] | params_flow.rb:9:20:9:21 | p2 | +| params_flow.rb:47:13:47:16 | args [element 0] | params_flow.rb:47:12:47:16 | * ... [element 0] | +| params_flow.rb:47:13:47:16 | args [element 1] | params_flow.rb:47:12:47:16 | * ... [element 1] | | params_flow.rb:49:13:49:14 | p1 | params_flow.rb:50:10:50:11 | p1 | -| params_flow.rb:54:9:54:17 | call to taint | params_flow.rb:49:13:49:14 | p1 | -| params_flow.rb:57:9:57:17 | call to taint | params_flow.rb:49:13:49:14 | p1 | -| params_flow.rb:62:1:62:4 | args | params_flow.rb:66:13:66:16 | args | -| params_flow.rb:62:8:62:16 | call to taint | params_flow.rb:62:1:62:4 | args | -| params_flow.rb:63:16:63:17 | *x [element 0] | params_flow.rb:64:10:64:10 | x [element 0] | -| params_flow.rb:64:10:64:10 | x [element 0] | params_flow.rb:64:10:64:13 | ...[...] | -| params_flow.rb:66:12:66:16 | * ... [element 0] | params_flow.rb:63:16:63:17 | *x [element 0] | -| params_flow.rb:66:13:66:16 | args | params_flow.rb:66:12:66:16 | * ... [element 0] | +| params_flow.rb:49:17:49:24 | *posargs [element 0] | params_flow.rb:51:11:51:17 | posargs [element 0] | +| params_flow.rb:51:11:51:17 | posargs [element 0] | params_flow.rb:51:11:51:20 | ...[...] | +| params_flow.rb:51:11:51:20 | ...[...] | params_flow.rb:51:10:51:21 | ( ... ) | +| params_flow.rb:55:9:55:17 | call to taint | params_flow.rb:49:13:49:14 | p1 | +| params_flow.rb:57:1:57:4 | args [element 0] | params_flow.rb:58:21:58:24 | args [element 0] | +| params_flow.rb:57:9:57:17 | call to taint | params_flow.rb:57:1:57:4 | args [element 0] | +| params_flow.rb:58:9:58:17 | call to taint | params_flow.rb:49:13:49:14 | p1 | +| params_flow.rb:58:20:58:24 | * ... [element 0] | params_flow.rb:49:17:49:24 | *posargs [element 0] | +| params_flow.rb:58:21:58:24 | args [element 0] | params_flow.rb:58:20:58:24 | * ... [element 0] | +| params_flow.rb:60:1:60:4 | args [element 0] | params_flow.rb:61:10:61:13 | args [element 0] | +| params_flow.rb:60:9:60:17 | call to taint | params_flow.rb:60:1:60:4 | args [element 0] | +| params_flow.rb:61:9:61:13 | * ... [element 0] | params_flow.rb:49:13:49:14 | p1 | +| params_flow.rb:61:10:61:13 | args [element 0] | params_flow.rb:61:9:61:13 | * ... [element 0] | +| params_flow.rb:63:1:63:4 | args | params_flow.rb:67:13:67:16 | args | +| params_flow.rb:63:8:63:16 | call to taint | params_flow.rb:63:1:63:4 | args | +| params_flow.rb:64:16:64:17 | *x [element 0] | params_flow.rb:65:10:65:10 | x [element 0] | +| params_flow.rb:65:10:65:10 | x [element 0] | params_flow.rb:65:10:65:13 | ...[...] | +| params_flow.rb:67:12:67:16 | * ... [element 0] | params_flow.rb:64:16:64:17 | *x [element 0] | +| params_flow.rb:67:13:67:16 | args | params_flow.rb:67:12:67:16 | * ... [element 0] | +| params_flow.rb:69:14:69:14 | x | params_flow.rb:70:10:70:10 | x | +| params_flow.rb:69:17:69:17 | y | params_flow.rb:71:10:71:10 | y | +| params_flow.rb:69:24:69:24 | w | params_flow.rb:74:10:74:10 | w | +| params_flow.rb:69:27:69:27 | r | params_flow.rb:75:10:75:10 | r | +| params_flow.rb:78:10:78:18 | call to taint | params_flow.rb:69:14:69:14 | x | +| params_flow.rb:78:21:78:29 | call to taint | params_flow.rb:69:17:69:17 | y | +| params_flow.rb:78:43:78:51 | call to taint | params_flow.rb:69:24:69:24 | w | +| params_flow.rb:78:54:78:62 | call to taint | params_flow.rb:69:27:69:27 | r | +| params_flow.rb:81:10:81:18 | call to taint | params_flow.rb:69:14:69:14 | x | +| params_flow.rb:83:14:83:14 | t | params_flow.rb:84:10:84:10 | t | +| params_flow.rb:83:17:83:17 | u | params_flow.rb:85:10:85:10 | u | +| params_flow.rb:83:23:83:23 | w | params_flow.rb:87:10:87:10 | w | +| params_flow.rb:94:10:94:18 | call to taint | params_flow.rb:83:14:83:14 | t | +| params_flow.rb:94:21:94:29 | call to taint | params_flow.rb:83:17:83:17 | u | +| params_flow.rb:94:39:94:47 | call to taint | params_flow.rb:83:23:83:23 | w | +| params_flow.rb:96:10:96:18 | call to taint | params_flow.rb:69:14:69:14 | x | +| params_flow.rb:96:21:96:29 | call to taint | params_flow.rb:69:17:69:17 | y | +| params_flow.rb:96:68:96:76 | call to taint | params_flow.rb:69:24:69:24 | w | +| params_flow.rb:96:79:96:87 | call to taint | params_flow.rb:69:27:69:27 | r | +| params_flow.rb:98:19:98:19 | a | params_flow.rb:99:10:99:10 | a | +| params_flow.rb:98:31:98:31 | b | params_flow.rb:102:10:102:10 | b | +| params_flow.rb:105:15:105:23 | call to taint | params_flow.rb:98:19:98:19 | a | +| params_flow.rb:106:15:106:23 | call to taint | params_flow.rb:98:19:98:19 | a | +| params_flow.rb:106:37:106:45 | call to taint | params_flow.rb:98:31:98:31 | b | +| params_flow.rb:108:37:108:37 | a | params_flow.rb:109:10:109:10 | a | +| params_flow.rb:108:44:108:44 | c | params_flow.rb:111:10:111:10 | c | +| params_flow.rb:114:33:114:41 | call to taint | params_flow.rb:108:37:108:37 | a | +| params_flow.rb:114:58:114:66 | call to taint | params_flow.rb:108:44:108:44 | c | +| params_flow.rb:117:1:117:1 | [post] x [element] | params_flow.rb:118:13:118:13 | x [element] | +| params_flow.rb:117:19:117:27 | call to taint | params_flow.rb:117:1:117:1 | [post] x [element] | +| params_flow.rb:118:12:118:13 | * ... [element] | params_flow.rb:9:16:9:17 | p1 | +| params_flow.rb:118:12:118:13 | * ... [element] | params_flow.rb:9:20:9:21 | p2 | +| params_flow.rb:118:13:118:13 | x [element] | params_flow.rb:118:12:118:13 | * ... [element] | nodes | params_flow.rb:9:16:9:17 | p1 | semmle.label | p1 | | params_flow.rb:9:20:9:21 | p2 | semmle.label | p2 | @@ -100,22 +152,89 @@ nodes | params_flow.rb:41:24:41:29 | ** ... [element :p1] | semmle.label | ** ... [element :p1] | | params_flow.rb:41:26:41:29 | args [element :p1] | semmle.label | args [element :p1] | | params_flow.rb:44:12:44:20 | call to taint | semmle.label | call to taint | +| params_flow.rb:46:1:46:4 | args [element 0] | semmle.label | args [element 0] | +| params_flow.rb:46:1:46:4 | args [element 1] | semmle.label | args [element 1] | +| params_flow.rb:46:9:46:17 | call to taint | semmle.label | call to taint | +| params_flow.rb:46:20:46:28 | call to taint | semmle.label | call to taint | +| params_flow.rb:47:12:47:16 | * ... [element 0] | semmle.label | * ... [element 0] | +| params_flow.rb:47:12:47:16 | * ... [element 1] | semmle.label | * ... [element 1] | +| params_flow.rb:47:13:47:16 | args [element 0] | semmle.label | args [element 0] | +| params_flow.rb:47:13:47:16 | args [element 1] | semmle.label | args [element 1] | | params_flow.rb:49:13:49:14 | p1 | semmle.label | p1 | +| params_flow.rb:49:17:49:24 | *posargs [element 0] | semmle.label | *posargs [element 0] | | params_flow.rb:50:10:50:11 | p1 | semmle.label | p1 | -| params_flow.rb:54:9:54:17 | call to taint | semmle.label | call to taint | +| params_flow.rb:51:10:51:21 | ( ... ) | semmle.label | ( ... ) | +| params_flow.rb:51:11:51:17 | posargs [element 0] | semmle.label | posargs [element 0] | +| params_flow.rb:51:11:51:20 | ...[...] | semmle.label | ...[...] | +| params_flow.rb:55:9:55:17 | call to taint | semmle.label | call to taint | +| params_flow.rb:57:1:57:4 | args [element 0] | semmle.label | args [element 0] | | params_flow.rb:57:9:57:17 | call to taint | semmle.label | call to taint | -| params_flow.rb:62:1:62:4 | args | semmle.label | args | -| params_flow.rb:62:8:62:16 | call to taint | semmle.label | call to taint | -| params_flow.rb:63:16:63:17 | *x [element 0] | semmle.label | *x [element 0] | -| params_flow.rb:64:10:64:10 | x [element 0] | semmle.label | x [element 0] | -| params_flow.rb:64:10:64:13 | ...[...] | semmle.label | ...[...] | -| params_flow.rb:66:12:66:16 | * ... [element 0] | semmle.label | * ... [element 0] | -| params_flow.rb:66:13:66:16 | args | semmle.label | args | +| params_flow.rb:58:9:58:17 | call to taint | semmle.label | call to taint | +| params_flow.rb:58:20:58:24 | * ... [element 0] | semmle.label | * ... [element 0] | +| params_flow.rb:58:21:58:24 | args [element 0] | semmle.label | args [element 0] | +| params_flow.rb:60:1:60:4 | args [element 0] | semmle.label | args [element 0] | +| params_flow.rb:60:9:60:17 | call to taint | semmle.label | call to taint | +| params_flow.rb:61:9:61:13 | * ... [element 0] | semmle.label | * ... [element 0] | +| params_flow.rb:61:10:61:13 | args [element 0] | semmle.label | args [element 0] | +| params_flow.rb:63:1:63:4 | args | semmle.label | args | +| params_flow.rb:63:8:63:16 | call to taint | semmle.label | call to taint | +| params_flow.rb:64:16:64:17 | *x [element 0] | semmle.label | *x [element 0] | +| params_flow.rb:65:10:65:10 | x [element 0] | semmle.label | x [element 0] | +| params_flow.rb:65:10:65:13 | ...[...] | semmle.label | ...[...] | +| params_flow.rb:67:12:67:16 | * ... [element 0] | semmle.label | * ... [element 0] | +| params_flow.rb:67:13:67:16 | args | semmle.label | args | +| params_flow.rb:69:14:69:14 | x | semmle.label | x | +| params_flow.rb:69:17:69:17 | y | semmle.label | y | +| params_flow.rb:69:24:69:24 | w | semmle.label | w | +| params_flow.rb:69:27:69:27 | r | semmle.label | r | +| params_flow.rb:70:10:70:10 | x | semmle.label | x | +| params_flow.rb:71:10:71:10 | y | semmle.label | y | +| params_flow.rb:74:10:74:10 | w | semmle.label | w | +| params_flow.rb:75:10:75:10 | r | semmle.label | r | +| params_flow.rb:78:10:78:18 | call to taint | semmle.label | call to taint | +| params_flow.rb:78:21:78:29 | call to taint | semmle.label | call to taint | +| params_flow.rb:78:43:78:51 | call to taint | semmle.label | call to taint | +| params_flow.rb:78:54:78:62 | call to taint | semmle.label | call to taint | +| params_flow.rb:81:10:81:18 | call to taint | semmle.label | call to taint | +| params_flow.rb:83:14:83:14 | t | semmle.label | t | +| params_flow.rb:83:17:83:17 | u | semmle.label | u | +| params_flow.rb:83:23:83:23 | w | semmle.label | w | +| params_flow.rb:84:10:84:10 | t | semmle.label | t | +| params_flow.rb:85:10:85:10 | u | semmle.label | u | +| params_flow.rb:87:10:87:10 | w | semmle.label | w | +| params_flow.rb:94:10:94:18 | call to taint | semmle.label | call to taint | +| params_flow.rb:94:21:94:29 | call to taint | semmle.label | call to taint | +| params_flow.rb:94:39:94:47 | call to taint | semmle.label | call to taint | +| params_flow.rb:96:10:96:18 | call to taint | semmle.label | call to taint | +| params_flow.rb:96:21:96:29 | call to taint | semmle.label | call to taint | +| params_flow.rb:96:68:96:76 | call to taint | semmle.label | call to taint | +| params_flow.rb:96:79:96:87 | call to taint | semmle.label | call to taint | +| params_flow.rb:98:19:98:19 | a | semmle.label | a | +| params_flow.rb:98:31:98:31 | b | semmle.label | b | +| params_flow.rb:99:10:99:10 | a | semmle.label | a | +| params_flow.rb:102:10:102:10 | b | semmle.label | b | +| params_flow.rb:105:15:105:23 | call to taint | semmle.label | call to taint | +| params_flow.rb:106:15:106:23 | call to taint | semmle.label | call to taint | +| params_flow.rb:106:37:106:45 | call to taint | semmle.label | call to taint | +| params_flow.rb:108:37:108:37 | a | semmle.label | a | +| params_flow.rb:108:44:108:44 | c | semmle.label | c | +| params_flow.rb:109:10:109:10 | a | semmle.label | a | +| params_flow.rb:111:10:111:10 | c | semmle.label | c | +| params_flow.rb:114:33:114:41 | call to taint | semmle.label | call to taint | +| params_flow.rb:114:58:114:66 | call to taint | semmle.label | call to taint | +| params_flow.rb:117:1:117:1 | [post] x [element] | semmle.label | [post] x [element] | +| params_flow.rb:117:19:117:27 | call to taint | semmle.label | call to taint | +| params_flow.rb:118:12:118:13 | * ... [element] | semmle.label | * ... [element] | +| params_flow.rb:118:13:118:13 | x [element] | semmle.label | x [element] | subpaths #select | params_flow.rb:10:10:10:11 | p1 | params_flow.rb:14:12:14:19 | call to taint | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:14:12:14:19 | call to taint | call to taint | | params_flow.rb:10:10:10:11 | p1 | params_flow.rb:44:12:44:20 | call to taint | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:44:12:44:20 | call to taint | call to taint | +| params_flow.rb:10:10:10:11 | p1 | params_flow.rb:46:9:46:17 | call to taint | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:46:9:46:17 | call to taint | call to taint | +| params_flow.rb:10:10:10:11 | p1 | params_flow.rb:117:19:117:27 | call to taint | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:117:19:117:27 | call to taint | call to taint | | params_flow.rb:11:10:11:11 | p2 | params_flow.rb:14:22:14:29 | call to taint | params_flow.rb:11:10:11:11 | p2 | $@ | params_flow.rb:14:22:14:29 | call to taint | call to taint | +| params_flow.rb:11:10:11:11 | p2 | params_flow.rb:46:20:46:28 | call to taint | params_flow.rb:11:10:11:11 | p2 | $@ | params_flow.rb:46:20:46:28 | call to taint | call to taint | +| params_flow.rb:11:10:11:11 | p2 | params_flow.rb:117:19:117:27 | call to taint | params_flow.rb:11:10:11:11 | p2 | $@ | params_flow.rb:117:19:117:27 | call to taint | call to taint | | params_flow.rb:17:10:17:11 | p1 | params_flow.rb:21:13:21:20 | call to taint | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:21:13:21:20 | call to taint | call to taint | | params_flow.rb:17:10:17:11 | p1 | params_flow.rb:22:27:22:34 | call to taint | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:22:27:22:34 | call to taint | call to taint | | params_flow.rb:17:10:17:11 | p1 | params_flow.rb:23:33:23:40 | call to taint | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:23:33:23:40 | call to taint | call to taint | @@ -131,6 +250,25 @@ subpaths | params_flow.rb:28:10:28:22 | ( ... ) | params_flow.rb:37:34:37:42 | call to taint | params_flow.rb:28:10:28:22 | ( ... ) | $@ | params_flow.rb:37:34:37:42 | call to taint | call to taint | | params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:33:41:33:49 | call to taint | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:33:41:33:49 | call to taint | call to taint | | params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:34:14:34:22 | call to taint | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:34:14:34:22 | call to taint | call to taint | -| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:54:9:54:17 | call to taint | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:54:9:54:17 | call to taint | call to taint | -| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:57:9:57:17 | call to taint | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:57:9:57:17 | call to taint | call to taint | -| params_flow.rb:64:10:64:13 | ...[...] | params_flow.rb:62:8:62:16 | call to taint | params_flow.rb:64:10:64:13 | ...[...] | $@ | params_flow.rb:62:8:62:16 | call to taint | call to taint | +| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:55:9:55:17 | call to taint | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:55:9:55:17 | call to taint | call to taint | +| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:58:9:58:17 | call to taint | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:58:9:58:17 | call to taint | call to taint | +| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:60:9:60:17 | call to taint | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:60:9:60:17 | call to taint | call to taint | +| params_flow.rb:51:10:51:21 | ( ... ) | params_flow.rb:57:9:57:17 | call to taint | params_flow.rb:51:10:51:21 | ( ... ) | $@ | params_flow.rb:57:9:57:17 | call to taint | call to taint | +| params_flow.rb:65:10:65:13 | ...[...] | params_flow.rb:63:8:63:16 | call to taint | params_flow.rb:65:10:65:13 | ...[...] | $@ | params_flow.rb:63:8:63:16 | call to taint | call to taint | +| params_flow.rb:70:10:70:10 | x | params_flow.rb:78:10:78:18 | call to taint | params_flow.rb:70:10:70:10 | x | $@ | params_flow.rb:78:10:78:18 | call to taint | call to taint | +| params_flow.rb:70:10:70:10 | x | params_flow.rb:81:10:81:18 | call to taint | params_flow.rb:70:10:70:10 | x | $@ | params_flow.rb:81:10:81:18 | call to taint | call to taint | +| params_flow.rb:70:10:70:10 | x | params_flow.rb:96:10:96:18 | call to taint | params_flow.rb:70:10:70:10 | x | $@ | params_flow.rb:96:10:96:18 | call to taint | call to taint | +| params_flow.rb:71:10:71:10 | y | params_flow.rb:78:21:78:29 | call to taint | params_flow.rb:71:10:71:10 | y | $@ | params_flow.rb:78:21:78:29 | call to taint | call to taint | +| params_flow.rb:71:10:71:10 | y | params_flow.rb:96:21:96:29 | call to taint | params_flow.rb:71:10:71:10 | y | $@ | params_flow.rb:96:21:96:29 | call to taint | call to taint | +| params_flow.rb:74:10:74:10 | w | params_flow.rb:78:43:78:51 | call to taint | params_flow.rb:74:10:74:10 | w | $@ | params_flow.rb:78:43:78:51 | call to taint | call to taint | +| params_flow.rb:74:10:74:10 | w | params_flow.rb:96:68:96:76 | call to taint | params_flow.rb:74:10:74:10 | w | $@ | params_flow.rb:96:68:96:76 | call to taint | call to taint | +| params_flow.rb:75:10:75:10 | r | params_flow.rb:78:54:78:62 | call to taint | params_flow.rb:75:10:75:10 | r | $@ | params_flow.rb:78:54:78:62 | call to taint | call to taint | +| params_flow.rb:75:10:75:10 | r | params_flow.rb:96:79:96:87 | call to taint | params_flow.rb:75:10:75:10 | r | $@ | params_flow.rb:96:79:96:87 | call to taint | call to taint | +| params_flow.rb:84:10:84:10 | t | params_flow.rb:94:10:94:18 | call to taint | params_flow.rb:84:10:84:10 | t | $@ | params_flow.rb:94:10:94:18 | call to taint | call to taint | +| params_flow.rb:85:10:85:10 | u | params_flow.rb:94:21:94:29 | call to taint | params_flow.rb:85:10:85:10 | u | $@ | params_flow.rb:94:21:94:29 | call to taint | call to taint | +| params_flow.rb:87:10:87:10 | w | params_flow.rb:94:39:94:47 | call to taint | params_flow.rb:87:10:87:10 | w | $@ | params_flow.rb:94:39:94:47 | call to taint | call to taint | +| params_flow.rb:99:10:99:10 | a | params_flow.rb:105:15:105:23 | call to taint | params_flow.rb:99:10:99:10 | a | $@ | params_flow.rb:105:15:105:23 | call to taint | call to taint | +| params_flow.rb:99:10:99:10 | a | params_flow.rb:106:15:106:23 | call to taint | params_flow.rb:99:10:99:10 | a | $@ | params_flow.rb:106:15:106:23 | call to taint | call to taint | +| params_flow.rb:102:10:102:10 | b | params_flow.rb:106:37:106:45 | call to taint | params_flow.rb:102:10:102:10 | b | $@ | params_flow.rb:106:37:106:45 | call to taint | call to taint | +| params_flow.rb:109:10:109:10 | a | params_flow.rb:114:33:114:41 | call to taint | params_flow.rb:109:10:109:10 | a | $@ | params_flow.rb:114:33:114:41 | call to taint | call to taint | +| params_flow.rb:111:10:111:10 | c | params_flow.rb:114:58:114:66 | call to taint | params_flow.rb:111:10:111:10 | c | $@ | params_flow.rb:114:58:114:66 | call to taint | call to taint | diff --git a/ruby/ql/test/library-tests/dataflow/params/params_flow.rb b/ruby/ql/test/library-tests/dataflow/params/params_flow.rb index 41ae58a58da..964f573fd64 100644 --- a/ruby/ql/test/library-tests/dataflow/params/params_flow.rb +++ b/ruby/ql/test/library-tests/dataflow/params/params_flow.rb @@ -7,8 +7,8 @@ def sink x end def positional(p1, p2) - sink p1 # $ hasValueFlow=1 $ hasValueFlow=16 $ MISSING: hasValueFlow=18 - sink p2 # $ hasValueFlow=2 $ MISSING: hasValueFlow=17 $ MISSING: hasValueFlow=19 + sink p1 # $ hasValueFlow=1 $ hasValueFlow=16 $ hasValueFlow=18 $ hasValueFlow=61 + sink p2 # $ hasValueFlow=2 $ hasValueFlow=19 $ hasValueFlow=61 $ MISSING: hasValueFlow=17 end positional(taint(1), taint(2)) @@ -47,8 +47,9 @@ args = [taint(18), taint(19)] positional(*args) def posargs(p1, *posargs) - sink p1 # $ hasValueFlow=20 $ hasValueFlow=23 $ MISSING: hasValueFlow=24 - sink (posargs[0]) # $ MISSING: hasValueFlow=21 $ MISSING: hasValueFlow=22 $ MISSING: hasValueFlow=25 + sink p1 # $ hasValueFlow=20 $ hasValueFlow=23 $ hasValueFlow=24 + sink (posargs[0]) # $ hasValueFlow=22 $ MISSING: hasValueFlow=21 $ MISSING: hasValueFlow=25 + sink (posargs[1]) end posargs(taint(20), taint(21)) @@ -63,4 +64,65 @@ args = taint(26) def splatstuff(*x) sink x[0] # $ hasValueFlow=26 end -splatstuff(*args) \ No newline at end of file +splatstuff(*args) + +def splatmid(x, y, *z, w, r) + sink x # $ hasValueFlow=27 $ hasValueFlow=32 $ hasValueFlow=45 + sink y # $ hasValueFlow=28 $ hasValueFlow=46 $ MISSING: hasValueFlow=33 + sink z[0] # MISSING: $ hasValueFlow=47 $ hasValueFlow=29 $ hasValueFlow=34 + sink z[1] # $ MISSING: hasValueFlow=48 $ hasValueFlow=35 + sink w # $ hasValueFlow=30 $ hasValueFlow=50 $ MISSING: hasValueFlow=36 + sink r # $ hasValueFlow=31 $ hasValueFlow=51 $ MISSING: hasValueFlow=37 +end + +splatmid(taint(27), taint(28), taint(29), taint(30), taint(31)) + +args = [taint(33), taint(34), taint(35), taint(36)] +splatmid(taint(32), *args, taint(37)) + +def pos_many(t, u, v, w, x, y, z) + sink t # $ hasValueFlow=38 + sink u # $ hasValueFlow=39 + sink v # $ MISSING: hasValueFlow=40 + sink w # $ MISSING: hasValueFlow=41 $ SPURIOUS: hasValueFlow=44 + sink x # $ MISSING: hasValueFlow=42 + sink y # $ MISSING: hasValueFlow=43 + sink z # $ MISSING: hasValueFlow=44 +end + +args = [taint(40), taint(41), taint(42), taint(43)] +pos_many(taint(38), taint(39), *args, taint(44)) + +splatmid(taint(45), taint(46), *[taint(47), taint(48), taint(49)], taint(50), taint(51)) + +def splatmidsmall(a, *splats, b) + sink a # $ hasValueFlow=52 $ hasValueFlow=55 + sink splats[0] # $ MISSING: hasValueFlow=53 + sink splats[1] # $ MISSING: hasValueFlow=54 + sink b # $ hasValueFlow=57 +end + +splatmidsmall(taint(52), *[taint(53), taint(54)]) +splatmidsmall(taint(55), taint(56), taint(57)) + +def splat_followed_by_keyword_param(a, *b, c:) + sink a # $ hasValueFlow=58 + sink b[0] # $ MISSING: hasValueFlow=59 + sink c # $ hasValueFlow=60 +end + +splat_followed_by_keyword_param(taint(58), taint(59), c: taint(60)) + +x = [] +x[some_index()] = taint(61) +positional(*x) + +def destruct((a,b), (c,(d,e))) + sink a # $ MISSING: hasValueFlow=62 + sink b # $ MISSING: hasValueFlow=63 + sink c # $ MISSING: hasValueFlow=64 + sink d + sink e # $ MISSING: hasValueFlow=65 +end + +destruct([taint(62), taint(63)], [taint(64), [0, taint(65)]]) diff --git a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected index 9a4277f4c0e..63116a97076 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -7,6 +7,7 @@ track | type_tracker.rb:2:5:5:7 | field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | field= | | type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:2:5:5:7 | self in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | self in field= | +| type_tracker.rb:2:5:5:7 | synthetic *args | type tracker without call steps | type_tracker.rb:2:5:5:7 | synthetic *args | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field | @@ -46,6 +47,7 @@ track | type_tracker.rb:18:1:21:3 | &block | type tracker without call steps | type_tracker.rb:18:1:21:3 | &block | | type_tracker.rb:18:1:21:3 | positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | positional | | type_tracker.rb:18:1:21:3 | self in positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | self in positional | +| type_tracker.rb:18:1:21:3 | synthetic *args | type tracker without call steps | type_tracker.rb:18:1:21:3 | synthetic *args | | type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 | @@ -118,6 +120,7 @@ track | type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps with content element :p1 | type_tracker.rb:32:1:32:27 | ** | | type_tracker.rb:34:1:53:3 | &block | type tracker without call steps | type_tracker.rb:34:1:53:3 | &block | | type_tracker.rb:34:1:53:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | self in throughArray | +| type_tracker.rb:34:1:53:3 | synthetic *args | type tracker without call steps | type_tracker.rb:34:1:53:3 | synthetic *args | | type_tracker.rb:34:1:53:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | throughArray | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | @@ -272,6 +275,7 @@ trackEnd | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:8:9:8:14 | self | +| type_tracker.rb:2:5:5:7 | synthetic *args | type_tracker.rb:2:5:5:7 | synthetic *args | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | @@ -340,6 +344,7 @@ trackEnd | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self in positional | | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:19:5:19:11 | self | | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:20:5:20:11 | self | +| type_tracker.rb:18:1:21:3 | synthetic *args | type_tracker.rb:18:1:21:3 | synthetic *args | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | @@ -427,6 +432,7 @@ trackEnd | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 | | type_tracker.rb:34:1:53:3 | &block | type_tracker.rb:34:1:53:3 | &block | | type_tracker.rb:34:1:53:3 | self in throughArray | type_tracker.rb:34:1:53:3 | self in throughArray | +| type_tracker.rb:34:1:53:3 | synthetic *args | type_tracker.rb:34:1:53:3 | synthetic *args | | type_tracker.rb:34:1:53:3 | throughArray | type_tracker.rb:34:1:53:3 | throughArray | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | diff --git a/ruby/ql/test/library-tests/variables/ssa.expected b/ruby/ql/test/library-tests/variables/ssa.expected index 962cd733a08..b03f5ea0e02 100644 --- a/ruby/ql/test/library-tests/variables/ssa.expected +++ b/ruby/ql/test/library-tests/variables/ssa.expected @@ -103,13 +103,9 @@ definition | scopes.rb:26:1:26:12 | self (A) | scopes.rb:26:1:26:12 | self | | scopes.rb:27:1:27:1 | x | scopes.rb:27:1:27:1 | x | | scopes.rb:28:1:30:3 | self (B) | scopes.rb:28:1:30:3 | self | -| scopes.rb:29:3:29:3 | x | scopes.rb:29:3:29:3 | x | -| scopes.rb:32:3:32:3 | x | scopes.rb:32:3:32:3 | x | | scopes.rb:34:1:36:3 | self (C) | scopes.rb:34:1:36:3 | self | -| scopes.rb:35:3:35:3 | x | scopes.rb:35:3:35:3 | x | | scopes.rb:41:1:49:3 | self (M) | scopes.rb:41:1:49:3 | self | | scopes.rb:42:2:42:4 | var | scopes.rb:42:2:42:4 | var | -| scopes.rb:43:2:43:4 | foo | scopes.rb:43:2:43:4 | foo | | scopes.rb:46:5:46:8 | var2 | scopes.rb:46:5:46:8 | var2 | | ssa.rb:1:1:16:3 | self (m) | ssa.rb:1:1:16:3 | self | | ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | @@ -128,6 +124,8 @@ definition | ssa.rb:26:3:28:5 | elem | ssa.rb:26:7:26:10 | elem | | ssa.rb:26:3:28:5 | __synth__0__1 | ssa.rb:26:3:28:5 | __synth__0__1 | | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | +| ssa.rb:26:7:26:10 | phi | ssa.rb:26:7:26:10 | elem | | ssa.rb:32:1:36:3 | self (m3) | ssa.rb:32:1:36:3 | self | | ssa.rb:33:16:35:5 | self | ssa.rb:32:1:36:3 | self | | ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | @@ -316,6 +314,7 @@ read | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:20:10:20:10 | x | | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:25:1:30:3 | | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | | ssa.rb:25:1:30:3 | self (m2) | ssa.rb:25:1:30:3 | self | ssa.rb:29:3:29:11 | self | | ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | | ssa.rb:26:3:28:5 | self | ssa.rb:25:1:30:3 | self | ssa.rb:27:5:27:13 | self | @@ -470,6 +469,7 @@ firstRead | ssa.rb:10:5:10:5 | i | ssa.rb:2:3:2:3 | i | ssa.rb:11:10:11:10 | i | | ssa.rb:18:1:23:3 | self (m1) | ssa.rb:18:1:23:3 | self | ssa.rb:20:5:20:10 | self | | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | +| ssa.rb:25:1:30:3 | | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | | ssa.rb:25:1:30:3 | self (m2) | ssa.rb:25:1:30:3 | self | ssa.rb:29:3:29:11 | self | | ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | | ssa.rb:26:3:28:5 | self | ssa.rb:25:1:30:3 | self | ssa.rb:27:5:27:13 | self | @@ -621,6 +621,7 @@ lastRead | ssa.rb:18:1:23:3 | self (m1) | ssa.rb:18:1:23:3 | self | ssa.rb:20:5:20:10 | self | | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:25:1:30:3 | | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | | ssa.rb:25:1:30:3 | self (m2) | ssa.rb:25:1:30:3 | self | ssa.rb:29:3:29:11 | self | | ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | | ssa.rb:26:3:28:5 | self | ssa.rb:25:1:30:3 | self | ssa.rb:27:5:27:13 | self | @@ -736,6 +737,8 @@ phi | ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:10:5:10:5 | i | | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | | ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:26:7:26:10 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | | +| ssa.rb:26:7:26:10 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | | ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:44:1:47:3 | | | ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:3 | x | | ssa.rb:50:3:50:8 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:49:1:51:3 | | diff --git a/ruby/ql/test/library-tests/variables/varaccess.expected b/ruby/ql/test/library-tests/variables/varaccess.expected index 82bc6c8cd58..e79f6ca3023 100644 --- a/ruby/ql/test/library-tests/variables/varaccess.expected +++ b/ruby/ql/test/library-tests/variables/varaccess.expected @@ -226,6 +226,8 @@ variableAccess | ssa.rb:21:5:21:5 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | m1 | | ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:25:1:30:3 | m2 | | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | m2 | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | m2 | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | m2 | | ssa.rb:26:15:26:22 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:25:1:30:3 | m2 | | ssa.rb:27:5:27:13 | self | ssa.rb:25:1:30:3 | self | ssa.rb:25:1:30:3 | m2 | | ssa.rb:27:10:27:13 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | m2 | @@ -354,6 +356,7 @@ explicitWrite | ssa.rb:21:5:21:5 | x | ssa.rb:21:5:21:10 | ... -= ... | | ssa.rb:21:5:21:5 | x | ssa.rb:21:5:21:10 | ... = ... | | ssa.rb:26:7:26:10 | elem | ssa.rb:26:3:28:5 | ... = ... | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | ... = ... | | ssa.rb:40:3:40:4 | m3 | ssa.rb:40:3:40:9 | ... = ... | | ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:7 | ... = ... | | ssa.rb:49:14:49:14 | y | ssa.rb:49:14:49:19 | ... = ... | @@ -567,6 +570,7 @@ readAccess | ssa.rb:20:10:20:10 | x | | ssa.rb:21:5:21:5 | x | | ssa.rb:26:3:28:5 | __synth__0__1 | +| ssa.rb:26:7:26:10 | elem | | ssa.rb:26:15:26:22 | elements | | ssa.rb:27:5:27:13 | self | | ssa.rb:27:10:27:13 | elem | @@ -616,3 +620,44 @@ readAccess | ssa.rb:100:10:100:11 | b4 | | ssa.rb:101:5:101:10 | self | | ssa.rb:101:10:101:10 | x | +captureAccess +| instance_variables.rb:28:3:28:4 | self | +| instance_variables.rb:32:12:32:13 | self | +| nested_scopes.rb:18:29:18:34 | self | +| nested_scopes.rb:18:34:18:34 | a | +| parameters.rb:3:4:3:9 | self | +| parameters.rb:4:4:4:9 | self | +| parameters.rb:17:5:17:28 | self | +| parameters.rb:54:19:54:19 | x | +| parameters.rb:55:4:55:9 | self | +| parameters.rb:55:9:55:9 | x | +| parameters.rb:56:4:56:9 | self | +| scopes.rb:3:4:3:9 | self | +| scopes.rb:3:9:3:9 | self | +| scopes.rb:5:4:5:9 | self | +| scopes.rb:10:4:10:9 | self | +| scopes.rb:10:9:10:9 | a | +| scopes.rb:11:4:11:4 | a | +| scopes.rb:11:4:11:4 | a | +| scopes.rb:12:4:12:9 | self | +| scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:4 | a | +| scopes.rb:14:4:14:9 | self | +| scopes.rb:14:9:14:9 | a | +| scopes.rb:15:4:15:9 | self | +| scopes.rb:16:4:16:9 | self | +| scopes.rb:17:4:17:9 | self | +| ssa.rb:26:7:26:10 | elem | +| ssa.rb:27:5:27:13 | self | +| ssa.rb:27:10:27:13 | elem | +| ssa.rb:34:5:34:10 | self | +| ssa.rb:67:5:67:10 | self | +| ssa.rb:68:5:68:17 | self | +| ssa.rb:68:10:68:17 | captured | +| ssa.rb:69:5:69:12 | captured | +| ssa.rb:69:5:69:12 | captured | +| ssa.rb:77:6:77:23 | self | +| ssa.rb:77:15:77:22 | captured | +| ssa.rb:84:6:86:8 | self | +| ssa.rb:85:10:85:22 | self | +| ssa.rb:85:15:85:22 | captured | diff --git a/ruby/ql/test/library-tests/variables/varaccess.ql b/ruby/ql/test/library-tests/variables/varaccess.ql index 94363c9c532..8e511731629 100644 --- a/ruby/ql/test/library-tests/variables/varaccess.ql +++ b/ruby/ql/test/library-tests/variables/varaccess.ql @@ -12,3 +12,5 @@ query predicate explicitWrite(VariableWriteAccess write, AstNode assignment) { query predicate implicitWrite(VariableWriteAccess write) { write.isImplicitWrite() } query predicate readAccess(VariableReadAccess read) { any() } + +query predicate captureAccess(LocalVariableAccess access) { access.isCapturedAccess() } diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.expected b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.expected index ee274031bf0..f8fa2e9dc2e 100644 --- a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.expected +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.expected @@ -33,9 +33,9 @@ | tst.rb:137:11:137:17 | (\\w\|G)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'G'. | | tst.rb:143:11:143:18 | (\\d\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | tst.rb:146:11:146:17 | (\\d\|5)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '5'. | -| tst.rb:149:11:149:20 | (\\s\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. | -| tst.rb:152:11:152:24 | (\\s\|[\\v]\|\\\\v)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000b'. | -| tst.rb:155:11:155:20 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. | +| tst.rb:149:11:149:20 | (\\s\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000c'. | +| tst.rb:152:11:152:24 | (\\s\|[\\v]\|\\\\v)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000b'. | +| tst.rb:155:11:155:20 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\u000c'. | | tst.rb:158:11:158:18 | (\\W\|\\D)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' '. | | tst.rb:161:11:161:18 | (\\S\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | | tst.rb:164:11:164:20 | (\\S\|[\\w])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | diff --git a/shared/controlflow/change-notes/2023-06-27-initial-version.md b/shared/controlflow/CHANGELOG.md similarity index 61% rename from shared/controlflow/change-notes/2023-06-27-initial-version.md rename to shared/controlflow/CHANGELOG.md index cd1e9b5c3d1..21f862198c9 100644 --- a/shared/controlflow/change-notes/2023-06-27-initial-version.md +++ b/shared/controlflow/CHANGELOG.md @@ -1,4 +1,5 @@ ---- -category: majorAnalysis ---- +## 0.0.1 + +### Major Analysis Improvements + * Initial release. Adds a shared library for control flow analyses. diff --git a/shared/controlflow/change-notes/released/0.0.1.md b/shared/controlflow/change-notes/released/0.0.1.md new file mode 100644 index 00000000000..21f862198c9 --- /dev/null +++ b/shared/controlflow/change-notes/released/0.0.1.md @@ -0,0 +1,5 @@ +## 0.0.1 + +### Major Analysis Improvements + +* Initial release. Adds a shared library for control flow analyses. diff --git a/shared/controlflow/codeql-pack.release.yml b/shared/controlflow/codeql-pack.release.yml new file mode 100644 index 00000000000..c6933410b71 --- /dev/null +++ b/shared/controlflow/codeql-pack.release.yml @@ -0,0 +1,2 @@ +--- +lastReleaseVersion: 0.0.1 diff --git a/shared/controlflow/qlpack.yml b/shared/controlflow/qlpack.yml index 24308d7d0cf..17115260f55 100644 --- a/shared/controlflow/qlpack.yml +++ b/shared/controlflow/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/controlflow -version: 0.0.1-dev +version: 0.0.2-dev groups: shared library: true dependencies: diff --git a/shared/dataflow/change-notes/2023-08-02-dataflow-isSink.md b/shared/dataflow/CHANGELOG.md similarity index 50% rename from shared/dataflow/change-notes/2023-08-02-dataflow-isSink.md rename to shared/dataflow/CHANGELOG.md index 6c2c0fc1225..8510e78ec2c 100644 --- a/shared/dataflow/change-notes/2023-08-02-dataflow-isSink.md +++ b/shared/dataflow/CHANGELOG.md @@ -1,4 +1,9 @@ ---- -category: feature ---- -* The `StateConfigSig` signature now supports a unary `isSink` predicate that does not specify the `FlowState` for which the given node is a sink. Instead, any `FlowState` is considered a valid `FlowState` for such a sink. \ No newline at end of file +## 0.0.1 + +### New Features + +* The `StateConfigSig` signature now supports a unary `isSink` predicate that does not specify the `FlowState` for which the given node is a sink. Instead, any `FlowState` is considered a valid `FlowState` for such a sink. + +### Minor Analysis Improvements + +* Initial release. Moves the shared inter-procedural data-flow library into its own qlpack. diff --git a/shared/dataflow/change-notes/2023-06-16-initial-version.md b/shared/dataflow/change-notes/2023-06-16-initial-version.md new file mode 100644 index 00000000000..3846cb70c1d --- /dev/null +++ b/shared/dataflow/change-notes/2023-06-16-initial-version.md @@ -0,0 +1,4 @@ +--- +category: majorAnalysis +--- +* Initial release. Adds a library to implement flow through captured variables that properly adheres to inter-procedural control flow. diff --git a/shared/dataflow/change-notes/2023-08-02-dataflow-initial.md b/shared/dataflow/change-notes/2023-08-02-dataflow-initial.md deleted file mode 100644 index 7e3a51cba8f..00000000000 --- a/shared/dataflow/change-notes/2023-08-02-dataflow-initial.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: minorAnalysis ---- -* Initial release. Moves the shared inter-procedural data-flow library into its own qlpack. diff --git a/shared/dataflow/change-notes/released/0.0.1.md b/shared/dataflow/change-notes/released/0.0.1.md new file mode 100644 index 00000000000..8510e78ec2c --- /dev/null +++ b/shared/dataflow/change-notes/released/0.0.1.md @@ -0,0 +1,9 @@ +## 0.0.1 + +### New Features + +* The `StateConfigSig` signature now supports a unary `isSink` predicate that does not specify the `FlowState` for which the given node is a sink. Instead, any `FlowState` is considered a valid `FlowState` for such a sink. + +### Minor Analysis Improvements + +* Initial release. Moves the shared inter-procedural data-flow library into its own qlpack. diff --git a/shared/dataflow/codeql-pack.release.yml b/shared/dataflow/codeql-pack.release.yml new file mode 100644 index 00000000000..c6933410b71 --- /dev/null +++ b/shared/dataflow/codeql-pack.release.yml @@ -0,0 +1,2 @@ +--- +lastReleaseVersion: 0.0.1 diff --git a/shared/dataflow/codeql/dataflow/VariableCapture.qll b/shared/dataflow/codeql/dataflow/VariableCapture.qll new file mode 100644 index 00000000000..8cee2b4c042 --- /dev/null +++ b/shared/dataflow/codeql/dataflow/VariableCapture.qll @@ -0,0 +1,930 @@ +/** + * Provides a module for synthesizing data-flow nodes and related step relations + * for supporting flow through captured variables. + */ + +private import codeql.util.Boolean +private import codeql.util.Unit +private import codeql.ssa.Ssa as Ssa + +signature module InputSig { + class Location { + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ); + } + + /** + * A basic block, that is, a maximal straight-line sequence of control flow nodes + * without branches or joins. + */ + class BasicBlock { + /** Gets a textual representation of this basic block. */ + string toString(); + + /** Gets the enclosing callable. */ + Callable getEnclosingCallable(); + + /** Gets the location of this basic block. */ + Location getLocation(); + } + + /** + * Gets the basic block that immediately dominates basic block `bb`, if any. + * + * That is, all paths reaching `bb` from some entry point basic block must go + * through the result. + * + * Example: + * + * ```csharp + * int M(string s) { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The basic block starting on line 2 is an immediate dominator of + * the basic block on line 4 (all paths from the entry point of `M` + * to `return s.Length;` must go through the null check. + */ + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); + + /** Gets an immediate successor of basic block `bb`, if any. */ + BasicBlock getABasicBlockSuccessor(BasicBlock bb); + + /** Holds if `bb` is a control-flow entry point. */ + default predicate entryBlock(BasicBlock bb) { not exists(getImmediateBasicBlockDominator(bb)) } + + /** Holds if `bb` is a control-flow exit point. */ + default predicate exitBlock(BasicBlock bb) { not exists(getABasicBlockSuccessor(bb)) } + + /** A variable that is captured in a closure. */ + class CapturedVariable { + /** Gets a textual representation of this variable. */ + string toString(); + + /** Gets the callable that defines this variable. */ + Callable getCallable(); + + /** Gets the location of this variable. */ + Location getLocation(); + } + + /** A parameter that is captured in a closure. */ + class CapturedParameter extends CapturedVariable; + + /** + * An expression with a value. That is, we expect these expressions to be + * represented in the data flow graph. + */ + class Expr { + /** Gets a textual representation of this expression. */ + string toString(); + + /** Gets the location of this expression. */ + Location getLocation(); + + /** Holds if the `i`th node of basic block `bb` evaluates this expression. */ + predicate hasCfgNode(BasicBlock bb, int i); + } + + /** A write to a captured variable. */ + class VariableWrite { + /** Gets the variable that is the target of this write. */ + CapturedVariable getVariable(); + + /** Gets the expression that is the source of this write. */ + Expr getSource(); + + /** Gets the location of this write. */ + Location getLocation(); + + /** Holds if the `i`th node of basic block `bb` evaluates this expression. */ + predicate hasCfgNode(BasicBlock bb, int i); + } + + /** A read of a captured variable. */ + class VariableRead extends Expr { + /** Gets the variable that this expression reads. */ + CapturedVariable getVariable(); + } + + /** + * An expression constructing a closure that may capture one or more + * variables. This can for example be a lambda or a constructor call of a + * locally defined object. + */ + class ClosureExpr extends Expr { + /** + * Holds if `body` is the callable body of this closure. A lambda expression + * only has one body, but in general a locally defined object may have + * multiple such methods and constructors. + */ + predicate hasBody(Callable body); + + /** + * Holds if `f` is an expression that may hold the value of the closure and + * may occur in a position where the value escapes or where the closure may + * be invoked. + * + * For example, if a lambda is assigned to a variable, then references to + * that variable in return or argument positions should be included. + */ + predicate hasAliasedAccess(Expr f); + } + + class Callable { + /** Gets a textual representation of this callable. */ + string toString(); + + /** Gets the location of this callable. */ + Location getLocation(); + + /** Holds if this callable is a constructor. */ + predicate isConstructor(); + } +} + +signature module OutputSig { + /** + * A data flow node that we need to reference in the step relations for + * captured variables. + * + * Note that only the `SynthesizedCaptureNode` subclass is expected to be + * added as additional nodes in `DataFlow::Node`. The other subclasses are + * expected to already be present and are included here in order to reference + * them in the step relations. + */ + class ClosureNode; + + /** + * A synthesized data flow node representing the storage of a captured + * variable. + */ + class SynthesizedCaptureNode extends ClosureNode { + /** Gets a textual representation of this node. */ + string toString(); + + /** Gets the location of this node. */ + I::Location getLocation(); + + /** Gets the enclosing callable. */ + I::Callable getEnclosingCallable(); + + /** Holds if this node is a synthesized access of `v`. */ + predicate isVariableAccess(I::CapturedVariable v); + + /** Holds if this node is a synthesized instance access. */ + predicate isInstanceAccess(); + } + + /** A data flow node for an expression. */ + class ExprNode extends ClosureNode { + /** Gets the expression corresponding to this node. */ + I::Expr getExpr(); + } + + /** A data flow node for the `PostUpdateNode` of an expression. */ + class ExprPostUpdateNode extends ClosureNode { + /** Gets the expression corresponding to this node. */ + I::Expr getExpr(); + } + + /** A data flow node for a parameter. */ + class ParameterNode extends ClosureNode { + /** Gets the parameter corresponding to this node. */ + I::CapturedParameter getParameter(); + } + + /** A data flow node for an instance parameter. */ + class ThisParameterNode extends ClosureNode { + /** Gets the callable this instance parameter belongs to. */ + I::Callable getCallable(); + } + + /** A data flow node for the instance parameter argument of a constructor call. */ + class MallocNode extends ClosureNode { + /** Gets the closure construction that is the post-update of this node. */ + I::ClosureExpr getClosureExpr(); + } + + /** Holds if `post` is a `PostUpdateNode` for `pre`. */ + predicate capturePostUpdateNode(SynthesizedCaptureNode post, SynthesizedCaptureNode pre); + + /** Holds if there is a local flow step from `node1` to `node2`. */ + predicate localFlowStep(ClosureNode node1, ClosureNode node2); + + /** Holds if there is a store step from `node1` to `node2`. */ + predicate storeStep(ClosureNode node1, I::CapturedVariable v, ClosureNode node2); + + /** Holds if there is a read step from `node1` to `node2`. */ + predicate readStep(ClosureNode node1, I::CapturedVariable v, ClosureNode node2); + + /** Holds if this-to-this summaries are expected for `c`. */ + predicate heuristicAllowInstanceParameterReturnInSelf(I::Callable c); +} + +/** + * Constructs the type `ClosureNode` and associated step relations, which are + * intended to be included in the data-flow node and step relations. + */ +module Flow implements OutputSig { + private import Input + + additional module ConsistencyChecks { + final private class FinalExpr = Expr; + + private class RelevantExpr extends FinalExpr { + RelevantExpr() { + this instanceof VariableRead or + any(VariableWrite vw).getSource() = this or + this instanceof ClosureExpr or + any(ClosureExpr ce).hasAliasedAccess(this) + } + } + + final private class FinalBasicBlock = BasicBlock; + + private class RelevantBasicBlock extends FinalBasicBlock { + RelevantBasicBlock() { + exists(RelevantExpr e | e.hasCfgNode(this, _)) + or + exists(VariableWrite vw | vw.hasCfgNode(this, _)) + } + } + + final private class FinalCallable = Callable; + + private class RelevantCallable extends FinalCallable { + RelevantCallable() { + exists(RelevantBasicBlock bb | bb.getEnclosingCallable() = this) + or + exists(CapturedVariable v | v.getCallable() = this) + or + exists(ClosureExpr ce | ce.hasBody(this)) + } + } + + query predicate uniqueToString(string msg, int n) { + exists(string elem | + n = strictcount(RelevantBasicBlock bb | not exists(bb.toString())) and + elem = "BasicBlock" + or + n = strictcount(CapturedVariable v | not exists(v.toString())) and elem = "CapturedVariable" + or + n = strictcount(RelevantExpr e | not exists(e.toString())) and elem = "Expr" + or + n = strictcount(RelevantCallable c | not exists(c.toString())) and + elem = "Callable" + | + msg = n + " " + elem + "(s) are missing toString" + ) + or + exists(string elem | + n = strictcount(RelevantBasicBlock bb | 2 <= strictcount(bb.toString())) and + elem = "BasicBlock" + or + n = strictcount(CapturedVariable v | 2 <= strictcount(v.toString())) and + elem = "CapturedVariable" + or + n = strictcount(RelevantExpr e | 2 <= strictcount(e.toString())) and + elem = "Expr" + or + n = strictcount(RelevantCallable c | 2 <= strictcount(c.toString())) and + elem = "Callable" + | + msg = n + " " + elem + "(s) have multiple toStrings" + ) + } + + query predicate uniqueEnclosingCallable(RelevantBasicBlock bb, string msg) { + msg = "BasicBlock has no enclosing callable" and not exists(bb.getEnclosingCallable()) + or + msg = "BasicBlock has multiple enclosing callables" and + 2 <= strictcount(bb.getEnclosingCallable()) + } + + query predicate uniqueDominator(RelevantBasicBlock bb, string msg) { + msg = "BasicBlock has multiple immediate dominators" and + 2 <= strictcount(getImmediateBasicBlockDominator(bb)) + } + + query predicate localDominator(RelevantBasicBlock bb, string msg) { + msg = "BasicBlock has non-local dominator" and + bb.getEnclosingCallable() != getImmediateBasicBlockDominator(bb).getEnclosingCallable() + } + + query predicate localSuccessor(RelevantBasicBlock bb, string msg) { + msg = "BasicBlock has non-local successor" and + bb.getEnclosingCallable() != getABasicBlockSuccessor(bb).getEnclosingCallable() + } + + query predicate uniqueDefiningScope(CapturedVariable v, string msg) { + msg = "CapturedVariable has no defining callable" and not exists(v.getCallable()) + or + msg = "CapturedVariable has multiple defining callables" and 2 <= strictcount(v.getCallable()) + } + + query predicate variableIsCaptured(CapturedVariable v, string msg) { + msg = "CapturedVariable is not captured" and + not captureAccess(v, _) + } + + query predicate uniqueLocation(RelevantExpr e, string msg) { + msg = "Expr has no location" and not exists(e.getLocation()) + or + msg = "Expr has multiple locations" and 2 <= strictcount(e.getLocation()) + } + + query predicate uniqueCfgNode(RelevantExpr e, string msg) { + msg = "Expr has no cfg node" and not e.hasCfgNode(_, _) + or + msg = "Expr has multiple cfg nodes" and + 2 <= strictcount(BasicBlock bb, int i | e.hasCfgNode(bb, i)) + } + + private predicate uniqueWriteTarget(VariableWrite vw, string msg) { + msg = "VariableWrite has no target variable" and not exists(vw.getVariable()) + or + msg = "VariableWrite has multiple target variables" and 2 <= strictcount(vw.getVariable()) + } + + query predicate uniqueWriteTarget(string msg) { uniqueWriteTarget(_, msg) } + + private predicate uniqueWriteSource(VariableWrite vw, string msg) { + msg = "VariableWrite has no source expression" and not exists(vw.getSource()) + or + msg = "VariableWrite has multiple source expressions" and 2 <= strictcount(vw.getSource()) + } + + query predicate uniqueWriteSource(string msg) { uniqueWriteSource(_, msg) } + + private predicate uniqueWriteCfgNode(VariableWrite vw, string msg) { + msg = "VariableWrite has no cfg node" and not vw.hasCfgNode(_, _) + or + msg = "VariableWrite has multiple cfg nodes" and + 2 <= strictcount(BasicBlock bb, int i | vw.hasCfgNode(bb, i)) + } + + query predicate uniqueWriteCfgNode(string msg) { uniqueWriteCfgNode(_, msg) } + + private predicate localWriteStep(VariableWrite vw, string msg) { + exists(BasicBlock bb1, BasicBlock bb2 | + vw.hasCfgNode(bb1, _) and + vw.getSource().hasCfgNode(bb2, _) and + bb1.getEnclosingCallable() != bb2.getEnclosingCallable() and + msg = "VariableWrite is not a local step" + ) + } + + query predicate localWriteStep(string msg) { localWriteStep(_, msg) } + + query predicate uniqueReadVariable(VariableRead vr, string msg) { + msg = "VariableRead has no source variable" and not exists(vr.getVariable()) + or + msg = "VariableRead has multiple source variables" and 2 <= strictcount(vr.getVariable()) + } + + query predicate closureMustHaveBody(ClosureExpr ce, string msg) { + msg = "ClosureExpr has no body" and not ce.hasBody(_) + } + + query predicate closureAliasMustBeLocal(ClosureExpr ce, Expr access, string msg) { + exists(BasicBlock bb1, BasicBlock bb2 | + ce.hasAliasedAccess(access) and + ce.hasCfgNode(bb1, _) and + access.hasCfgNode(bb2, _) and + bb1.getEnclosingCallable() != bb2.getEnclosingCallable() and + msg = "ClosureExpr has non-local alias - these are ignored" + ) + } + + private predicate astClosureParent(Callable closure, Callable parent) { + exists(ClosureExpr ce, BasicBlock bb | + ce.hasBody(closure) and ce.hasCfgNode(bb, _) and parent = bb.getEnclosingCallable() + ) + } + + query predicate variableAccessAstNesting(CapturedVariable v, Callable c, string msg) { + exists(BasicBlock bb, Callable parent | + captureRead(v, bb, _, false, _) or captureWrite(v, bb, _, false, _) + | + bb.getEnclosingCallable() = c and + v.getCallable() = parent and + not astClosureParent+(c, parent) and + msg = "CapturedVariable access is not nested in the defining callable" + ) + } + + query predicate uniqueCallableLocation(RelevantCallable c, string msg) { + msg = "Callable has no location" and not exists(c.getLocation()) + or + msg = "Callable has multiple locations" and 2 <= strictcount(c.getLocation()) + } + + query predicate consistencyOverview(string msg, int n) { + uniqueToString(msg, n) or + n = strictcount(BasicBlock bb | uniqueEnclosingCallable(bb, msg)) or + n = strictcount(BasicBlock bb | uniqueDominator(bb, msg)) or + n = strictcount(BasicBlock bb | localDominator(bb, msg)) or + n = strictcount(BasicBlock bb | localSuccessor(bb, msg)) or + n = strictcount(CapturedVariable v | uniqueDefiningScope(v, msg)) or + n = strictcount(CapturedVariable v | variableIsCaptured(v, msg)) or + n = strictcount(Expr e | uniqueLocation(e, msg)) or + n = strictcount(Expr e | uniqueCfgNode(e, msg)) or + n = strictcount(VariableWrite vw | uniqueWriteTarget(vw, msg)) or + n = strictcount(VariableWrite vw | uniqueWriteSource(vw, msg)) or + n = strictcount(VariableWrite vw | uniqueWriteCfgNode(vw, msg)) or + n = strictcount(VariableWrite vw | localWriteStep(vw, msg)) or + n = strictcount(VariableRead vr | uniqueReadVariable(vr, msg)) or + n = strictcount(ClosureExpr ce | closureMustHaveBody(ce, msg)) or + n = strictcount(ClosureExpr ce, Expr access | closureAliasMustBeLocal(ce, access, msg)) or + n = strictcount(CapturedVariable v, Callable c | variableAccessAstNesting(v, c, msg)) or + n = strictcount(Callable c | uniqueCallableLocation(c, msg)) + } + } + + /* + * Flow through captured variables is handled by making each captured variable + * a field on the closures that capture them. + * + * For each closure creation we add a store step from the captured variable to + * the closure, and inside the closures we access the captured variables with + * a `this.` qualifier. This allows capture flow into closures. + * + * It also means that we get several aliased versions of a captured variable + * so proper care must be taken to be able to observe side-effects or flow out + * of closures. E.g. if two closures `l1` and `l2` capture `x` then we'll have + * three names, `x`, `l1.x`, and `l2.x`, plus any potential aliasing of the + * closures. + * + * To handle this, we select a primary name for a captured variable in each of + * its scopes, keep that name updated, and update the other names from the + * primary name. + * + * In the defining scope of a captured variable, we use the local variable + * itself as the primary storage location, and in the capturing scopes we use + * the synthesized field. For each relevant reference to a closure object we + * then update its field from the primary storage location, and we read the + * field back from the post-update of the closure object reference and back + * into the primary storage location. + * + * If we include references to a closure object that may lead to a call as + * relevant, then this means that we'll be able to observe the side-effects of + * such calls in the primary storage location. + * + * Details: + * For a reference to a closure `f` that captures `x` we synthesize a read of + * `x` at the same control-flow node. We then add a store step from `x` to `f` + * and a read step from `postupdate(f)` to `postupdate(x)`. + * ``` + * SsaRead(x) --store[x]--> f + * postupdate(f) --read[x]--> postupdate(SsaRead(x)) + * ``` + * In a closure scope with a nested closure `g` that also captures `x` the + * steps instead look like this: + * ``` + * SsaRead(this) --read[x]--> this.x --store[x]--> g + * postupdate(g) --read[x]--> postupdate(this.x) + * ``` + * The final store from `postupdate(this.x)` to `postupdate(this)` is + * introduced automatically as a reverse read by the data flow library. + */ + + /** + * Holds if `vr` is a read of `v` in the `i`th node of `bb`. + * `topScope` is true if the read is in the defining callable of `v`. + */ + private predicate captureRead( + CapturedVariable v, BasicBlock bb, int i, boolean topScope, VariableRead vr + ) { + vr.getVariable() = v and + vr.hasCfgNode(bb, i) and + if v.getCallable() != bb.getEnclosingCallable() then topScope = false else topScope = true + } + + /** + * Holds if `vw` is a write of `v` in the `i`th node of `bb`. + * `topScope` is true if the write is in the defining callable of `v`. + */ + private predicate captureWrite( + CapturedVariable v, BasicBlock bb, int i, boolean topScope, VariableWrite vw + ) { + vw.getVariable() = v and + vw.hasCfgNode(bb, i) and + if v.getCallable() != bb.getEnclosingCallable() then topScope = false else topScope = true + } + + /** Gets the enclosing callable of `ce`. */ + private Callable closureExprGetCallable(ClosureExpr ce) { + exists(BasicBlock bb | ce.hasCfgNode(bb, _) and result = bb.getEnclosingCallable()) + } + + /** + * Holds if `v` is available in `c` through capture. This can either be due to + * an explicit variable reference or through the construction of a closure + * that has a nested capture. + */ + private predicate captureAccess(CapturedVariable v, Callable c) { + exists(BasicBlock bb | captureRead(v, bb, _, _, _) or captureWrite(v, bb, _, _, _) | + c = bb.getEnclosingCallable() and + c != v.getCallable() + ) + or + exists(ClosureExpr ce | + c = closureExprGetCallable(ce) and + closureCaptures(ce, v) and + c != v.getCallable() + ) + } + + /** Holds if the closure defined by `ce` captures `v`. */ + private predicate closureCaptures(ClosureExpr ce, CapturedVariable v) { + exists(Callable c | ce.hasBody(c) and captureAccess(v, c)) + } + + predicate heuristicAllowInstanceParameterReturnInSelf(Callable c) { + // If multiple variables are captured, then we should allow flow from one to + // another, which entails a this-to-this summary. + 2 <= strictcount(CapturedVariable v | captureAccess(v, c)) + or + // Constructors that capture a variable may assign it to a field, which also + // entails a this-to-this summary. + captureAccess(_, c) and c.isConstructor() + } + + /** Holds if the constructor, if any, for the closure defined by `ce` captures `v`. */ + private predicate hasConstructorCapture(ClosureExpr ce, CapturedVariable v) { + exists(Callable c | ce.hasBody(c) and c.isConstructor() and captureAccess(v, c)) + } + + /** + * Holds if `access` is a reference to `ce` evaluated in the `i`th node of `bb`. + * The reference is restricted to be in the same callable as `ce` as a + * precaution, even though this is expected to hold for all the given aliased + * accesses. + */ + private predicate localClosureAccess(ClosureExpr ce, Expr access, BasicBlock bb, int i) { + ce.hasAliasedAccess(access) and + access.hasCfgNode(bb, i) and + pragma[only_bind_out](bb.getEnclosingCallable()) = + pragma[only_bind_out](closureExprGetCallable(ce)) + } + + /** + * Holds if we need an additional read of `v` in the `i`th node of `bb` in + * order to synchronize the value stored on `closure`. + * `topScope` is true if the read is in the defining callable of `v`. + * + * Side-effects of potentially calling `closure` at this point will be + * observed in a similarly synthesized post-update node for this read of `v`. + */ + private predicate synthRead( + CapturedVariable v, BasicBlock bb, int i, boolean topScope, Expr closure + ) { + exists(ClosureExpr ce | closureCaptures(ce, v) | + ce.hasCfgNode(bb, i) and ce = closure + or + localClosureAccess(ce, closure, bb, i) + ) and + if v.getCallable() != bb.getEnclosingCallable() then topScope = false else topScope = true + } + + /** + * Holds if there is an access of a captured variable inside a closure in the + * `i`th node of `bb`, such that we need to synthesize a `this.` qualifier. + */ + private predicate synthThisQualifier(BasicBlock bb, int i) { + synthRead(_, bb, i, false, _) or + captureRead(_, bb, i, false, _) or + captureWrite(_, bb, i, false, _) + } + + private newtype TCaptureContainer = + TVariable(CapturedVariable v) or + TThis(Callable c) { captureAccess(_, c) } + + /** + * A storage location for a captured variable in a specific callable. This is + * either the variable itself (in its defining scope) or an instance variable + * `this` (in a capturing scope). + */ + private class CaptureContainer extends TCaptureContainer { + string toString() { + exists(CapturedVariable v | this = TVariable(v) and result = v.toString()) + or + result = "this" and this = TThis(_) + } + } + + /** Holds if `cc` needs a definition at the entry of its callable scope. */ + private predicate entryDef(CaptureContainer cc, BasicBlock bb, int i) { + exists(Callable c | + entryBlock(bb) and + pragma[only_bind_out](bb.getEnclosingCallable()) = c and + i = + min(int j | + j = 1 or + captureRead(_, bb, j, _, _) or + captureWrite(_, bb, j, _, _) or + synthRead(_, bb, j, _, _) + ) - 1 + | + cc = TThis(c) + or + exists(CapturedParameter p | cc = TVariable(p) and p.getCallable() = c) + ) + } + + private module CaptureSsaInput implements Ssa::InputSig { + final class BasicBlock = Input::BasicBlock; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { + result = Input::getImmediateBasicBlockDominator(bb) + } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { + result = Input::getABasicBlockSuccessor(bb) + } + + class ExitBasicBlock extends BasicBlock { + ExitBasicBlock() { exitBlock(this) } + } + + class SourceVariable = CaptureContainer; + + predicate variableWrite(BasicBlock bb, int i, SourceVariable cc, boolean certain) { + ( + exists(CapturedVariable v | cc = TVariable(v) and captureWrite(v, bb, i, true, _)) + or + entryDef(cc, bb, i) + ) and + certain = true + } + + predicate variableRead(BasicBlock bb, int i, SourceVariable cc, boolean certain) { + ( + synthThisQualifier(bb, i) and cc = TThis(bb.getEnclosingCallable()) + or + exists(CapturedVariable v | cc = TVariable(v) | + captureRead(v, bb, i, true, _) or synthRead(v, bb, i, true, _) + ) + ) and + certain = true + } + } + + private module CaptureSsa = Ssa::Make; + + private newtype TClosureNode = + TSynthRead(CapturedVariable v, BasicBlock bb, int i, Boolean isPost) { + synthRead(v, bb, i, _, _) + } or + TSynthThisQualifier(BasicBlock bb, int i, Boolean isPost) { synthThisQualifier(bb, i) } or + TSynthPhi(CaptureSsa::DefinitionExt phi) { + phi instanceof CaptureSsa::PhiNode or phi instanceof CaptureSsa::PhiReadNode + } or + TExprNode(Expr expr, boolean isPost) { + expr instanceof VariableRead and isPost = [false, true] + or + exists(VariableWrite vw | expr = vw.getSource() and isPost = false) + or + synthRead(_, _, _, _, expr) and isPost = [false, true] + } or + TParamNode(CapturedParameter p) or + TThisParamNode(Callable c) { captureAccess(_, c) } or + TMallocNode(ClosureExpr ce) { hasConstructorCapture(ce, _) } + + class ClosureNode extends TClosureNode { + /** Gets a textual representation of this node. */ + string toString() { + exists(CapturedVariable v | this = TSynthRead(v, _, _, _) and result = v.toString()) + or + result = "this" and this = TSynthThisQualifier(_, _, _) + or + exists(CaptureSsa::DefinitionExt phi, CaptureContainer cc | + this = TSynthPhi(phi) and + phi.definesAt(cc, _, _, _) and + result = "phi(" + cc.toString() + ")" + ) + or + exists(Expr expr, boolean isPost | this = TExprNode(expr, isPost) | + isPost = false and result = expr.toString() + or + isPost = true and result = expr.toString() + " [postupdate]" + ) + or + exists(CapturedParameter p | this = TParamNode(p) and result = p.toString()) + or + result = "this" and this = TThisParamNode(_) + or + result = "malloc" and this = TMallocNode(_) + } + + /** Gets the location of this node. */ + Location getLocation() { + exists(CapturedVariable v, BasicBlock bb, int i, Expr closure | + this = TSynthRead(v, bb, i, _) and + synthRead(v, bb, i, _, closure) and + result = closure.getLocation() + ) + or + exists(BasicBlock bb, int i | this = TSynthThisQualifier(bb, i, _) | + synthRead(_, bb, i, false, any(Expr closure | result = closure.getLocation())) or + captureRead(_, bb, i, false, any(VariableRead vr | result = vr.getLocation())) or + captureWrite(_, bb, i, false, any(VariableWrite vw | result = vw.getLocation())) + ) + or + exists(CaptureSsa::DefinitionExt phi, BasicBlock bb | + this = TSynthPhi(phi) and phi.definesAt(_, bb, _, _) and result = bb.getLocation() + ) + or + exists(Expr expr | this = TExprNode(expr, _) and result = expr.getLocation()) + or + exists(CapturedParameter p | this = TParamNode(p) and result = p.getCallable().getLocation()) + or + exists(Callable c | this = TThisParamNode(c) and result = c.getLocation()) + or + exists(ClosureExpr ce | this = TMallocNode(ce) and result = ce.getLocation()) + } + } + + private class TSynthesizedCaptureNode = TSynthRead or TSynthThisQualifier or TSynthPhi; + + class SynthesizedCaptureNode extends ClosureNode, TSynthesizedCaptureNode { + Callable getEnclosingCallable() { + exists(BasicBlock bb | this = TSynthRead(_, bb, _, _) and result = bb.getEnclosingCallable()) + or + exists(BasicBlock bb | + this = TSynthThisQualifier(bb, _, _) and result = bb.getEnclosingCallable() + ) + or + exists(CaptureSsa::DefinitionExt phi, BasicBlock bb | + this = TSynthPhi(phi) and phi.definesAt(_, bb, _, _) and result = bb.getEnclosingCallable() + ) + } + + predicate isVariableAccess(CapturedVariable v) { + this = TSynthRead(v, _, _, _) + or + exists(CaptureSsa::DefinitionExt phi | + this = TSynthPhi(phi) and phi.definesAt(TVariable(v), _, _, _) + ) + } + + predicate isInstanceAccess() { + this instanceof TSynthThisQualifier + or + exists(CaptureSsa::DefinitionExt phi | + this = TSynthPhi(phi) and phi.definesAt(TThis(_), _, _, _) + ) + } + } + + class ExprNode extends ClosureNode, TExprNode { + ExprNode() { this = TExprNode(_, false) } + + Expr getExpr() { this = TExprNode(result, _) } + } + + class ExprPostUpdateNode extends ClosureNode, TExprNode { + ExprPostUpdateNode() { this = TExprNode(_, true) } + + Expr getExpr() { this = TExprNode(result, _) } + } + + class ParameterNode extends ClosureNode, TParamNode { + CapturedParameter getParameter() { this = TParamNode(result) } + } + + class ThisParameterNode extends ClosureNode, TThisParamNode { + Callable getCallable() { this = TThisParamNode(result) } + } + + class MallocNode extends ClosureNode, TMallocNode { + ClosureExpr getClosureExpr() { this = TMallocNode(result) } + } + + predicate capturePostUpdateNode(SynthesizedCaptureNode post, SynthesizedCaptureNode pre) { + exists(CapturedVariable v, BasicBlock bb, int i | + pre = TSynthRead(v, bb, i, false) and post = TSynthRead(v, bb, i, true) + ) + or + exists(BasicBlock bb, int i | + pre = TSynthThisQualifier(bb, i, false) and post = TSynthThisQualifier(bb, i, true) + ) + } + + private predicate step(CaptureContainer cc, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + CaptureSsa::adjacentDefReadExt(_, cc, bb1, i1, bb2, i2) + } + + private predicate stepToPhi(CaptureContainer cc, BasicBlock bb, int i, TSynthPhi phi) { + exists(CaptureSsa::DefinitionExt next | + CaptureSsa::lastRefRedefExt(_, cc, bb, i, next) and + phi = TSynthPhi(next) + ) + } + + private predicate ssaAccessAt( + ClosureNode n, CaptureContainer cc, boolean isPost, BasicBlock bb, int i + ) { + exists(CapturedVariable v | + synthRead(v, bb, i, true, _) and + n = TSynthRead(v, bb, i, isPost) and + cc = TVariable(v) + ) + or + n = TSynthThisQualifier(bb, i, isPost) and cc = TThis(bb.getEnclosingCallable()) + or + exists(CaptureSsa::DefinitionExt phi | + n = TSynthPhi(phi) and phi.definesAt(cc, bb, i, _) and isPost = false + ) + or + exists(VariableRead vr, CapturedVariable v | + captureRead(v, bb, i, true, vr) and + n = TExprNode(vr, isPost) and + cc = TVariable(v) + ) + or + exists(VariableWrite vw, CapturedVariable v | + captureWrite(v, bb, i, true, vw) and + n = TExprNode(vw.getSource(), false) and + isPost = false and + cc = TVariable(v) + ) + or + exists(CapturedParameter p | + entryDef(cc, bb, i) and + cc = TVariable(p) and + n = TParamNode(p) and + isPost = false + ) + or + exists(Callable c | + entryDef(cc, bb, i) and + cc = TThis(c) and + n = TThisParamNode(c) and + isPost = false + ) + } + + predicate localFlowStep(ClosureNode node1, ClosureNode node2) { + exists(CaptureContainer cc, BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + step(cc, bb1, i1, bb2, i2) and + ssaAccessAt(node1, pragma[only_bind_into](cc), _, bb1, i1) and + ssaAccessAt(node2, pragma[only_bind_into](cc), false, bb2, i2) + ) + or + exists(CaptureContainer cc, BasicBlock bb, int i | + stepToPhi(cc, bb, i, node2) and + ssaAccessAt(node1, cc, _, bb, i) + ) + } + + predicate storeStep(ClosureNode node1, CapturedVariable v, ClosureNode node2) { + // store v in the closure or in the malloc in case of a relevant constructor call + exists(BasicBlock bb, int i, Expr closure | + synthRead(v, bb, i, _, closure) and + node1 = TSynthRead(v, bb, i, false) + | + node2 = TExprNode(closure, false) + or + node2 = TMallocNode(closure) and hasConstructorCapture(closure, v) + ) + or + // write to v inside the closure body + exists(BasicBlock bb, int i, VariableWrite vw | + captureWrite(v, bb, i, false, vw) and + node1 = TExprNode(vw.getSource(), false) and + node2 = TSynthThisQualifier(bb, i, true) + ) + } + + predicate readStep(ClosureNode node1, CapturedVariable v, ClosureNode node2) { + // read v from the closure post-update to observe side-effects + exists(BasicBlock bb, int i, Expr closure, boolean post | + synthRead(v, bb, i, _, closure) and + node1 = TExprNode(closure, post) and + node2 = TSynthRead(v, bb, i, true) + | + post = true + or + // for a constructor call the regular ExprNode is the post-update for the MallocNode + post = false and hasConstructorCapture(closure, v) + ) + or + // read v from the closure inside the closure body + exists(BasicBlock bb, int i | node1 = TSynthThisQualifier(bb, i, false) | + synthRead(v, bb, i, false, _) and + node2 = TSynthRead(v, bb, i, false) + or + exists(VariableRead vr | + captureRead(v, bb, i, false, vr) and + node2 = TExprNode(vr, false) + ) + ) + } +} diff --git a/shared/dataflow/qlpack.yml b/shared/dataflow/qlpack.yml index 5ed99b579c9..8f004a44d75 100644 --- a/shared/dataflow/qlpack.yml +++ b/shared/dataflow/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/dataflow -version: 0.0.1-dev +version: 0.0.2-dev groups: shared library: true dependencies: diff --git a/shared/mad/CHANGELOG.md b/shared/mad/CHANGELOG.md index 5712c750565..ea8b30ac3fe 100644 --- a/shared/mad/CHANGELOG.md +++ b/shared/mad/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 No user-facing changes. diff --git a/shared/mad/change-notes/released/0.1.2.md b/shared/mad/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/mad/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/mad/codeql-pack.release.yml b/shared/mad/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/mad/codeql-pack.release.yml +++ b/shared/mad/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/mad/qlpack.yml b/shared/mad/qlpack.yml index df4e00b59fc..6a51cc1e249 100644 --- a/shared/mad/qlpack.yml +++ b/shared/mad/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/mad -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true dependencies: diff --git a/shared/regex/CHANGELOG.md b/shared/regex/CHANGELOG.md index 932a90a2b80..f81edeca8d2 100644 --- a/shared/regex/CHANGELOG.md +++ b/shared/regex/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 No user-facing changes. diff --git a/shared/regex/change-notes/released/0.1.2.md b/shared/regex/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/regex/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/regex/codeql-pack.release.yml b/shared/regex/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/regex/codeql-pack.release.yml +++ b/shared/regex/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/regex/codeql/regex/nfa/NfaUtils.qll b/shared/regex/codeql/regex/nfa/NfaUtils.qll index 005228e8970..62ecf1e7bba 100644 --- a/shared/regex/codeql/regex/nfa/NfaUtils.qll +++ b/shared/regex/codeql/regex/nfa/NfaUtils.qll @@ -3,6 +3,7 @@ */ private import codeql.regex.RegexTreeView +private import codeql.util.Numbers /** * Classes and predicates that create an NFA and various algorithms for working with it. @@ -17,6 +18,20 @@ module Make { exists(int code | code = ascii(c) | code + 1 = ascii(result)) } + /** + * Gets the `i`th codepoint in `s`. + */ + bindingset[s] + private string getCodepointAt(string s, int i) { result = s.regexpFind("(.|\\s)", i, _) } + + /** + * Gets the length of `s` in codepoints. + */ + bindingset[str] + private int getCodepointLength(string str) { + result = str.regexpReplaceAll("(.|\\s)", "x").length() + } + /** * Gets an approximation for the ASCII code for `char`. * Only the easily printable chars are included (so no newline, tab, null, etc). @@ -189,17 +204,17 @@ module Make { /** An input symbol corresponding to character `c`. */ Char(string c) { c = - any(RegexpCharacterConstant cc | - cc instanceof RelevantRegExpTerm and - not isIgnoreCase(cc.getRootTerm()) - ).getValue().charAt(_) + getCodepointAt(any(RegexpCharacterConstant cc | + cc instanceof RelevantRegExpTerm and + not isIgnoreCase(cc.getRootTerm()) + ).getValue(), _) or // normalize everything to lower case if the regexp is case insensitive c = any(RegexpCharacterConstant cc, string char | cc instanceof RelevantRegExpTerm and isIgnoreCase(cc.getRootTerm()) and - char = cc.getValue().charAt(_) + char = getCodepointAt(cc.getValue(), _) | char.toLowerCase() ) @@ -395,7 +410,7 @@ module Make { string getARelevantChar() { exists(ascii(result)) or - exists(RegexpCharacterConstant c | result = c.getValue().charAt(_)) + exists(RegexpCharacterConstant c | result = getCodepointAt(c.getValue(), _)) or classEscapeMatches(_, result) } @@ -693,6 +708,12 @@ module Make { ) } + pragma[noinline] + private int getCodepointLengthForState(string s) { + result = getCodepointLength(s) and + s = any(RegexpCharacterConstant reg).getValue() + } + /** * Holds if the NFA has a transition from `q1` to `q2` labelled with `lbl`. */ @@ -701,16 +722,16 @@ module Make { q1 = Match(s, i) and ( not isIgnoreCase(s.getRootTerm()) and - lbl = Char(s.getValue().charAt(i)) + lbl = Char(getCodepointAt(s.getValue(), i)) or // normalize everything to lower case if the regexp is case insensitive isIgnoreCase(s.getRootTerm()) and - exists(string c | c = s.getValue().charAt(i) | lbl = Char(c.toLowerCase())) + exists(string c | c = getCodepointAt(s.getValue(), i) | lbl = Char(c.toLowerCase())) ) and ( q2 = Match(s, i + 1) or - s.getValue().length() = i + 1 and + getCodepointLengthForState(s.getValue()) = i + 1 and q2 = after(s) ) ) @@ -811,7 +832,7 @@ module Make { Match(RelevantRegExpTerm t, int i) { i = 0 or - exists(t.(RegexpCharacterConstant).getValue().charAt(i)) + exists(getCodepointAt(t.(RegexpCharacterConstant).getValue(), i)) } or /** * An accept state, where exactly the given input string is accepted. @@ -1104,7 +1125,9 @@ module Make { */ predicate reachesOnlyRejectableSuffixes(State fork, string w) { isReDoSCandidate(fork, w) and - forex(State next | next = process(fork, w, w.length() - 1) | isLikelyRejectable(next)) and + forex(State next | next = process(fork, w, getCodepointLengthForCandidate(w) - 1) | + isLikelyRejectable(next) + ) and not getProcessPrevious(fork, _, w) = acceptsAnySuffix() // we stop `process(..)` early if we can, check here if it happened. } @@ -1214,6 +1237,13 @@ module Make { exists(string char | char = ["|", "\n", "Z"] | not deltaClosedChar(s, char, _)) } + // `process` can't use pragma[inline] predicates. So a materialized version of `getCodepointAt` is needed. + pragma[noinline] + private string getCodePointAtForProcess(string str, int i) { + result = getCodepointAt(str, i) and + isReDoSCandidate(_, str) + } + /** * Gets a state that can be reached from pumpable `fork` consuming all * chars in `w` any number of times followed by the first `i+1` characters of `w`. @@ -1223,7 +1253,7 @@ module Make { exists(State prev | prev = getProcessPrevious(fork, i, w) | not prev = acceptsAnySuffix() and // we stop `process(..)` early if we can. If the successor accepts any suffix, then we know it can never be rejected. exists(string char, InputSymbol sym | - char = w.charAt(i) and + char = getCodePointAtForProcess(w, i) and deltaClosed(prev, sym, result) and // noopt to prevent joining `prev` with all possible `chars` that could transition away from `prev`. // Instead only join with the set of `chars` where a relevant `InputSymbol` has already been found. @@ -1232,6 +1262,12 @@ module Make { ) } + pragma[noinline] + private int getCodepointLengthForCandidate(string s) { + result = getCodepointLength(s) and + isReDoSCandidate(_, s) + } + /** * Gets a state that can be reached from pumpable `fork` consuming all * chars in `w` any number of times followed by the first `i` characters of `w`. @@ -1245,7 +1281,7 @@ module Make { or // repeat until fixpoint i = 0 and - result = process(fork, w, w.length() - 1) + result = process(fork, w, getCodepointLengthForCandidate(w) - 1) ) } @@ -1261,7 +1297,9 @@ module Make { /** * Gets a `char` that occurs in a `pump` string. */ - private string getAProcessChar() { result = any(string s | isReDoSCandidate(_, s)).charAt(_) } + private string getAProcessChar() { + result = getCodepointAt(any(string s | isReDoSCandidate(_, s)), _) + } } /** @@ -1305,10 +1343,40 @@ module Make { bindingset[s] private string escape(string s) { result = - s.replaceAll("\\", "\\\\") - .replaceAll("\n", "\\n") - .replaceAll("\r", "\\r") - .replaceAll("\t", "\\t") + escapeUnicodeString(s.replaceAll("\\", "\\\\") + .replaceAll("\n", "\\n") + .replaceAll("\r", "\\r") + .replaceAll("\t", "\\t")) + } + + /** + * Gets a string where the unicode characters in `s` have been escaped. + */ + bindingset[s] + private string escapeUnicodeString(string s) { + result = + concat(int i, string char | char = escapeUnicodeChar(getCodepointAt(s, i)) | char order by i) + } + + /** + * Gets a unicode escaped string for `char`. + * If `char` is a printable char, then `char` is returned. + */ + bindingset[char] + private string escapeUnicodeChar(string char) { + if isPrintable(char) + then result = char + else + if exists(to4digitHex(any(int i | i.toUnicode() = char))) + then result = "\\u" + to4digitHex(any(int i | i.toUnicode() = char)) + else result = "\\u{" + toHex(any(int i | i.toUnicode() = char)) + "}" + } + + /** Holds if `char` is easily printable char, or whitespace. */ + private predicate isPrintable(string char) { + exists(ascii(char)) + or + char = "\n\r\t".charAt(_) } /** diff --git a/shared/regex/qlpack.yml b/shared/regex/qlpack.yml index 7af709c09df..c67822520be 100644 --- a/shared/regex/qlpack.yml +++ b/shared/regex/qlpack.yml @@ -1,6 +1,7 @@ name: codeql/regex -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true dependencies: + codeql/util: ${workspace} warnOnImplicitThis: true diff --git a/shared/ssa/CHANGELOG.md b/shared/ssa/CHANGELOG.md index df49982f4bd..f4d3b9239cb 100644 --- a/shared/ssa/CHANGELOG.md +++ b/shared/ssa/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 No user-facing changes. diff --git a/shared/ssa/change-notes/released/0.1.2.md b/shared/ssa/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/ssa/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/ssa/codeql-pack.release.yml b/shared/ssa/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/ssa/codeql-pack.release.yml +++ b/shared/ssa/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/ssa/qlpack.yml b/shared/ssa/qlpack.yml index db8deef4bbb..670d0d9f7da 100644 --- a/shared/ssa/qlpack.yml +++ b/shared/ssa/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ssa -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true warnOnImplicitThis: true diff --git a/shared/tutorial/CHANGELOG.md b/shared/tutorial/CHANGELOG.md index 01dff93e6be..77ce73b6acc 100644 --- a/shared/tutorial/CHANGELOG.md +++ b/shared/tutorial/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 No user-facing changes. diff --git a/shared/tutorial/change-notes/released/0.1.2.md b/shared/tutorial/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/tutorial/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/tutorial/codeql-pack.release.yml b/shared/tutorial/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/tutorial/codeql-pack.release.yml +++ b/shared/tutorial/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/tutorial/qlpack.yml b/shared/tutorial/qlpack.yml index ac12902a7f4..dee8f89a34c 100644 --- a/shared/tutorial/qlpack.yml +++ b/shared/tutorial/qlpack.yml @@ -1,6 +1,6 @@ name: codeql/tutorial description: Library for the CodeQL detective tutorials, helping new users learn to write CodeQL queries. -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true warnOnImplicitThis: true diff --git a/shared/typetracking/CHANGELOG.md b/shared/typetracking/CHANGELOG.md index 84420295d07..e5bed327a86 100644 --- a/shared/typetracking/CHANGELOG.md +++ b/shared/typetracking/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 No user-facing changes. diff --git a/shared/typetracking/change-notes/released/0.1.2.md b/shared/typetracking/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/typetracking/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/typetracking/codeql-pack.release.yml b/shared/typetracking/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/typetracking/codeql-pack.release.yml +++ b/shared/typetracking/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/typetracking/qlpack.yml b/shared/typetracking/qlpack.yml index 94e654c4d37..2f9642218da 100644 --- a/shared/typetracking/qlpack.yml +++ b/shared/typetracking/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typetracking -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true dependencies: diff --git a/shared/typos/CHANGELOG.md b/shared/typos/CHANGELOG.md index da65658ea76..66d8aacf9d8 100644 --- a/shared/typos/CHANGELOG.md +++ b/shared/typos/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 No user-facing changes. diff --git a/shared/typos/change-notes/released/0.1.2.md b/shared/typos/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/typos/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/typos/codeql-pack.release.yml b/shared/typos/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/typos/codeql-pack.release.yml +++ b/shared/typos/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/typos/qlpack.yml b/shared/typos/qlpack.yml index 6f3076ec2d9..1019d6ea773 100644 --- a/shared/typos/qlpack.yml +++ b/shared/typos/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typos -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true warnOnImplicitThis: true diff --git a/shared/util/CHANGELOG.md b/shared/util/CHANGELOG.md index e90bdd0f977..3faa7e80d47 100644 --- a/shared/util/CHANGELOG.md +++ b/shared/util/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 ### Deprecated APIs diff --git a/shared/util/change-notes/released/0.1.2.md b/shared/util/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/util/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/util/codeql-pack.release.yml b/shared/util/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/util/codeql-pack.release.yml +++ b/shared/util/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/util/codeql/util/Numbers.qll b/shared/util/codeql/util/Numbers.qll index 2c12438300d..050f3c023f1 100644 --- a/shared/util/codeql/util/Numbers.qll +++ b/shared/util/codeql/util/Numbers.qll @@ -50,7 +50,7 @@ int parseHexInt(string hex) { sum(int index, string c | c = stripped.charAt(index) | - sixteenToThe(stripped.length() - 1 - index) * toHex(c) + sixteenToThe(stripped.length() - 1 - index) * charToHex(c) ) ) } @@ -83,7 +83,7 @@ int parseOctalInt(string octal) { } /** Gets the integer value of the `hex` char. */ -private int toHex(string hex) { +private int charToHex(string hex) { hex = [0 .. 9].toString() and result = hex.toInt() or @@ -100,6 +100,32 @@ private int toHex(string hex) { result = 15 and hex = ["f", "F"] } +/** + * Gets a 4-digit hex representation of `i`. + */ +bindingset[i] +string to4digitHex(int i) { + i >= 0 and + i <= 65535 and + exists(string hex | hex = toHex(i) | + result = concat(int zeroes | zeroes = [1 .. 4 - hex.length()] | "0") + hex + ) +} + +/** + * Gets a hex representation of `i`. + */ +bindingset[i] +string toHex(int i) { + result = + // make the number with lots of preceding zeroes, then remove all preceding zeroes in a post-processing step + concat(int shift | + shift in [28, 24, 20, 16, 12, 8, 4, 0] + | + "0123456789abcdef".charAt(i.bitShiftRight(shift).bitAnd(15)) order by shift desc + ).regexpReplaceAll("^0*", "") +} + /** * Gets the value of 16 to the power of `n`. Holds only for `n` in the range * 0..7 (inclusive). diff --git a/shared/util/qlpack.yml b/shared/util/qlpack.yml index 0fa6d93943c..97b3023582c 100644 --- a/shared/util/qlpack.yml +++ b/shared/util/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/util -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true dependencies: diff --git a/shared/yaml/CHANGELOG.md b/shared/yaml/CHANGELOG.md index 84397a7f5ef..96f1fbefd02 100644 --- a/shared/yaml/CHANGELOG.md +++ b/shared/yaml/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +No user-facing changes. + ## 0.1.1 No user-facing changes. diff --git a/shared/yaml/change-notes/2023-08-07-serverless-library.md b/shared/yaml/change-notes/2023-08-07-serverless-library.md new file mode 100644 index 00000000000..ab1cfa26343 --- /dev/null +++ b/shared/yaml/change-notes/2023-08-07-serverless-library.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added library for serverless functions. Currently used by JavaScript and Python. diff --git a/shared/yaml/change-notes/released/0.1.2.md b/shared/yaml/change-notes/released/0.1.2.md new file mode 100644 index 00000000000..9b0e2e7d717 --- /dev/null +++ b/shared/yaml/change-notes/released/0.1.2.md @@ -0,0 +1,3 @@ +## 0.1.2 + +No user-facing changes. diff --git a/shared/yaml/codeql-pack.release.yml b/shared/yaml/codeql-pack.release.yml index 92d1505475f..6abd14b1ef8 100644 --- a/shared/yaml/codeql-pack.release.yml +++ b/shared/yaml/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.1 +lastReleaseVersion: 0.1.2 diff --git a/shared/yaml/codeql/serverless/ServerLess.qll b/shared/yaml/codeql/serverless/ServerLess.qll new file mode 100644 index 00000000000..a0322ad47a1 --- /dev/null +++ b/shared/yaml/codeql/serverless/ServerLess.qll @@ -0,0 +1,165 @@ +/** + * Provides classes and predicates for working with serverless handlers. + * E.g. [AWS](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) or [serverless](https://npmjs.com/package/serverless) + */ + +/** + * Provides the input for the `ServerLess` module. + * Most of these should be provided by the `yaml` library. + */ +signature module Input { + // -------------------------------------------------- + // The below should be provided by the `yaml` library. + // -------------------------------------------------- + class Container { + string getAbsolutePath(); + + Container getParentContainer(); + } + + class File extends Container; + + class YamlNode { + File getFile(); + + YamlCollection getParentNode(); + } + + class YamlValue extends YamlNode; + + class YamlCollection extends YamlValue; + + class YamlScalar extends YamlValue { + string getValue(); + } + + class YamlMapping extends YamlCollection { + YamlValue lookup(string key); + + YamlValue getValue(int i); + } +} + +/** + * Provides classes and predicates for working with serverless handlers. + * Supports AWS, Alibaba, and serverless. + * + * Common usage is to interpret the handlers as functions and add the + * first argument of these as remote flow sources. + */ +module ServerLess { + import I + + /** + * Gets the looked up value as a convenience. + */ + pragma[inline] + private string lookupValue(YamlMapping mapping, string property) { + result = mapping.lookup(property).(YamlScalar).getValue() + } + + /** + * Gets the looked up value if it exists or + * the empty string if it does not. + */ + bindingset[property] + pragma[inline] + private string lookupValueOrEmpty(YamlMapping mapping, string property) { + if exists(mapping.lookup(property)) + then result = mapping.lookup(property).(YamlScalar).getValue() + else result = "" + } + + /** + * Gets a string where an ending "/." is simplified to "/" (if it exists). + */ + bindingset[base] + private string removeTrailingDot(string base) { + if base.regexpMatch(".*/\\.") + then result = base.substring(0, base.length() - 1) + else result = base + } + + /** + * Gets a string where a leading "./" is simplified to "" (if it exists). + */ + bindingset[base] + private string removeLeadingDotSlash(string base) { + if base.regexpMatch("\\./.*") then result = base.substring(2, base.length()) else result = base + } + + /** + * Gets a string suitable as part of a file path. + * + * Maps the empty string to the empty string. + */ + bindingset[base] + private string normalizePath(string base) { + result = removeLeadingDotSlash(removeTrailingDot(base)) + } + + /** + * Holds if the `.yml` file `ymlFile` contains a serverless configuration from `framework` with + * `handler`, `codeURI`, and `runtime` properties. + * `codeURI` and `runtime` default to the empty string if no explicit value is set in the configuration. + * + * `handler` should be interpreted in a language specific way, see `mapping.md`. + */ + predicate hasServerlessHandler( + File ymlFile, string framework, string handler, string codeUri, string runtime + ) { + exists(YamlMapping resource | ymlFile = resource.getFile() | + // Official AWS API uses "AWS::Serverless::Function" but we've seen that Aliyun uses the same schema ("Aliyun::Serverless::Function"), so we allow any prefix to be used. + // Note that "AWS::Serverless::Function" expands to a "AWS::Lambda::Function" when deployed (described here: https://github.com/aws/serverless-application-model#getting-started). Also note that a "AWS::Lambda::Function" requires code in its definition, so needs different handling (see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) + resource.lookup("Type").(YamlScalar).getValue().regexpMatch(".*::Serverless::Function") and + framework = lookupValue(resource, "Type") and + exists(YamlMapping properties | properties = resource.lookup("Properties") | + ( + handler = lookupValue(properties, "Handler") and + codeUri = normalizePath(lookupValueOrEmpty(properties, "CodeUri")) and + runtime = lookupValueOrEmpty(properties, "Runtime") + ) + ) + or + // The `serverless` library, which specifies a top-level `functions` property + framework = "Serverless" and + exists(YamlMapping functions | + functions = resource.lookup("functions") and + not exists(resource.getParentNode()) and + handler = lookupValue(functions.getValue(_), "handler") and + codeUri = "" and + runtime = lookupValueOrEmpty(functions, "Runtime") + ) + ) + } + + /** + * Holds if `handler` = `filePart . astPart` and `filePart` does not contain a `.`. + * This is a convenience predicate, as in many cases the first part of the handler property + * should be interpreted as (the stem of) a file name. + */ + bindingset[handler] + predicate splitHandler(string handler, string filePart, string astPart) { + exists(string pattern | pattern = "(.*?)\\.(.*)" | + filePart = handler.regexpCapture(pattern, 1) and + astPart = handler.regexpCapture(pattern, 2) + ) + } + + /** + * Holds if a file with path `pathNoExt` (+ some extension) has a serverless handler denoted by `func`. + * + * This is a convenience predicate for the common case where the first part of the + * handler property is the file name. + * + * `func` should be interpreted in a language specific way, see `mapping.md`. + */ + predicate hasServerlessHandler(string pathNoExt, string func, string framework, string runtime) { + exists(File ymlFile, string handler, string codeUri, string filePart | + hasServerlessHandler(ymlFile, framework, handler, codeUri, runtime) + | + splitHandler(handler, filePart, func) and + pathNoExt = ymlFile.getParentContainer().getAbsolutePath() + "/" + codeUri + filePart + ) + } +} diff --git a/shared/yaml/codeql/serverless/mapping.md b/shared/yaml/codeql/serverless/mapping.md new file mode 100644 index 00000000000..c8745fdf3bb --- /dev/null +++ b/shared/yaml/codeql/serverless/mapping.md @@ -0,0 +1,81 @@ +# Mapping the `handler` property to a function definition + +## AWS + +[Documentation](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) + +### Node.js or Typescript +See [documentaion](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) + +Setting `handler` to `index.handler` means that `handler` is exported from `index.js`. + +For Typescript, code is first transpiled to JavaScript, see [documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-typescript.html). + + +### Python +See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html) + +Setting `handler` to `lambda_function.lambda_handler` means that `def lambda_handler` is found in `lambda_function.py`. + +### Ruby +See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/ruby-handler.html) + +Setting `handler` to `function.handler` means that `def handler` is found in `function.rb`. +Setting `handler` to `source.LambdaFunctions::Handler.process` means that `def self.process` is found inside `class Handler` inside `module LambdaFunctions` in `source.rb`. + +### Java +See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html) + +You can express the hander in the following formats: + +- `package.Class::method` – Full format. For example: `example.Handler::handleRequest`. + +- `package.Class` – Abbreviated format for functions that implement a handler interface. For example: `example.Handler`. + +### Go +See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html) + +When you configure a function in Go, the value of the handler setting is the executable file name. For example, if you set the value of the handler to `Handler`, Lambda will call the `main()` function in the `Handler` executable file. + +### C# +See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html) + +`handler` is of this format: `Assembly::Namespace.ClassName::MethodName`. +For example, `HelloWorldApp::Example.Hello::MyHandler` if `public Stream MyHandler` is found inside `public class Hello` inside `namespace Example` in the `HelloWorldApp` assembly. + + +## Aliyun (Alibaba Cloud) +[Properties](https://www.alibabacloud.com/help/en/resource-orchestration-service/latest/aliyun-serverless-function) +[Languages](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages) + +### Node.js +See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/node-request-handler) + +The handler must be in the `File name.Method name` format. For example, if your file name is `main.js` and your method name is `handler`, the handler is `main.handler`. + +### Python +See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages-python) + +In Python, your request handler must be in the `File name.Method name` format. For example, if your file name is `main.py` and your method name is `handler`, the handler is `main.handler`. + +### Java +See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages-java) + +The handler must be in the `[Package name].[Class name]::[Method name]` format. For example, if the name of your package is `example`, the class type is `HelloFC`, and method is `handleRequest`, the handler can be configured as `example.HelloFC::handleRequest`. + +### C# +See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages-csharp) + +The handler is in the format of `Assembly::Namespace.ClassName::MethodName`. + +### Go +See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/go-323505) + +The handler for FC functions in the Go language is compiled into an executable binary file. You only need to set the Request Handler parameter of the FC function to the name of the executable file. + +## Serverless +[Documentation](https://www.serverless.com/framework/docs/providers/aws/guide/functions) + +The handler property points to the file and module containing the code you want to run in your function. + +There seems to be nothing language specific written down about the handler property. diff --git a/shared/yaml/qlpack.yml b/shared/yaml/qlpack.yml index f6550d5ac1d..9bdb5bead02 100644 --- a/shared/yaml/qlpack.yml +++ b/shared/yaml/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/yaml -version: 0.1.2-dev +version: 0.1.3-dev groups: shared library: true warnOnImplicitThis: true diff --git a/swift/extractor/invocation/SwiftDiagnosticsConsumer.cpp b/swift/extractor/invocation/SwiftDiagnosticsConsumer.cpp index cee6f7caf55..9d5aa4654d5 100644 --- a/swift/extractor/invocation/SwiftDiagnosticsConsumer.cpp +++ b/swift/extractor/invocation/SwiftDiagnosticsConsumer.cpp @@ -1,5 +1,5 @@ #include "swift/extractor/invocation/SwiftDiagnosticsConsumer.h" -#include "swift/extractor/trap/generated/TrapClasses.h" +#include "swift/extractor/trap/generated/TrapEntries.h" #include "swift/extractor/trap/TrapDomain.h" #include "swift/extractor/infra/SwiftDiagnosticKind.h" @@ -13,17 +13,13 @@ using namespace codeql; void SwiftDiagnosticsConsumer::handleDiagnostic(swift::SourceManager& sourceManager, const swift::DiagnosticInfo& diagInfo) { - if (diagInfo.IsChildNote) return; - Diagnostics diag{trap.createTypedLabel()}; + auto message = getDiagMessage(sourceManager, diagInfo); + DiagnosticsTrap diag{}; + diag.id = trap.createTypedLabel(); diag.kind = translateDiagnosticsKind(diagInfo.Kind); - diag.text = getDiagMessage(sourceManager, diagInfo); + diag.text = message; trap.emit(diag); locationExtractor.attachLocation(sourceManager, diagInfo, diag.id); - - forwardToLog(sourceManager, diagInfo, diag.text); - for (const auto& child : diagInfo.ChildDiagnosticInfo) { - forwardToLog(sourceManager, *child); - } } std::string SwiftDiagnosticsConsumer::getDiagMessage(swift::SourceManager& sourceManager, @@ -33,29 +29,3 @@ std::string SwiftDiagnosticsConsumer::getDiagMessage(swift::SourceManager& sourc swift::DiagnosticEngine::formatDiagnosticText(out, diagInfo.FormatString, diagInfo.FormatArgs); return text.str().str(); } - -void SwiftDiagnosticsConsumer::forwardToLog(swift::SourceManager& sourceManager, - const swift::DiagnosticInfo& diagInfo, - const std::string& message) { - auto file = sourceManager.getDisplayNameForLoc(diagInfo.Loc); - auto [line, column] = sourceManager.getLineAndColumnInBuffer(diagInfo.Loc); - using Kind = swift::DiagnosticKind; - switch (diagInfo.Kind) { - case Kind::Error: - LOG_ERROR("{}:{}:{} {}", file, line, column, message); - break; - case Kind::Warning: - LOG_WARNING("{}:{}:{} {}", file, line, column, message); - break; - case Kind::Remark: - LOG_INFO("{}:{}:{} {}", file, line, column, message); - break; - case Kind::Note: - LOG_DEBUG("{}:{}:{} {}", file, line, column, message); - break; - default: - LOG_ERROR("unknown diagnostic kind {}, {}:{}:{} {}", diagInfo.Kind, file, line, column, - message); - break; - } -} diff --git a/swift/extractor/invocation/SwiftDiagnosticsConsumer.h b/swift/extractor/invocation/SwiftDiagnosticsConsumer.h index e2817981f76..c85233348fd 100644 --- a/swift/extractor/invocation/SwiftDiagnosticsConsumer.h +++ b/swift/extractor/invocation/SwiftDiagnosticsConsumer.h @@ -2,7 +2,6 @@ #include #include "swift/extractor/infra/SwiftLocationExtractor.h" -#include "swift/logging/SwiftLogging.h" namespace codeql { @@ -18,17 +17,8 @@ class SwiftDiagnosticsConsumer : public swift::DiagnosticConsumer { private: static std::string getDiagMessage(swift::SourceManager& sourceManager, const swift::DiagnosticInfo& diagInfo); - void forwardToLog(swift::SourceManager& sourceManager, - const swift::DiagnosticInfo& diagInfo, - const std::string& message); - - void forwardToLog(swift::SourceManager& sourceManager, const swift::DiagnosticInfo& diagInfo) { - forwardToLog(sourceManager, diagInfo, getDiagMessage(sourceManager, diagInfo)); - } - TrapDomain& trap; SwiftLocationExtractor locationExtractor; - Logger logger{"compiler"}; }; } // namespace codeql diff --git a/swift/extractor/main.cpp b/swift/extractor/main.cpp index 0abb5404035..4f1dd990be9 100644 --- a/swift/extractor/main.cpp +++ b/swift/extractor/main.cpp @@ -91,8 +91,6 @@ class Observer : public swift::FrontendObserver { } void configuredCompiler(swift::CompilerInstance& instance) override { - // remove default consumers to avoid double messaging - instance.getDiags().takeConsumers(); instance.addDiagnosticConsumer(&diagConsumer); } diff --git a/swift/extractor/translators/StmtTranslator.cpp b/swift/extractor/translators/StmtTranslator.cpp index 6ff54bd8edf..b7776a104b7 100644 --- a/swift/extractor/translators/StmtTranslator.cpp +++ b/swift/extractor/translators/StmtTranslator.cpp @@ -73,7 +73,7 @@ codeql::ForEachStmt StmtTranslator::translateForEachStmt(const swift::ForEachStm auto entry = dispatcher.createEntry(stmt); fillLabeledStmt(stmt, entry); entry.body = dispatcher.fetchLabel(stmt.getBody()); - entry.sequence = dispatcher.fetchLabel(stmt.getParsedSequence()); + entry.sequence = dispatcher.fetchLabel(stmt.getTypeCheckedSequence()); entry.pattern = dispatcher.fetchLabel(stmt.getPattern()); entry.where = dispatcher.fetchOptionalLabel(stmt.getWhere()); return entry; diff --git a/swift/ql/.generated.list b/swift/ql/.generated.list index a9ddc3be39d..f4a8f20d820 100644 --- a/swift/ql/.generated.list +++ b/swift/ql/.generated.list @@ -347,7 +347,6 @@ lib/codeql/swift/elements/type/StructTypeConstructor.qll a784445a9bb98bb59b866af lib/codeql/swift/elements/type/SubstitutableType.qll 78a240c6226c2167a85dce325f0f3c552364daf879c0309ebefd4787d792df23 cdc27e531f61fb50aaa9a20f5bf05c081759ac27df35e16afcdd2d1ecdac5da0 lib/codeql/swift/elements/type/SugarType.qll 0833a0f1bd26b066817f55df7a58243dbd5da69051272c38effb45653170d5c1 cbcbd68098b76d99c09e7ee43c9e7d04e1b2e860df943a520bf793e835c4db81 lib/codeql/swift/elements/type/SyntaxSugarType.qll 699fe9b4805494b62416dc86098342a725020f59a649138e6f5ba405dd536db5 a7a002cf597c3e3d0fda67111116c61a80f1e66ab8db8ddb3e189c6f15cadda6 -lib/codeql/swift/elements/type/TupleType.qll 1dc14882028be534d15e348fba318c0bb1b52e692ca833987e00c9a66a1921ad 0b34c17ce9db336d0be9a869da988f31f10f754d6ffab6fa88791e508044edd2 lib/codeql/swift/elements/type/TupleTypeConstructor.qll 060633b22ee9884cb98103b380963fac62a02799461d342372cfb9cc6303d693 c9a89f695c85e7e22947287bcc32909b1f701168fd89c3598a45c97909e879f4 lib/codeql/swift/elements/type/TypeAliasTypeConstructor.qll f63ada921beb95d5f3484ab072aa4412e93adfc8e7c0b1637273f99356f5cb13 f90d2789f7c922bc8254a0d131e36b40db1e00f9b32518633520d5c3341cd70a lib/codeql/swift/elements/type/TypeReprConstructor.qll 2bb9c5ece40c6caed9c3a614affc0efd47ad2309c09392800ad346bf369969bf 30429adc135eb8fc476bc9bc185cff0a4119ddc0e618368c44f4a43246b5287f diff --git a/swift/ql/.gitattributes b/swift/ql/.gitattributes index 0f00752d7db..0826b0e6966 100644 --- a/swift/ql/.gitattributes +++ b/swift/ql/.gitattributes @@ -349,7 +349,6 @@ /lib/codeql/swift/elements/type/SubstitutableType.qll linguist-generated /lib/codeql/swift/elements/type/SugarType.qll linguist-generated /lib/codeql/swift/elements/type/SyntaxSugarType.qll linguist-generated -/lib/codeql/swift/elements/type/TupleType.qll linguist-generated /lib/codeql/swift/elements/type/TupleTypeConstructor.qll linguist-generated /lib/codeql/swift/elements/type/TypeAliasTypeConstructor.qll linguist-generated /lib/codeql/swift/elements/type/TypeReprConstructor.qll linguist-generated diff --git a/swift/ql/lib/CHANGELOG.md b/swift/ql/lib/CHANGELOG.md index 433d053b3ab..1f8dabee28d 100644 --- a/swift/ql/lib/CHANGELOG.md +++ b/swift/ql/lib/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.2.2 + +### Major Analysis Improvements + +* Added `DataFlow::ArrayContent`, which will provide more accurate flow through arrays. + +### Minor Analysis Improvements + +* Flow through forced optional unwrapping (`!`) is modelled more accurately. +* Added flow models for `Sequence.withContiguousStorageIfAvailable`. +* Added taint flow for `NSUserActivity.referrerURL`. + ## 0.2.1 ### New Features diff --git a/swift/ql/lib/change-notes/2023-07-17-nsuseractivity-referrer-url.md b/swift/ql/lib/change-notes/2023-07-17-nsuseractivity-referrer-url.md deleted file mode 100644 index 03e90b39c05..00000000000 --- a/swift/ql/lib/change-notes/2023-07-17-nsuseractivity-referrer-url.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -category: minorAnalysis ---- - -* Added taint flow for `NSUserActivity.referrerURL`. diff --git a/swift/ql/lib/change-notes/2023-07-18-array-content.md b/swift/ql/lib/change-notes/2023-07-18-array-content.md deleted file mode 100644 index 6b4e081e93f..00000000000 --- a/swift/ql/lib/change-notes/2023-07-18-array-content.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: majorAnalysis ---- -* Added `DataFlow::ArrayContent`, which will provide more accurate flow through arrays. \ No newline at end of file diff --git a/swift/ql/lib/change-notes/2023-07-24-forced-unwrap.md b/swift/ql/lib/change-notes/2023-07-24-forced-unwrap.md deleted file mode 100644 index 37d785cd68a..00000000000 --- a/swift/ql/lib/change-notes/2023-07-24-forced-unwrap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -category: minorAnalysis ---- - -* Flow through forced optional unwrapping (`!`) is modelled more accurately. diff --git a/swift/ql/lib/change-notes/2023-07-24-with-contiguous-storage.md b/swift/ql/lib/change-notes/2023-07-24-with-contiguous-storage.md deleted file mode 100644 index 01326fba6b9..00000000000 --- a/swift/ql/lib/change-notes/2023-07-24-with-contiguous-storage.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -category: minorAnalysis ---- - -* Added flow models for `Sequence.withContiguousStorageIfAvailable`. diff --git a/swift/ql/lib/change-notes/2023-07-27-type-getname.md b/swift/ql/lib/change-notes/2023-07-27-type-getname.md new file mode 100644 index 00000000000..7a7c0faca5d --- /dev/null +++ b/swift/ql/lib/change-notes/2023-07-27-type-getname.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- + +* `Type.getName` now gets the name of the type alone without any enclosing types. Use `Type.getFullName` for the old behaviour. diff --git a/swift/ql/lib/change-notes/2023-08-04-collection-content.md b/swift/ql/lib/change-notes/2023-08-04-collection-content.md new file mode 100644 index 00000000000..28045e63027 --- /dev/null +++ b/swift/ql/lib/change-notes/2023-08-04-collection-content.md @@ -0,0 +1,4 @@ +--- +category: majorAnalysis +--- +* Added `DataFlow::CollectionContent`, which will enable more accurate flow through collections. \ No newline at end of file diff --git a/swift/ql/lib/change-notes/2023-08-07-forced-unwrap copy.md b/swift/ql/lib/change-notes/2023-08-07-forced-unwrap copy.md new file mode 100644 index 00000000000..1eb9bc53012 --- /dev/null +++ b/swift/ql/lib/change-notes/2023-08-07-forced-unwrap copy.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- + +* Flow through forced optional unwrapping (`!`) on the left side of assignment now works in most cases. diff --git a/swift/ql/lib/change-notes/2023-08-08-ui-text-input.md b/swift/ql/lib/change-notes/2023-08-08-ui-text-input.md new file mode 100644 index 00000000000..ca67fff053e --- /dev/null +++ b/swift/ql/lib/change-notes/2023-08-08-ui-text-input.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added local flow sources for `UITextInput` and related classes. diff --git a/swift/ql/lib/change-notes/released/0.2.2.md b/swift/ql/lib/change-notes/released/0.2.2.md new file mode 100644 index 00000000000..9ffc9a4d3e8 --- /dev/null +++ b/swift/ql/lib/change-notes/released/0.2.2.md @@ -0,0 +1,11 @@ +## 0.2.2 + +### Major Analysis Improvements + +* Added `DataFlow::ArrayContent`, which will provide more accurate flow through arrays. + +### Minor Analysis Improvements + +* Flow through forced optional unwrapping (`!`) is modelled more accurately. +* Added flow models for `Sequence.withContiguousStorageIfAvailable`. +* Added taint flow for `NSUserActivity.referrerURL`. diff --git a/swift/ql/lib/codeql-pack.release.yml b/swift/ql/lib/codeql-pack.release.yml index df29a726bcc..16a06790aa8 100644 --- a/swift/ql/lib/codeql-pack.release.yml +++ b/swift/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.2.1 +lastReleaseVersion: 0.2.2 diff --git a/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll b/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll index 5778a760fef..f2e31b71f07 100644 --- a/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll +++ b/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll @@ -476,22 +476,32 @@ private predicate parseField(AccessPathToken c, Content::FieldContent f) { ) } -private predicate parseEnum(AccessPathToken c, Content::EnumContent f) { +private predicate parseTuple(AccessPathToken c, Content::TupleContent t) { + c.getName() = "TupleElement" and + t.getIndex() = c.getAnArgument().toInt() +} + +private predicate parseEnum(AccessPathToken c, Content::EnumContent e) { c.getName() = "EnumElement" and - c.getAnArgument() = f.getSignature() + c.getAnArgument() = e.getSignature() or c.getName() = "OptionalSome" and - f.getSignature() = "some:0" + e.getSignature() = "some:0" } /** Holds if the specification component parses as a `Content`. */ predicate parseContent(AccessPathToken component, Content content) { parseField(component, content) or + parseTuple(component, content) + or parseEnum(component, content) or component.getName() = "ArrayElement" and content instanceof Content::ArrayContent + or + component.getName() = "CollectionElement" and + content instanceof Content::CollectionContent } cached diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll index 02b32270a88..145ac20573b 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll @@ -256,7 +256,8 @@ private module Cached { TFieldContent(FieldDecl f) or TTupleContent(int index) { exists(any(TupleExpr te).getElement(index)) } or TEnumContent(ParamDecl f) { exists(EnumElementDecl d | d.getAParam() = f) } or - TArrayContent() + TArrayContent() or + TCollectionContent() } /** @@ -702,6 +703,14 @@ predicate storeStep(Node node1, ContentSet c, Node node2) { init.isFailable() ) or + // assignment to an optional via `!`, e.g. `optional! = ...` + exists(ForceValueExpr fve, AssignExpr assign | + fve = assign.getDest() and + node1.asExpr() = assign.getSource() and + node2.asExpr() = fve.getSubExpr() and + c instanceof OptionalSomeContentSet + ) + or // creation of an array `[v1,v2]` exists(ArrayExpr arr | node1.asExpr() = arr.getAnElement() and @@ -797,6 +806,9 @@ predicate readStep(Node node1, ContentSet c, Node node2) { subscript.getBase().getType() instanceof ArrayType and c.isSingleton(any(Content::ArrayContent ac)) ) + or + FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c, + node2.(FlowSummaryNode).getSummaryNode()) } /** @@ -898,7 +910,9 @@ class DataFlowExpr = Expr; * Holds if access paths with `c` at their head always should be tracked at high * precision. This disables adaptive access path precision for such access paths. */ -predicate forceHighPrecision(Content c) { c instanceof Content::ArrayContent } +predicate forceHighPrecision(Content c) { + c instanceof Content::ArrayContent or c instanceof Content::CollectionContent +} /** * Holds if the node `n` is unreachable when the call context is `call`. diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll index 6fccd145928..956e31fce36 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll @@ -224,6 +224,11 @@ module Content { class ArrayContent extends Content, TArrayContent { override string toString() { result = "Array element" } } + + /** An element of a collection. */ + class CollectionContent extends Content, TCollectionContent { + override string toString() { result = "Collection element" } + } } /** diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/FlowSummaryImplSpecific.qll b/swift/ql/lib/codeql/swift/dataflow/internal/FlowSummaryImplSpecific.qll index ca1b9a316b5..9dad1d8c18f 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/FlowSummaryImplSpecific.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/FlowSummaryImplSpecific.qll @@ -110,10 +110,25 @@ private string getContentSpecific(ContentSet cs) { result = "Field[" + c.getField().getName() + "]" ) or + exists(Content::TupleContent c | + cs.isSingleton(c) and + result = "TupleElement[" + c.getIndex().toString() + "]" + ) + or + exists(Content::EnumContent c | + cs.isSingleton(c) and + result = "EnumElement[" + c.getSignature() + "]" + ) + or exists(Content::ArrayContent c | cs.isSingleton(c) and result = "ArrayElement" ) + or + exists(Content::CollectionContent c | + cs.isSingleton(c) and + result = "CollectionElement" + ) } /** Gets the textual representation of a summary component in the format used for MaD models. */ @@ -125,6 +140,16 @@ string getMadRepresentationSpecific(SummaryComponent sc) { not rk = getReturnValueKind() and result = "ReturnValue" + "[" + rk + "]" ) + or + exists(ContentSet c | + sc = TWithoutContentSummaryComponent(c) and + result = "WithoutContent" + c.toString() + ) + or + exists(ContentSet c | + sc = TWithContentSummaryComponent(c) and + result = "WithContent" + c.toString() + ) } /** Gets the textual representation of a parameter position in the format used for flow summaries. */ diff --git a/swift/ql/lib/codeql/swift/elements/type/NominalType.qll b/swift/ql/lib/codeql/swift/elements/type/NominalType.qll index 4abc15e30f2..0e65cdc2f94 100644 --- a/swift/ql/lib/codeql/swift/elements/type/NominalType.qll +++ b/swift/ql/lib/codeql/swift/elements/type/NominalType.qll @@ -6,18 +6,4 @@ class NominalType extends Generated::NominalType { override Type getABaseType() { result = this.getDeclaration().(NominalTypeDecl).getABaseType() } NominalType getADerivedType() { result.getABaseType() = this } - - /** - * Gets the full name of this `NominalType`. For example in: - * ```swift - * struct A { - * struct B { - * // ... - * } - * } - * ``` - * The name and full name of `A` is `A`. The name of `B` is `B`, but the - * full name of `B` is `A.B`. - */ - string getFullName() { result = this.getDeclaration().(NominalTypeDecl).getFullName() } } diff --git a/swift/ql/lib/codeql/swift/elements/type/TupleType.qll b/swift/ql/lib/codeql/swift/elements/type/TupleType.qll index b475d73e26c..13f50a5d0d4 100644 --- a/swift/ql/lib/codeql/swift/elements/type/TupleType.qll +++ b/swift/ql/lib/codeql/swift/elements/type/TupleType.qll @@ -1,4 +1,9 @@ -// generated by codegen/codegen.py, remove this comment if you wish to edit this file private import codeql.swift.generated.type.TupleType +/** + * A tuple type, for example: + * ``` + * (Int, String) + * ``` + */ class TupleType extends Generated::TupleType { } diff --git a/swift/ql/lib/codeql/swift/elements/type/Type.qll b/swift/ql/lib/codeql/swift/elements/type/Type.qll index 16547069499..9572229584a 100644 --- a/swift/ql/lib/codeql/swift/elements/type/Type.qll +++ b/swift/ql/lib/codeql/swift/elements/type/Type.qll @@ -6,7 +6,31 @@ private import codeql.swift.generated.type.Type * This QL class is the root of the Swift type hierarchy. */ class Type extends Generated::Type { - override string toString() { result = this.getName() } + override string toString() { result = this.getFullName() } + + /** + * Gets the name of this type. + */ + override string getName() { + // replace anything that looks like a full name `a.b.c` with just the + // short name `c`, by removing the `a.` and `b.` parts. Note that this + // has to be robust for tuple type names such as `(a, b.c)`. + result = super.getName().regexpReplaceAll("[^(),. ]++\\.(?!\\.)", "") + } + + /** + * Gets the full name of this `Type`. For example in: + * ```swift + * struct A { + * struct B { + * // ... + * } + * } + * ``` + * The name and full name of `A` is `A`. The name of `B` is `B`, but the + * full name of `B` is `A.B`. + */ + string getFullName() { result = super.getName() } /** * Gets this type after any type aliases have been resolved. For example in diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Collection.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Collection.qll index 6022d4b767a..adb78248ccd 100644 --- a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Collection.qll +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Collection.qll @@ -26,6 +26,8 @@ private class CollectionSummaries extends SummaryModelCsv { ";Collection;true;split(separator:maxSplits:omittingEmptySubsequences:);;;Argument[-1];ReturnValue;taint", ";Collection;true;removeFirst();;;Argument[-1];ReturnValue;taint", ";Collection;true;popFirst();;;Argument[-1];ReturnValue;taint", + ";Collection;true;randomElement();;;Argument[-1].CollectionElement;ReturnValue.OptionalSome;value", + ";Collection;true;randomElement();;;Argument[-1].ArrayElement;ReturnValue.OptionalSome;value", ";RangeReplaceableCollection;true;append(_:);;;Argument[0];Argument[-1];taint", ";RangeReplaceableCollection;true;append(contentsOf:);;;Argument[0];Argument[-1];taint", ";RangeReplaceableCollection;true;remove(at:);;;Argument[-1];ReturnValue;taint", diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Set.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Set.qll new file mode 100644 index 00000000000..e5198d043bb --- /dev/null +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Set.qll @@ -0,0 +1,20 @@ +/** + * Provides models for `Set` and related Swift classes. + */ + +private import codeql.swift.dataflow.ExternalFlow + +/** + * A model for `Set` and related class members that permit data flow. + */ +private class SetSummaries extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + ";Set;true;insert(_:);;;Argument[-1].CollectionElement;ReturnValue.TupleElement[1];value", + ";Set;true;insert(_:);;;Argument[0];Argument[-1].CollectionElement;value", + ";Set;true;insert(_:);;;Argument[0];ReturnValue.TupleElement[1];value", + ";Set;true;init(_:);;;Argument[0].ArrayElement;ReturnValue.CollectionElement;value" + ] + } +} diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll index 14fbb3f6006..93d60dba092 100644 --- a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll @@ -15,6 +15,7 @@ private import NsObject private import NsString private import NsUrl private import Sequence +private import Set private import String private import Url private import UrlSession diff --git a/swift/ql/lib/codeql/swift/frameworks/UIKit/UITextField.qll b/swift/ql/lib/codeql/swift/frameworks/UIKit/UITextField.qll index 3fbbdb0fee1..843c5e45275 100644 --- a/swift/ql/lib/codeql/swift/frameworks/UIKit/UITextField.qll +++ b/swift/ql/lib/codeql/swift/frameworks/UIKit/UITextField.qll @@ -1,15 +1,22 @@ /** - * Provides models for the `UITextField` Swift class. + * Provides models for the `UITextField` and related Swift class. */ import swift private import codeql.swift.dataflow.ExternalFlow /** - * A model for `UITextField` members that are flow sources. + * A model for `UITextField`, `UITextInput` and related class members that are flow sources. */ private class UITextFieldSource extends SourceModelCsv { override predicate row(string row) { - row = [";UITextField;true;text;;;;local", ";UITextField;true;attributedText;;;;local"] + row = + [ + ";UITextField;true;text;;;;local", ";UITextField;true;attributedText;;;;local", + ";UITextFieldDelegate;true;textField(_:shouldChangeCharactersIn:replacementString:);;;Parameter[2];local", + ";UITextViewDelegate;true;textView(_:shouldChangeTextIn:replacementText:);;;Parameter[2];local", + ";UITextInput;true;text(in:);;;ReturnValue;local", + ";UITextInput;true;shouldChangeText(in:replacementText:);;;Parameter[1];local", + ] } } diff --git a/swift/ql/lib/codeql/swift/security/StringLengthConflationExtensions.qll b/swift/ql/lib/codeql/swift/security/StringLengthConflationExtensions.qll index efc8eeefe35..b558d828a89 100644 --- a/swift/ql/lib/codeql/swift/security/StringLengthConflationExtensions.qll +++ b/swift/ql/lib/codeql/swift/security/StringLengthConflationExtensions.qll @@ -158,7 +158,7 @@ private class ExtraStringLengthConflationSource extends StringLengthConflationSo or stringType = TStringUnicodeScalars() ) and - memberRef.getBase().getType().(NominalType).getName() = stringType.getName() and + memberRef.getBase().getType().(NominalType).getFullName() = stringType.getName() and memberRef.getMember().(VarDecl).getName() = "count" and this.asExpr() = memberRef ) @@ -218,8 +218,8 @@ private class ExtraStringLengthConflationSink extends StringLengthConflationSink stringType = TStringUnicodeScalars() ) and ( - call.getQualifier().getType().(NominalType).getName() = stringType.getName() or - call.getQualifier().getType().(InOutType).getObjectType().(NominalType).getName() = + call.getQualifier().getType().(NominalType).getFullName() = stringType.getName() or + call.getQualifier().getType().(InOutType).getObjectType().(NominalType).getFullName() = stringType.getName() ) and ( diff --git a/swift/ql/lib/codeql/swift/security/UnsafeJsEvalExtensions.qll b/swift/ql/lib/codeql/swift/security/UnsafeJsEvalExtensions.qll index 42f69b5d3a5..1f53214a64d 100644 --- a/swift/ql/lib/codeql/swift/security/UnsafeJsEvalExtensions.qll +++ b/swift/ql/lib/codeql/swift/security/UnsafeJsEvalExtensions.qll @@ -127,7 +127,7 @@ private class DefaultUnsafeJsEvalAdditionalFlowStep extends UnsafeJsEvalAddition ) or exists(MemberRefExpr e, Expr self, VarDecl member | - self.getType().getName().matches(["Unsafe%Buffer%", "Unsafe%Pointer%"]) and + self.getType().getFullName().matches(["Unsafe%Buffer%", "Unsafe%Pointer%"]) and member.getName() = "baseAddress" | e.getBase() = self and diff --git a/swift/ql/lib/qlpack.yml b/swift/ql/lib/qlpack.yml index a81fc7d2c5c..d2de165ae93 100644 --- a/swift/ql/lib/qlpack.yml +++ b/swift/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/swift-all -version: 0.2.2-dev +version: 0.2.3-dev groups: swift extractor: swift dbscheme: swift.dbscheme diff --git a/swift/ql/src/CHANGELOG.md b/swift/ql/src/CHANGELOG.md index 00033599985..115a2266f1e 100644 --- a/swift/ql/src/CHANGELOG.md +++ b/swift/ql/src/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.2.2 + +### New Queries + +* Added new query "Command injection" (`swift/command-line-injection`). The query finds places where user input is used to execute system commands without proper escaping. +* Added new query "Bad HTML filtering regexp" (`swift/bad-tag-filter`). This query finds regular expressions that match HTML tags in a way that is not robust and can easily lead to security issues. + ## 0.2.1 ### New Queries diff --git a/swift/ql/src/change-notes/2023-06-23-bad-tag-filter-query.md b/swift/ql/src/change-notes/2023-06-23-bad-tag-filter-query.md deleted file mode 100644 index 9498212d4fd..00000000000 --- a/swift/ql/src/change-notes/2023-06-23-bad-tag-filter-query.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: newQuery ---- -* Added new query "Bad HTML filtering regexp" (`swift/bad-tag-filter`). This query finds regular expressions that match HTML tags in a way that is not robust and can easily lead to security issues. diff --git a/swift/ql/src/change-notes/2023-07-12-command-injection.md b/swift/ql/src/change-notes/2023-07-12-command-injection.md deleted file mode 100644 index 2befc7592b9..00000000000 --- a/swift/ql/src/change-notes/2023-07-12-command-injection.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: newQuery ---- -* Added new query "Command injection" (`swift/command-line-injection`). The query finds places where user input is used to execute system commands without proper escaping. \ No newline at end of file diff --git a/swift/ql/src/change-notes/released/0.2.2.md b/swift/ql/src/change-notes/released/0.2.2.md new file mode 100644 index 00000000000..7735974176e --- /dev/null +++ b/swift/ql/src/change-notes/released/0.2.2.md @@ -0,0 +1,6 @@ +## 0.2.2 + +### New Queries + +* Added new query "Command injection" (`swift/command-line-injection`). The query finds places where user input is used to execute system commands without proper escaping. +* Added new query "Bad HTML filtering regexp" (`swift/bad-tag-filter`). This query finds regular expressions that match HTML tags in a way that is not robust and can easily lead to security issues. diff --git a/swift/ql/src/codeql-pack.release.yml b/swift/ql/src/codeql-pack.release.yml index df29a726bcc..16a06790aa8 100644 --- a/swift/ql/src/codeql-pack.release.yml +++ b/swift/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.2.1 +lastReleaseVersion: 0.2.2 diff --git a/swift/ql/src/qlpack.yml b/swift/ql/src/qlpack.yml index 1c417508d2f..215b1f4280d 100644 --- a/swift/ql/src/qlpack.yml +++ b/swift/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/swift-queries -version: 0.2.2-dev +version: 0.2.3-dev groups: - swift - queries diff --git a/swift/ql/test/extractor-tests/generated/type/BuiltinIntegerType/BuiltinIntegerType.expected b/swift/ql/test/extractor-tests/generated/type/BuiltinIntegerType/BuiltinIntegerType.expected index 507407ef6ae..d57e72a708b 100644 --- a/swift/ql/test/extractor-tests/generated/type/BuiltinIntegerType/BuiltinIntegerType.expected +++ b/swift/ql/test/extractor-tests/generated/type/BuiltinIntegerType/BuiltinIntegerType.expected @@ -1,5 +1,5 @@ -| Builtin.Int8 | getName: | Builtin.Int8 | getCanonicalType: | Builtin.Int8 | hasWidth: | yes | -| Builtin.Int16 | getName: | Builtin.Int16 | getCanonicalType: | Builtin.Int16 | hasWidth: | yes | -| Builtin.Int32 | getName: | Builtin.Int32 | getCanonicalType: | Builtin.Int32 | hasWidth: | yes | -| Builtin.Int64 | getName: | Builtin.Int64 | getCanonicalType: | Builtin.Int64 | hasWidth: | yes | -| Builtin.Word | getName: | Builtin.Word | getCanonicalType: | Builtin.Word | hasWidth: | no | +| Builtin.Int8 | getName: | Int8 | getCanonicalType: | Builtin.Int8 | hasWidth: | yes | +| Builtin.Int16 | getName: | Int16 | getCanonicalType: | Builtin.Int16 | hasWidth: | yes | +| Builtin.Int32 | getName: | Int32 | getCanonicalType: | Builtin.Int32 | hasWidth: | yes | +| Builtin.Int64 | getName: | Int64 | getCanonicalType: | Builtin.Int64 | hasWidth: | yes | +| Builtin.Word | getName: | Word | getCanonicalType: | Builtin.Word | hasWidth: | no | diff --git a/swift/ql/test/extractor-tests/generated/type/BuiltinType/BuiltinType.expected b/swift/ql/test/extractor-tests/generated/type/BuiltinType/BuiltinType.expected index 5d5639084c3..27e4e6f0868 100644 --- a/swift/ql/test/extractor-tests/generated/type/BuiltinType/BuiltinType.expected +++ b/swift/ql/test/extractor-tests/generated/type/BuiltinType/BuiltinType.expected @@ -1,9 +1,9 @@ -| Builtin.BridgeObject | BuiltinBridgeObjectType | getName: | Builtin.BridgeObject | getCanonicalType: | Builtin.BridgeObject | -| Builtin.Executor | BuiltinExecutorType | getName: | Builtin.Executor | getCanonicalType: | Builtin.Executor | -| Builtin.FPIEEE32 | BuiltinFloatType | getName: | Builtin.FPIEEE32 | getCanonicalType: | Builtin.FPIEEE32 | -| Builtin.FPIEEE64 | BuiltinFloatType | getName: | Builtin.FPIEEE64 | getCanonicalType: | Builtin.FPIEEE64 | -| Builtin.IntLiteral | BuiltinIntegerLiteralType | getName: | Builtin.IntLiteral | getCanonicalType: | Builtin.IntLiteral | -| Builtin.Job | BuiltinJobType | getName: | Builtin.Job | getCanonicalType: | Builtin.Job | -| Builtin.NativeObject | BuiltinNativeObjectType | getName: | Builtin.NativeObject | getCanonicalType: | Builtin.NativeObject | -| Builtin.RawPointer | BuiltinRawPointerType | getName: | Builtin.RawPointer | getCanonicalType: | Builtin.RawPointer | -| Builtin.RawUnsafeContinuation | BuiltinRawUnsafeContinuationType | getName: | Builtin.RawUnsafeContinuation | getCanonicalType: | Builtin.RawUnsafeContinuation | +| Builtin.BridgeObject | BuiltinBridgeObjectType | getName: | BridgeObject | getCanonicalType: | Builtin.BridgeObject | +| Builtin.Executor | BuiltinExecutorType | getName: | Executor | getCanonicalType: | Builtin.Executor | +| Builtin.FPIEEE32 | BuiltinFloatType | getName: | FPIEEE32 | getCanonicalType: | Builtin.FPIEEE32 | +| Builtin.FPIEEE64 | BuiltinFloatType | getName: | FPIEEE64 | getCanonicalType: | Builtin.FPIEEE64 | +| Builtin.IntLiteral | BuiltinIntegerLiteralType | getName: | IntLiteral | getCanonicalType: | Builtin.IntLiteral | +| Builtin.Job | BuiltinJobType | getName: | Job | getCanonicalType: | Builtin.Job | +| Builtin.NativeObject | BuiltinNativeObjectType | getName: | NativeObject | getCanonicalType: | Builtin.NativeObject | +| Builtin.RawPointer | BuiltinRawPointerType | getName: | RawPointer | getCanonicalType: | Builtin.RawPointer | +| Builtin.RawUnsafeContinuation | BuiltinRawUnsafeContinuationType | getName: | RawUnsafeContinuation | getCanonicalType: | Builtin.RawUnsafeContinuation | diff --git a/swift/ql/test/extractor-tests/generated/type/TupleType/TupleType.expected b/swift/ql/test/extractor-tests/generated/type/TupleType/TupleType.expected index 5d73097ab93..9649b1f618e 100644 --- a/swift/ql/test/extractor-tests/generated/type/TupleType/TupleType.expected +++ b/swift/ql/test/extractor-tests/generated/type/TupleType/TupleType.expected @@ -1,5 +1,5 @@ -| (Builtin.IntLiteral, Builtin.IntLiteral) | getName: | (Builtin.IntLiteral, Builtin.IntLiteral) | getCanonicalType: | (Builtin.IntLiteral, Builtin.IntLiteral) | getNumberOfTypes: | 2 | -| (Builtin.IntLiteral, Builtin.IntLiteral) | getName: | (Builtin.IntLiteral, Builtin.IntLiteral) | getCanonicalType: | (Builtin.IntLiteral, Builtin.IntLiteral) | getNumberOfTypes: | 2 | +| (Builtin.IntLiteral, Builtin.IntLiteral) | getName: | (IntLiteral, IntLiteral) | getCanonicalType: | (Builtin.IntLiteral, Builtin.IntLiteral) | getNumberOfTypes: | 2 | +| (Builtin.IntLiteral, Builtin.IntLiteral) | getName: | (IntLiteral, IntLiteral) | getCanonicalType: | (Builtin.IntLiteral, Builtin.IntLiteral) | getNumberOfTypes: | 2 | | (Int, Int, Int, Int, Int) | getName: | (Int, Int, Int, Int, Int) | getCanonicalType: | (Int, Int, Int, Int, Int) | getNumberOfTypes: | 5 | | (Int, String, Double) | getName: | (Int, String, Double) | getCanonicalType: | (Int, String, Double) | getNumberOfTypes: | 3 | | (Int, s: String, Double) | getName: | (Int, s: String, Double) | getCanonicalType: | (Int, s: String, Double) | getNumberOfTypes: | 3 | diff --git a/swift/ql/test/library-tests/ast/PrintAst.expected b/swift/ql/test/library-tests/ast/PrintAst.expected index 4f962606e1b..c6dd456422d 100644 --- a/swift/ql/test/library-tests/ast/PrintAst.expected +++ b/swift/ql/test/library-tests/ast/PrintAst.expected @@ -703,15 +703,18 @@ cfg.swift: # 155| Type = Int? # 138| getElement(0): [ForEachStmt] for ... in ... { ... } # 138| getPattern(): [AnyPattern] _ -# 138| getSequence(): [BinaryExpr] ... ....(_:_:) ... -# 138| getFunction(): [MethodLookupExpr] ....(_:_:) -# 138| getBase(): [TypeExpr] Int.Type -# 138| getTypeRepr(): [TypeRepr] Int -# 138| getMethodRef(): [DeclRefExpr] ...(_:_:) -# 138| getArgument(0): [Argument] : 0 -# 138| getExpr(): [IntegerLiteralExpr] 0 -# 138| getArgument(1): [Argument] : 10 -# 138| getExpr(): [IntegerLiteralExpr] 10 +# 138| getSequence(): [CallExpr] call to makeIterator() +# 138| getFunction(): [MethodLookupExpr] .makeIterator() +# 138| getBase(): [BinaryExpr] ... ....(_:_:) ... +# 138| getFunction(): [MethodLookupExpr] ....(_:_:) +# 138| getBase(): [TypeExpr] Int.Type +# 138| getTypeRepr(): [TypeRepr] Int +# 138| getMethodRef(): [DeclRefExpr] ...(_:_:) +# 138| getArgument(0): [Argument] : 0 +# 138| getExpr(): [IntegerLiteralExpr] 0 +# 138| getArgument(1): [Argument] : 10 +# 138| getExpr(): [IntegerLiteralExpr] 10 +#-----| getMethodRef(): [DeclRefExpr] makeIterator() # 138| getBody(): [BraceStmt] { ... } # 140| getElement(1): [SwitchStmt] switch x { ... } # 140| getExpr(): [DeclRefExpr] x @@ -3206,6 +3209,97 @@ cfg.swift: # 519| getElement(6): [ReturnStmt] return ... # 519| getResult(): [DeclRefExpr] x # 519| getResult().getFullyConverted(): [LoadExpr] (Int) ... +# 522| [NamedFunction] testAsyncFor() +# 522| InterfaceType = () async -> () +# 522| getBody(): [BraceStmt] { ... } +# 523| getVariable(0): [ConcreteVarDecl] stream +# 523| Type = AsyncStream +# 523| getElement(0): [PatternBindingDecl] var ... = ... +# 523| getInit(0): [CallExpr] call to AsyncStream.init(_:bufferingPolicy:_:) +# 523| getFunction(): [MethodLookupExpr] AsyncStream.init(_:bufferingPolicy:_:) +# 523| getBase(): [TypeExpr] AsyncStream.Type +# 523| getTypeRepr(): [TypeRepr] AsyncStream +# 523| getMethodRef(): [DeclRefExpr] AsyncStream.init(_:bufferingPolicy:_:) +# 523| getArgument(0): [Argument] : Int.Type +# 523| getExpr(): [TypeExpr] Int.Type +# 523| getTypeRepr(): [TypeRepr] Int +# 523| getExpr().getFullyConverted(): [DotSelfExpr] .self +# 523| getArgument(1): [Argument] bufferingPolicy: call to ... +# 523| getExpr(): [CallExpr] call to ... +# 523| getFunction(): [MethodLookupExpr] .bufferingNewest +# 523| getBase(): [TypeExpr] AsyncStream.Continuation.BufferingPolicy.Type +# 523| getTypeRepr(): [TypeRepr] AsyncStream.Continuation.BufferingPolicy +# 523| getMethodRef(): [DeclRefExpr] bufferingNewest +# 523| getArgument(0): [Argument] : 5 +# 523| getExpr(): [IntegerLiteralExpr] 5 +# 523| getArgument(2): [Argument] : { ... } +# 523| getExpr(): [ExplicitClosureExpr] { ... } +# 524| getParam(0): [ParamDecl] continuation +# 524| Type = AsyncStream.Continuation +# 523| getBody(): [BraceStmt] { ... } +# 525| getElement(0): [BraceStmt] { ... } +# 525| getElement(0): [CallExpr] call to detached(priority:operation:) +# 525| getFunction(): [MethodLookupExpr] .detached(priority:operation:) +# 525| getBase(): [TypeExpr] Task<(), Never>.Type +# 525| getTypeRepr(): [TypeRepr] Task<(), Never> +# 525| getMethodRef(): [DeclRefExpr] detached(priority:operation:) +# 525| getArgument(0): [Argument] priority: default priority +# 525| getExpr(): [DefaultArgumentExpr] default priority +# 525| getArgument(1): [Argument] operation: { ... } +# 525| getExpr(): [ExplicitClosureExpr] { ... } +# 525| getBody(): [BraceStmt] { ... } +# 526| getElement(0): [ForEachStmt] for ... in ... { ... } +# 526| getPattern(): [NamedPattern] i +# 526| getSequence(): [CallExpr] call to makeIterator() +# 526| getFunction(): [MethodLookupExpr] .makeIterator() +# 526| getBase(): [BinaryExpr] ... ....(_:_:) ... +# 526| getFunction(): [MethodLookupExpr] ....(_:_:) +# 526| getBase(): [TypeExpr] Int.Type +# 526| getTypeRepr(): [TypeRepr] Int +# 526| getMethodRef(): [DeclRefExpr] ...(_:_:) +# 526| getArgument(0): [Argument] : 1 +# 526| getExpr(): [IntegerLiteralExpr] 1 +# 526| getArgument(1): [Argument] : 100 +# 526| getExpr(): [IntegerLiteralExpr] 100 +#-----| getMethodRef(): [DeclRefExpr] makeIterator() +# 526| getBody(): [BraceStmt] { ... } +# 527| getElement(0): [CallExpr] call to yield(_:) +# 527| getFunction(): [MethodLookupExpr] .yield(_:) +# 527| getBase(): [DeclRefExpr] continuation +# 527| getMethodRef(): [DeclRefExpr] yield(_:) +# 527| getArgument(0): [Argument] : i +# 527| getExpr(): [DeclRefExpr] i +# 529| getElement(1): [CallExpr] call to finish() +# 529| getFunction(): [MethodLookupExpr] .finish() +# 529| getBase(): [DeclRefExpr] continuation +# 529| getMethodRef(): [DeclRefExpr] finish() +# 527| getCapture(0): [CapturedDecl] continuation +# 525| getElement(1): [ReturnStmt] return +# 523| getPattern(0): [NamedPattern] stream +# 533| getElement(1): [ForEachStmt] for ... in ... { ... } +# 533| getPattern(): [NamedPattern] i +# 533| getSequence(): [CallExpr] call to makeAsyncIterator() +# 533| getFunction(): [MethodLookupExpr] .makeAsyncIterator() +# 533| getBase(): [DeclRefExpr] stream +# 533| getBase().getFullyConverted(): [LoadExpr] (AsyncStream) ... +#-----| getMethodRef(): [DeclRefExpr] makeAsyncIterator() +# 533| getBody(): [BraceStmt] { ... } +# 534| getElement(0): [CallExpr] call to print(_:separator:terminator:) +# 534| getFunction(): [DeclRefExpr] print(_:separator:terminator:) +# 534| getArgument(0): [Argument] : [...] +# 534| getExpr(): [VarargExpansionExpr] [...] +# 534| getSubExpr(): [ArrayExpr] [...] +# 534| getElement(0): [DeclRefExpr] i +# 534| getElement(0).getFullyConverted(): [ErasureExpr] (Any) ... +# 534| getArgument(1): [Argument] separator: default separator +# 534| getExpr(): [DefaultArgumentExpr] default separator +# 534| getArgument(2): [Argument] terminator: default terminator +# 534| getExpr(): [DefaultArgumentExpr] default terminator +# 525| [NilLiteralExpr] nil +# 526| [ConcreteVarDecl] i +# 526| Type = Int +# 533| [ConcreteVarDecl] i +# 533| Type = Int declarations.swift: # 1| [StructDecl] Foo # 2| getMember(0): [PatternBindingDecl] var ... = ... @@ -6493,15 +6587,18 @@ statements.swift: # 9| Type = Int # 2| getElement(0): [ForEachStmt] for ... in ... { ... } # 2| getPattern(): [NamedPattern] i -# 2| getSequence(): [BinaryExpr] ... ....(_:_:) ... -# 2| getFunction(): [MethodLookupExpr] ....(_:_:) -# 2| getBase(): [TypeExpr] Int.Type -# 2| getTypeRepr(): [TypeRepr] Int -# 2| getMethodRef(): [DeclRefExpr] ...(_:_:) -# 2| getArgument(0): [Argument] : 1 -# 2| getExpr(): [IntegerLiteralExpr] 1 -# 2| getArgument(1): [Argument] : 5 -# 2| getExpr(): [IntegerLiteralExpr] 5 +# 2| getSequence(): [CallExpr] call to makeIterator() +# 2| getFunction(): [MethodLookupExpr] .makeIterator() +# 2| getBase(): [BinaryExpr] ... ....(_:_:) ... +# 2| getFunction(): [MethodLookupExpr] ....(_:_:) +# 2| getBase(): [TypeExpr] Int.Type +# 2| getTypeRepr(): [TypeRepr] Int +# 2| getMethodRef(): [DeclRefExpr] ...(_:_:) +# 2| getArgument(0): [Argument] : 1 +# 2| getExpr(): [IntegerLiteralExpr] 1 +# 2| getArgument(1): [Argument] : 5 +# 2| getExpr(): [IntegerLiteralExpr] 5 +#-----| getMethodRef(): [DeclRefExpr] makeIterator() # 2| getBody(): [BraceStmt] { ... } # 3| getElement(0): [IfStmt] if ... then { ... } else { ... } # 3| getCondition(): [StmtCondition] StmtCondition @@ -6914,7 +7011,10 @@ statements.swift: # 71| getBody(): [BraceStmt] { ... } # 71| getElement(0): [ForEachStmt] for ... in ... where ... { ... } # 71| getPattern(): [NamedPattern] number -# 71| getSequence(): [DeclRefExpr] numbers +# 71| getSequence(): [CallExpr] call to makeIterator() +# 71| getFunction(): [MethodLookupExpr] .makeIterator() +# 71| getBase(): [DeclRefExpr] numbers +#-----| getMethodRef(): [DeclRefExpr] makeIterator() # 71| getWhere(): [BinaryExpr] ... .==(_:_:) ... # 71| getFunction(): [MethodLookupExpr] .==(_:_:) # 71| getBase(): [TypeExpr] Int.Type diff --git a/swift/ql/test/library-tests/ast/cfg.swift b/swift/ql/test/library-tests/ast/cfg.swift index 030e0639375..4d9169943a3 100644 --- a/swift/ql/test/library-tests/ast/cfg.swift +++ b/swift/ql/test/library-tests/ast/cfg.swift @@ -518,3 +518,19 @@ func testAvailable() -> Int { return x } + +func testAsyncFor () async { + var stream = AsyncStream(Int.self, bufferingPolicy: .bufferingNewest(5), { + continuation in + Task.detached { + for i in 1...100 { + continuation.yield(i) + } + continuation.finish() + } + }) + + for try await i in stream { + print(i) + } +} diff --git a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected index 4f87ff785ed..d3f07e39c15 100644 --- a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1422,7 +1422,7 @@ cfg.swift: #-----| -> x # 137| x -#-----| -> ....(_:_:) +#-----| -> .makeIterator() # 138| for ... in ... { ... } #-----| non-empty -> _ @@ -1435,6 +1435,12 @@ cfg.swift: #-----| -> 10 # 138| ... ....(_:_:) ... +#-----| -> call to makeIterator() + +# 138| .makeIterator() +#-----| -> ....(_:_:) + +# 138| call to makeIterator() #-----| -> for ... in ... { ... } # 138| ....(_:_:) @@ -5992,3 +5998,185 @@ cfg.swift: # 519| x #-----| -> (Int) ... + +# 522| enter testAsyncFor() +#-----| -> testAsyncFor() + +# 522| exit testAsyncFor() + +# 522| exit testAsyncFor() (normal) +#-----| -> exit testAsyncFor() + +# 522| testAsyncFor() +#-----| -> stream + +# 523| var ... = ... +#-----| -> .makeAsyncIterator() + +# 523| stream +#-----| match -> AsyncStream.init(_:bufferingPolicy:_:) + +# 523| AsyncStream.init(_:bufferingPolicy:_:) +#-----| -> AsyncStream.Type + +# 523| AsyncStream.Type +#-----| -> Int.Type + +# 523| call to AsyncStream.init(_:bufferingPolicy:_:) +#-----| -> var ... = ... + +# 523| Int.Type +#-----| -> .self + +# 523| .self +#-----| -> .bufferingNewest + +# 523| AsyncStream.Continuation.BufferingPolicy.Type +#-----| -> 5 + +# 523| .bufferingNewest +#-----| -> AsyncStream.Continuation.BufferingPolicy.Type + +# 523| call to ... +#-----| -> { ... } + +# 523| 5 +#-----| -> call to ... + +# 523| enter { ... } +#-----| -> { ... } + +# 523| exit { ... } + +# 523| exit { ... } (normal) +#-----| -> exit { ... } + +# 523| { ... } +#-----| -> continuation + +# 523| { ... } +#-----| -> call to AsyncStream.init(_:bufferingPolicy:_:) + +# 524| continuation +#-----| -> .detached(priority:operation:) + +# 525| Task<(), Never>.Type +#-----| -> default priority + +# 525| return +#-----| return -> exit { ... } (normal) + +# 525| .detached(priority:operation:) +#-----| -> Task<(), Never>.Type + +# 525| call to detached(priority:operation:) +#-----| -> return + +# 525| default priority +#-----| -> { ... } + +# 525| enter { ... } +#-----| -> { ... } + +# 525| exit { ... } + +# 525| exit { ... } (normal) +#-----| -> exit { ... } + +# 525| { ... } +#-----| -> .makeIterator() + +# 525| { ... } +#-----| -> call to detached(priority:operation:) + +# 526| for ... in ... { ... } +#-----| non-empty -> i +#-----| empty -> .finish() + +# 526| i +#-----| match -> .yield(_:) + +# 526| 1 +#-----| -> 100 + +# 526| ... ....(_:_:) ... +#-----| -> call to makeIterator() + +# 526| .makeIterator() +#-----| -> ....(_:_:) + +# 526| call to makeIterator() +#-----| -> for ... in ... { ... } + +# 526| ....(_:_:) +#-----| -> Int.Type + +# 526| Int.Type +#-----| -> 1 + +# 526| 100 +#-----| -> ... ....(_:_:) ... + +# 527| continuation +#-----| -> i + +# 527| .yield(_:) +#-----| -> continuation + +# 527| call to yield(_:) +#-----| -> for ... in ... { ... } + +# 527| i +#-----| -> call to yield(_:) + +# 529| continuation +#-----| -> call to finish() + +# 529| .finish() +#-----| -> continuation + +# 529| call to finish() +#-----| -> exit { ... } (normal) + +# 533| for ... in ... { ... } +#-----| empty -> exit testAsyncFor() (normal) +#-----| non-empty -> i + +# 533| i +#-----| match -> print(_:separator:terminator:) + +# 533| (AsyncStream) ... +#-----| -> call to makeAsyncIterator() + +# 533| .makeAsyncIterator() +#-----| -> stream + +# 533| call to makeAsyncIterator() +#-----| -> for ... in ... { ... } + +# 533| stream +#-----| -> (AsyncStream) ... + +# 534| print(_:separator:terminator:) +#-----| -> i + +# 534| call to print(_:separator:terminator:) +#-----| -> for ... in ... { ... } + +# 534| default separator +#-----| -> default terminator + +# 534| default terminator +#-----| -> call to print(_:separator:terminator:) + +# 534| (Any) ... +#-----| -> [...] + +# 534| [...] +#-----| -> [...] + +# 534| [...] +#-----| -> default separator + +# 534| i +#-----| -> (Any) ... diff --git a/swift/ql/test/library-tests/controlflow/graph/cfg.swift b/swift/ql/test/library-tests/controlflow/graph/cfg.swift index 030e0639375..4d9169943a3 100644 --- a/swift/ql/test/library-tests/controlflow/graph/cfg.swift +++ b/swift/ql/test/library-tests/controlflow/graph/cfg.swift @@ -518,3 +518,19 @@ func testAvailable() -> Int { return x } + +func testAsyncFor () async { + var stream = AsyncStream(Int.self, bufferingPolicy: .bufferingNewest(5), { + continuation in + Task.detached { + for i in 1...100 { + continuation.yield(i) + } + continuation.finish() + } + }) + + for try await i in stream { + print(i) + } +} diff --git a/swift/ql/test/library-tests/dataflow/dataflow/CONSISTENCY/CfgConsistency.expected b/swift/ql/test/library-tests/dataflow/dataflow/CONSISTENCY/CfgConsistency.expected index 53bfb69f0dd..f17b01278c4 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/CONSISTENCY/CfgConsistency.expected +++ b/swift/ql/test/library-tests/dataflow/dataflow/CONSISTENCY/CfgConsistency.expected @@ -1,3 +1,3 @@ multipleSuccessors -| test.swift:519:8:519:12 | let ...? | no-match | test.swift:519:27:519:27 | y | -| test.swift:519:8:519:12 | let ...? | no-match | test.swift:524:9:524:9 | tuple1 | +| test.swift:538:8:538:12 | let ...? | no-match | test.swift:538:27:538:27 | y | +| test.swift:538:8:538:12 | let ...? | no-match | test.swift:543:9:543:9 | tuple1 | diff --git a/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected index bab6db77c24..c638ea8e344 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected +++ b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected @@ -1,9 +1,15 @@ edges | file://:0:0:0:0 | self [a, x] | file://:0:0:0:0 | .a [x] | | file://:0:0:0:0 | self [str] | file://:0:0:0:0 | .str | +| file://:0:0:0:0 | self [v2, some:0] | file://:0:0:0:0 | .v2 [some:0] | +| file://:0:0:0:0 | self [v2] | file://:0:0:0:0 | .v2 | +| file://:0:0:0:0 | self [v3] | file://:0:0:0:0 | .v3 | | file://:0:0:0:0 | self [x, some:0] | file://:0:0:0:0 | .x [some:0] | | file://:0:0:0:0 | self [x] | file://:0:0:0:0 | .x | +| file://:0:0:0:0 | value | file://:0:0:0:0 | [post] self [v2] | +| file://:0:0:0:0 | value | file://:0:0:0:0 | [post] self [v3] | | file://:0:0:0:0 | value | file://:0:0:0:0 | [post] self [x] | +| file://:0:0:0:0 | value [some:0] | file://:0:0:0:0 | [post] self [v2, some:0] | | file://:0:0:0:0 | value [some:0] | file://:0:0:0:0 | [post] self [x, some:0] | | test.swift:6:19:6:26 | call to source() | test.swift:7:15:7:15 | t1 | | test.swift:6:19:6:26 | call to source() | test.swift:9:15:9:15 | t1 | @@ -104,8 +110,8 @@ edges | test.swift:259:12:259:19 | call to source() | test.swift:259:12:259:19 | call to source() [some:0] | | test.swift:259:12:259:19 | call to source() | test.swift:263:13:263:28 | call to optionalSource() | | test.swift:259:12:259:19 | call to source() [some:0] | test.swift:263:13:263:28 | call to optionalSource() [some:0] | -| test.swift:259:12:259:19 | call to source() [some:0] | test.swift:517:13:517:28 | call to optionalSource() [some:0] | -| test.swift:259:12:259:19 | call to source() [some:0] | test.swift:544:13:544:28 | call to optionalSource() [some:0] | +| test.swift:259:12:259:19 | call to source() [some:0] | test.swift:536:13:536:28 | call to optionalSource() [some:0] | +| test.swift:259:12:259:19 | call to source() [some:0] | test.swift:563:13:563:28 | call to optionalSource() [some:0] | | test.swift:263:13:263:28 | call to optionalSource() | test.swift:265:15:265:15 | x | | test.swift:263:13:263:28 | call to optionalSource() | test.swift:267:15:267:16 | ...! | | test.swift:263:13:263:28 | call to optionalSource() | test.swift:271:15:271:16 | ...? | @@ -172,182 +178,256 @@ edges | test.swift:357:15:357:15 | t1 [Tuple element at index 1] | test.swift:357:15:357:18 | .1 | | test.swift:360:15:360:15 | t2 [Tuple element at index 0] | test.swift:360:15:360:18 | .0 | | test.swift:361:15:361:15 | t2 [Tuple element at index 1] | test.swift:361:15:361:18 | .1 | -| test.swift:375:16:375:21 | v | test.swift:375:61:375:61 | v | -| test.swift:375:61:375:61 | v | test.swift:375:45:375:62 | call to ... [mySingle:0] | -| test.swift:377:18:377:23 | v | test.swift:377:59:377:59 | v | -| test.swift:377:59:377:59 | v | test.swift:377:45:377:60 | call to ... [some:0] | -| test.swift:403:9:403:27 | call to ... [mySingle:0] | test.swift:408:10:408:25 | .mySingle(...) [mySingle:0] | -| test.swift:403:9:403:27 | call to ... [mySingle:0] | test.swift:417:13:417:28 | .mySingle(...) [mySingle:0] | -| test.swift:403:19:403:26 | call to source() | test.swift:403:9:403:27 | call to ... [mySingle:0] | -| test.swift:408:10:408:25 | .mySingle(...) [mySingle:0] | test.swift:408:24:408:24 | a | -| test.swift:408:24:408:24 | a | test.swift:409:19:409:19 | a | -| test.swift:417:13:417:28 | .mySingle(...) [mySingle:0] | test.swift:417:27:417:27 | x | -| test.swift:417:27:417:27 | x | test.swift:418:19:418:19 | x | -| test.swift:425:9:425:34 | call to ... [myPair:1] | test.swift:432:10:432:30 | .myPair(...) [myPair:1] | -| test.swift:425:9:425:34 | call to ... [myPair:1] | test.swift:442:13:442:33 | .myPair(...) [myPair:1] | -| test.swift:425:9:425:34 | call to ... [myPair:1] | test.swift:447:33:447:33 | a [myPair:1] | -| test.swift:425:9:425:34 | call to ... [myPair:1] | test.swift:476:13:476:13 | a [myPair:1] | -| test.swift:425:26:425:33 | call to source() | test.swift:425:9:425:34 | call to ... [myPair:1] | -| test.swift:432:10:432:30 | .myPair(...) [myPair:1] | test.swift:432:29:432:29 | b | -| test.swift:432:29:432:29 | b | test.swift:434:19:434:19 | b | -| test.swift:442:13:442:33 | .myPair(...) [myPair:1] | test.swift:442:32:442:32 | y | -| test.swift:442:32:442:32 | y | test.swift:444:19:444:19 | y | -| test.swift:447:21:447:34 | call to ... [myCons:1, myPair:1] | test.swift:457:14:457:38 | .myCons(...) [myCons:1, myPair:1] | -| test.swift:447:21:447:34 | call to ... [myCons:1, myPair:1] | test.swift:472:17:472:41 | .myCons(...) [myCons:1, myPair:1] | -| test.swift:447:21:447:34 | call to ... [myCons:1, myPair:1] | test.swift:476:16:476:16 | b [myCons:1, myPair:1] | -| test.swift:447:33:447:33 | a [myPair:1] | test.swift:447:21:447:34 | call to ... [myCons:1, myPair:1] | -| test.swift:457:14:457:38 | .myCons(...) [myCons:1, myPair:1] | test.swift:457:25:457:37 | .myPair(...) [myPair:1] | -| test.swift:457:25:457:37 | .myPair(...) [myPair:1] | test.swift:457:36:457:36 | c | -| test.swift:457:36:457:36 | c | test.swift:460:19:460:19 | c | -| test.swift:468:13:468:39 | .myPair(...) [myPair:0] | test.swift:468:31:468:31 | x | -| test.swift:468:31:468:31 | x | test.swift:469:19:469:19 | x | -| test.swift:468:43:468:62 | call to ... [myPair:0] | test.swift:468:13:468:39 | .myPair(...) [myPair:0] | -| test.swift:468:51:468:58 | call to source() | test.swift:468:43:468:62 | call to ... [myPair:0] | -| test.swift:472:17:472:41 | .myCons(...) [myCons:1, myPair:1] | test.swift:472:28:472:40 | .myPair(...) [myPair:1] | -| test.swift:472:28:472:40 | .myPair(...) [myPair:1] | test.swift:472:39:472:39 | c | -| test.swift:472:39:472:39 | c | test.swift:473:19:473:19 | c | -| test.swift:476:12:476:17 | (...) [Tuple element at index 0, myPair:1] | test.swift:477:14:477:55 | (...) [Tuple element at index 0, myPair:1] | -| test.swift:476:12:476:17 | (...) [Tuple element at index 1, myCons:1, myPair:1] | test.swift:477:14:477:55 | (...) [Tuple element at index 1, myCons:1, myPair:1] | -| test.swift:476:13:476:13 | a [myPair:1] | test.swift:476:12:476:17 | (...) [Tuple element at index 0, myPair:1] | -| test.swift:476:16:476:16 | b [myCons:1, myPair:1] | test.swift:476:12:476:17 | (...) [Tuple element at index 1, myCons:1, myPair:1] | -| test.swift:477:14:477:55 | (...) [Tuple element at index 0, myPair:1] | test.swift:477:15:477:27 | .myPair(...) [myPair:1] | -| test.swift:477:14:477:55 | (...) [Tuple element at index 1, myCons:1, myPair:1] | test.swift:477:30:477:54 | .myCons(...) [myCons:1, myPair:1] | -| test.swift:477:15:477:27 | .myPair(...) [myPair:1] | test.swift:477:26:477:26 | b | -| test.swift:477:26:477:26 | b | test.swift:479:19:479:19 | b | -| test.swift:477:30:477:54 | .myCons(...) [myCons:1, myPair:1] | test.swift:477:41:477:53 | .myPair(...) [myPair:1] | -| test.swift:477:41:477:53 | .myPair(...) [myPair:1] | test.swift:477:52:477:52 | e | -| test.swift:477:52:477:52 | e | test.swift:482:19:482:19 | e | -| test.swift:488:14:488:38 | call to ... [mySingle:0] | test.swift:494:13:494:35 | .mySingle(...) [mySingle:0] | -| test.swift:488:30:488:37 | call to source() | test.swift:488:14:488:38 | call to ... [mySingle:0] | -| test.swift:490:14:490:32 | call to mkMyEnum1(_:) [mySingle:0] | test.swift:496:13:496:35 | .mySingle(...) [mySingle:0] | -| test.swift:490:24:490:31 | call to source() | test.swift:375:16:375:21 | v | -| test.swift:490:24:490:31 | call to source() | test.swift:490:14:490:32 | call to mkMyEnum1(_:) [mySingle:0] | -| test.swift:492:14:492:32 | call to mkMyEnum2(_:) [mySingle:0] | test.swift:498:13:498:35 | .mySingle(...) [mySingle:0] | -| test.swift:492:24:492:31 | call to source() | test.swift:492:14:492:32 | call to mkMyEnum2(_:) [mySingle:0] | -| test.swift:494:13:494:35 | .mySingle(...) [mySingle:0] | test.swift:494:33:494:33 | d2 | -| test.swift:494:33:494:33 | d2 | test.swift:494:54:494:54 | d2 | -| test.swift:496:13:496:35 | .mySingle(...) [mySingle:0] | test.swift:496:33:496:33 | d4 | -| test.swift:496:33:496:33 | d4 | test.swift:496:54:496:54 | d4 | -| test.swift:498:13:498:35 | .mySingle(...) [mySingle:0] | test.swift:498:33:498:33 | d6 | -| test.swift:498:33:498:33 | d6 | test.swift:498:54:498:54 | d6 | -| test.swift:501:14:501:36 | call to ... [some:0] | test.swift:507:15:507:15 | e2 [some:0] | -| test.swift:501:28:501:35 | call to source() | test.swift:501:14:501:36 | call to ... [some:0] | -| test.swift:503:14:503:34 | call to mkOptional1(_:) [some:0] | test.swift:509:15:509:15 | e4 [some:0] | -| test.swift:503:26:503:33 | call to source() | test.swift:377:18:377:23 | v | -| test.swift:503:26:503:33 | call to source() | test.swift:503:14:503:34 | call to mkOptional1(_:) [some:0] | -| test.swift:505:14:505:34 | call to mkOptional2(_:) [some:0] | test.swift:511:15:511:15 | e6 [some:0] | -| test.swift:505:26:505:33 | call to source() | test.swift:505:14:505:34 | call to mkOptional2(_:) [some:0] | -| test.swift:507:15:507:15 | e2 [some:0] | test.swift:507:15:507:17 | ...! | -| test.swift:509:15:509:15 | e4 [some:0] | test.swift:509:15:509:17 | ...! | -| test.swift:511:15:511:15 | e6 [some:0] | test.swift:511:15:511:17 | ...! | -| test.swift:517:13:517:28 | call to optionalSource() [some:0] | test.swift:519:8:519:12 | let ...? [some:0] | -| test.swift:517:13:517:28 | call to optionalSource() [some:0] | test.swift:524:19:524:19 | x [some:0] | -| test.swift:519:8:519:12 | let ...? [some:0] | test.swift:519:12:519:12 | a | -| test.swift:519:12:519:12 | a | test.swift:520:19:520:19 | a | -| test.swift:524:18:524:23 | (...) [Tuple element at index 0, some:0] | test.swift:526:10:526:37 | (...) [Tuple element at index 0, some:0] | -| test.swift:524:19:524:19 | x [some:0] | test.swift:524:18:524:23 | (...) [Tuple element at index 0, some:0] | -| test.swift:526:10:526:37 | (...) [Tuple element at index 0, some:0] | test.swift:526:11:526:22 | .some(...) [some:0] | -| test.swift:526:11:526:22 | .some(...) [some:0] | test.swift:526:21:526:21 | a | -| test.swift:526:21:526:21 | a | test.swift:527:19:527:19 | a | -| test.swift:540:9:540:9 | self [x, some:0] | file://:0:0:0:0 | self [x, some:0] | -| test.swift:540:9:540:9 | value [some:0] | file://:0:0:0:0 | value [some:0] | -| test.swift:544:13:544:28 | call to optionalSource() [some:0] | test.swift:546:12:546:12 | x [some:0] | -| test.swift:546:5:546:5 | [post] cx [x, some:0] | test.swift:550:20:550:20 | cx [x, some:0] | -| test.swift:546:12:546:12 | x [some:0] | test.swift:540:9:540:9 | value [some:0] | -| test.swift:546:12:546:12 | x [some:0] | test.swift:546:5:546:5 | [post] cx [x, some:0] | -| test.swift:550:11:550:15 | let ...? [some:0] | test.swift:550:15:550:15 | z1 | -| test.swift:550:15:550:15 | z1 | test.swift:551:15:551:15 | z1 | -| test.swift:550:20:550:20 | cx [x, some:0] | test.swift:540:9:540:9 | self [x, some:0] | -| test.swift:550:20:550:20 | cx [x, some:0] | test.swift:550:20:550:23 | .x [some:0] | -| test.swift:550:20:550:23 | .x [some:0] | test.swift:550:11:550:15 | let ...? [some:0] | -| test.swift:557:14:557:21 | call to source() | test.swift:557:13:557:21 | call to +(_:) | -| test.swift:566:9:566:9 | self [str] | file://:0:0:0:0 | self [str] | -| test.swift:567:10:567:13 | s | test.swift:568:13:568:13 | s | -| test.swift:568:7:568:7 | [post] self [str] | test.swift:567:5:569:5 | self[return] [str] | -| test.swift:568:13:568:13 | s | test.swift:568:7:568:7 | [post] self [str] | -| test.swift:573:17:576:5 | self[return] [str] | test.swift:581:13:581:41 | call to MyClass.init(contentsOfFile:) [str] | -| test.swift:574:7:574:7 | [post] self [str] | test.swift:573:17:576:5 | self[return] [str] | -| test.swift:574:7:574:7 | [post] self [str] | test.swift:575:17:575:17 | self [str] | -| test.swift:574:20:574:28 | call to source3() | test.swift:567:10:567:13 | s | -| test.swift:574:20:574:28 | call to source3() | test.swift:574:7:574:7 | [post] self [str] | -| test.swift:575:17:575:17 | self [str] | test.swift:575:17:575:17 | .str | -| test.swift:580:13:580:33 | call to MyClass.init(s:) [str] | test.swift:566:9:566:9 | self [str] | -| test.swift:580:13:580:33 | call to MyClass.init(s:) [str] | test.swift:580:13:580:35 | .str | -| test.swift:580:24:580:32 | call to source3() | test.swift:567:10:567:13 | s | -| test.swift:580:24:580:32 | call to source3() | test.swift:580:13:580:33 | call to MyClass.init(s:) [str] | -| test.swift:581:13:581:41 | call to MyClass.init(contentsOfFile:) [str] | test.swift:566:9:566:9 | self [str] | -| test.swift:581:13:581:41 | call to MyClass.init(contentsOfFile:) [str] | test.swift:581:13:581:43 | .str | -| test.swift:598:8:598:11 | x | test.swift:599:14:599:14 | x | -| test.swift:599:5:599:5 | [post] self [x] | test.swift:598:3:600:3 | self[return] [x] | -| test.swift:599:14:599:14 | x | test.swift:599:5:599:5 | [post] self [x] | -| test.swift:604:11:604:24 | call to S.init(x:) [x] | test.swift:606:13:606:13 | s [x] | -| test.swift:604:11:604:24 | call to S.init(x:) [x] | test.swift:609:13:609:13 | s [x] | -| test.swift:604:16:604:23 | call to source() | test.swift:598:8:598:11 | x | -| test.swift:604:16:604:23 | call to source() | test.swift:604:11:604:24 | call to S.init(x:) [x] | -| test.swift:605:11:605:14 | enter #keyPath(...) [x] | test.swift:605:14:605:14 | KeyPathComponent [x] | -| test.swift:605:14:605:14 | KeyPathComponent [x] | test.swift:605:11:605:14 | exit #keyPath(...) | -| test.swift:606:13:606:13 | s [x] | test.swift:605:11:605:14 | enter #keyPath(...) [x] | -| test.swift:606:13:606:13 | s [x] | test.swift:606:13:606:25 | \\...[...] | -| test.swift:608:36:608:38 | enter #keyPath(...) [x] | test.swift:608:38:608:38 | KeyPathComponent [x] | -| test.swift:608:38:608:38 | KeyPathComponent [x] | test.swift:608:36:608:38 | exit #keyPath(...) | -| test.swift:609:13:609:13 | s [x] | test.swift:608:36:608:38 | enter #keyPath(...) [x] | -| test.swift:609:13:609:13 | s [x] | test.swift:609:13:609:32 | \\...[...] | -| test.swift:615:8:615:11 | s [x] | test.swift:616:14:616:14 | s [x] | -| test.swift:616:5:616:5 | [post] self [s, x] | test.swift:615:3:617:3 | self[return] [s, x] | -| test.swift:616:14:616:14 | s [x] | test.swift:616:5:616:5 | [post] self [s, x] | -| test.swift:621:11:621:24 | call to S.init(x:) [x] | test.swift:622:18:622:18 | s [x] | -| test.swift:621:16:621:23 | call to source() | test.swift:598:8:598:11 | x | -| test.swift:621:16:621:23 | call to source() | test.swift:621:11:621:24 | call to S.init(x:) [x] | -| test.swift:622:12:622:19 | call to S2.init(s:) [s, x] | test.swift:624:13:624:13 | s2 [s, x] | -| test.swift:622:18:622:18 | s [x] | test.swift:615:8:615:11 | s [x] | -| test.swift:622:18:622:18 | s [x] | test.swift:622:12:622:19 | call to S2.init(s:) [s, x] | -| test.swift:623:11:623:17 | enter #keyPath(...) [s, x] | test.swift:623:15:623:15 | KeyPathComponent [s, x] | -| test.swift:623:15:623:15 | KeyPathComponent [s, x] | test.swift:623:17:623:17 | KeyPathComponent [x] | -| test.swift:623:17:623:17 | KeyPathComponent [x] | test.swift:623:11:623:17 | exit #keyPath(...) | -| test.swift:624:13:624:13 | s2 [s, x] | test.swift:623:11:623:17 | enter #keyPath(...) [s, x] | -| test.swift:624:13:624:13 | s2 [s, x] | test.swift:624:13:624:26 | \\...[...] | -| test.swift:628:17:628:26 | [...] [Array element] | test.swift:630:15:630:15 | array [Array element] | -| test.swift:628:18:628:25 | call to source() | test.swift:628:17:628:26 | [...] [Array element] | -| test.swift:629:13:629:22 | enter #keyPath(...) [Array element] | test.swift:629:20:629:22 | KeyPathComponent [Array element] | -| test.swift:629:20:629:22 | KeyPathComponent [Array element] | test.swift:629:13:629:22 | exit #keyPath(...) | -| test.swift:630:15:630:15 | array [Array element] | test.swift:629:13:629:22 | enter #keyPath(...) [Array element] | -| test.swift:630:15:630:15 | array [Array element] | test.swift:630:15:630:31 | \\...[...] | -| test.swift:649:13:649:20 | call to source() | test.swift:657:15:657:15 | y | -| test.swift:659:9:659:16 | call to source() | test.swift:661:11:661:11 | x | -| test.swift:659:9:659:16 | call to source() | test.swift:662:15:662:15 | x | -| test.swift:661:11:661:11 | x | test.swift:661:15:661:15 | [post] y | -| test.swift:661:15:661:15 | [post] y | test.swift:663:15:663:15 | y | -| test.swift:669:5:669:5 | [post] arr1 [Array element] | test.swift:670:15:670:15 | arr1 [Array element] | -| test.swift:669:15:669:22 | call to source() | test.swift:669:5:669:5 | [post] arr1 [Array element] | -| test.swift:670:15:670:15 | arr1 [Array element] | test.swift:670:15:670:21 | ...[...] | -| test.swift:673:16:673:25 | [...] [Array element] | test.swift:674:15:674:15 | arr2 [Array element] | -| test.swift:673:17:673:24 | call to source() | test.swift:673:16:673:25 | [...] [Array element] | -| test.swift:674:15:674:15 | arr2 [Array element] | test.swift:674:15:674:21 | ...[...] | -| test.swift:676:18:676:29 | [...] [Array element, Array element] | test.swift:678:15:678:15 | matrix [Array element, Array element] | -| test.swift:676:19:676:28 | [...] [Array element] | test.swift:676:18:676:29 | [...] [Array element, Array element] | -| test.swift:676:20:676:27 | call to source() | test.swift:676:19:676:28 | [...] [Array element] | -| test.swift:678:15:678:15 | matrix [Array element, Array element] | test.swift:678:15:678:23 | ...[...] [Array element] | -| test.swift:678:15:678:23 | ...[...] [Array element] | test.swift:678:15:678:26 | ...[...] | -| test.swift:681:5:681:5 | [post] matrix2 [Array element, Array element] | test.swift:682:15:682:15 | matrix2 [Array element, Array element] | -| test.swift:681:5:681:14 | [post] getter for ...[...] [Array element] | test.swift:681:5:681:5 | [post] matrix2 [Array element, Array element] | -| test.swift:681:21:681:28 | call to source() | test.swift:681:5:681:14 | [post] getter for ...[...] [Array element] | -| test.swift:682:15:682:15 | matrix2 [Array element, Array element] | test.swift:682:15:682:24 | ...[...] [Array element] | -| test.swift:682:15:682:24 | ...[...] [Array element] | test.swift:682:15:682:27 | ...[...] | -| test.swift:693:5:693:5 | [post] arr6 [Array element] | test.swift:694:15:694:15 | arr6 [Array element] | -| test.swift:693:17:693:24 | call to source() | test.swift:693:5:693:5 | [post] arr6 [Array element] | -| test.swift:694:15:694:15 | arr6 [Array element] | test.swift:694:15:694:21 | ...[...] | +| test.swift:368:22:368:36 | t [Tuple element at index 1] | test.swift:369:13:369:13 | t [Tuple element at index 1] | +| test.swift:369:13:369:13 | t [Tuple element at index 1] | test.swift:369:13:369:15 | .1 | +| test.swift:369:13:369:15 | .1 | test.swift:369:12:369:19 | (...) [Tuple element at index 0] | +| test.swift:375:14:375:26 | (...) [Tuple element at index 1] | test.swift:376:30:376:30 | t1 [Tuple element at index 1] | +| test.swift:375:14:375:26 | (...) [Tuple element at index 1] | test.swift:377:30:377:30 | t1 [Tuple element at index 1] | +| test.swift:375:14:375:26 | (...) [Tuple element at index 1] | test.swift:380:15:380:15 | t1 [Tuple element at index 1] | +| test.swift:375:18:375:25 | call to source() | test.swift:375:14:375:26 | (...) [Tuple element at index 1] | +| test.swift:376:14:376:32 | call to tupleShiftLeft1(_:) [Tuple element at index 0] | test.swift:381:15:381:15 | t2 [Tuple element at index 0] | +| test.swift:376:30:376:30 | t1 [Tuple element at index 1] | test.swift:368:22:368:36 | t [Tuple element at index 1] | +| test.swift:376:30:376:30 | t1 [Tuple element at index 1] | test.swift:376:14:376:32 | call to tupleShiftLeft1(_:) [Tuple element at index 0] | +| test.swift:377:14:377:32 | call to tupleShiftLeft2(_:) [Tuple element at index 0] | test.swift:383:15:383:15 | t3 [Tuple element at index 0] | +| test.swift:377:30:377:30 | t1 [Tuple element at index 1] | test.swift:377:14:377:32 | call to tupleShiftLeft2(_:) [Tuple element at index 0] | +| test.swift:380:15:380:15 | t1 [Tuple element at index 1] | test.swift:380:15:380:18 | .1 | +| test.swift:381:15:381:15 | t2 [Tuple element at index 0] | test.swift:381:15:381:18 | .0 | +| test.swift:383:15:383:15 | t3 [Tuple element at index 0] | test.swift:383:15:383:18 | .0 | +| test.swift:394:16:394:21 | v | test.swift:394:61:394:61 | v | +| test.swift:394:61:394:61 | v | test.swift:394:45:394:62 | call to ... [mySingle:0] | +| test.swift:396:18:396:23 | v | test.swift:396:59:396:59 | v | +| test.swift:396:59:396:59 | v | test.swift:396:45:396:60 | call to ... [some:0] | +| test.swift:422:9:422:27 | call to ... [mySingle:0] | test.swift:427:10:427:25 | .mySingle(...) [mySingle:0] | +| test.swift:422:9:422:27 | call to ... [mySingle:0] | test.swift:436:13:436:28 | .mySingle(...) [mySingle:0] | +| test.swift:422:19:422:26 | call to source() | test.swift:422:9:422:27 | call to ... [mySingle:0] | +| test.swift:427:10:427:25 | .mySingle(...) [mySingle:0] | test.swift:427:24:427:24 | a | +| test.swift:427:24:427:24 | a | test.swift:428:19:428:19 | a | +| test.swift:436:13:436:28 | .mySingle(...) [mySingle:0] | test.swift:436:27:436:27 | x | +| test.swift:436:27:436:27 | x | test.swift:437:19:437:19 | x | +| test.swift:444:9:444:34 | call to ... [myPair:1] | test.swift:451:10:451:30 | .myPair(...) [myPair:1] | +| test.swift:444:9:444:34 | call to ... [myPair:1] | test.swift:461:13:461:33 | .myPair(...) [myPair:1] | +| test.swift:444:9:444:34 | call to ... [myPair:1] | test.swift:466:33:466:33 | a [myPair:1] | +| test.swift:444:9:444:34 | call to ... [myPair:1] | test.swift:495:13:495:13 | a [myPair:1] | +| test.swift:444:26:444:33 | call to source() | test.swift:444:9:444:34 | call to ... [myPair:1] | +| test.swift:451:10:451:30 | .myPair(...) [myPair:1] | test.swift:451:29:451:29 | b | +| test.swift:451:29:451:29 | b | test.swift:453:19:453:19 | b | +| test.swift:461:13:461:33 | .myPair(...) [myPair:1] | test.swift:461:32:461:32 | y | +| test.swift:461:32:461:32 | y | test.swift:463:19:463:19 | y | +| test.swift:466:21:466:34 | call to ... [myCons:1, myPair:1] | test.swift:476:14:476:38 | .myCons(...) [myCons:1, myPair:1] | +| test.swift:466:21:466:34 | call to ... [myCons:1, myPair:1] | test.swift:491:17:491:41 | .myCons(...) [myCons:1, myPair:1] | +| test.swift:466:21:466:34 | call to ... [myCons:1, myPair:1] | test.swift:495:16:495:16 | b [myCons:1, myPair:1] | +| test.swift:466:33:466:33 | a [myPair:1] | test.swift:466:21:466:34 | call to ... [myCons:1, myPair:1] | +| test.swift:476:14:476:38 | .myCons(...) [myCons:1, myPair:1] | test.swift:476:25:476:37 | .myPair(...) [myPair:1] | +| test.swift:476:25:476:37 | .myPair(...) [myPair:1] | test.swift:476:36:476:36 | c | +| test.swift:476:36:476:36 | c | test.swift:479:19:479:19 | c | +| test.swift:487:13:487:39 | .myPair(...) [myPair:0] | test.swift:487:31:487:31 | x | +| test.swift:487:31:487:31 | x | test.swift:488:19:488:19 | x | +| test.swift:487:43:487:62 | call to ... [myPair:0] | test.swift:487:13:487:39 | .myPair(...) [myPair:0] | +| test.swift:487:51:487:58 | call to source() | test.swift:487:43:487:62 | call to ... [myPair:0] | +| test.swift:491:17:491:41 | .myCons(...) [myCons:1, myPair:1] | test.swift:491:28:491:40 | .myPair(...) [myPair:1] | +| test.swift:491:28:491:40 | .myPair(...) [myPair:1] | test.swift:491:39:491:39 | c | +| test.swift:491:39:491:39 | c | test.swift:492:19:492:19 | c | +| test.swift:495:12:495:17 | (...) [Tuple element at index 0, myPair:1] | test.swift:496:14:496:55 | (...) [Tuple element at index 0, myPair:1] | +| test.swift:495:12:495:17 | (...) [Tuple element at index 1, myCons:1, myPair:1] | test.swift:496:14:496:55 | (...) [Tuple element at index 1, myCons:1, myPair:1] | +| test.swift:495:13:495:13 | a [myPair:1] | test.swift:495:12:495:17 | (...) [Tuple element at index 0, myPair:1] | +| test.swift:495:16:495:16 | b [myCons:1, myPair:1] | test.swift:495:12:495:17 | (...) [Tuple element at index 1, myCons:1, myPair:1] | +| test.swift:496:14:496:55 | (...) [Tuple element at index 0, myPair:1] | test.swift:496:15:496:27 | .myPair(...) [myPair:1] | +| test.swift:496:14:496:55 | (...) [Tuple element at index 1, myCons:1, myPair:1] | test.swift:496:30:496:54 | .myCons(...) [myCons:1, myPair:1] | +| test.swift:496:15:496:27 | .myPair(...) [myPair:1] | test.swift:496:26:496:26 | b | +| test.swift:496:26:496:26 | b | test.swift:498:19:498:19 | b | +| test.swift:496:30:496:54 | .myCons(...) [myCons:1, myPair:1] | test.swift:496:41:496:53 | .myPair(...) [myPair:1] | +| test.swift:496:41:496:53 | .myPair(...) [myPair:1] | test.swift:496:52:496:52 | e | +| test.swift:496:52:496:52 | e | test.swift:501:19:501:19 | e | +| test.swift:507:14:507:38 | call to ... [mySingle:0] | test.swift:513:13:513:35 | .mySingle(...) [mySingle:0] | +| test.swift:507:30:507:37 | call to source() | test.swift:507:14:507:38 | call to ... [mySingle:0] | +| test.swift:509:14:509:32 | call to mkMyEnum1(_:) [mySingle:0] | test.swift:515:13:515:35 | .mySingle(...) [mySingle:0] | +| test.swift:509:24:509:31 | call to source() | test.swift:394:16:394:21 | v | +| test.swift:509:24:509:31 | call to source() | test.swift:509:14:509:32 | call to mkMyEnum1(_:) [mySingle:0] | +| test.swift:511:14:511:32 | call to mkMyEnum2(_:) [mySingle:0] | test.swift:517:13:517:35 | .mySingle(...) [mySingle:0] | +| test.swift:511:24:511:31 | call to source() | test.swift:511:14:511:32 | call to mkMyEnum2(_:) [mySingle:0] | +| test.swift:513:13:513:35 | .mySingle(...) [mySingle:0] | test.swift:513:33:513:33 | d2 | +| test.swift:513:33:513:33 | d2 | test.swift:513:54:513:54 | d2 | +| test.swift:515:13:515:35 | .mySingle(...) [mySingle:0] | test.swift:515:33:515:33 | d4 | +| test.swift:515:33:515:33 | d4 | test.swift:515:54:515:54 | d4 | +| test.swift:517:13:517:35 | .mySingle(...) [mySingle:0] | test.swift:517:33:517:33 | d6 | +| test.swift:517:33:517:33 | d6 | test.swift:517:54:517:54 | d6 | +| test.swift:520:14:520:36 | call to ... [some:0] | test.swift:526:15:526:15 | e2 [some:0] | +| test.swift:520:28:520:35 | call to source() | test.swift:520:14:520:36 | call to ... [some:0] | +| test.swift:522:14:522:34 | call to mkOptional1(_:) [some:0] | test.swift:528:15:528:15 | e4 [some:0] | +| test.swift:522:26:522:33 | call to source() | test.swift:396:18:396:23 | v | +| test.swift:522:26:522:33 | call to source() | test.swift:522:14:522:34 | call to mkOptional1(_:) [some:0] | +| test.swift:524:14:524:34 | call to mkOptional2(_:) [some:0] | test.swift:530:15:530:15 | e6 [some:0] | +| test.swift:524:26:524:33 | call to source() | test.swift:524:14:524:34 | call to mkOptional2(_:) [some:0] | +| test.swift:526:15:526:15 | e2 [some:0] | test.swift:526:15:526:17 | ...! | +| test.swift:528:15:528:15 | e4 [some:0] | test.swift:528:15:528:17 | ...! | +| test.swift:530:15:530:15 | e6 [some:0] | test.swift:530:15:530:17 | ...! | +| test.swift:536:13:536:28 | call to optionalSource() [some:0] | test.swift:538:8:538:12 | let ...? [some:0] | +| test.swift:536:13:536:28 | call to optionalSource() [some:0] | test.swift:543:19:543:19 | x [some:0] | +| test.swift:538:8:538:12 | let ...? [some:0] | test.swift:538:12:538:12 | a | +| test.swift:538:12:538:12 | a | test.swift:539:19:539:19 | a | +| test.swift:543:18:543:23 | (...) [Tuple element at index 0, some:0] | test.swift:545:10:545:37 | (...) [Tuple element at index 0, some:0] | +| test.swift:543:19:543:19 | x [some:0] | test.swift:543:18:543:23 | (...) [Tuple element at index 0, some:0] | +| test.swift:545:10:545:37 | (...) [Tuple element at index 0, some:0] | test.swift:545:11:545:22 | .some(...) [some:0] | +| test.swift:545:11:545:22 | .some(...) [some:0] | test.swift:545:21:545:21 | a | +| test.swift:545:21:545:21 | a | test.swift:546:19:546:19 | a | +| test.swift:559:9:559:9 | self [x, some:0] | file://:0:0:0:0 | self [x, some:0] | +| test.swift:559:9:559:9 | value [some:0] | file://:0:0:0:0 | value [some:0] | +| test.swift:563:13:563:28 | call to optionalSource() [some:0] | test.swift:565:12:565:12 | x [some:0] | +| test.swift:565:5:565:5 | [post] cx [x, some:0] | test.swift:569:20:569:20 | cx [x, some:0] | +| test.swift:565:12:565:12 | x [some:0] | test.swift:559:9:559:9 | value [some:0] | +| test.swift:565:12:565:12 | x [some:0] | test.swift:565:5:565:5 | [post] cx [x, some:0] | +| test.swift:569:11:569:15 | let ...? [some:0] | test.swift:569:15:569:15 | z1 | +| test.swift:569:15:569:15 | z1 | test.swift:570:15:570:15 | z1 | +| test.swift:569:20:569:20 | cx [x, some:0] | test.swift:559:9:559:9 | self [x, some:0] | +| test.swift:569:20:569:20 | cx [x, some:0] | test.swift:569:20:569:23 | .x [some:0] | +| test.swift:569:20:569:23 | .x [some:0] | test.swift:569:11:569:15 | let ...? [some:0] | +| test.swift:576:14:576:21 | call to source() | test.swift:576:13:576:21 | call to +(_:) | +| test.swift:585:9:585:9 | self [str] | file://:0:0:0:0 | self [str] | +| test.swift:586:10:586:13 | s | test.swift:587:13:587:13 | s | +| test.swift:587:7:587:7 | [post] self [str] | test.swift:586:5:588:5 | self[return] [str] | +| test.swift:587:13:587:13 | s | test.swift:587:7:587:7 | [post] self [str] | +| test.swift:592:17:595:5 | self[return] [str] | test.swift:600:13:600:41 | call to MyClass.init(contentsOfFile:) [str] | +| test.swift:593:7:593:7 | [post] self [str] | test.swift:592:17:595:5 | self[return] [str] | +| test.swift:593:7:593:7 | [post] self [str] | test.swift:594:17:594:17 | self [str] | +| test.swift:593:20:593:28 | call to source3() | test.swift:586:10:586:13 | s | +| test.swift:593:20:593:28 | call to source3() | test.swift:593:7:593:7 | [post] self [str] | +| test.swift:594:17:594:17 | self [str] | test.swift:594:17:594:17 | .str | +| test.swift:599:13:599:33 | call to MyClass.init(s:) [str] | test.swift:585:9:585:9 | self [str] | +| test.swift:599:13:599:33 | call to MyClass.init(s:) [str] | test.swift:599:13:599:35 | .str | +| test.swift:599:24:599:32 | call to source3() | test.swift:586:10:586:13 | s | +| test.swift:599:24:599:32 | call to source3() | test.swift:599:13:599:33 | call to MyClass.init(s:) [str] | +| test.swift:600:13:600:41 | call to MyClass.init(contentsOfFile:) [str] | test.swift:585:9:585:9 | self [str] | +| test.swift:600:13:600:41 | call to MyClass.init(contentsOfFile:) [str] | test.swift:600:13:600:43 | .str | +| test.swift:617:8:617:11 | x | test.swift:618:14:618:14 | x | +| test.swift:618:5:618:5 | [post] self [x] | test.swift:617:3:619:3 | self[return] [x] | +| test.swift:618:14:618:14 | x | test.swift:618:5:618:5 | [post] self [x] | +| test.swift:623:11:623:24 | call to S.init(x:) [x] | test.swift:625:13:625:13 | s [x] | +| test.swift:623:11:623:24 | call to S.init(x:) [x] | test.swift:628:13:628:13 | s [x] | +| test.swift:623:16:623:23 | call to source() | test.swift:617:8:617:11 | x | +| test.swift:623:16:623:23 | call to source() | test.swift:623:11:623:24 | call to S.init(x:) [x] | +| test.swift:624:11:624:14 | enter #keyPath(...) [x] | test.swift:624:14:624:14 | KeyPathComponent [x] | +| test.swift:624:14:624:14 | KeyPathComponent [x] | test.swift:624:11:624:14 | exit #keyPath(...) | +| test.swift:625:13:625:13 | s [x] | test.swift:624:11:624:14 | enter #keyPath(...) [x] | +| test.swift:625:13:625:13 | s [x] | test.swift:625:13:625:25 | \\...[...] | +| test.swift:627:36:627:38 | enter #keyPath(...) [x] | test.swift:627:38:627:38 | KeyPathComponent [x] | +| test.swift:627:38:627:38 | KeyPathComponent [x] | test.swift:627:36:627:38 | exit #keyPath(...) | +| test.swift:628:13:628:13 | s [x] | test.swift:627:36:627:38 | enter #keyPath(...) [x] | +| test.swift:628:13:628:13 | s [x] | test.swift:628:13:628:32 | \\...[...] | +| test.swift:634:8:634:11 | s [x] | test.swift:635:14:635:14 | s [x] | +| test.swift:635:5:635:5 | [post] self [s, x] | test.swift:634:3:636:3 | self[return] [s, x] | +| test.swift:635:14:635:14 | s [x] | test.swift:635:5:635:5 | [post] self [s, x] | +| test.swift:640:11:640:24 | call to S.init(x:) [x] | test.swift:641:18:641:18 | s [x] | +| test.swift:640:16:640:23 | call to source() | test.swift:617:8:617:11 | x | +| test.swift:640:16:640:23 | call to source() | test.swift:640:11:640:24 | call to S.init(x:) [x] | +| test.swift:641:12:641:19 | call to S2.init(s:) [s, x] | test.swift:643:13:643:13 | s2 [s, x] | +| test.swift:641:18:641:18 | s [x] | test.swift:634:8:634:11 | s [x] | +| test.swift:641:18:641:18 | s [x] | test.swift:641:12:641:19 | call to S2.init(s:) [s, x] | +| test.swift:642:11:642:17 | enter #keyPath(...) [s, x] | test.swift:642:15:642:15 | KeyPathComponent [s, x] | +| test.swift:642:15:642:15 | KeyPathComponent [s, x] | test.swift:642:17:642:17 | KeyPathComponent [x] | +| test.swift:642:17:642:17 | KeyPathComponent [x] | test.swift:642:11:642:17 | exit #keyPath(...) | +| test.swift:643:13:643:13 | s2 [s, x] | test.swift:642:11:642:17 | enter #keyPath(...) [s, x] | +| test.swift:643:13:643:13 | s2 [s, x] | test.swift:643:13:643:26 | \\...[...] | +| test.swift:647:17:647:26 | [...] [Array element] | test.swift:649:15:649:15 | array [Array element] | +| test.swift:647:18:647:25 | call to source() | test.swift:647:17:647:26 | [...] [Array element] | +| test.swift:648:13:648:22 | enter #keyPath(...) [Array element] | test.swift:648:20:648:22 | KeyPathComponent [Array element] | +| test.swift:648:20:648:22 | KeyPathComponent [Array element] | test.swift:648:13:648:22 | exit #keyPath(...) | +| test.swift:649:15:649:15 | array [Array element] | test.swift:648:13:648:22 | enter #keyPath(...) [Array element] | +| test.swift:649:15:649:15 | array [Array element] | test.swift:649:15:649:31 | \\...[...] | +| test.swift:668:13:668:20 | call to source() | test.swift:676:15:676:15 | y | +| test.swift:678:9:678:16 | call to source() | test.swift:680:11:680:11 | x | +| test.swift:678:9:678:16 | call to source() | test.swift:681:15:681:15 | x | +| test.swift:680:11:680:11 | x | test.swift:680:15:680:15 | [post] y | +| test.swift:680:15:680:15 | [post] y | test.swift:682:15:682:15 | y | +| test.swift:688:5:688:5 | [post] arr1 [Array element] | test.swift:689:15:689:15 | arr1 [Array element] | +| test.swift:688:15:688:22 | call to source() | test.swift:688:5:688:5 | [post] arr1 [Array element] | +| test.swift:689:15:689:15 | arr1 [Array element] | test.swift:689:15:689:21 | ...[...] | +| test.swift:692:16:692:25 | [...] [Array element] | test.swift:693:15:693:15 | arr2 [Array element] | +| test.swift:692:17:692:24 | call to source() | test.swift:692:16:692:25 | [...] [Array element] | +| test.swift:693:15:693:15 | arr2 [Array element] | test.swift:693:15:693:21 | ...[...] | +| test.swift:695:18:695:29 | [...] [Array element, Array element] | test.swift:697:15:697:15 | matrix [Array element, Array element] | +| test.swift:695:19:695:28 | [...] [Array element] | test.swift:695:18:695:29 | [...] [Array element, Array element] | +| test.swift:695:20:695:27 | call to source() | test.swift:695:19:695:28 | [...] [Array element] | +| test.swift:697:15:697:15 | matrix [Array element, Array element] | test.swift:697:15:697:23 | ...[...] [Array element] | +| test.swift:697:15:697:23 | ...[...] [Array element] | test.swift:697:15:697:26 | ...[...] | +| test.swift:700:5:700:5 | [post] matrix2 [Array element, Array element] | test.swift:701:15:701:15 | matrix2 [Array element, Array element] | +| test.swift:700:5:700:14 | [post] getter for ...[...] [Array element] | test.swift:700:5:700:5 | [post] matrix2 [Array element, Array element] | +| test.swift:700:21:700:28 | call to source() | test.swift:700:5:700:14 | [post] getter for ...[...] [Array element] | +| test.swift:701:15:701:15 | matrix2 [Array element, Array element] | test.swift:701:15:701:24 | ...[...] [Array element] | +| test.swift:701:15:701:24 | ...[...] [Array element] | test.swift:701:15:701:27 | ...[...] | +| test.swift:712:5:712:5 | [post] arr6 [Array element] | test.swift:713:15:713:15 | arr6 [Array element] | +| test.swift:712:17:712:24 | call to source() | test.swift:712:5:712:5 | [post] arr6 [Array element] | +| test.swift:713:15:713:15 | arr6 [Array element] | test.swift:713:15:713:21 | ...[...] | +| test.swift:715:16:715:25 | [...] [Array element] | test.swift:716:15:716:15 | arr7 [Array element] | +| test.swift:715:17:715:24 | call to source() | test.swift:715:16:715:25 | [...] [Array element] | +| test.swift:716:15:716:15 | arr7 [Array element] | test.swift:716:15:716:34 | call to randomElement() [some:0] | +| test.swift:716:15:716:34 | call to randomElement() [some:0] | test.swift:716:15:716:35 | ...! | +| test.swift:722:5:722:5 | [post] set1 [Collection element] | test.swift:723:15:723:15 | set1 [Collection element] | +| test.swift:722:17:722:24 | call to source() | test.swift:722:5:722:5 | [post] set1 [Collection element] | +| test.swift:723:15:723:15 | set1 [Collection element] | test.swift:723:15:723:34 | call to randomElement() [some:0] | +| test.swift:723:15:723:34 | call to randomElement() [some:0] | test.swift:723:15:723:35 | ...! | +| test.swift:725:16:725:30 | call to Set.init(_:) [Collection element] | test.swift:726:15:726:15 | set2 [Collection element] | +| test.swift:725:20:725:29 | [...] [Array element] | test.swift:725:16:725:30 | call to Set.init(_:) [Collection element] | +| test.swift:725:21:725:28 | call to source() | test.swift:725:20:725:29 | [...] [Array element] | +| test.swift:726:15:726:15 | set2 [Collection element] | test.swift:726:15:726:34 | call to randomElement() [some:0] | +| test.swift:726:15:726:34 | call to randomElement() [some:0] | test.swift:726:15:726:35 | ...! | +| test.swift:731:9:731:9 | self [v2, some:0] | file://:0:0:0:0 | self [v2, some:0] | +| test.swift:731:9:731:9 | self [v2] | file://:0:0:0:0 | self [v2] | +| test.swift:731:9:731:9 | value | file://:0:0:0:0 | value | +| test.swift:731:9:731:9 | value [some:0] | file://:0:0:0:0 | value [some:0] | +| test.swift:732:9:732:9 | self [v3] | file://:0:0:0:0 | self [v3] | +| test.swift:732:9:732:9 | value | file://:0:0:0:0 | value | +| test.swift:742:5:742:5 | v1 [some:0] | test.swift:752:15:752:15 | v1 [some:0] | +| test.swift:742:11:742:18 | call to source() | test.swift:742:5:742:5 | v1 [some:0] | +| test.swift:743:10:743:17 | call to source() | test.swift:743:10:743:17 | call to source() [some:0] | +| test.swift:743:10:743:17 | call to source() | test.swift:753:15:753:17 | ...! | +| test.swift:743:10:743:17 | call to source() [some:0] | test.swift:753:15:753:15 | v2 [some:0] | +| test.swift:744:10:744:17 | call to source() | test.swift:754:15:754:15 | v3 | +| test.swift:746:5:746:5 | [post] mo1 [v2, some:0] | test.swift:747:5:747:5 | mo1 [v2, some:0] | +| test.swift:746:5:746:5 | [post] mo1 [v2] | test.swift:747:5:747:5 | mo1 [v2] | +| test.swift:746:14:746:21 | call to source() | test.swift:731:9:731:9 | value | +| test.swift:746:14:746:21 | call to source() | test.swift:746:5:746:5 | [post] mo1 [v2] | +| test.swift:746:14:746:21 | call to source() | test.swift:746:14:746:21 | call to source() [some:0] | +| test.swift:746:14:746:21 | call to source() [some:0] | test.swift:731:9:731:9 | value [some:0] | +| test.swift:746:14:746:21 | call to source() [some:0] | test.swift:746:5:746:5 | [post] mo1 [v2, some:0] | +| test.swift:747:5:747:5 | [post] mo1 [v3] | test.swift:757:15:757:15 | mo1 [v3] | +| test.swift:747:5:747:5 | mo1 [v2, some:0] | test.swift:756:15:756:15 | mo1 [v2, some:0] | +| test.swift:747:5:747:5 | mo1 [v2] | test.swift:756:15:756:15 | mo1 [v2] | +| test.swift:747:14:747:21 | call to source() | test.swift:732:9:732:9 | value | +| test.swift:747:14:747:21 | call to source() | test.swift:747:5:747:5 | [post] mo1 [v3] | +| test.swift:752:15:752:15 | v1 [some:0] | test.swift:752:15:752:17 | ...! | +| test.swift:753:15:753:15 | v2 [some:0] | test.swift:753:15:753:17 | ...! | +| test.swift:756:15:756:15 | mo1 [v2, some:0] | test.swift:731:9:731:9 | self [v2, some:0] | +| test.swift:756:15:756:15 | mo1 [v2, some:0] | test.swift:756:15:756:19 | .v2 [some:0] | +| test.swift:756:15:756:15 | mo1 [v2] | test.swift:731:9:731:9 | self [v2] | +| test.swift:756:15:756:15 | mo1 [v2] | test.swift:756:15:756:19 | .v2 | +| test.swift:756:15:756:19 | .v2 | test.swift:756:15:756:21 | ...! | +| test.swift:756:15:756:19 | .v2 [some:0] | test.swift:756:15:756:21 | ...! | +| test.swift:757:15:757:15 | mo1 [v3] | test.swift:732:9:732:9 | self [v3] | +| test.swift:757:15:757:15 | mo1 [v3] | test.swift:757:15:757:19 | .v3 | nodes | file://:0:0:0:0 | .a [x] | semmle.label | .a [x] | | file://:0:0:0:0 | .str | semmle.label | .str | +| file://:0:0:0:0 | .v2 | semmle.label | .v2 | +| file://:0:0:0:0 | .v2 [some:0] | semmle.label | .v2 [some:0] | +| file://:0:0:0:0 | .v3 | semmle.label | .v3 | | file://:0:0:0:0 | .x | semmle.label | .x | | file://:0:0:0:0 | .x [some:0] | semmle.label | .x [some:0] | +| file://:0:0:0:0 | [post] self [v2, some:0] | semmle.label | [post] self [v2, some:0] | +| file://:0:0:0:0 | [post] self [v2] | semmle.label | [post] self [v2] | +| file://:0:0:0:0 | [post] self [v3] | semmle.label | [post] self [v3] | | file://:0:0:0:0 | [post] self [x, some:0] | semmle.label | [post] self [x, some:0] | | file://:0:0:0:0 | [post] self [x] | semmle.label | [post] self [x] | | file://:0:0:0:0 | self [a, x] | semmle.label | self [a, x] | | file://:0:0:0:0 | self [str] | semmle.label | self [str] | +| file://:0:0:0:0 | self [v2, some:0] | semmle.label | self [v2, some:0] | +| file://:0:0:0:0 | self [v2] | semmle.label | self [v2] | +| file://:0:0:0:0 | self [v3] | semmle.label | self [v3] | | file://:0:0:0:0 | self [x, some:0] | semmle.label | self [x, some:0] | | file://:0:0:0:0 | self [x] | semmle.label | self [x] | | file://:0:0:0:0 | value | semmle.label | value | +| file://:0:0:0:0 | value | semmle.label | value | +| file://:0:0:0:0 | value | semmle.label | value | +| file://:0:0:0:0 | value [some:0] | semmle.label | value [some:0] | | file://:0:0:0:0 | value [some:0] | semmle.label | value [some:0] | | test.swift:6:19:6:26 | call to source() | semmle.label | call to source() | | test.swift:7:15:7:15 | t1 | semmle.label | t1 | @@ -523,189 +603,252 @@ nodes | test.swift:361:15:361:18 | .1 | semmle.label | .1 | | test.swift:363:15:363:15 | a | semmle.label | a | | test.swift:364:15:364:15 | b | semmle.label | b | -| test.swift:375:16:375:21 | v | semmle.label | v | -| test.swift:375:45:375:62 | call to ... [mySingle:0] | semmle.label | call to ... [mySingle:0] | -| test.swift:375:61:375:61 | v | semmle.label | v | -| test.swift:377:18:377:23 | v | semmle.label | v | -| test.swift:377:45:377:60 | call to ... [some:0] | semmle.label | call to ... [some:0] | -| test.swift:377:59:377:59 | v | semmle.label | v | -| test.swift:403:9:403:27 | call to ... [mySingle:0] | semmle.label | call to ... [mySingle:0] | -| test.swift:403:19:403:26 | call to source() | semmle.label | call to source() | -| test.swift:408:10:408:25 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | -| test.swift:408:24:408:24 | a | semmle.label | a | -| test.swift:409:19:409:19 | a | semmle.label | a | -| test.swift:417:13:417:28 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | -| test.swift:417:27:417:27 | x | semmle.label | x | -| test.swift:418:19:418:19 | x | semmle.label | x | -| test.swift:425:9:425:34 | call to ... [myPair:1] | semmle.label | call to ... [myPair:1] | -| test.swift:425:26:425:33 | call to source() | semmle.label | call to source() | -| test.swift:432:10:432:30 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | -| test.swift:432:29:432:29 | b | semmle.label | b | -| test.swift:434:19:434:19 | b | semmle.label | b | -| test.swift:442:13:442:33 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | -| test.swift:442:32:442:32 | y | semmle.label | y | -| test.swift:444:19:444:19 | y | semmle.label | y | -| test.swift:447:21:447:34 | call to ... [myCons:1, myPair:1] | semmle.label | call to ... [myCons:1, myPair:1] | -| test.swift:447:33:447:33 | a [myPair:1] | semmle.label | a [myPair:1] | -| test.swift:457:14:457:38 | .myCons(...) [myCons:1, myPair:1] | semmle.label | .myCons(...) [myCons:1, myPair:1] | -| test.swift:457:25:457:37 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | -| test.swift:457:36:457:36 | c | semmle.label | c | -| test.swift:460:19:460:19 | c | semmle.label | c | -| test.swift:468:13:468:39 | .myPair(...) [myPair:0] | semmle.label | .myPair(...) [myPair:0] | -| test.swift:468:31:468:31 | x | semmle.label | x | -| test.swift:468:43:468:62 | call to ... [myPair:0] | semmle.label | call to ... [myPair:0] | -| test.swift:468:51:468:58 | call to source() | semmle.label | call to source() | -| test.swift:469:19:469:19 | x | semmle.label | x | -| test.swift:472:17:472:41 | .myCons(...) [myCons:1, myPair:1] | semmle.label | .myCons(...) [myCons:1, myPair:1] | -| test.swift:472:28:472:40 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | -| test.swift:472:39:472:39 | c | semmle.label | c | -| test.swift:473:19:473:19 | c | semmle.label | c | -| test.swift:476:12:476:17 | (...) [Tuple element at index 0, myPair:1] | semmle.label | (...) [Tuple element at index 0, myPair:1] | -| test.swift:476:12:476:17 | (...) [Tuple element at index 1, myCons:1, myPair:1] | semmle.label | (...) [Tuple element at index 1, myCons:1, myPair:1] | -| test.swift:476:13:476:13 | a [myPair:1] | semmle.label | a [myPair:1] | -| test.swift:476:16:476:16 | b [myCons:1, myPair:1] | semmle.label | b [myCons:1, myPair:1] | -| test.swift:477:14:477:55 | (...) [Tuple element at index 0, myPair:1] | semmle.label | (...) [Tuple element at index 0, myPair:1] | -| test.swift:477:14:477:55 | (...) [Tuple element at index 1, myCons:1, myPair:1] | semmle.label | (...) [Tuple element at index 1, myCons:1, myPair:1] | -| test.swift:477:15:477:27 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | -| test.swift:477:26:477:26 | b | semmle.label | b | -| test.swift:477:30:477:54 | .myCons(...) [myCons:1, myPair:1] | semmle.label | .myCons(...) [myCons:1, myPair:1] | -| test.swift:477:41:477:53 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | -| test.swift:477:52:477:52 | e | semmle.label | e | -| test.swift:479:19:479:19 | b | semmle.label | b | -| test.swift:482:19:482:19 | e | semmle.label | e | -| test.swift:488:14:488:38 | call to ... [mySingle:0] | semmle.label | call to ... [mySingle:0] | -| test.swift:488:30:488:37 | call to source() | semmle.label | call to source() | -| test.swift:490:14:490:32 | call to mkMyEnum1(_:) [mySingle:0] | semmle.label | call to mkMyEnum1(_:) [mySingle:0] | -| test.swift:490:24:490:31 | call to source() | semmle.label | call to source() | -| test.swift:492:14:492:32 | call to mkMyEnum2(_:) [mySingle:0] | semmle.label | call to mkMyEnum2(_:) [mySingle:0] | -| test.swift:492:24:492:31 | call to source() | semmle.label | call to source() | -| test.swift:494:13:494:35 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | -| test.swift:494:33:494:33 | d2 | semmle.label | d2 | -| test.swift:494:54:494:54 | d2 | semmle.label | d2 | -| test.swift:496:13:496:35 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | -| test.swift:496:33:496:33 | d4 | semmle.label | d4 | -| test.swift:496:54:496:54 | d4 | semmle.label | d4 | -| test.swift:498:13:498:35 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | -| test.swift:498:33:498:33 | d6 | semmle.label | d6 | -| test.swift:498:54:498:54 | d6 | semmle.label | d6 | -| test.swift:501:14:501:36 | call to ... [some:0] | semmle.label | call to ... [some:0] | -| test.swift:501:28:501:35 | call to source() | semmle.label | call to source() | -| test.swift:503:14:503:34 | call to mkOptional1(_:) [some:0] | semmle.label | call to mkOptional1(_:) [some:0] | -| test.swift:503:26:503:33 | call to source() | semmle.label | call to source() | -| test.swift:505:14:505:34 | call to mkOptional2(_:) [some:0] | semmle.label | call to mkOptional2(_:) [some:0] | -| test.swift:505:26:505:33 | call to source() | semmle.label | call to source() | -| test.swift:507:15:507:15 | e2 [some:0] | semmle.label | e2 [some:0] | -| test.swift:507:15:507:17 | ...! | semmle.label | ...! | -| test.swift:509:15:509:15 | e4 [some:0] | semmle.label | e4 [some:0] | -| test.swift:509:15:509:17 | ...! | semmle.label | ...! | -| test.swift:511:15:511:15 | e6 [some:0] | semmle.label | e6 [some:0] | -| test.swift:511:15:511:17 | ...! | semmle.label | ...! | -| test.swift:517:13:517:28 | call to optionalSource() [some:0] | semmle.label | call to optionalSource() [some:0] | -| test.swift:519:8:519:12 | let ...? [some:0] | semmle.label | let ...? [some:0] | -| test.swift:519:12:519:12 | a | semmle.label | a | -| test.swift:520:19:520:19 | a | semmle.label | a | -| test.swift:524:18:524:23 | (...) [Tuple element at index 0, some:0] | semmle.label | (...) [Tuple element at index 0, some:0] | -| test.swift:524:19:524:19 | x [some:0] | semmle.label | x [some:0] | -| test.swift:526:10:526:37 | (...) [Tuple element at index 0, some:0] | semmle.label | (...) [Tuple element at index 0, some:0] | -| test.swift:526:11:526:22 | .some(...) [some:0] | semmle.label | .some(...) [some:0] | -| test.swift:526:21:526:21 | a | semmle.label | a | -| test.swift:527:19:527:19 | a | semmle.label | a | -| test.swift:540:9:540:9 | self [x, some:0] | semmle.label | self [x, some:0] | -| test.swift:540:9:540:9 | value [some:0] | semmle.label | value [some:0] | -| test.swift:544:13:544:28 | call to optionalSource() [some:0] | semmle.label | call to optionalSource() [some:0] | -| test.swift:546:5:546:5 | [post] cx [x, some:0] | semmle.label | [post] cx [x, some:0] | -| test.swift:546:12:546:12 | x [some:0] | semmle.label | x [some:0] | -| test.swift:550:11:550:15 | let ...? [some:0] | semmle.label | let ...? [some:0] | -| test.swift:550:15:550:15 | z1 | semmle.label | z1 | -| test.swift:550:20:550:20 | cx [x, some:0] | semmle.label | cx [x, some:0] | -| test.swift:550:20:550:23 | .x [some:0] | semmle.label | .x [some:0] | -| test.swift:551:15:551:15 | z1 | semmle.label | z1 | -| test.swift:557:13:557:21 | call to +(_:) | semmle.label | call to +(_:) | -| test.swift:557:14:557:21 | call to source() | semmle.label | call to source() | -| test.swift:558:14:558:21 | call to source() | semmle.label | call to source() | -| test.swift:566:9:566:9 | self [str] | semmle.label | self [str] | -| test.swift:567:5:569:5 | self[return] [str] | semmle.label | self[return] [str] | -| test.swift:567:10:567:13 | s | semmle.label | s | -| test.swift:568:7:568:7 | [post] self [str] | semmle.label | [post] self [str] | -| test.swift:568:13:568:13 | s | semmle.label | s | -| test.swift:573:17:576:5 | self[return] [str] | semmle.label | self[return] [str] | -| test.swift:574:7:574:7 | [post] self [str] | semmle.label | [post] self [str] | -| test.swift:574:20:574:28 | call to source3() | semmle.label | call to source3() | -| test.swift:575:17:575:17 | .str | semmle.label | .str | -| test.swift:575:17:575:17 | self [str] | semmle.label | self [str] | -| test.swift:580:13:580:33 | call to MyClass.init(s:) [str] | semmle.label | call to MyClass.init(s:) [str] | -| test.swift:580:13:580:35 | .str | semmle.label | .str | -| test.swift:580:24:580:32 | call to source3() | semmle.label | call to source3() | -| test.swift:581:13:581:41 | call to MyClass.init(contentsOfFile:) [str] | semmle.label | call to MyClass.init(contentsOfFile:) [str] | -| test.swift:581:13:581:43 | .str | semmle.label | .str | -| test.swift:598:3:600:3 | self[return] [x] | semmle.label | self[return] [x] | -| test.swift:598:8:598:11 | x | semmle.label | x | -| test.swift:599:5:599:5 | [post] self [x] | semmle.label | [post] self [x] | -| test.swift:599:14:599:14 | x | semmle.label | x | -| test.swift:604:11:604:24 | call to S.init(x:) [x] | semmle.label | call to S.init(x:) [x] | -| test.swift:604:16:604:23 | call to source() | semmle.label | call to source() | -| test.swift:605:11:605:14 | enter #keyPath(...) [x] | semmle.label | enter #keyPath(...) [x] | -| test.swift:605:11:605:14 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | -| test.swift:605:14:605:14 | KeyPathComponent [x] | semmle.label | KeyPathComponent [x] | -| test.swift:606:13:606:13 | s [x] | semmle.label | s [x] | -| test.swift:606:13:606:25 | \\...[...] | semmle.label | \\...[...] | -| test.swift:608:36:608:38 | enter #keyPath(...) [x] | semmle.label | enter #keyPath(...) [x] | -| test.swift:608:36:608:38 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | -| test.swift:608:38:608:38 | KeyPathComponent [x] | semmle.label | KeyPathComponent [x] | -| test.swift:609:13:609:13 | s [x] | semmle.label | s [x] | -| test.swift:609:13:609:32 | \\...[...] | semmle.label | \\...[...] | -| test.swift:615:3:617:3 | self[return] [s, x] | semmle.label | self[return] [s, x] | -| test.swift:615:8:615:11 | s [x] | semmle.label | s [x] | -| test.swift:616:5:616:5 | [post] self [s, x] | semmle.label | [post] self [s, x] | -| test.swift:616:14:616:14 | s [x] | semmle.label | s [x] | -| test.swift:621:11:621:24 | call to S.init(x:) [x] | semmle.label | call to S.init(x:) [x] | -| test.swift:621:16:621:23 | call to source() | semmle.label | call to source() | -| test.swift:622:12:622:19 | call to S2.init(s:) [s, x] | semmle.label | call to S2.init(s:) [s, x] | -| test.swift:622:18:622:18 | s [x] | semmle.label | s [x] | -| test.swift:623:11:623:17 | enter #keyPath(...) [s, x] | semmle.label | enter #keyPath(...) [s, x] | -| test.swift:623:11:623:17 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | -| test.swift:623:15:623:15 | KeyPathComponent [s, x] | semmle.label | KeyPathComponent [s, x] | -| test.swift:623:17:623:17 | KeyPathComponent [x] | semmle.label | KeyPathComponent [x] | -| test.swift:624:13:624:13 | s2 [s, x] | semmle.label | s2 [s, x] | -| test.swift:624:13:624:26 | \\...[...] | semmle.label | \\...[...] | -| test.swift:628:17:628:26 | [...] [Array element] | semmle.label | [...] [Array element] | -| test.swift:628:18:628:25 | call to source() | semmle.label | call to source() | -| test.swift:629:13:629:22 | enter #keyPath(...) [Array element] | semmle.label | enter #keyPath(...) [Array element] | -| test.swift:629:13:629:22 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | -| test.swift:629:20:629:22 | KeyPathComponent [Array element] | semmle.label | KeyPathComponent [Array element] | -| test.swift:630:15:630:15 | array [Array element] | semmle.label | array [Array element] | -| test.swift:630:15:630:31 | \\...[...] | semmle.label | \\...[...] | -| test.swift:649:13:649:20 | call to source() | semmle.label | call to source() | -| test.swift:657:15:657:15 | y | semmle.label | y | -| test.swift:659:9:659:16 | call to source() | semmle.label | call to source() | -| test.swift:661:11:661:11 | x | semmle.label | x | -| test.swift:661:15:661:15 | [post] y | semmle.label | [post] y | -| test.swift:662:15:662:15 | x | semmle.label | x | -| test.swift:663:15:663:15 | y | semmle.label | y | -| test.swift:669:5:669:5 | [post] arr1 [Array element] | semmle.label | [post] arr1 [Array element] | -| test.swift:669:15:669:22 | call to source() | semmle.label | call to source() | -| test.swift:670:15:670:15 | arr1 [Array element] | semmle.label | arr1 [Array element] | -| test.swift:670:15:670:21 | ...[...] | semmle.label | ...[...] | -| test.swift:673:16:673:25 | [...] [Array element] | semmle.label | [...] [Array element] | -| test.swift:673:17:673:24 | call to source() | semmle.label | call to source() | -| test.swift:674:15:674:15 | arr2 [Array element] | semmle.label | arr2 [Array element] | -| test.swift:674:15:674:21 | ...[...] | semmle.label | ...[...] | -| test.swift:676:18:676:29 | [...] [Array element, Array element] | semmle.label | [...] [Array element, Array element] | -| test.swift:676:19:676:28 | [...] [Array element] | semmle.label | [...] [Array element] | -| test.swift:676:20:676:27 | call to source() | semmle.label | call to source() | -| test.swift:678:15:678:15 | matrix [Array element, Array element] | semmle.label | matrix [Array element, Array element] | -| test.swift:678:15:678:23 | ...[...] [Array element] | semmle.label | ...[...] [Array element] | -| test.swift:678:15:678:26 | ...[...] | semmle.label | ...[...] | -| test.swift:681:5:681:5 | [post] matrix2 [Array element, Array element] | semmle.label | [post] matrix2 [Array element, Array element] | -| test.swift:681:5:681:14 | [post] getter for ...[...] [Array element] | semmle.label | [post] getter for ...[...] [Array element] | -| test.swift:681:21:681:28 | call to source() | semmle.label | call to source() | -| test.swift:682:15:682:15 | matrix2 [Array element, Array element] | semmle.label | matrix2 [Array element, Array element] | -| test.swift:682:15:682:24 | ...[...] [Array element] | semmle.label | ...[...] [Array element] | -| test.swift:682:15:682:27 | ...[...] | semmle.label | ...[...] | -| test.swift:693:5:693:5 | [post] arr6 [Array element] | semmle.label | [post] arr6 [Array element] | -| test.swift:693:17:693:24 | call to source() | semmle.label | call to source() | -| test.swift:694:15:694:15 | arr6 [Array element] | semmle.label | arr6 [Array element] | -| test.swift:694:15:694:21 | ...[...] | semmle.label | ...[...] | +| test.swift:368:22:368:36 | t [Tuple element at index 1] | semmle.label | t [Tuple element at index 1] | +| test.swift:369:12:369:19 | (...) [Tuple element at index 0] | semmle.label | (...) [Tuple element at index 0] | +| test.swift:369:13:369:13 | t [Tuple element at index 1] | semmle.label | t [Tuple element at index 1] | +| test.swift:369:13:369:15 | .1 | semmle.label | .1 | +| test.swift:375:14:375:26 | (...) [Tuple element at index 1] | semmle.label | (...) [Tuple element at index 1] | +| test.swift:375:18:375:25 | call to source() | semmle.label | call to source() | +| test.swift:376:14:376:32 | call to tupleShiftLeft1(_:) [Tuple element at index 0] | semmle.label | call to tupleShiftLeft1(_:) [Tuple element at index 0] | +| test.swift:376:30:376:30 | t1 [Tuple element at index 1] | semmle.label | t1 [Tuple element at index 1] | +| test.swift:377:14:377:32 | call to tupleShiftLeft2(_:) [Tuple element at index 0] | semmle.label | call to tupleShiftLeft2(_:) [Tuple element at index 0] | +| test.swift:377:30:377:30 | t1 [Tuple element at index 1] | semmle.label | t1 [Tuple element at index 1] | +| test.swift:380:15:380:15 | t1 [Tuple element at index 1] | semmle.label | t1 [Tuple element at index 1] | +| test.swift:380:15:380:18 | .1 | semmle.label | .1 | +| test.swift:381:15:381:15 | t2 [Tuple element at index 0] | semmle.label | t2 [Tuple element at index 0] | +| test.swift:381:15:381:18 | .0 | semmle.label | .0 | +| test.swift:383:15:383:15 | t3 [Tuple element at index 0] | semmle.label | t3 [Tuple element at index 0] | +| test.swift:383:15:383:18 | .0 | semmle.label | .0 | +| test.swift:394:16:394:21 | v | semmle.label | v | +| test.swift:394:45:394:62 | call to ... [mySingle:0] | semmle.label | call to ... [mySingle:0] | +| test.swift:394:61:394:61 | v | semmle.label | v | +| test.swift:396:18:396:23 | v | semmle.label | v | +| test.swift:396:45:396:60 | call to ... [some:0] | semmle.label | call to ... [some:0] | +| test.swift:396:59:396:59 | v | semmle.label | v | +| test.swift:422:9:422:27 | call to ... [mySingle:0] | semmle.label | call to ... [mySingle:0] | +| test.swift:422:19:422:26 | call to source() | semmle.label | call to source() | +| test.swift:427:10:427:25 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | +| test.swift:427:24:427:24 | a | semmle.label | a | +| test.swift:428:19:428:19 | a | semmle.label | a | +| test.swift:436:13:436:28 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | +| test.swift:436:27:436:27 | x | semmle.label | x | +| test.swift:437:19:437:19 | x | semmle.label | x | +| test.swift:444:9:444:34 | call to ... [myPair:1] | semmle.label | call to ... [myPair:1] | +| test.swift:444:26:444:33 | call to source() | semmle.label | call to source() | +| test.swift:451:10:451:30 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | +| test.swift:451:29:451:29 | b | semmle.label | b | +| test.swift:453:19:453:19 | b | semmle.label | b | +| test.swift:461:13:461:33 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | +| test.swift:461:32:461:32 | y | semmle.label | y | +| test.swift:463:19:463:19 | y | semmle.label | y | +| test.swift:466:21:466:34 | call to ... [myCons:1, myPair:1] | semmle.label | call to ... [myCons:1, myPair:1] | +| test.swift:466:33:466:33 | a [myPair:1] | semmle.label | a [myPair:1] | +| test.swift:476:14:476:38 | .myCons(...) [myCons:1, myPair:1] | semmle.label | .myCons(...) [myCons:1, myPair:1] | +| test.swift:476:25:476:37 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | +| test.swift:476:36:476:36 | c | semmle.label | c | +| test.swift:479:19:479:19 | c | semmle.label | c | +| test.swift:487:13:487:39 | .myPair(...) [myPair:0] | semmle.label | .myPair(...) [myPair:0] | +| test.swift:487:31:487:31 | x | semmle.label | x | +| test.swift:487:43:487:62 | call to ... [myPair:0] | semmle.label | call to ... [myPair:0] | +| test.swift:487:51:487:58 | call to source() | semmle.label | call to source() | +| test.swift:488:19:488:19 | x | semmle.label | x | +| test.swift:491:17:491:41 | .myCons(...) [myCons:1, myPair:1] | semmle.label | .myCons(...) [myCons:1, myPair:1] | +| test.swift:491:28:491:40 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | +| test.swift:491:39:491:39 | c | semmle.label | c | +| test.swift:492:19:492:19 | c | semmle.label | c | +| test.swift:495:12:495:17 | (...) [Tuple element at index 0, myPair:1] | semmle.label | (...) [Tuple element at index 0, myPair:1] | +| test.swift:495:12:495:17 | (...) [Tuple element at index 1, myCons:1, myPair:1] | semmle.label | (...) [Tuple element at index 1, myCons:1, myPair:1] | +| test.swift:495:13:495:13 | a [myPair:1] | semmle.label | a [myPair:1] | +| test.swift:495:16:495:16 | b [myCons:1, myPair:1] | semmle.label | b [myCons:1, myPair:1] | +| test.swift:496:14:496:55 | (...) [Tuple element at index 0, myPair:1] | semmle.label | (...) [Tuple element at index 0, myPair:1] | +| test.swift:496:14:496:55 | (...) [Tuple element at index 1, myCons:1, myPair:1] | semmle.label | (...) [Tuple element at index 1, myCons:1, myPair:1] | +| test.swift:496:15:496:27 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | +| test.swift:496:26:496:26 | b | semmle.label | b | +| test.swift:496:30:496:54 | .myCons(...) [myCons:1, myPair:1] | semmle.label | .myCons(...) [myCons:1, myPair:1] | +| test.swift:496:41:496:53 | .myPair(...) [myPair:1] | semmle.label | .myPair(...) [myPair:1] | +| test.swift:496:52:496:52 | e | semmle.label | e | +| test.swift:498:19:498:19 | b | semmle.label | b | +| test.swift:501:19:501:19 | e | semmle.label | e | +| test.swift:507:14:507:38 | call to ... [mySingle:0] | semmle.label | call to ... [mySingle:0] | +| test.swift:507:30:507:37 | call to source() | semmle.label | call to source() | +| test.swift:509:14:509:32 | call to mkMyEnum1(_:) [mySingle:0] | semmle.label | call to mkMyEnum1(_:) [mySingle:0] | +| test.swift:509:24:509:31 | call to source() | semmle.label | call to source() | +| test.swift:511:14:511:32 | call to mkMyEnum2(_:) [mySingle:0] | semmle.label | call to mkMyEnum2(_:) [mySingle:0] | +| test.swift:511:24:511:31 | call to source() | semmle.label | call to source() | +| test.swift:513:13:513:35 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | +| test.swift:513:33:513:33 | d2 | semmle.label | d2 | +| test.swift:513:54:513:54 | d2 | semmle.label | d2 | +| test.swift:515:13:515:35 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | +| test.swift:515:33:515:33 | d4 | semmle.label | d4 | +| test.swift:515:54:515:54 | d4 | semmle.label | d4 | +| test.swift:517:13:517:35 | .mySingle(...) [mySingle:0] | semmle.label | .mySingle(...) [mySingle:0] | +| test.swift:517:33:517:33 | d6 | semmle.label | d6 | +| test.swift:517:54:517:54 | d6 | semmle.label | d6 | +| test.swift:520:14:520:36 | call to ... [some:0] | semmle.label | call to ... [some:0] | +| test.swift:520:28:520:35 | call to source() | semmle.label | call to source() | +| test.swift:522:14:522:34 | call to mkOptional1(_:) [some:0] | semmle.label | call to mkOptional1(_:) [some:0] | +| test.swift:522:26:522:33 | call to source() | semmle.label | call to source() | +| test.swift:524:14:524:34 | call to mkOptional2(_:) [some:0] | semmle.label | call to mkOptional2(_:) [some:0] | +| test.swift:524:26:524:33 | call to source() | semmle.label | call to source() | +| test.swift:526:15:526:15 | e2 [some:0] | semmle.label | e2 [some:0] | +| test.swift:526:15:526:17 | ...! | semmle.label | ...! | +| test.swift:528:15:528:15 | e4 [some:0] | semmle.label | e4 [some:0] | +| test.swift:528:15:528:17 | ...! | semmle.label | ...! | +| test.swift:530:15:530:15 | e6 [some:0] | semmle.label | e6 [some:0] | +| test.swift:530:15:530:17 | ...! | semmle.label | ...! | +| test.swift:536:13:536:28 | call to optionalSource() [some:0] | semmle.label | call to optionalSource() [some:0] | +| test.swift:538:8:538:12 | let ...? [some:0] | semmle.label | let ...? [some:0] | +| test.swift:538:12:538:12 | a | semmle.label | a | +| test.swift:539:19:539:19 | a | semmle.label | a | +| test.swift:543:18:543:23 | (...) [Tuple element at index 0, some:0] | semmle.label | (...) [Tuple element at index 0, some:0] | +| test.swift:543:19:543:19 | x [some:0] | semmle.label | x [some:0] | +| test.swift:545:10:545:37 | (...) [Tuple element at index 0, some:0] | semmle.label | (...) [Tuple element at index 0, some:0] | +| test.swift:545:11:545:22 | .some(...) [some:0] | semmle.label | .some(...) [some:0] | +| test.swift:545:21:545:21 | a | semmle.label | a | +| test.swift:546:19:546:19 | a | semmle.label | a | +| test.swift:559:9:559:9 | self [x, some:0] | semmle.label | self [x, some:0] | +| test.swift:559:9:559:9 | value [some:0] | semmle.label | value [some:0] | +| test.swift:563:13:563:28 | call to optionalSource() [some:0] | semmle.label | call to optionalSource() [some:0] | +| test.swift:565:5:565:5 | [post] cx [x, some:0] | semmle.label | [post] cx [x, some:0] | +| test.swift:565:12:565:12 | x [some:0] | semmle.label | x [some:0] | +| test.swift:569:11:569:15 | let ...? [some:0] | semmle.label | let ...? [some:0] | +| test.swift:569:15:569:15 | z1 | semmle.label | z1 | +| test.swift:569:20:569:20 | cx [x, some:0] | semmle.label | cx [x, some:0] | +| test.swift:569:20:569:23 | .x [some:0] | semmle.label | .x [some:0] | +| test.swift:570:15:570:15 | z1 | semmle.label | z1 | +| test.swift:576:13:576:21 | call to +(_:) | semmle.label | call to +(_:) | +| test.swift:576:14:576:21 | call to source() | semmle.label | call to source() | +| test.swift:577:14:577:21 | call to source() | semmle.label | call to source() | +| test.swift:585:9:585:9 | self [str] | semmle.label | self [str] | +| test.swift:586:5:588:5 | self[return] [str] | semmle.label | self[return] [str] | +| test.swift:586:10:586:13 | s | semmle.label | s | +| test.swift:587:7:587:7 | [post] self [str] | semmle.label | [post] self [str] | +| test.swift:587:13:587:13 | s | semmle.label | s | +| test.swift:592:17:595:5 | self[return] [str] | semmle.label | self[return] [str] | +| test.swift:593:7:593:7 | [post] self [str] | semmle.label | [post] self [str] | +| test.swift:593:20:593:28 | call to source3() | semmle.label | call to source3() | +| test.swift:594:17:594:17 | .str | semmle.label | .str | +| test.swift:594:17:594:17 | self [str] | semmle.label | self [str] | +| test.swift:599:13:599:33 | call to MyClass.init(s:) [str] | semmle.label | call to MyClass.init(s:) [str] | +| test.swift:599:13:599:35 | .str | semmle.label | .str | +| test.swift:599:24:599:32 | call to source3() | semmle.label | call to source3() | +| test.swift:600:13:600:41 | call to MyClass.init(contentsOfFile:) [str] | semmle.label | call to MyClass.init(contentsOfFile:) [str] | +| test.swift:600:13:600:43 | .str | semmle.label | .str | +| test.swift:617:3:619:3 | self[return] [x] | semmle.label | self[return] [x] | +| test.swift:617:8:617:11 | x | semmle.label | x | +| test.swift:618:5:618:5 | [post] self [x] | semmle.label | [post] self [x] | +| test.swift:618:14:618:14 | x | semmle.label | x | +| test.swift:623:11:623:24 | call to S.init(x:) [x] | semmle.label | call to S.init(x:) [x] | +| test.swift:623:16:623:23 | call to source() | semmle.label | call to source() | +| test.swift:624:11:624:14 | enter #keyPath(...) [x] | semmle.label | enter #keyPath(...) [x] | +| test.swift:624:11:624:14 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | +| test.swift:624:14:624:14 | KeyPathComponent [x] | semmle.label | KeyPathComponent [x] | +| test.swift:625:13:625:13 | s [x] | semmle.label | s [x] | +| test.swift:625:13:625:25 | \\...[...] | semmle.label | \\...[...] | +| test.swift:627:36:627:38 | enter #keyPath(...) [x] | semmle.label | enter #keyPath(...) [x] | +| test.swift:627:36:627:38 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | +| test.swift:627:38:627:38 | KeyPathComponent [x] | semmle.label | KeyPathComponent [x] | +| test.swift:628:13:628:13 | s [x] | semmle.label | s [x] | +| test.swift:628:13:628:32 | \\...[...] | semmle.label | \\...[...] | +| test.swift:634:3:636:3 | self[return] [s, x] | semmle.label | self[return] [s, x] | +| test.swift:634:8:634:11 | s [x] | semmle.label | s [x] | +| test.swift:635:5:635:5 | [post] self [s, x] | semmle.label | [post] self [s, x] | +| test.swift:635:14:635:14 | s [x] | semmle.label | s [x] | +| test.swift:640:11:640:24 | call to S.init(x:) [x] | semmle.label | call to S.init(x:) [x] | +| test.swift:640:16:640:23 | call to source() | semmle.label | call to source() | +| test.swift:641:12:641:19 | call to S2.init(s:) [s, x] | semmle.label | call to S2.init(s:) [s, x] | +| test.swift:641:18:641:18 | s [x] | semmle.label | s [x] | +| test.swift:642:11:642:17 | enter #keyPath(...) [s, x] | semmle.label | enter #keyPath(...) [s, x] | +| test.swift:642:11:642:17 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | +| test.swift:642:15:642:15 | KeyPathComponent [s, x] | semmle.label | KeyPathComponent [s, x] | +| test.swift:642:17:642:17 | KeyPathComponent [x] | semmle.label | KeyPathComponent [x] | +| test.swift:643:13:643:13 | s2 [s, x] | semmle.label | s2 [s, x] | +| test.swift:643:13:643:26 | \\...[...] | semmle.label | \\...[...] | +| test.swift:647:17:647:26 | [...] [Array element] | semmle.label | [...] [Array element] | +| test.swift:647:18:647:25 | call to source() | semmle.label | call to source() | +| test.swift:648:13:648:22 | enter #keyPath(...) [Array element] | semmle.label | enter #keyPath(...) [Array element] | +| test.swift:648:13:648:22 | exit #keyPath(...) | semmle.label | exit #keyPath(...) | +| test.swift:648:20:648:22 | KeyPathComponent [Array element] | semmle.label | KeyPathComponent [Array element] | +| test.swift:649:15:649:15 | array [Array element] | semmle.label | array [Array element] | +| test.swift:649:15:649:31 | \\...[...] | semmle.label | \\...[...] | +| test.swift:668:13:668:20 | call to source() | semmle.label | call to source() | +| test.swift:676:15:676:15 | y | semmle.label | y | +| test.swift:678:9:678:16 | call to source() | semmle.label | call to source() | +| test.swift:680:11:680:11 | x | semmle.label | x | +| test.swift:680:15:680:15 | [post] y | semmle.label | [post] y | +| test.swift:681:15:681:15 | x | semmle.label | x | +| test.swift:682:15:682:15 | y | semmle.label | y | +| test.swift:688:5:688:5 | [post] arr1 [Array element] | semmle.label | [post] arr1 [Array element] | +| test.swift:688:15:688:22 | call to source() | semmle.label | call to source() | +| test.swift:689:15:689:15 | arr1 [Array element] | semmle.label | arr1 [Array element] | +| test.swift:689:15:689:21 | ...[...] | semmle.label | ...[...] | +| test.swift:692:16:692:25 | [...] [Array element] | semmle.label | [...] [Array element] | +| test.swift:692:17:692:24 | call to source() | semmle.label | call to source() | +| test.swift:693:15:693:15 | arr2 [Array element] | semmle.label | arr2 [Array element] | +| test.swift:693:15:693:21 | ...[...] | semmle.label | ...[...] | +| test.swift:695:18:695:29 | [...] [Array element, Array element] | semmle.label | [...] [Array element, Array element] | +| test.swift:695:19:695:28 | [...] [Array element] | semmle.label | [...] [Array element] | +| test.swift:695:20:695:27 | call to source() | semmle.label | call to source() | +| test.swift:697:15:697:15 | matrix [Array element, Array element] | semmle.label | matrix [Array element, Array element] | +| test.swift:697:15:697:23 | ...[...] [Array element] | semmle.label | ...[...] [Array element] | +| test.swift:697:15:697:26 | ...[...] | semmle.label | ...[...] | +| test.swift:700:5:700:5 | [post] matrix2 [Array element, Array element] | semmle.label | [post] matrix2 [Array element, Array element] | +| test.swift:700:5:700:14 | [post] getter for ...[...] [Array element] | semmle.label | [post] getter for ...[...] [Array element] | +| test.swift:700:21:700:28 | call to source() | semmle.label | call to source() | +| test.swift:701:15:701:15 | matrix2 [Array element, Array element] | semmle.label | matrix2 [Array element, Array element] | +| test.swift:701:15:701:24 | ...[...] [Array element] | semmle.label | ...[...] [Array element] | +| test.swift:701:15:701:27 | ...[...] | semmle.label | ...[...] | +| test.swift:712:5:712:5 | [post] arr6 [Array element] | semmle.label | [post] arr6 [Array element] | +| test.swift:712:17:712:24 | call to source() | semmle.label | call to source() | +| test.swift:713:15:713:15 | arr6 [Array element] | semmle.label | arr6 [Array element] | +| test.swift:713:15:713:21 | ...[...] | semmle.label | ...[...] | +| test.swift:715:16:715:25 | [...] [Array element] | semmle.label | [...] [Array element] | +| test.swift:715:17:715:24 | call to source() | semmle.label | call to source() | +| test.swift:716:15:716:15 | arr7 [Array element] | semmle.label | arr7 [Array element] | +| test.swift:716:15:716:34 | call to randomElement() [some:0] | semmle.label | call to randomElement() [some:0] | +| test.swift:716:15:716:35 | ...! | semmle.label | ...! | +| test.swift:722:5:722:5 | [post] set1 [Collection element] | semmle.label | [post] set1 [Collection element] | +| test.swift:722:17:722:24 | call to source() | semmle.label | call to source() | +| test.swift:723:15:723:15 | set1 [Collection element] | semmle.label | set1 [Collection element] | +| test.swift:723:15:723:34 | call to randomElement() [some:0] | semmle.label | call to randomElement() [some:0] | +| test.swift:723:15:723:35 | ...! | semmle.label | ...! | +| test.swift:725:16:725:30 | call to Set.init(_:) [Collection element] | semmle.label | call to Set.init(_:) [Collection element] | +| test.swift:725:20:725:29 | [...] [Array element] | semmle.label | [...] [Array element] | +| test.swift:725:21:725:28 | call to source() | semmle.label | call to source() | +| test.swift:726:15:726:15 | set2 [Collection element] | semmle.label | set2 [Collection element] | +| test.swift:726:15:726:34 | call to randomElement() [some:0] | semmle.label | call to randomElement() [some:0] | +| test.swift:726:15:726:35 | ...! | semmle.label | ...! | +| test.swift:731:9:731:9 | self [v2, some:0] | semmle.label | self [v2, some:0] | +| test.swift:731:9:731:9 | self [v2] | semmle.label | self [v2] | +| test.swift:731:9:731:9 | value | semmle.label | value | +| test.swift:731:9:731:9 | value [some:0] | semmle.label | value [some:0] | +| test.swift:732:9:732:9 | self [v3] | semmle.label | self [v3] | +| test.swift:732:9:732:9 | value | semmle.label | value | +| test.swift:742:5:742:5 | v1 [some:0] | semmle.label | v1 [some:0] | +| test.swift:742:11:742:18 | call to source() | semmle.label | call to source() | +| test.swift:743:10:743:17 | call to source() | semmle.label | call to source() | +| test.swift:743:10:743:17 | call to source() [some:0] | semmle.label | call to source() [some:0] | +| test.swift:744:10:744:17 | call to source() | semmle.label | call to source() | +| test.swift:746:5:746:5 | [post] mo1 [v2, some:0] | semmle.label | [post] mo1 [v2, some:0] | +| test.swift:746:5:746:5 | [post] mo1 [v2] | semmle.label | [post] mo1 [v2] | +| test.swift:746:14:746:21 | call to source() | semmle.label | call to source() | +| test.swift:746:14:746:21 | call to source() [some:0] | semmle.label | call to source() [some:0] | +| test.swift:747:5:747:5 | [post] mo1 [v3] | semmle.label | [post] mo1 [v3] | +| test.swift:747:5:747:5 | mo1 [v2, some:0] | semmle.label | mo1 [v2, some:0] | +| test.swift:747:5:747:5 | mo1 [v2] | semmle.label | mo1 [v2] | +| test.swift:747:14:747:21 | call to source() | semmle.label | call to source() | +| test.swift:752:15:752:15 | v1 [some:0] | semmle.label | v1 [some:0] | +| test.swift:752:15:752:17 | ...! | semmle.label | ...! | +| test.swift:753:15:753:15 | v2 [some:0] | semmle.label | v2 [some:0] | +| test.swift:753:15:753:17 | ...! | semmle.label | ...! | +| test.swift:754:15:754:15 | v3 | semmle.label | v3 | +| test.swift:756:15:756:15 | mo1 [v2, some:0] | semmle.label | mo1 [v2, some:0] | +| test.swift:756:15:756:15 | mo1 [v2] | semmle.label | mo1 [v2] | +| test.swift:756:15:756:19 | .v2 | semmle.label | .v2 | +| test.swift:756:15:756:19 | .v2 [some:0] | semmle.label | .v2 [some:0] | +| test.swift:756:15:756:21 | ...! | semmle.label | ...! | +| test.swift:757:15:757:15 | mo1 [v3] | semmle.label | mo1 [v3] | +| test.swift:757:15:757:19 | .v3 | semmle.label | .v3 | subpaths | test.swift:75:22:75:22 | x | test.swift:65:16:65:28 | arg1 | test.swift:65:1:70:1 | arg2[return] | test.swift:75:32:75:32 | [post] y | | test.swift:114:19:114:19 | arg | test.swift:109:9:109:14 | arg | test.swift:110:12:110:12 | arg | test.swift:114:12:114:22 | call to ... | @@ -732,21 +875,28 @@ subpaths | test.swift:218:11:218:18 | call to source() | test.swift:169:12:169:22 | value | test.swift:170:5:170:5 | [post] self [x] | test.swift:218:3:218:5 | [post] getter for .a [x] | | test.swift:219:13:219:13 | b [a, x] | test.swift:185:7:185:7 | self [a, x] | file://:0:0:0:0 | .a [x] | test.swift:219:13:219:15 | .a [x] | | test.swift:219:13:219:15 | .a [x] | test.swift:163:7:163:7 | self [x] | file://:0:0:0:0 | .x | test.swift:219:13:219:17 | .x | -| test.swift:490:24:490:31 | call to source() | test.swift:375:16:375:21 | v | test.swift:375:45:375:62 | call to ... [mySingle:0] | test.swift:490:14:490:32 | call to mkMyEnum1(_:) [mySingle:0] | -| test.swift:503:26:503:33 | call to source() | test.swift:377:18:377:23 | v | test.swift:377:45:377:60 | call to ... [some:0] | test.swift:503:14:503:34 | call to mkOptional1(_:) [some:0] | -| test.swift:546:12:546:12 | x [some:0] | test.swift:540:9:540:9 | value [some:0] | file://:0:0:0:0 | [post] self [x, some:0] | test.swift:546:5:546:5 | [post] cx [x, some:0] | -| test.swift:550:20:550:20 | cx [x, some:0] | test.swift:540:9:540:9 | self [x, some:0] | file://:0:0:0:0 | .x [some:0] | test.swift:550:20:550:23 | .x [some:0] | -| test.swift:574:20:574:28 | call to source3() | test.swift:567:10:567:13 | s | test.swift:568:7:568:7 | [post] self [str] | test.swift:574:7:574:7 | [post] self [str] | -| test.swift:580:13:580:33 | call to MyClass.init(s:) [str] | test.swift:566:9:566:9 | self [str] | file://:0:0:0:0 | .str | test.swift:580:13:580:35 | .str | -| test.swift:580:24:580:32 | call to source3() | test.swift:567:10:567:13 | s | test.swift:567:5:569:5 | self[return] [str] | test.swift:580:13:580:33 | call to MyClass.init(s:) [str] | -| test.swift:581:13:581:41 | call to MyClass.init(contentsOfFile:) [str] | test.swift:566:9:566:9 | self [str] | file://:0:0:0:0 | .str | test.swift:581:13:581:43 | .str | -| test.swift:604:16:604:23 | call to source() | test.swift:598:8:598:11 | x | test.swift:598:3:600:3 | self[return] [x] | test.swift:604:11:604:24 | call to S.init(x:) [x] | -| test.swift:606:13:606:13 | s [x] | test.swift:605:11:605:14 | enter #keyPath(...) [x] | test.swift:605:11:605:14 | exit #keyPath(...) | test.swift:606:13:606:25 | \\...[...] | -| test.swift:609:13:609:13 | s [x] | test.swift:608:36:608:38 | enter #keyPath(...) [x] | test.swift:608:36:608:38 | exit #keyPath(...) | test.swift:609:13:609:32 | \\...[...] | -| test.swift:621:16:621:23 | call to source() | test.swift:598:8:598:11 | x | test.swift:598:3:600:3 | self[return] [x] | test.swift:621:11:621:24 | call to S.init(x:) [x] | -| test.swift:622:18:622:18 | s [x] | test.swift:615:8:615:11 | s [x] | test.swift:615:3:617:3 | self[return] [s, x] | test.swift:622:12:622:19 | call to S2.init(s:) [s, x] | -| test.swift:624:13:624:13 | s2 [s, x] | test.swift:623:11:623:17 | enter #keyPath(...) [s, x] | test.swift:623:11:623:17 | exit #keyPath(...) | test.swift:624:13:624:26 | \\...[...] | -| test.swift:630:15:630:15 | array [Array element] | test.swift:629:13:629:22 | enter #keyPath(...) [Array element] | test.swift:629:13:629:22 | exit #keyPath(...) | test.swift:630:15:630:31 | \\...[...] | +| test.swift:376:30:376:30 | t1 [Tuple element at index 1] | test.swift:368:22:368:36 | t [Tuple element at index 1] | test.swift:369:12:369:19 | (...) [Tuple element at index 0] | test.swift:376:14:376:32 | call to tupleShiftLeft1(_:) [Tuple element at index 0] | +| test.swift:509:24:509:31 | call to source() | test.swift:394:16:394:21 | v | test.swift:394:45:394:62 | call to ... [mySingle:0] | test.swift:509:14:509:32 | call to mkMyEnum1(_:) [mySingle:0] | +| test.swift:522:26:522:33 | call to source() | test.swift:396:18:396:23 | v | test.swift:396:45:396:60 | call to ... [some:0] | test.swift:522:14:522:34 | call to mkOptional1(_:) [some:0] | +| test.swift:565:12:565:12 | x [some:0] | test.swift:559:9:559:9 | value [some:0] | file://:0:0:0:0 | [post] self [x, some:0] | test.swift:565:5:565:5 | [post] cx [x, some:0] | +| test.swift:569:20:569:20 | cx [x, some:0] | test.swift:559:9:559:9 | self [x, some:0] | file://:0:0:0:0 | .x [some:0] | test.swift:569:20:569:23 | .x [some:0] | +| test.swift:593:20:593:28 | call to source3() | test.swift:586:10:586:13 | s | test.swift:587:7:587:7 | [post] self [str] | test.swift:593:7:593:7 | [post] self [str] | +| test.swift:599:13:599:33 | call to MyClass.init(s:) [str] | test.swift:585:9:585:9 | self [str] | file://:0:0:0:0 | .str | test.swift:599:13:599:35 | .str | +| test.swift:599:24:599:32 | call to source3() | test.swift:586:10:586:13 | s | test.swift:586:5:588:5 | self[return] [str] | test.swift:599:13:599:33 | call to MyClass.init(s:) [str] | +| test.swift:600:13:600:41 | call to MyClass.init(contentsOfFile:) [str] | test.swift:585:9:585:9 | self [str] | file://:0:0:0:0 | .str | test.swift:600:13:600:43 | .str | +| test.swift:623:16:623:23 | call to source() | test.swift:617:8:617:11 | x | test.swift:617:3:619:3 | self[return] [x] | test.swift:623:11:623:24 | call to S.init(x:) [x] | +| test.swift:625:13:625:13 | s [x] | test.swift:624:11:624:14 | enter #keyPath(...) [x] | test.swift:624:11:624:14 | exit #keyPath(...) | test.swift:625:13:625:25 | \\...[...] | +| test.swift:628:13:628:13 | s [x] | test.swift:627:36:627:38 | enter #keyPath(...) [x] | test.swift:627:36:627:38 | exit #keyPath(...) | test.swift:628:13:628:32 | \\...[...] | +| test.swift:640:16:640:23 | call to source() | test.swift:617:8:617:11 | x | test.swift:617:3:619:3 | self[return] [x] | test.swift:640:11:640:24 | call to S.init(x:) [x] | +| test.swift:641:18:641:18 | s [x] | test.swift:634:8:634:11 | s [x] | test.swift:634:3:636:3 | self[return] [s, x] | test.swift:641:12:641:19 | call to S2.init(s:) [s, x] | +| test.swift:643:13:643:13 | s2 [s, x] | test.swift:642:11:642:17 | enter #keyPath(...) [s, x] | test.swift:642:11:642:17 | exit #keyPath(...) | test.swift:643:13:643:26 | \\...[...] | +| test.swift:649:15:649:15 | array [Array element] | test.swift:648:13:648:22 | enter #keyPath(...) [Array element] | test.swift:648:13:648:22 | exit #keyPath(...) | test.swift:649:15:649:31 | \\...[...] | +| test.swift:746:14:746:21 | call to source() | test.swift:731:9:731:9 | value | file://:0:0:0:0 | [post] self [v2] | test.swift:746:5:746:5 | [post] mo1 [v2] | +| test.swift:746:14:746:21 | call to source() [some:0] | test.swift:731:9:731:9 | value [some:0] | file://:0:0:0:0 | [post] self [v2, some:0] | test.swift:746:5:746:5 | [post] mo1 [v2, some:0] | +| test.swift:747:14:747:21 | call to source() | test.swift:732:9:732:9 | value | file://:0:0:0:0 | [post] self [v3] | test.swift:747:5:747:5 | [post] mo1 [v3] | +| test.swift:756:15:756:15 | mo1 [v2, some:0] | test.swift:731:9:731:9 | self [v2, some:0] | file://:0:0:0:0 | .v2 [some:0] | test.swift:756:15:756:19 | .v2 [some:0] | +| test.swift:756:15:756:15 | mo1 [v2] | test.swift:731:9:731:9 | self [v2] | file://:0:0:0:0 | .v2 | test.swift:756:15:756:19 | .v2 | +| test.swift:757:15:757:15 | mo1 [v3] | test.swift:732:9:732:9 | self [v3] | file://:0:0:0:0 | .v3 | test.swift:757:15:757:19 | .v3 | #select | test.swift:7:15:7:15 | t1 | test.swift:6:19:6:26 | call to source() | test.swift:7:15:7:15 | t1 | result | | test.swift:9:15:9:15 | t1 | test.swift:6:19:6:26 | call to source() | test.swift:9:15:9:15 | t1 | result | @@ -800,38 +950,49 @@ subpaths | test.swift:361:15:361:18 | .1 | test.swift:351:31:351:38 | call to source() | test.swift:361:15:361:18 | .1 | result | | test.swift:363:15:363:15 | a | test.swift:351:18:351:25 | call to source() | test.swift:363:15:363:15 | a | result | | test.swift:364:15:364:15 | b | test.swift:351:31:351:38 | call to source() | test.swift:364:15:364:15 | b | result | -| test.swift:409:19:409:19 | a | test.swift:403:19:403:26 | call to source() | test.swift:409:19:409:19 | a | result | -| test.swift:418:19:418:19 | x | test.swift:403:19:403:26 | call to source() | test.swift:418:19:418:19 | x | result | -| test.swift:434:19:434:19 | b | test.swift:425:26:425:33 | call to source() | test.swift:434:19:434:19 | b | result | -| test.swift:444:19:444:19 | y | test.swift:425:26:425:33 | call to source() | test.swift:444:19:444:19 | y | result | -| test.swift:460:19:460:19 | c | test.swift:425:26:425:33 | call to source() | test.swift:460:19:460:19 | c | result | -| test.swift:469:19:469:19 | x | test.swift:468:51:468:58 | call to source() | test.swift:469:19:469:19 | x | result | -| test.swift:473:19:473:19 | c | test.swift:425:26:425:33 | call to source() | test.swift:473:19:473:19 | c | result | -| test.swift:479:19:479:19 | b | test.swift:425:26:425:33 | call to source() | test.swift:479:19:479:19 | b | result | -| test.swift:482:19:482:19 | e | test.swift:425:26:425:33 | call to source() | test.swift:482:19:482:19 | e | result | -| test.swift:494:54:494:54 | d2 | test.swift:488:30:488:37 | call to source() | test.swift:494:54:494:54 | d2 | result | -| test.swift:496:54:496:54 | d4 | test.swift:490:24:490:31 | call to source() | test.swift:496:54:496:54 | d4 | result | -| test.swift:498:54:498:54 | d6 | test.swift:492:24:492:31 | call to source() | test.swift:498:54:498:54 | d6 | result | -| test.swift:507:15:507:17 | ...! | test.swift:501:28:501:35 | call to source() | test.swift:507:15:507:17 | ...! | result | -| test.swift:509:15:509:17 | ...! | test.swift:503:26:503:33 | call to source() | test.swift:509:15:509:17 | ...! | result | -| test.swift:511:15:511:17 | ...! | test.swift:505:26:505:33 | call to source() | test.swift:511:15:511:17 | ...! | result | -| test.swift:520:19:520:19 | a | test.swift:259:12:259:19 | call to source() | test.swift:520:19:520:19 | a | result | -| test.swift:527:19:527:19 | a | test.swift:259:12:259:19 | call to source() | test.swift:527:19:527:19 | a | result | -| test.swift:551:15:551:15 | z1 | test.swift:259:12:259:19 | call to source() | test.swift:551:15:551:15 | z1 | result | -| test.swift:557:13:557:21 | call to +(_:) | test.swift:557:14:557:21 | call to source() | test.swift:557:13:557:21 | call to +(_:) | result | -| test.swift:558:14:558:21 | call to source() | test.swift:558:14:558:21 | call to source() | test.swift:558:14:558:21 | call to source() | result | -| test.swift:575:17:575:17 | .str | test.swift:574:20:574:28 | call to source3() | test.swift:575:17:575:17 | .str | result | -| test.swift:580:13:580:35 | .str | test.swift:580:24:580:32 | call to source3() | test.swift:580:13:580:35 | .str | result | -| test.swift:581:13:581:43 | .str | test.swift:574:20:574:28 | call to source3() | test.swift:581:13:581:43 | .str | result | -| test.swift:606:13:606:25 | \\...[...] | test.swift:604:16:604:23 | call to source() | test.swift:606:13:606:25 | \\...[...] | result | -| test.swift:609:13:609:32 | \\...[...] | test.swift:604:16:604:23 | call to source() | test.swift:609:13:609:32 | \\...[...] | result | -| test.swift:624:13:624:26 | \\...[...] | test.swift:621:16:621:23 | call to source() | test.swift:624:13:624:26 | \\...[...] | result | -| test.swift:630:15:630:31 | \\...[...] | test.swift:628:18:628:25 | call to source() | test.swift:630:15:630:31 | \\...[...] | result | -| test.swift:657:15:657:15 | y | test.swift:649:13:649:20 | call to source() | test.swift:657:15:657:15 | y | result | -| test.swift:662:15:662:15 | x | test.swift:659:9:659:16 | call to source() | test.swift:662:15:662:15 | x | result | -| test.swift:663:15:663:15 | y | test.swift:659:9:659:16 | call to source() | test.swift:663:15:663:15 | y | result | -| test.swift:670:15:670:21 | ...[...] | test.swift:669:15:669:22 | call to source() | test.swift:670:15:670:21 | ...[...] | result | -| test.swift:674:15:674:21 | ...[...] | test.swift:673:17:673:24 | call to source() | test.swift:674:15:674:21 | ...[...] | result | -| test.swift:678:15:678:26 | ...[...] | test.swift:676:20:676:27 | call to source() | test.swift:678:15:678:26 | ...[...] | result | -| test.swift:682:15:682:27 | ...[...] | test.swift:681:21:681:28 | call to source() | test.swift:682:15:682:27 | ...[...] | result | -| test.swift:694:15:694:21 | ...[...] | test.swift:693:17:693:24 | call to source() | test.swift:694:15:694:21 | ...[...] | result | +| test.swift:380:15:380:18 | .1 | test.swift:375:18:375:25 | call to source() | test.swift:380:15:380:18 | .1 | result | +| test.swift:381:15:381:18 | .0 | test.swift:375:18:375:25 | call to source() | test.swift:381:15:381:18 | .0 | result | +| test.swift:383:15:383:18 | .0 | test.swift:375:18:375:25 | call to source() | test.swift:383:15:383:18 | .0 | result | +| test.swift:428:19:428:19 | a | test.swift:422:19:422:26 | call to source() | test.swift:428:19:428:19 | a | result | +| test.swift:437:19:437:19 | x | test.swift:422:19:422:26 | call to source() | test.swift:437:19:437:19 | x | result | +| test.swift:453:19:453:19 | b | test.swift:444:26:444:33 | call to source() | test.swift:453:19:453:19 | b | result | +| test.swift:463:19:463:19 | y | test.swift:444:26:444:33 | call to source() | test.swift:463:19:463:19 | y | result | +| test.swift:479:19:479:19 | c | test.swift:444:26:444:33 | call to source() | test.swift:479:19:479:19 | c | result | +| test.swift:488:19:488:19 | x | test.swift:487:51:487:58 | call to source() | test.swift:488:19:488:19 | x | result | +| test.swift:492:19:492:19 | c | test.swift:444:26:444:33 | call to source() | test.swift:492:19:492:19 | c | result | +| test.swift:498:19:498:19 | b | test.swift:444:26:444:33 | call to source() | test.swift:498:19:498:19 | b | result | +| test.swift:501:19:501:19 | e | test.swift:444:26:444:33 | call to source() | test.swift:501:19:501:19 | e | result | +| test.swift:513:54:513:54 | d2 | test.swift:507:30:507:37 | call to source() | test.swift:513:54:513:54 | d2 | result | +| test.swift:515:54:515:54 | d4 | test.swift:509:24:509:31 | call to source() | test.swift:515:54:515:54 | d4 | result | +| test.swift:517:54:517:54 | d6 | test.swift:511:24:511:31 | call to source() | test.swift:517:54:517:54 | d6 | result | +| test.swift:526:15:526:17 | ...! | test.swift:520:28:520:35 | call to source() | test.swift:526:15:526:17 | ...! | result | +| test.swift:528:15:528:17 | ...! | test.swift:522:26:522:33 | call to source() | test.swift:528:15:528:17 | ...! | result | +| test.swift:530:15:530:17 | ...! | test.swift:524:26:524:33 | call to source() | test.swift:530:15:530:17 | ...! | result | +| test.swift:539:19:539:19 | a | test.swift:259:12:259:19 | call to source() | test.swift:539:19:539:19 | a | result | +| test.swift:546:19:546:19 | a | test.swift:259:12:259:19 | call to source() | test.swift:546:19:546:19 | a | result | +| test.swift:570:15:570:15 | z1 | test.swift:259:12:259:19 | call to source() | test.swift:570:15:570:15 | z1 | result | +| test.swift:576:13:576:21 | call to +(_:) | test.swift:576:14:576:21 | call to source() | test.swift:576:13:576:21 | call to +(_:) | result | +| test.swift:577:14:577:21 | call to source() | test.swift:577:14:577:21 | call to source() | test.swift:577:14:577:21 | call to source() | result | +| test.swift:594:17:594:17 | .str | test.swift:593:20:593:28 | call to source3() | test.swift:594:17:594:17 | .str | result | +| test.swift:599:13:599:35 | .str | test.swift:599:24:599:32 | call to source3() | test.swift:599:13:599:35 | .str | result | +| test.swift:600:13:600:43 | .str | test.swift:593:20:593:28 | call to source3() | test.swift:600:13:600:43 | .str | result | +| test.swift:625:13:625:25 | \\...[...] | test.swift:623:16:623:23 | call to source() | test.swift:625:13:625:25 | \\...[...] | result | +| test.swift:628:13:628:32 | \\...[...] | test.swift:623:16:623:23 | call to source() | test.swift:628:13:628:32 | \\...[...] | result | +| test.swift:643:13:643:26 | \\...[...] | test.swift:640:16:640:23 | call to source() | test.swift:643:13:643:26 | \\...[...] | result | +| test.swift:649:15:649:31 | \\...[...] | test.swift:647:18:647:25 | call to source() | test.swift:649:15:649:31 | \\...[...] | result | +| test.swift:676:15:676:15 | y | test.swift:668:13:668:20 | call to source() | test.swift:676:15:676:15 | y | result | +| test.swift:681:15:681:15 | x | test.swift:678:9:678:16 | call to source() | test.swift:681:15:681:15 | x | result | +| test.swift:682:15:682:15 | y | test.swift:678:9:678:16 | call to source() | test.swift:682:15:682:15 | y | result | +| test.swift:689:15:689:21 | ...[...] | test.swift:688:15:688:22 | call to source() | test.swift:689:15:689:21 | ...[...] | result | +| test.swift:693:15:693:21 | ...[...] | test.swift:692:17:692:24 | call to source() | test.swift:693:15:693:21 | ...[...] | result | +| test.swift:697:15:697:26 | ...[...] | test.swift:695:20:695:27 | call to source() | test.swift:697:15:697:26 | ...[...] | result | +| test.swift:701:15:701:27 | ...[...] | test.swift:700:21:700:28 | call to source() | test.swift:701:15:701:27 | ...[...] | result | +| test.swift:713:15:713:21 | ...[...] | test.swift:712:17:712:24 | call to source() | test.swift:713:15:713:21 | ...[...] | result | +| test.swift:716:15:716:35 | ...! | test.swift:715:17:715:24 | call to source() | test.swift:716:15:716:35 | ...! | result | +| test.swift:723:15:723:35 | ...! | test.swift:722:17:722:24 | call to source() | test.swift:723:15:723:35 | ...! | result | +| test.swift:726:15:726:35 | ...! | test.swift:725:21:725:28 | call to source() | test.swift:726:15:726:35 | ...! | result | +| test.swift:752:15:752:17 | ...! | test.swift:742:11:742:18 | call to source() | test.swift:752:15:752:17 | ...! | result | +| test.swift:753:15:753:17 | ...! | test.swift:743:10:743:17 | call to source() | test.swift:753:15:753:17 | ...! | result | +| test.swift:754:15:754:15 | v3 | test.swift:744:10:744:17 | call to source() | test.swift:754:15:754:15 | v3 | result | +| test.swift:756:15:756:21 | ...! | test.swift:746:14:746:21 | call to source() | test.swift:756:15:756:21 | ...! | result | +| test.swift:757:15:757:19 | .v3 | test.swift:747:14:747:21 | call to source() | test.swift:757:15:757:19 | .v3 | result | diff --git a/swift/ql/test/library-tests/dataflow/dataflow/FlowConfig.qll b/swift/ql/test/library-tests/dataflow/dataflow/FlowConfig.qll index 952035f7c77..5de4983b7aa 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/FlowConfig.qll +++ b/swift/ql/test/library-tests/dataflow/dataflow/FlowConfig.qll @@ -26,6 +26,8 @@ private class TestSummaries extends SummaryModelCsv { [ // model to allow data flow through `signum()` as though it were an identity function, for the benefit of testing flow through optional chaining (`x?.`). ";Int;true;signum();;;Argument[-1];ReturnValue;value", + // test Tuple content in MAD + ";;false;tupleShiftLeft2(_:);;;Argument[0].TupleElement[1];ReturnValue.TupleElement[0];value", // test Enum content in MAD ";;false;mkMyEnum2(_:);;;Argument[0];ReturnValue.EnumElement[mySingle:0];value", ";;false;mkOptional2(_:);;;Argument[0];ReturnValue.OptionalSome;value" diff --git a/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected b/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected index 99e9ceb5ba8..18e5d25010d 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected +++ b/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected @@ -374,440 +374,542 @@ | test.swift:360:15:360:15 | t2 | test.swift:361:15:361:15 | t2 | | test.swift:361:15:361:15 | [post] t2 | test.swift:362:15:362:15 | t2 | | test.swift:361:15:361:15 | t2 | test.swift:362:15:362:15 | t2 | -| test.swift:375:16:375:21 | SSA def(v) | test.swift:375:61:375:61 | v | -| test.swift:375:16:375:21 | v | test.swift:375:16:375:21 | SSA def(v) | -| test.swift:377:18:377:23 | SSA def(v) | test.swift:377:59:377:59 | v | -| test.swift:377:18:377:23 | v | test.swift:377:18:377:23 | SSA def(v) | -| test.swift:381:9:381:9 | SSA def(a) | test.swift:383:12:383:12 | a | -| test.swift:381:9:381:9 | a | test.swift:381:9:381:9 | SSA def(a) | -| test.swift:381:9:381:13 | ... as ... | test.swift:381:9:381:9 | a | -| test.swift:381:22:381:23 | .myNone | test.swift:381:9:381:13 | ... as ... | -| test.swift:383:12:383:12 | a | test.swift:384:10:384:11 | .myNone | -| test.swift:383:12:383:12 | a | test.swift:386:10:386:25 | .mySingle(...) | -| test.swift:383:12:383:12 | a | test.swift:388:10:388:30 | .myPair(...) | -| test.swift:383:12:383:12 | a | test.swift:391:14:391:26 | .myCons(...) | -| test.swift:383:12:383:12 | a | test.swift:395:32:395:32 | a | -| test.swift:386:24:386:24 | SSA def(a) | test.swift:387:19:387:19 | a | -| test.swift:386:24:386:24 | a | test.swift:386:24:386:24 | SSA def(a) | -| test.swift:388:22:388:22 | SSA def(a) | test.swift:389:19:389:19 | a | -| test.swift:388:22:388:22 | a | test.swift:388:22:388:22 | SSA def(a) | -| test.swift:388:29:388:29 | SSA def(b) | test.swift:390:19:390:19 | b | -| test.swift:388:29:388:29 | b | test.swift:388:29:388:29 | SSA def(b) | -| test.swift:391:10:391:26 | SSA phi(a) | test.swift:392:19:392:19 | a | -| test.swift:391:22:391:22 | SSA def(a) | test.swift:391:10:391:26 | SSA phi(a) | -| test.swift:391:22:391:22 | a | test.swift:391:22:391:22 | SSA def(a) | -| test.swift:395:27:395:27 | SSA def(x) | test.swift:396:19:396:19 | x | -| test.swift:395:27:395:27 | x | test.swift:395:27:395:27 | SSA def(x) | -| test.swift:395:32:395:32 | a | test.swift:395:13:395:28 | .mySingle(...) | -| test.swift:395:32:395:32 | a | test.swift:398:37:398:37 | a | -| test.swift:398:25:398:25 | SSA def(x) | test.swift:399:19:399:19 | x | -| test.swift:398:25:398:25 | x | test.swift:398:25:398:25 | SSA def(x) | -| test.swift:398:32:398:32 | SSA def(y) | test.swift:400:19:400:19 | y | -| test.swift:398:32:398:32 | y | test.swift:398:32:398:32 | SSA def(y) | -| test.swift:398:37:398:37 | a | test.swift:398:13:398:33 | .myPair(...) | -| test.swift:403:5:403:27 | SSA def(a) | test.swift:405:12:405:12 | a | -| test.swift:403:9:403:27 | call to ... | test.swift:403:5:403:27 | SSA def(a) | -| test.swift:405:12:405:12 | a | test.swift:406:10:406:11 | .myNone | -| test.swift:405:12:405:12 | a | test.swift:408:10:408:25 | .mySingle(...) | -| test.swift:405:12:405:12 | a | test.swift:410:10:410:30 | .myPair(...) | -| test.swift:405:12:405:12 | a | test.swift:413:14:413:26 | .myCons(...) | -| test.swift:405:12:405:12 | a | test.swift:417:32:417:32 | a | -| test.swift:408:24:408:24 | SSA def(a) | test.swift:409:19:409:19 | a | -| test.swift:408:24:408:24 | a | test.swift:408:24:408:24 | SSA def(a) | -| test.swift:410:22:410:22 | SSA def(a) | test.swift:411:19:411:19 | a | +| test.swift:368:22:368:36 | SSA def(t) | test.swift:369:13:369:13 | t | +| test.swift:368:22:368:36 | t | test.swift:368:22:368:36 | SSA def(t) | +| test.swift:375:9:375:9 | SSA def(t1) | test.swift:376:30:376:30 | t1 | +| test.swift:375:9:375:9 | t1 | test.swift:375:9:375:9 | SSA def(t1) | +| test.swift:375:14:375:26 | (...) | test.swift:375:9:375:9 | t1 | +| test.swift:376:9:376:9 | SSA def(t2) | test.swift:381:15:381:15 | t2 | +| test.swift:376:9:376:9 | t2 | test.swift:376:9:376:9 | SSA def(t2) | +| test.swift:376:14:376:32 | call to tupleShiftLeft1(_:) | test.swift:376:9:376:9 | t2 | +| test.swift:376:30:376:30 | t1 | test.swift:377:30:377:30 | t1 | +| test.swift:377:9:377:9 | SSA def(t3) | test.swift:383:15:383:15 | t3 | +| test.swift:377:9:377:9 | t3 | test.swift:377:9:377:9 | SSA def(t3) | +| test.swift:377:14:377:32 | call to tupleShiftLeft2(_:) | test.swift:377:9:377:9 | t3 | +| test.swift:377:30:377:30 | t1 | test.swift:379:15:379:15 | t1 | +| test.swift:379:15:379:15 | [post] t1 | test.swift:380:15:380:15 | t1 | +| test.swift:379:15:379:15 | t1 | test.swift:380:15:380:15 | t1 | +| test.swift:381:15:381:15 | [post] t2 | test.swift:382:15:382:15 | t2 | +| test.swift:381:15:381:15 | t2 | test.swift:382:15:382:15 | t2 | +| test.swift:383:15:383:15 | [post] t3 | test.swift:384:15:384:15 | t3 | +| test.swift:383:15:383:15 | t3 | test.swift:384:15:384:15 | t3 | +| test.swift:394:16:394:21 | SSA def(v) | test.swift:394:61:394:61 | v | +| test.swift:394:16:394:21 | v | test.swift:394:16:394:21 | SSA def(v) | +| test.swift:396:18:396:23 | SSA def(v) | test.swift:396:59:396:59 | v | +| test.swift:396:18:396:23 | v | test.swift:396:18:396:23 | SSA def(v) | +| test.swift:400:9:400:9 | SSA def(a) | test.swift:402:12:402:12 | a | +| test.swift:400:9:400:9 | a | test.swift:400:9:400:9 | SSA def(a) | +| test.swift:400:9:400:13 | ... as ... | test.swift:400:9:400:9 | a | +| test.swift:400:22:400:23 | .myNone | test.swift:400:9:400:13 | ... as ... | +| test.swift:402:12:402:12 | a | test.swift:403:10:403:11 | .myNone | +| test.swift:402:12:402:12 | a | test.swift:405:10:405:25 | .mySingle(...) | +| test.swift:402:12:402:12 | a | test.swift:407:10:407:30 | .myPair(...) | +| test.swift:402:12:402:12 | a | test.swift:410:14:410:26 | .myCons(...) | +| test.swift:402:12:402:12 | a | test.swift:414:32:414:32 | a | +| test.swift:405:24:405:24 | SSA def(a) | test.swift:406:19:406:19 | a | +| test.swift:405:24:405:24 | a | test.swift:405:24:405:24 | SSA def(a) | +| test.swift:407:22:407:22 | SSA def(a) | test.swift:408:19:408:19 | a | +| test.swift:407:22:407:22 | a | test.swift:407:22:407:22 | SSA def(a) | +| test.swift:407:29:407:29 | SSA def(b) | test.swift:409:19:409:19 | b | +| test.swift:407:29:407:29 | b | test.swift:407:29:407:29 | SSA def(b) | +| test.swift:410:10:410:26 | SSA phi(a) | test.swift:411:19:411:19 | a | +| test.swift:410:22:410:22 | SSA def(a) | test.swift:410:10:410:26 | SSA phi(a) | | test.swift:410:22:410:22 | a | test.swift:410:22:410:22 | SSA def(a) | -| test.swift:410:29:410:29 | SSA def(b) | test.swift:412:19:412:19 | b | -| test.swift:410:29:410:29 | b | test.swift:410:29:410:29 | SSA def(b) | -| test.swift:413:10:413:26 | SSA phi(a) | test.swift:414:19:414:19 | a | -| test.swift:413:22:413:22 | SSA def(a) | test.swift:413:10:413:26 | SSA phi(a) | -| test.swift:413:22:413:22 | a | test.swift:413:22:413:22 | SSA def(a) | -| test.swift:417:27:417:27 | SSA def(x) | test.swift:418:19:418:19 | x | -| test.swift:417:27:417:27 | x | test.swift:417:27:417:27 | SSA def(x) | -| test.swift:417:32:417:32 | a | test.swift:417:13:417:28 | .mySingle(...) | -| test.swift:417:32:417:32 | a | test.swift:420:37:420:37 | a | -| test.swift:420:25:420:25 | SSA def(x) | test.swift:421:19:421:19 | x | -| test.swift:420:25:420:25 | x | test.swift:420:25:420:25 | SSA def(x) | -| test.swift:420:32:420:32 | SSA def(y) | test.swift:422:19:422:19 | y | -| test.swift:420:32:420:32 | y | test.swift:420:32:420:32 | SSA def(y) | -| test.swift:420:37:420:37 | a | test.swift:420:13:420:33 | .myPair(...) | -| test.swift:425:5:425:34 | SSA def(a) | test.swift:427:12:427:12 | a | -| test.swift:425:9:425:34 | call to ... | test.swift:425:5:425:34 | SSA def(a) | -| test.swift:427:12:427:12 | a | test.swift:428:10:428:11 | .myNone | -| test.swift:427:12:427:12 | a | test.swift:430:10:430:25 | .mySingle(...) | -| test.swift:427:12:427:12 | a | test.swift:432:10:432:30 | .myPair(...) | -| test.swift:427:12:427:12 | a | test.swift:435:14:435:26 | .myCons(...) | -| test.swift:427:12:427:12 | a | test.swift:439:32:439:32 | a | -| test.swift:430:24:430:24 | SSA def(a) | test.swift:431:19:431:19 | a | -| test.swift:430:24:430:24 | a | test.swift:430:24:430:24 | SSA def(a) | -| test.swift:432:22:432:22 | SSA def(a) | test.swift:433:19:433:19 | a | +| test.swift:414:27:414:27 | SSA def(x) | test.swift:415:19:415:19 | x | +| test.swift:414:27:414:27 | x | test.swift:414:27:414:27 | SSA def(x) | +| test.swift:414:32:414:32 | a | test.swift:414:13:414:28 | .mySingle(...) | +| test.swift:414:32:414:32 | a | test.swift:417:37:417:37 | a | +| test.swift:417:25:417:25 | SSA def(x) | test.swift:418:19:418:19 | x | +| test.swift:417:25:417:25 | x | test.swift:417:25:417:25 | SSA def(x) | +| test.swift:417:32:417:32 | SSA def(y) | test.swift:419:19:419:19 | y | +| test.swift:417:32:417:32 | y | test.swift:417:32:417:32 | SSA def(y) | +| test.swift:417:37:417:37 | a | test.swift:417:13:417:33 | .myPair(...) | +| test.swift:422:5:422:27 | SSA def(a) | test.swift:424:12:424:12 | a | +| test.swift:422:9:422:27 | call to ... | test.swift:422:5:422:27 | SSA def(a) | +| test.swift:424:12:424:12 | a | test.swift:425:10:425:11 | .myNone | +| test.swift:424:12:424:12 | a | test.swift:427:10:427:25 | .mySingle(...) | +| test.swift:424:12:424:12 | a | test.swift:429:10:429:30 | .myPair(...) | +| test.swift:424:12:424:12 | a | test.swift:432:14:432:26 | .myCons(...) | +| test.swift:424:12:424:12 | a | test.swift:436:32:436:32 | a | +| test.swift:427:24:427:24 | SSA def(a) | test.swift:428:19:428:19 | a | +| test.swift:427:24:427:24 | a | test.swift:427:24:427:24 | SSA def(a) | +| test.swift:429:22:429:22 | SSA def(a) | test.swift:430:19:430:19 | a | +| test.swift:429:22:429:22 | a | test.swift:429:22:429:22 | SSA def(a) | +| test.swift:429:29:429:29 | SSA def(b) | test.swift:431:19:431:19 | b | +| test.swift:429:29:429:29 | b | test.swift:429:29:429:29 | SSA def(b) | +| test.swift:432:10:432:26 | SSA phi(a) | test.swift:433:19:433:19 | a | +| test.swift:432:22:432:22 | SSA def(a) | test.swift:432:10:432:26 | SSA phi(a) | | test.swift:432:22:432:22 | a | test.swift:432:22:432:22 | SSA def(a) | -| test.swift:432:29:432:29 | SSA def(b) | test.swift:434:19:434:19 | b | -| test.swift:432:29:432:29 | b | test.swift:432:29:432:29 | SSA def(b) | -| test.swift:435:10:435:26 | SSA phi(a) | test.swift:436:19:436:19 | a | -| test.swift:435:22:435:22 | SSA def(a) | test.swift:435:10:435:26 | SSA phi(a) | -| test.swift:435:22:435:22 | a | test.swift:435:22:435:22 | SSA def(a) | -| test.swift:439:27:439:27 | SSA def(x) | test.swift:440:19:440:19 | x | -| test.swift:439:27:439:27 | x | test.swift:439:27:439:27 | SSA def(x) | -| test.swift:439:32:439:32 | a | test.swift:439:13:439:28 | .mySingle(...) | -| test.swift:439:32:439:32 | a | test.swift:442:37:442:37 | a | -| test.swift:442:25:442:25 | SSA def(x) | test.swift:443:19:443:19 | x | -| test.swift:442:25:442:25 | x | test.swift:442:25:442:25 | SSA def(x) | -| test.swift:442:32:442:32 | SSA def(y) | test.swift:444:19:444:19 | y | -| test.swift:442:32:442:32 | y | test.swift:442:32:442:32 | SSA def(y) | -| test.swift:442:37:442:37 | a | test.swift:442:13:442:33 | .myPair(...) | -| test.swift:442:37:442:37 | a | test.swift:447:33:447:33 | a | -| test.swift:447:9:447:9 | SSA def(b) | test.swift:449:12:449:12 | b | -| test.swift:447:9:447:9 | b | test.swift:447:9:447:9 | SSA def(b) | -| test.swift:447:9:447:12 | ... as ... | test.swift:447:9:447:9 | b | -| test.swift:447:21:447:34 | call to ... | test.swift:447:9:447:12 | ... as ... | -| test.swift:447:33:447:33 | [post] a | test.swift:476:13:476:13 | a | -| test.swift:447:33:447:33 | a | test.swift:476:13:476:13 | a | -| test.swift:449:12:449:12 | b | test.swift:450:10:450:11 | .myNone | -| test.swift:449:12:449:12 | b | test.swift:452:10:452:25 | .mySingle(...) | -| test.swift:449:12:449:12 | b | test.swift:454:10:454:30 | .myPair(...) | -| test.swift:449:12:449:12 | b | test.swift:457:14:457:38 | .myCons(...) | -| test.swift:449:12:449:12 | b | test.swift:461:14:461:26 | .myCons(...) | -| test.swift:449:12:449:12 | b | test.swift:472:45:472:45 | b | -| test.swift:452:24:452:24 | SSA def(a) | test.swift:453:19:453:19 | a | -| test.swift:452:24:452:24 | a | test.swift:452:24:452:24 | SSA def(a) | -| test.swift:454:22:454:22 | SSA def(a) | test.swift:455:19:455:19 | a | +| test.swift:436:27:436:27 | SSA def(x) | test.swift:437:19:437:19 | x | +| test.swift:436:27:436:27 | x | test.swift:436:27:436:27 | SSA def(x) | +| test.swift:436:32:436:32 | a | test.swift:436:13:436:28 | .mySingle(...) | +| test.swift:436:32:436:32 | a | test.swift:439:37:439:37 | a | +| test.swift:439:25:439:25 | SSA def(x) | test.swift:440:19:440:19 | x | +| test.swift:439:25:439:25 | x | test.swift:439:25:439:25 | SSA def(x) | +| test.swift:439:32:439:32 | SSA def(y) | test.swift:441:19:441:19 | y | +| test.swift:439:32:439:32 | y | test.swift:439:32:439:32 | SSA def(y) | +| test.swift:439:37:439:37 | a | test.swift:439:13:439:33 | .myPair(...) | +| test.swift:444:5:444:34 | SSA def(a) | test.swift:446:12:446:12 | a | +| test.swift:444:9:444:34 | call to ... | test.swift:444:5:444:34 | SSA def(a) | +| test.swift:446:12:446:12 | a | test.swift:447:10:447:11 | .myNone | +| test.swift:446:12:446:12 | a | test.swift:449:10:449:25 | .mySingle(...) | +| test.swift:446:12:446:12 | a | test.swift:451:10:451:30 | .myPair(...) | +| test.swift:446:12:446:12 | a | test.swift:454:14:454:26 | .myCons(...) | +| test.swift:446:12:446:12 | a | test.swift:458:32:458:32 | a | +| test.swift:449:24:449:24 | SSA def(a) | test.swift:450:19:450:19 | a | +| test.swift:449:24:449:24 | a | test.swift:449:24:449:24 | SSA def(a) | +| test.swift:451:22:451:22 | SSA def(a) | test.swift:452:19:452:19 | a | +| test.swift:451:22:451:22 | a | test.swift:451:22:451:22 | SSA def(a) | +| test.swift:451:29:451:29 | SSA def(b) | test.swift:453:19:453:19 | b | +| test.swift:451:29:451:29 | b | test.swift:451:29:451:29 | SSA def(b) | +| test.swift:454:10:454:26 | SSA phi(a) | test.swift:455:19:455:19 | a | +| test.swift:454:22:454:22 | SSA def(a) | test.swift:454:10:454:26 | SSA phi(a) | | test.swift:454:22:454:22 | a | test.swift:454:22:454:22 | SSA def(a) | -| test.swift:454:29:454:29 | SSA def(b) | test.swift:456:19:456:19 | b | -| test.swift:454:29:454:29 | b | test.swift:454:29:454:29 | SSA def(b) | -| test.swift:457:10:457:38 | SSA phi(a) | test.swift:458:19:458:19 | a | -| test.swift:457:10:457:38 | SSA phi(b) | test.swift:459:19:459:19 | b | -| test.swift:457:10:457:38 | SSA phi(c) | test.swift:460:19:460:19 | c | -| test.swift:457:22:457:22 | SSA def(a) | test.swift:457:10:457:38 | SSA phi(a) | -| test.swift:457:22:457:22 | a | test.swift:457:22:457:22 | SSA def(a) | -| test.swift:457:33:457:33 | SSA def(b) | test.swift:457:10:457:38 | SSA phi(b) | -| test.swift:457:33:457:33 | b | test.swift:457:33:457:33 | SSA def(b) | -| test.swift:457:36:457:36 | SSA def(c) | test.swift:457:10:457:38 | SSA phi(c) | -| test.swift:457:36:457:36 | c | test.swift:457:36:457:36 | SSA def(c) | -| test.swift:461:10:461:26 | SSA phi(a) | test.swift:462:19:462:19 | a | -| test.swift:461:22:461:22 | SSA def(a) | test.swift:461:10:461:26 | SSA phi(a) | -| test.swift:461:22:461:22 | a | test.swift:461:22:461:22 | SSA def(a) | -| test.swift:465:27:465:27 | SSA def(x) | test.swift:466:19:466:19 | x | -| test.swift:465:27:465:27 | x | test.swift:465:27:465:27 | SSA def(x) | -| test.swift:465:32:465:57 | call to ... | test.swift:465:13:465:28 | .mySingle(...) | -| test.swift:468:31:468:31 | SSA def(x) | test.swift:469:19:469:19 | x | -| test.swift:468:31:468:31 | x | test.swift:468:31:468:31 | SSA def(x) | -| test.swift:468:38:468:38 | SSA def(y) | test.swift:470:19:470:19 | y | -| test.swift:468:38:468:38 | y | test.swift:468:38:468:38 | SSA def(y) | -| test.swift:468:43:468:62 | call to ... | test.swift:468:13:468:39 | .myPair(...) | -| test.swift:472:13:472:41 | SSA phi(c) | test.swift:473:19:473:19 | c | -| test.swift:472:39:472:39 | SSA def(c) | test.swift:472:13:472:41 | SSA phi(c) | -| test.swift:472:39:472:39 | c | test.swift:472:39:472:39 | SSA def(c) | -| test.swift:472:45:472:45 | b | test.swift:472:17:472:41 | .myCons(...) | -| test.swift:472:45:472:45 | b | test.swift:476:16:476:16 | b | -| test.swift:476:12:476:17 | (...) | test.swift:477:14:477:55 | (...) | -| test.swift:476:12:476:17 | (...) | test.swift:483:5:483:5 | _ | -| test.swift:477:10:477:55 | SSA phi(a) | test.swift:478:19:478:19 | a | -| test.swift:477:10:477:55 | SSA phi(b) | test.swift:479:19:479:19 | b | -| test.swift:477:10:477:55 | SSA phi(c) | test.swift:480:19:480:19 | c | -| test.swift:477:10:477:55 | SSA phi(d) | test.swift:481:19:481:19 | d | -| test.swift:477:10:477:55 | SSA phi(e) | test.swift:482:19:482:19 | e | -| test.swift:477:23:477:23 | SSA def(a) | test.swift:477:10:477:55 | SSA phi(a) | -| test.swift:477:23:477:23 | a | test.swift:477:23:477:23 | SSA def(a) | -| test.swift:477:26:477:26 | SSA def(b) | test.swift:477:10:477:55 | SSA phi(b) | -| test.swift:477:26:477:26 | b | test.swift:477:26:477:26 | SSA def(b) | -| test.swift:477:38:477:38 | SSA def(c) | test.swift:477:10:477:55 | SSA phi(c) | -| test.swift:477:38:477:38 | c | test.swift:477:38:477:38 | SSA def(c) | -| test.swift:477:49:477:49 | SSA def(d) | test.swift:477:10:477:55 | SSA phi(d) | -| test.swift:477:49:477:49 | d | test.swift:477:49:477:49 | SSA def(d) | -| test.swift:477:52:477:52 | SSA def(e) | test.swift:477:10:477:55 | SSA phi(e) | -| test.swift:477:52:477:52 | e | test.swift:477:52:477:52 | SSA def(e) | -| test.swift:487:9:487:9 | SSA def(c1) | test.swift:493:39:493:39 | c1 | -| test.swift:487:9:487:9 | c1 | test.swift:487:9:487:9 | SSA def(c1) | -| test.swift:487:14:487:31 | call to ... | test.swift:487:9:487:9 | c1 | -| test.swift:488:9:488:9 | SSA def(c2) | test.swift:494:39:494:39 | c2 | -| test.swift:488:9:488:9 | c2 | test.swift:488:9:488:9 | SSA def(c2) | -| test.swift:488:14:488:38 | call to ... | test.swift:488:9:488:9 | c2 | -| test.swift:489:9:489:9 | SSA def(c3) | test.swift:495:39:495:39 | c3 | -| test.swift:489:9:489:9 | c3 | test.swift:489:9:489:9 | SSA def(c3) | -| test.swift:489:14:489:25 | call to mkMyEnum1(_:) | test.swift:489:9:489:9 | c3 | -| test.swift:490:9:490:9 | SSA def(c4) | test.swift:496:39:496:39 | c4 | -| test.swift:490:9:490:9 | c4 | test.swift:490:9:490:9 | SSA def(c4) | -| test.swift:490:14:490:32 | call to mkMyEnum1(_:) | test.swift:490:9:490:9 | c4 | -| test.swift:491:9:491:9 | SSA def(c5) | test.swift:497:39:497:39 | c5 | -| test.swift:491:9:491:9 | c5 | test.swift:491:9:491:9 | SSA def(c5) | -| test.swift:491:14:491:25 | call to mkMyEnum2(_:) | test.swift:491:9:491:9 | c5 | -| test.swift:492:9:492:9 | SSA def(c6) | test.swift:498:39:498:39 | c6 | -| test.swift:492:9:492:9 | c6 | test.swift:492:9:492:9 | SSA def(c6) | -| test.swift:492:14:492:32 | call to mkMyEnum2(_:) | test.swift:492:9:492:9 | c6 | -| test.swift:493:33:493:33 | SSA def(d1) | test.swift:493:54:493:54 | d1 | -| test.swift:493:33:493:33 | d1 | test.swift:493:33:493:33 | SSA def(d1) | -| test.swift:493:39:493:39 | c1 | test.swift:493:13:493:35 | .mySingle(...) | -| test.swift:494:33:494:33 | SSA def(d2) | test.swift:494:54:494:54 | d2 | -| test.swift:494:33:494:33 | d2 | test.swift:494:33:494:33 | SSA def(d2) | -| test.swift:494:39:494:39 | c2 | test.swift:494:13:494:35 | .mySingle(...) | -| test.swift:495:33:495:33 | SSA def(d3) | test.swift:495:54:495:54 | d3 | -| test.swift:495:33:495:33 | d3 | test.swift:495:33:495:33 | SSA def(d3) | -| test.swift:495:39:495:39 | c3 | test.swift:495:13:495:35 | .mySingle(...) | -| test.swift:496:33:496:33 | SSA def(d4) | test.swift:496:54:496:54 | d4 | -| test.swift:496:33:496:33 | d4 | test.swift:496:33:496:33 | SSA def(d4) | -| test.swift:496:39:496:39 | c4 | test.swift:496:13:496:35 | .mySingle(...) | -| test.swift:497:33:497:33 | SSA def(d5) | test.swift:497:54:497:54 | d5 | -| test.swift:497:33:497:33 | d5 | test.swift:497:33:497:33 | SSA def(d5) | -| test.swift:497:39:497:39 | c5 | test.swift:497:13:497:35 | .mySingle(...) | -| test.swift:498:33:498:33 | SSA def(d6) | test.swift:498:54:498:54 | d6 | -| test.swift:498:33:498:33 | d6 | test.swift:498:33:498:33 | SSA def(d6) | -| test.swift:498:39:498:39 | c6 | test.swift:498:13:498:35 | .mySingle(...) | -| test.swift:500:9:500:9 | SSA def(e1) | test.swift:506:15:506:15 | e1 | -| test.swift:500:9:500:9 | e1 | test.swift:500:9:500:9 | SSA def(e1) | -| test.swift:500:14:500:29 | call to ... | test.swift:500:9:500:9 | e1 | -| test.swift:501:9:501:9 | SSA def(e2) | test.swift:507:15:507:15 | e2 | -| test.swift:501:9:501:9 | e2 | test.swift:501:9:501:9 | SSA def(e2) | -| test.swift:501:14:501:36 | call to ... | test.swift:501:9:501:9 | e2 | -| test.swift:502:9:502:9 | SSA def(e3) | test.swift:508:15:508:15 | e3 | -| test.swift:502:9:502:9 | e3 | test.swift:502:9:502:9 | SSA def(e3) | -| test.swift:502:14:502:27 | call to mkOptional1(_:) | test.swift:502:9:502:9 | e3 | -| test.swift:503:9:503:9 | SSA def(e4) | test.swift:509:15:509:15 | e4 | -| test.swift:503:9:503:9 | e4 | test.swift:503:9:503:9 | SSA def(e4) | -| test.swift:503:14:503:34 | call to mkOptional1(_:) | test.swift:503:9:503:9 | e4 | -| test.swift:504:9:504:9 | SSA def(e5) | test.swift:510:15:510:15 | e5 | -| test.swift:504:9:504:9 | e5 | test.swift:504:9:504:9 | SSA def(e5) | -| test.swift:504:14:504:27 | call to mkOptional2(_:) | test.swift:504:9:504:9 | e5 | -| test.swift:505:9:505:9 | SSA def(e6) | test.swift:511:15:511:15 | e6 | -| test.swift:505:9:505:9 | e6 | test.swift:505:9:505:9 | SSA def(e6) | -| test.swift:505:14:505:34 | call to mkOptional2(_:) | test.swift:505:9:505:9 | e6 | -| test.swift:506:15:506:15 | e1 | test.swift:506:15:506:17 | ...! | -| test.swift:507:15:507:15 | e2 | test.swift:507:15:507:17 | ...! | -| test.swift:508:15:508:15 | e3 | test.swift:508:15:508:17 | ...! | -| test.swift:509:15:509:15 | e4 | test.swift:509:15:509:17 | ...! | -| test.swift:510:15:510:15 | e5 | test.swift:510:15:510:17 | ...! | -| test.swift:511:15:511:15 | e6 | test.swift:511:15:511:17 | ...! | -| test.swift:516:21:516:27 | SSA def(y) | test.swift:519:27:519:27 | y | -| test.swift:516:21:516:27 | SSA def(y) | test.swift:524:22:524:22 | y | -| test.swift:516:21:516:27 | y | test.swift:516:21:516:27 | SSA def(y) | -| test.swift:517:9:517:9 | SSA def(x) | test.swift:519:16:519:16 | x | -| test.swift:517:9:517:9 | x | test.swift:517:9:517:9 | SSA def(x) | -| test.swift:517:13:517:28 | call to optionalSource() | test.swift:517:9:517:9 | x | -| test.swift:519:12:519:12 | SSA def(a) | test.swift:519:27:519:27 | SSA phi(a) | -| test.swift:519:12:519:12 | a | test.swift:519:12:519:12 | SSA def(a) | -| test.swift:519:16:519:16 | x | test.swift:519:8:519:12 | let ...? | -| test.swift:519:16:519:16 | x | test.swift:524:19:524:19 | x | -| test.swift:519:23:519:23 | SSA def(b) | test.swift:521:19:521:19 | b | -| test.swift:519:23:519:23 | b | test.swift:519:23:519:23 | SSA def(b) | -| test.swift:519:27:519:27 | SSA phi(a) | test.swift:520:19:520:19 | a | -| test.swift:519:27:519:27 | y | test.swift:519:19:519:23 | let ...? | -| test.swift:519:27:519:27 | y | test.swift:524:22:524:22 | y | -| test.swift:524:9:524:9 | SSA def(tuple1) | test.swift:525:12:525:12 | tuple1 | -| test.swift:524:9:524:9 | tuple1 | test.swift:524:9:524:9 | SSA def(tuple1) | -| test.swift:524:18:524:23 | (...) | test.swift:524:9:524:9 | tuple1 | -| test.swift:525:12:525:12 | tuple1 | test.swift:526:10:526:37 | (...) | -| test.swift:525:12:525:12 | tuple1 | test.swift:529:5:529:5 | _ | -| test.swift:526:21:526:21 | SSA def(a) | test.swift:527:19:527:19 | a | -| test.swift:526:21:526:21 | a | test.swift:526:21:526:21 | SSA def(a) | -| test.swift:526:35:526:35 | SSA def(b) | test.swift:528:19:528:19 | b | -| test.swift:526:35:526:35 | b | test.swift:526:35:526:35 | SSA def(b) | -| test.swift:533:13:533:13 | SSA def(x) | test.swift:534:19:534:19 | x | -| test.swift:533:13:533:13 | x | test.swift:533:13:533:13 | SSA def(x) | -| test.swift:533:16:533:16 | SSA def(y) | test.swift:535:19:535:19 | y | -| test.swift:533:16:533:16 | y | test.swift:533:16:533:16 | SSA def(y) | -| test.swift:533:21:533:29 | call to source2() | test.swift:533:8:533:17 | let ...? | -| test.swift:539:7:539:7 | SSA def(self) | test.swift:539:7:539:7 | self[return] | -| test.swift:539:7:539:7 | SSA def(self) | test.swift:539:7:539:7 | self[return] | -| test.swift:539:7:539:7 | self | test.swift:539:7:539:7 | SSA def(self) | -| test.swift:539:7:539:7 | self | test.swift:539:7:539:7 | SSA def(self) | -| test.swift:540:9:540:9 | self | test.swift:540:9:540:9 | SSA def(self) | -| test.swift:540:9:540:9 | self | test.swift:540:9:540:9 | SSA def(self) | -| test.swift:540:9:540:9 | self | test.swift:540:9:540:9 | SSA def(self) | -| test.swift:540:9:540:9 | value | test.swift:540:9:540:9 | SSA def(value) | -| test.swift:543:33:543:39 | SSA def(y) | test.swift:548:12:548:12 | y | -| test.swift:543:33:543:39 | y | test.swift:543:33:543:39 | SSA def(y) | -| test.swift:544:9:544:9 | SSA def(x) | test.swift:546:12:546:12 | x | -| test.swift:544:9:544:9 | x | test.swift:544:9:544:9 | SSA def(x) | -| test.swift:544:13:544:28 | call to optionalSource() | test.swift:544:9:544:9 | x | -| test.swift:545:9:545:9 | SSA def(cx) | test.swift:546:5:546:5 | cx | -| test.swift:545:9:545:9 | cx | test.swift:545:9:545:9 | SSA def(cx) | -| test.swift:545:14:545:16 | call to C.init() | test.swift:545:9:545:9 | cx | -| test.swift:546:5:546:5 | [post] cx | test.swift:550:20:550:20 | cx | -| test.swift:546:5:546:5 | cx | test.swift:550:20:550:20 | cx | -| test.swift:547:9:547:9 | SSA def(cy) | test.swift:548:5:548:5 | cy | -| test.swift:547:9:547:9 | cy | test.swift:547:9:547:9 | SSA def(cy) | -| test.swift:547:14:547:16 | call to C.init() | test.swift:547:9:547:9 | cy | -| test.swift:548:5:548:5 | [post] cy | test.swift:552:20:552:20 | cy | -| test.swift:548:5:548:5 | cy | test.swift:552:20:552:20 | cy | -| test.swift:550:15:550:15 | SSA def(z1) | test.swift:551:15:551:15 | z1 | -| test.swift:550:15:550:15 | z1 | test.swift:550:15:550:15 | SSA def(z1) | -| test.swift:550:20:550:23 | .x | test.swift:550:11:550:15 | let ...? | -| test.swift:552:15:552:15 | SSA def(z2) | test.swift:553:15:553:15 | z2 | -| test.swift:552:15:552:15 | z2 | test.swift:552:15:552:15 | SSA def(z2) | -| test.swift:552:20:552:23 | .x | test.swift:552:11:552:15 | let ...? | -| test.swift:557:14:557:21 | call to source() | test.swift:557:13:557:21 | call to +(_:) | -| test.swift:565:7:565:7 | SSA def(self) | test.swift:565:7:565:7 | self[return] | -| test.swift:565:7:565:7 | self | test.swift:565:7:565:7 | SSA def(self) | -| test.swift:566:9:566:9 | self | test.swift:566:9:566:9 | SSA def(self) | -| test.swift:566:9:566:9 | self | test.swift:566:9:566:9 | SSA def(self) | -| test.swift:566:9:566:9 | self | test.swift:566:9:566:9 | SSA def(self) | -| test.swift:566:9:566:9 | value | test.swift:566:9:566:9 | SSA def(value) | -| test.swift:567:5:567:5 | SSA def(self) | test.swift:568:7:568:7 | self | -| test.swift:567:5:567:5 | self | test.swift:567:5:567:5 | SSA def(self) | -| test.swift:567:10:567:13 | SSA def(s) | test.swift:568:13:568:13 | s | -| test.swift:567:10:567:13 | s | test.swift:567:10:567:13 | SSA def(s) | -| test.swift:568:7:568:7 | [post] self | test.swift:567:5:569:5 | self[return] | -| test.swift:568:7:568:7 | self | test.swift:567:5:569:5 | self[return] | -| test.swift:573:17:573:17 | SSA def(self) | test.swift:574:7:574:7 | self | -| test.swift:573:17:573:17 | self | test.swift:573:17:573:17 | SSA def(self) | -| test.swift:574:7:574:7 | [post] self | test.swift:575:17:575:17 | self | -| test.swift:574:7:574:7 | self | test.swift:575:17:575:17 | self | -| test.swift:575:17:575:17 | [post] self | test.swift:573:17:576:5 | self[return] | -| test.swift:575:17:575:17 | self | test.swift:573:17:576:5 | self[return] | -| test.swift:579:21:579:27 | SSA def(path) | test.swift:581:37:581:37 | path | -| test.swift:579:21:579:27 | path | test.swift:579:21:579:27 | SSA def(path) | +| test.swift:458:27:458:27 | SSA def(x) | test.swift:459:19:459:19 | x | +| test.swift:458:27:458:27 | x | test.swift:458:27:458:27 | SSA def(x) | +| test.swift:458:32:458:32 | a | test.swift:458:13:458:28 | .mySingle(...) | +| test.swift:458:32:458:32 | a | test.swift:461:37:461:37 | a | +| test.swift:461:25:461:25 | SSA def(x) | test.swift:462:19:462:19 | x | +| test.swift:461:25:461:25 | x | test.swift:461:25:461:25 | SSA def(x) | +| test.swift:461:32:461:32 | SSA def(y) | test.swift:463:19:463:19 | y | +| test.swift:461:32:461:32 | y | test.swift:461:32:461:32 | SSA def(y) | +| test.swift:461:37:461:37 | a | test.swift:461:13:461:33 | .myPair(...) | +| test.swift:461:37:461:37 | a | test.swift:466:33:466:33 | a | +| test.swift:466:9:466:9 | SSA def(b) | test.swift:468:12:468:12 | b | +| test.swift:466:9:466:9 | b | test.swift:466:9:466:9 | SSA def(b) | +| test.swift:466:9:466:12 | ... as ... | test.swift:466:9:466:9 | b | +| test.swift:466:21:466:34 | call to ... | test.swift:466:9:466:12 | ... as ... | +| test.swift:466:33:466:33 | [post] a | test.swift:495:13:495:13 | a | +| test.swift:466:33:466:33 | a | test.swift:495:13:495:13 | a | +| test.swift:468:12:468:12 | b | test.swift:469:10:469:11 | .myNone | +| test.swift:468:12:468:12 | b | test.swift:471:10:471:25 | .mySingle(...) | +| test.swift:468:12:468:12 | b | test.swift:473:10:473:30 | .myPair(...) | +| test.swift:468:12:468:12 | b | test.swift:476:14:476:38 | .myCons(...) | +| test.swift:468:12:468:12 | b | test.swift:480:14:480:26 | .myCons(...) | +| test.swift:468:12:468:12 | b | test.swift:491:45:491:45 | b | +| test.swift:471:24:471:24 | SSA def(a) | test.swift:472:19:472:19 | a | +| test.swift:471:24:471:24 | a | test.swift:471:24:471:24 | SSA def(a) | +| test.swift:473:22:473:22 | SSA def(a) | test.swift:474:19:474:19 | a | +| test.swift:473:22:473:22 | a | test.swift:473:22:473:22 | SSA def(a) | +| test.swift:473:29:473:29 | SSA def(b) | test.swift:475:19:475:19 | b | +| test.swift:473:29:473:29 | b | test.swift:473:29:473:29 | SSA def(b) | +| test.swift:476:10:476:38 | SSA phi(a) | test.swift:477:19:477:19 | a | +| test.swift:476:10:476:38 | SSA phi(b) | test.swift:478:19:478:19 | b | +| test.swift:476:10:476:38 | SSA phi(c) | test.swift:479:19:479:19 | c | +| test.swift:476:22:476:22 | SSA def(a) | test.swift:476:10:476:38 | SSA phi(a) | +| test.swift:476:22:476:22 | a | test.swift:476:22:476:22 | SSA def(a) | +| test.swift:476:33:476:33 | SSA def(b) | test.swift:476:10:476:38 | SSA phi(b) | +| test.swift:476:33:476:33 | b | test.swift:476:33:476:33 | SSA def(b) | +| test.swift:476:36:476:36 | SSA def(c) | test.swift:476:10:476:38 | SSA phi(c) | +| test.swift:476:36:476:36 | c | test.swift:476:36:476:36 | SSA def(c) | +| test.swift:480:10:480:26 | SSA phi(a) | test.swift:481:19:481:19 | a | +| test.swift:480:22:480:22 | SSA def(a) | test.swift:480:10:480:26 | SSA phi(a) | +| test.swift:480:22:480:22 | a | test.swift:480:22:480:22 | SSA def(a) | +| test.swift:484:27:484:27 | SSA def(x) | test.swift:485:19:485:19 | x | +| test.swift:484:27:484:27 | x | test.swift:484:27:484:27 | SSA def(x) | +| test.swift:484:32:484:57 | call to ... | test.swift:484:13:484:28 | .mySingle(...) | +| test.swift:487:31:487:31 | SSA def(x) | test.swift:488:19:488:19 | x | +| test.swift:487:31:487:31 | x | test.swift:487:31:487:31 | SSA def(x) | +| test.swift:487:38:487:38 | SSA def(y) | test.swift:489:19:489:19 | y | +| test.swift:487:38:487:38 | y | test.swift:487:38:487:38 | SSA def(y) | +| test.swift:487:43:487:62 | call to ... | test.swift:487:13:487:39 | .myPair(...) | +| test.swift:491:13:491:41 | SSA phi(c) | test.swift:492:19:492:19 | c | +| test.swift:491:39:491:39 | SSA def(c) | test.swift:491:13:491:41 | SSA phi(c) | +| test.swift:491:39:491:39 | c | test.swift:491:39:491:39 | SSA def(c) | +| test.swift:491:45:491:45 | b | test.swift:491:17:491:41 | .myCons(...) | +| test.swift:491:45:491:45 | b | test.swift:495:16:495:16 | b | +| test.swift:495:12:495:17 | (...) | test.swift:496:14:496:55 | (...) | +| test.swift:495:12:495:17 | (...) | test.swift:502:5:502:5 | _ | +| test.swift:496:10:496:55 | SSA phi(a) | test.swift:497:19:497:19 | a | +| test.swift:496:10:496:55 | SSA phi(b) | test.swift:498:19:498:19 | b | +| test.swift:496:10:496:55 | SSA phi(c) | test.swift:499:19:499:19 | c | +| test.swift:496:10:496:55 | SSA phi(d) | test.swift:500:19:500:19 | d | +| test.swift:496:10:496:55 | SSA phi(e) | test.swift:501:19:501:19 | e | +| test.swift:496:23:496:23 | SSA def(a) | test.swift:496:10:496:55 | SSA phi(a) | +| test.swift:496:23:496:23 | a | test.swift:496:23:496:23 | SSA def(a) | +| test.swift:496:26:496:26 | SSA def(b) | test.swift:496:10:496:55 | SSA phi(b) | +| test.swift:496:26:496:26 | b | test.swift:496:26:496:26 | SSA def(b) | +| test.swift:496:38:496:38 | SSA def(c) | test.swift:496:10:496:55 | SSA phi(c) | +| test.swift:496:38:496:38 | c | test.swift:496:38:496:38 | SSA def(c) | +| test.swift:496:49:496:49 | SSA def(d) | test.swift:496:10:496:55 | SSA phi(d) | +| test.swift:496:49:496:49 | d | test.swift:496:49:496:49 | SSA def(d) | +| test.swift:496:52:496:52 | SSA def(e) | test.swift:496:10:496:55 | SSA phi(e) | +| test.swift:496:52:496:52 | e | test.swift:496:52:496:52 | SSA def(e) | +| test.swift:506:9:506:9 | SSA def(c1) | test.swift:512:39:512:39 | c1 | +| test.swift:506:9:506:9 | c1 | test.swift:506:9:506:9 | SSA def(c1) | +| test.swift:506:14:506:31 | call to ... | test.swift:506:9:506:9 | c1 | +| test.swift:507:9:507:9 | SSA def(c2) | test.swift:513:39:513:39 | c2 | +| test.swift:507:9:507:9 | c2 | test.swift:507:9:507:9 | SSA def(c2) | +| test.swift:507:14:507:38 | call to ... | test.swift:507:9:507:9 | c2 | +| test.swift:508:9:508:9 | SSA def(c3) | test.swift:514:39:514:39 | c3 | +| test.swift:508:9:508:9 | c3 | test.swift:508:9:508:9 | SSA def(c3) | +| test.swift:508:14:508:25 | call to mkMyEnum1(_:) | test.swift:508:9:508:9 | c3 | +| test.swift:509:9:509:9 | SSA def(c4) | test.swift:515:39:515:39 | c4 | +| test.swift:509:9:509:9 | c4 | test.swift:509:9:509:9 | SSA def(c4) | +| test.swift:509:14:509:32 | call to mkMyEnum1(_:) | test.swift:509:9:509:9 | c4 | +| test.swift:510:9:510:9 | SSA def(c5) | test.swift:516:39:516:39 | c5 | +| test.swift:510:9:510:9 | c5 | test.swift:510:9:510:9 | SSA def(c5) | +| test.swift:510:14:510:25 | call to mkMyEnum2(_:) | test.swift:510:9:510:9 | c5 | +| test.swift:511:9:511:9 | SSA def(c6) | test.swift:517:39:517:39 | c6 | +| test.swift:511:9:511:9 | c6 | test.swift:511:9:511:9 | SSA def(c6) | +| test.swift:511:14:511:32 | call to mkMyEnum2(_:) | test.swift:511:9:511:9 | c6 | +| test.swift:512:33:512:33 | SSA def(d1) | test.swift:512:54:512:54 | d1 | +| test.swift:512:33:512:33 | d1 | test.swift:512:33:512:33 | SSA def(d1) | +| test.swift:512:39:512:39 | c1 | test.swift:512:13:512:35 | .mySingle(...) | +| test.swift:513:33:513:33 | SSA def(d2) | test.swift:513:54:513:54 | d2 | +| test.swift:513:33:513:33 | d2 | test.swift:513:33:513:33 | SSA def(d2) | +| test.swift:513:39:513:39 | c2 | test.swift:513:13:513:35 | .mySingle(...) | +| test.swift:514:33:514:33 | SSA def(d3) | test.swift:514:54:514:54 | d3 | +| test.swift:514:33:514:33 | d3 | test.swift:514:33:514:33 | SSA def(d3) | +| test.swift:514:39:514:39 | c3 | test.swift:514:13:514:35 | .mySingle(...) | +| test.swift:515:33:515:33 | SSA def(d4) | test.swift:515:54:515:54 | d4 | +| test.swift:515:33:515:33 | d4 | test.swift:515:33:515:33 | SSA def(d4) | +| test.swift:515:39:515:39 | c4 | test.swift:515:13:515:35 | .mySingle(...) | +| test.swift:516:33:516:33 | SSA def(d5) | test.swift:516:54:516:54 | d5 | +| test.swift:516:33:516:33 | d5 | test.swift:516:33:516:33 | SSA def(d5) | +| test.swift:516:39:516:39 | c5 | test.swift:516:13:516:35 | .mySingle(...) | +| test.swift:517:33:517:33 | SSA def(d6) | test.swift:517:54:517:54 | d6 | +| test.swift:517:33:517:33 | d6 | test.swift:517:33:517:33 | SSA def(d6) | +| test.swift:517:39:517:39 | c6 | test.swift:517:13:517:35 | .mySingle(...) | +| test.swift:519:9:519:9 | SSA def(e1) | test.swift:525:15:525:15 | e1 | +| test.swift:519:9:519:9 | e1 | test.swift:519:9:519:9 | SSA def(e1) | +| test.swift:519:14:519:29 | call to ... | test.swift:519:9:519:9 | e1 | +| test.swift:520:9:520:9 | SSA def(e2) | test.swift:526:15:526:15 | e2 | +| test.swift:520:9:520:9 | e2 | test.swift:520:9:520:9 | SSA def(e2) | +| test.swift:520:14:520:36 | call to ... | test.swift:520:9:520:9 | e2 | +| test.swift:521:9:521:9 | SSA def(e3) | test.swift:527:15:527:15 | e3 | +| test.swift:521:9:521:9 | e3 | test.swift:521:9:521:9 | SSA def(e3) | +| test.swift:521:14:521:27 | call to mkOptional1(_:) | test.swift:521:9:521:9 | e3 | +| test.swift:522:9:522:9 | SSA def(e4) | test.swift:528:15:528:15 | e4 | +| test.swift:522:9:522:9 | e4 | test.swift:522:9:522:9 | SSA def(e4) | +| test.swift:522:14:522:34 | call to mkOptional1(_:) | test.swift:522:9:522:9 | e4 | +| test.swift:523:9:523:9 | SSA def(e5) | test.swift:529:15:529:15 | e5 | +| test.swift:523:9:523:9 | e5 | test.swift:523:9:523:9 | SSA def(e5) | +| test.swift:523:14:523:27 | call to mkOptional2(_:) | test.swift:523:9:523:9 | e5 | +| test.swift:524:9:524:9 | SSA def(e6) | test.swift:530:15:530:15 | e6 | +| test.swift:524:9:524:9 | e6 | test.swift:524:9:524:9 | SSA def(e6) | +| test.swift:524:14:524:34 | call to mkOptional2(_:) | test.swift:524:9:524:9 | e6 | +| test.swift:525:15:525:15 | e1 | test.swift:525:15:525:17 | ...! | +| test.swift:526:15:526:15 | e2 | test.swift:526:15:526:17 | ...! | +| test.swift:527:15:527:15 | e3 | test.swift:527:15:527:17 | ...! | +| test.swift:528:15:528:15 | e4 | test.swift:528:15:528:17 | ...! | +| test.swift:529:15:529:15 | e5 | test.swift:529:15:529:17 | ...! | +| test.swift:530:15:530:15 | e6 | test.swift:530:15:530:17 | ...! | +| test.swift:535:21:535:27 | SSA def(y) | test.swift:538:27:538:27 | y | +| test.swift:535:21:535:27 | SSA def(y) | test.swift:543:22:543:22 | y | +| test.swift:535:21:535:27 | y | test.swift:535:21:535:27 | SSA def(y) | +| test.swift:536:9:536:9 | SSA def(x) | test.swift:538:16:538:16 | x | +| test.swift:536:9:536:9 | x | test.swift:536:9:536:9 | SSA def(x) | +| test.swift:536:13:536:28 | call to optionalSource() | test.swift:536:9:536:9 | x | +| test.swift:538:12:538:12 | SSA def(a) | test.swift:538:27:538:27 | SSA phi(a) | +| test.swift:538:12:538:12 | a | test.swift:538:12:538:12 | SSA def(a) | +| test.swift:538:16:538:16 | x | test.swift:538:8:538:12 | let ...? | +| test.swift:538:16:538:16 | x | test.swift:543:19:543:19 | x | +| test.swift:538:23:538:23 | SSA def(b) | test.swift:540:19:540:19 | b | +| test.swift:538:23:538:23 | b | test.swift:538:23:538:23 | SSA def(b) | +| test.swift:538:27:538:27 | SSA phi(a) | test.swift:539:19:539:19 | a | +| test.swift:538:27:538:27 | y | test.swift:538:19:538:23 | let ...? | +| test.swift:538:27:538:27 | y | test.swift:543:22:543:22 | y | +| test.swift:543:9:543:9 | SSA def(tuple1) | test.swift:544:12:544:12 | tuple1 | +| test.swift:543:9:543:9 | tuple1 | test.swift:543:9:543:9 | SSA def(tuple1) | +| test.swift:543:18:543:23 | (...) | test.swift:543:9:543:9 | tuple1 | +| test.swift:544:12:544:12 | tuple1 | test.swift:545:10:545:37 | (...) | +| test.swift:544:12:544:12 | tuple1 | test.swift:548:5:548:5 | _ | +| test.swift:545:21:545:21 | SSA def(a) | test.swift:546:19:546:19 | a | +| test.swift:545:21:545:21 | a | test.swift:545:21:545:21 | SSA def(a) | +| test.swift:545:35:545:35 | SSA def(b) | test.swift:547:19:547:19 | b | +| test.swift:545:35:545:35 | b | test.swift:545:35:545:35 | SSA def(b) | +| test.swift:552:13:552:13 | SSA def(x) | test.swift:553:19:553:19 | x | +| test.swift:552:13:552:13 | x | test.swift:552:13:552:13 | SSA def(x) | +| test.swift:552:16:552:16 | SSA def(y) | test.swift:554:19:554:19 | y | +| test.swift:552:16:552:16 | y | test.swift:552:16:552:16 | SSA def(y) | +| test.swift:552:21:552:29 | call to source2() | test.swift:552:8:552:17 | let ...? | +| test.swift:558:7:558:7 | SSA def(self) | test.swift:558:7:558:7 | self[return] | +| test.swift:558:7:558:7 | SSA def(self) | test.swift:558:7:558:7 | self[return] | +| test.swift:558:7:558:7 | self | test.swift:558:7:558:7 | SSA def(self) | +| test.swift:558:7:558:7 | self | test.swift:558:7:558:7 | SSA def(self) | +| test.swift:559:9:559:9 | self | test.swift:559:9:559:9 | SSA def(self) | +| test.swift:559:9:559:9 | self | test.swift:559:9:559:9 | SSA def(self) | +| test.swift:559:9:559:9 | self | test.swift:559:9:559:9 | SSA def(self) | +| test.swift:559:9:559:9 | value | test.swift:559:9:559:9 | SSA def(value) | +| test.swift:562:33:562:39 | SSA def(y) | test.swift:567:12:567:12 | y | +| test.swift:562:33:562:39 | y | test.swift:562:33:562:39 | SSA def(y) | +| test.swift:563:9:563:9 | SSA def(x) | test.swift:565:12:565:12 | x | +| test.swift:563:9:563:9 | x | test.swift:563:9:563:9 | SSA def(x) | +| test.swift:563:13:563:28 | call to optionalSource() | test.swift:563:9:563:9 | x | +| test.swift:564:9:564:9 | SSA def(cx) | test.swift:565:5:565:5 | cx | +| test.swift:564:9:564:9 | cx | test.swift:564:9:564:9 | SSA def(cx) | +| test.swift:564:14:564:16 | call to C.init() | test.swift:564:9:564:9 | cx | +| test.swift:565:5:565:5 | [post] cx | test.swift:569:20:569:20 | cx | +| test.swift:565:5:565:5 | cx | test.swift:569:20:569:20 | cx | +| test.swift:566:9:566:9 | SSA def(cy) | test.swift:567:5:567:5 | cy | +| test.swift:566:9:566:9 | cy | test.swift:566:9:566:9 | SSA def(cy) | +| test.swift:566:14:566:16 | call to C.init() | test.swift:566:9:566:9 | cy | +| test.swift:567:5:567:5 | [post] cy | test.swift:571:20:571:20 | cy | +| test.swift:567:5:567:5 | cy | test.swift:571:20:571:20 | cy | +| test.swift:569:15:569:15 | SSA def(z1) | test.swift:570:15:570:15 | z1 | +| test.swift:569:15:569:15 | z1 | test.swift:569:15:569:15 | SSA def(z1) | +| test.swift:569:20:569:23 | .x | test.swift:569:11:569:15 | let ...? | +| test.swift:571:15:571:15 | SSA def(z2) | test.swift:572:15:572:15 | z2 | +| test.swift:571:15:571:15 | z2 | test.swift:571:15:571:15 | SSA def(z2) | +| test.swift:571:20:571:23 | .x | test.swift:571:11:571:15 | let ...? | +| test.swift:576:14:576:21 | call to source() | test.swift:576:13:576:21 | call to +(_:) | | test.swift:584:7:584:7 | SSA def(self) | test.swift:584:7:584:7 | self[return] | | test.swift:584:7:584:7 | self | test.swift:584:7:584:7 | SSA def(self) | -| test.swift:585:3:585:3 | SSA def(self) | test.swift:585:3:585:40 | self[return] | -| test.swift:585:3:585:3 | self | test.swift:585:3:585:3 | SSA def(self) | -| test.swift:585:27:585:38 | SSA def(n) | test.swift:585:3:585:40 | n[return] | -| test.swift:585:31:585:38 | call to source() | test.swift:585:27:585:38 | SSA def(n) | -| test.swift:591:7:591:7 | SSA def(n) | test.swift:592:36:592:36 | n | -| test.swift:591:7:591:7 | n | test.swift:591:7:591:7 | SSA def(n) | -| test.swift:591:11:591:11 | 0 | test.swift:591:7:591:7 | n | -| test.swift:592:36:592:36 | [post] n | test.swift:592:35:592:36 | &... | -| test.swift:592:36:592:36 | n | test.swift:592:35:592:36 | &... | -| test.swift:596:7:596:7 | self | test.swift:596:7:596:7 | SSA def(self) | -| test.swift:598:3:598:3 | SSA def(self) | test.swift:599:5:599:5 | self | -| test.swift:598:3:598:3 | self | test.swift:598:3:598:3 | SSA def(self) | -| test.swift:598:8:598:11 | SSA def(x) | test.swift:599:14:599:14 | x | -| test.swift:598:8:598:11 | x | test.swift:598:8:598:11 | SSA def(x) | -| test.swift:599:5:599:5 | [post] self | test.swift:598:3:600:3 | self[return] | -| test.swift:599:5:599:5 | self | test.swift:598:3:600:3 | self[return] | -| test.swift:604:7:604:7 | SSA def(s) | test.swift:606:13:606:13 | s | -| test.swift:604:7:604:7 | s | test.swift:604:7:604:7 | SSA def(s) | -| test.swift:604:11:604:24 | call to S.init(x:) | test.swift:604:7:604:7 | s | -| test.swift:605:7:605:7 | SSA def(f) | test.swift:606:24:606:24 | f | -| test.swift:605:7:605:7 | f | test.swift:605:7:605:7 | SSA def(f) | -| test.swift:605:11:605:14 | #keyPath(...) | test.swift:605:7:605:7 | f | -| test.swift:605:11:605:14 | enter #keyPath(...) | test.swift:605:14:605:14 | KeyPathComponent | -| test.swift:606:13:606:13 | s | test.swift:609:13:609:13 | s | -| test.swift:608:7:608:7 | SSA def(inferred) | test.swift:609:24:609:24 | inferred | -| test.swift:608:7:608:7 | inferred | test.swift:608:7:608:7 | SSA def(inferred) | -| test.swift:608:7:608:32 | ... as ... | test.swift:608:7:608:7 | inferred | -| test.swift:608:36:608:38 | #keyPath(...) | test.swift:608:7:608:32 | ... as ... | -| test.swift:608:36:608:38 | enter #keyPath(...) | test.swift:608:38:608:38 | KeyPathComponent | -| test.swift:613:7:613:7 | self | test.swift:613:7:613:7 | SSA def(self) | -| test.swift:615:3:615:3 | SSA def(self) | test.swift:616:5:616:5 | self | -| test.swift:615:3:615:3 | self | test.swift:615:3:615:3 | SSA def(self) | -| test.swift:615:8:615:11 | SSA def(s) | test.swift:616:14:616:14 | s | -| test.swift:615:8:615:11 | s | test.swift:615:8:615:11 | SSA def(s) | -| test.swift:616:5:616:5 | [post] self | test.swift:615:3:617:3 | self[return] | -| test.swift:616:5:616:5 | self | test.swift:615:3:617:3 | self[return] | -| test.swift:621:7:621:7 | SSA def(s) | test.swift:622:18:622:18 | s | -| test.swift:621:7:621:7 | s | test.swift:621:7:621:7 | SSA def(s) | -| test.swift:621:11:621:24 | call to S.init(x:) | test.swift:621:7:621:7 | s | -| test.swift:622:7:622:7 | SSA def(s2) | test.swift:624:13:624:13 | s2 | -| test.swift:622:7:622:7 | s2 | test.swift:622:7:622:7 | SSA def(s2) | -| test.swift:622:12:622:19 | call to S2.init(s:) | test.swift:622:7:622:7 | s2 | -| test.swift:623:7:623:7 | SSA def(f) | test.swift:624:25:624:25 | f | -| test.swift:623:7:623:7 | f | test.swift:623:7:623:7 | SSA def(f) | -| test.swift:623:11:623:17 | #keyPath(...) | test.swift:623:7:623:7 | f | -| test.swift:623:11:623:17 | enter #keyPath(...) | test.swift:623:15:623:15 | KeyPathComponent | -| test.swift:628:9:628:9 | SSA def(array) | test.swift:630:15:630:15 | array | -| test.swift:628:9:628:9 | array | test.swift:628:9:628:9 | SSA def(array) | -| test.swift:628:17:628:26 | [...] | test.swift:628:9:628:9 | array | -| test.swift:629:9:629:9 | SSA def(f) | test.swift:630:30:630:30 | f | -| test.swift:629:9:629:9 | f | test.swift:629:9:629:9 | SSA def(f) | -| test.swift:629:13:629:22 | #keyPath(...) | test.swift:629:9:629:9 | f | -| test.swift:629:13:629:22 | enter #keyPath(...) | test.swift:629:20:629:22 | KeyPathComponent | -| test.swift:634:7:634:7 | self | test.swift:634:7:634:7 | SSA def(self) | -| test.swift:636:3:636:3 | SSA def(self) | test.swift:637:5:637:5 | self | -| test.swift:636:3:636:3 | self | test.swift:636:3:636:3 | SSA def(self) | -| test.swift:636:8:636:12 | SSA def(s) | test.swift:637:14:637:14 | s | -| test.swift:636:8:636:12 | s | test.swift:636:8:636:12 | SSA def(s) | -| test.swift:637:5:637:5 | [post] self | test.swift:636:3:638:3 | self[return] | -| test.swift:637:5:637:5 | self | test.swift:636:3:638:3 | self[return] | -| test.swift:642:9:642:9 | SSA def(s) | test.swift:643:29:643:29 | s | -| test.swift:642:9:642:9 | s | test.swift:642:9:642:9 | SSA def(s) | -| test.swift:642:13:642:26 | call to S.init(x:) | test.swift:642:9:642:9 | s | -| test.swift:643:9:643:9 | SSA def(s2) | test.swift:645:15:645:15 | s2 | -| test.swift:643:9:643:9 | s2 | test.swift:643:9:643:9 | SSA def(s2) | -| test.swift:643:14:643:30 | call to S2_Optional.init(s:) | test.swift:643:9:643:9 | s2 | -| test.swift:644:9:644:9 | SSA def(f) | test.swift:645:27:645:27 | f | -| test.swift:644:9:644:9 | f | test.swift:644:9:644:9 | SSA def(f) | -| test.swift:644:13:644:29 | #keyPath(...) | test.swift:644:9:644:9 | f | -| test.swift:644:13:644:29 | enter #keyPath(...) | test.swift:644:26:644:26 | KeyPathComponent | -| test.swift:649:9:649:9 | SSA def(x) | test.swift:653:9:653:9 | x | -| test.swift:649:9:649:9 | x | test.swift:649:9:649:9 | SSA def(x) | -| test.swift:649:13:649:20 | call to source() | test.swift:649:9:649:9 | x | -| test.swift:650:9:650:9 | SSA def(y) | test.swift:654:9:654:9 | y | -| test.swift:650:9:650:9 | y | test.swift:650:9:650:9 | SSA def(y) | -| test.swift:650:13:650:13 | 0 | test.swift:650:9:650:9 | y | -| test.swift:651:9:651:12 | ... as ... | test.swift:651:9:651:9 | t | -| test.swift:653:5:653:9 | SSA def(t) | test.swift:655:9:655:9 | t | -| test.swift:653:9:653:9 | x | test.swift:653:5:653:9 | SSA def(t) | -| test.swift:654:5:654:9 | SSA def(x) | test.swift:656:15:656:15 | x | -| test.swift:654:9:654:9 | y | test.swift:654:5:654:9 | SSA def(x) | -| test.swift:655:5:655:9 | SSA def(y) | test.swift:657:15:657:15 | y | -| test.swift:655:9:655:9 | t | test.swift:655:5:655:9 | SSA def(y) | -| test.swift:659:5:659:16 | SSA def(x) | test.swift:661:11:661:11 | x | -| test.swift:659:9:659:16 | call to source() | test.swift:659:5:659:16 | SSA def(x) | -| test.swift:660:5:660:9 | SSA def(y) | test.swift:661:15:661:15 | y | -| test.swift:660:9:660:9 | 0 | test.swift:660:5:660:9 | SSA def(y) | -| test.swift:661:10:661:11 | &... | test.swift:662:15:662:15 | x | -| test.swift:661:11:661:11 | [post] x | test.swift:661:10:661:11 | &... | -| test.swift:661:11:661:11 | x | test.swift:661:10:661:11 | &... | -| test.swift:661:14:661:15 | &... | test.swift:663:15:663:15 | y | -| test.swift:661:15:661:15 | [post] y | test.swift:661:14:661:15 | &... | -| test.swift:661:15:661:15 | y | test.swift:661:14:661:15 | &... | -| test.swift:667:9:667:9 | SSA def(arr1) | test.swift:668:15:668:15 | arr1 | -| test.swift:667:9:667:9 | arr1 | test.swift:667:9:667:9 | SSA def(arr1) | -| test.swift:667:16:667:22 | [...] | test.swift:667:9:667:9 | arr1 | -| test.swift:668:15:668:15 | &... | test.swift:669:5:669:5 | arr1 | -| test.swift:668:15:668:15 | [post] arr1 | test.swift:668:15:668:15 | &... | -| test.swift:668:15:668:15 | arr1 | test.swift:668:15:668:15 | &... | -| test.swift:669:5:669:5 | &... | test.swift:670:15:670:15 | arr1 | -| test.swift:669:5:669:5 | [post] arr1 | test.swift:669:5:669:5 | &... | -| test.swift:669:5:669:5 | arr1 | test.swift:669:5:669:5 | &... | -| test.swift:670:15:670:15 | &... | test.swift:671:15:671:15 | arr1 | -| test.swift:670:15:670:15 | [post] arr1 | test.swift:670:15:670:15 | &... | -| test.swift:670:15:670:15 | arr1 | test.swift:670:15:670:15 | &... | -| test.swift:673:9:673:9 | SSA def(arr2) | test.swift:674:15:674:15 | arr2 | -| test.swift:673:9:673:9 | arr2 | test.swift:673:9:673:9 | SSA def(arr2) | -| test.swift:673:16:673:25 | [...] | test.swift:673:9:673:9 | arr2 | -| test.swift:674:15:674:15 | &... | test.swift:685:16:685:16 | arr2 | -| test.swift:674:15:674:15 | [post] arr2 | test.swift:674:15:674:15 | &... | -| test.swift:674:15:674:15 | arr2 | test.swift:674:15:674:15 | &... | -| test.swift:676:9:676:9 | SSA def(matrix) | test.swift:677:15:677:15 | matrix | -| test.swift:676:9:676:9 | matrix | test.swift:676:9:676:9 | SSA def(matrix) | -| test.swift:676:18:676:29 | [...] | test.swift:676:9:676:9 | matrix | -| test.swift:677:15:677:15 | &... | test.swift:678:15:678:15 | matrix | -| test.swift:677:15:677:15 | [post] matrix | test.swift:677:15:677:15 | &... | -| test.swift:677:15:677:15 | matrix | test.swift:677:15:677:15 | &... | -| test.swift:678:15:678:15 | [post] matrix | test.swift:678:15:678:15 | &... | -| test.swift:678:15:678:15 | matrix | test.swift:678:15:678:15 | &... | -| test.swift:678:15:678:23 | ...[...] | test.swift:678:15:678:23 | &... | -| test.swift:680:9:680:9 | SSA def(matrix2) | test.swift:681:5:681:5 | matrix2 | -| test.swift:680:9:680:9 | matrix2 | test.swift:680:9:680:9 | SSA def(matrix2) | -| test.swift:680:19:680:23 | [...] | test.swift:680:9:680:9 | matrix2 | -| test.swift:681:5:681:5 | &... | test.swift:682:15:682:15 | matrix2 | -| test.swift:681:5:681:5 | [post] matrix2 | test.swift:681:5:681:5 | &... | -| test.swift:681:5:681:5 | matrix2 | test.swift:681:5:681:5 | &... | -| test.swift:681:5:681:14 | ...[...] | test.swift:681:5:681:14 | &... | -| test.swift:682:15:682:15 | [post] matrix2 | test.swift:682:15:682:15 | &... | -| test.swift:682:15:682:15 | matrix2 | test.swift:682:15:682:15 | &... | -| test.swift:682:15:682:24 | ...[...] | test.swift:682:15:682:24 | &... | -| test.swift:684:9:684:9 | SSA def(arr3) | test.swift:685:23:685:23 | arr3 | -| test.swift:684:9:684:9 | arr3 | test.swift:684:9:684:9 | SSA def(arr3) | -| test.swift:684:16:684:18 | [...] | test.swift:684:9:684:9 | arr3 | -| test.swift:685:9:685:9 | SSA def(arr4) | test.swift:687:15:687:15 | arr4 | -| test.swift:685:9:685:9 | arr4 | test.swift:685:9:685:9 | SSA def(arr4) | -| test.swift:685:16:685:23 | ... .+(_:_:) ... | test.swift:685:9:685:9 | arr4 | -| test.swift:685:23:685:23 | arr3 | test.swift:686:15:686:15 | arr3 | -| test.swift:686:15:686:15 | [post] arr3 | test.swift:686:15:686:15 | &... | -| test.swift:686:15:686:15 | arr3 | test.swift:686:15:686:15 | &... | -| test.swift:687:15:687:15 | [post] arr4 | test.swift:687:15:687:15 | &... | -| test.swift:687:15:687:15 | arr4 | test.swift:687:15:687:15 | &... | -| test.swift:689:9:689:9 | SSA def(arr5) | test.swift:690:15:690:15 | arr5 | -| test.swift:689:9:689:9 | arr5 | test.swift:689:9:689:9 | SSA def(arr5) | -| test.swift:689:16:689:51 | call to Array.init(repeating:count:) | test.swift:689:9:689:9 | arr5 | -| test.swift:690:15:690:15 | [post] arr5 | test.swift:690:15:690:15 | &... | -| test.swift:690:15:690:15 | arr5 | test.swift:690:15:690:15 | &... | -| test.swift:692:9:692:9 | SSA def(arr6) | test.swift:693:5:693:5 | arr6 | -| test.swift:692:9:692:9 | arr6 | test.swift:692:9:692:9 | SSA def(arr6) | -| test.swift:692:16:692:22 | [...] | test.swift:692:9:692:9 | arr6 | -| test.swift:693:5:693:5 | &... | test.swift:694:15:694:15 | arr6 | -| test.swift:693:5:693:5 | [post] arr6 | test.swift:693:5:693:5 | &... | -| test.swift:693:5:693:5 | arr6 | test.swift:693:5:693:5 | &... | -| test.swift:694:15:694:15 | [post] arr6 | test.swift:694:15:694:15 | &... | -| test.swift:694:15:694:15 | arr6 | test.swift:694:15:694:15 | &... | +| test.swift:585:9:585:9 | self | test.swift:585:9:585:9 | SSA def(self) | +| test.swift:585:9:585:9 | self | test.swift:585:9:585:9 | SSA def(self) | +| test.swift:585:9:585:9 | self | test.swift:585:9:585:9 | SSA def(self) | +| test.swift:585:9:585:9 | value | test.swift:585:9:585:9 | SSA def(value) | +| test.swift:586:5:586:5 | SSA def(self) | test.swift:587:7:587:7 | self | +| test.swift:586:5:586:5 | self | test.swift:586:5:586:5 | SSA def(self) | +| test.swift:586:10:586:13 | SSA def(s) | test.swift:587:13:587:13 | s | +| test.swift:586:10:586:13 | s | test.swift:586:10:586:13 | SSA def(s) | +| test.swift:587:7:587:7 | [post] self | test.swift:586:5:588:5 | self[return] | +| test.swift:587:7:587:7 | self | test.swift:586:5:588:5 | self[return] | +| test.swift:592:17:592:17 | SSA def(self) | test.swift:593:7:593:7 | self | +| test.swift:592:17:592:17 | self | test.swift:592:17:592:17 | SSA def(self) | +| test.swift:593:7:593:7 | [post] self | test.swift:594:17:594:17 | self | +| test.swift:593:7:593:7 | self | test.swift:594:17:594:17 | self | +| test.swift:594:17:594:17 | [post] self | test.swift:592:17:595:5 | self[return] | +| test.swift:594:17:594:17 | self | test.swift:592:17:595:5 | self[return] | +| test.swift:598:21:598:27 | SSA def(path) | test.swift:600:37:600:37 | path | +| test.swift:598:21:598:27 | path | test.swift:598:21:598:27 | SSA def(path) | +| test.swift:603:7:603:7 | SSA def(self) | test.swift:603:7:603:7 | self[return] | +| test.swift:603:7:603:7 | self | test.swift:603:7:603:7 | SSA def(self) | +| test.swift:604:3:604:3 | SSA def(self) | test.swift:604:3:604:40 | self[return] | +| test.swift:604:3:604:3 | self | test.swift:604:3:604:3 | SSA def(self) | +| test.swift:604:27:604:38 | SSA def(n) | test.swift:604:3:604:40 | n[return] | +| test.swift:604:31:604:38 | call to source() | test.swift:604:27:604:38 | SSA def(n) | +| test.swift:610:7:610:7 | SSA def(n) | test.swift:611:36:611:36 | n | +| test.swift:610:7:610:7 | n | test.swift:610:7:610:7 | SSA def(n) | +| test.swift:610:11:610:11 | 0 | test.swift:610:7:610:7 | n | +| test.swift:611:36:611:36 | [post] n | test.swift:611:35:611:36 | &... | +| test.swift:611:36:611:36 | n | test.swift:611:35:611:36 | &... | +| test.swift:615:7:615:7 | self | test.swift:615:7:615:7 | SSA def(self) | +| test.swift:617:3:617:3 | SSA def(self) | test.swift:618:5:618:5 | self | +| test.swift:617:3:617:3 | self | test.swift:617:3:617:3 | SSA def(self) | +| test.swift:617:8:617:11 | SSA def(x) | test.swift:618:14:618:14 | x | +| test.swift:617:8:617:11 | x | test.swift:617:8:617:11 | SSA def(x) | +| test.swift:618:5:618:5 | [post] self | test.swift:617:3:619:3 | self[return] | +| test.swift:618:5:618:5 | self | test.swift:617:3:619:3 | self[return] | +| test.swift:623:7:623:7 | SSA def(s) | test.swift:625:13:625:13 | s | +| test.swift:623:7:623:7 | s | test.swift:623:7:623:7 | SSA def(s) | +| test.swift:623:11:623:24 | call to S.init(x:) | test.swift:623:7:623:7 | s | +| test.swift:624:7:624:7 | SSA def(f) | test.swift:625:24:625:24 | f | +| test.swift:624:7:624:7 | f | test.swift:624:7:624:7 | SSA def(f) | +| test.swift:624:11:624:14 | #keyPath(...) | test.swift:624:7:624:7 | f | +| test.swift:624:11:624:14 | enter #keyPath(...) | test.swift:624:14:624:14 | KeyPathComponent | +| test.swift:625:13:625:13 | s | test.swift:628:13:628:13 | s | +| test.swift:627:7:627:7 | SSA def(inferred) | test.swift:628:24:628:24 | inferred | +| test.swift:627:7:627:7 | inferred | test.swift:627:7:627:7 | SSA def(inferred) | +| test.swift:627:7:627:32 | ... as ... | test.swift:627:7:627:7 | inferred | +| test.swift:627:36:627:38 | #keyPath(...) | test.swift:627:7:627:32 | ... as ... | +| test.swift:627:36:627:38 | enter #keyPath(...) | test.swift:627:38:627:38 | KeyPathComponent | +| test.swift:632:7:632:7 | self | test.swift:632:7:632:7 | SSA def(self) | +| test.swift:634:3:634:3 | SSA def(self) | test.swift:635:5:635:5 | self | +| test.swift:634:3:634:3 | self | test.swift:634:3:634:3 | SSA def(self) | +| test.swift:634:8:634:11 | SSA def(s) | test.swift:635:14:635:14 | s | +| test.swift:634:8:634:11 | s | test.swift:634:8:634:11 | SSA def(s) | +| test.swift:635:5:635:5 | [post] self | test.swift:634:3:636:3 | self[return] | +| test.swift:635:5:635:5 | self | test.swift:634:3:636:3 | self[return] | +| test.swift:640:7:640:7 | SSA def(s) | test.swift:641:18:641:18 | s | +| test.swift:640:7:640:7 | s | test.swift:640:7:640:7 | SSA def(s) | +| test.swift:640:11:640:24 | call to S.init(x:) | test.swift:640:7:640:7 | s | +| test.swift:641:7:641:7 | SSA def(s2) | test.swift:643:13:643:13 | s2 | +| test.swift:641:7:641:7 | s2 | test.swift:641:7:641:7 | SSA def(s2) | +| test.swift:641:12:641:19 | call to S2.init(s:) | test.swift:641:7:641:7 | s2 | +| test.swift:642:7:642:7 | SSA def(f) | test.swift:643:25:643:25 | f | +| test.swift:642:7:642:7 | f | test.swift:642:7:642:7 | SSA def(f) | +| test.swift:642:11:642:17 | #keyPath(...) | test.swift:642:7:642:7 | f | +| test.swift:642:11:642:17 | enter #keyPath(...) | test.swift:642:15:642:15 | KeyPathComponent | +| test.swift:647:9:647:9 | SSA def(array) | test.swift:649:15:649:15 | array | +| test.swift:647:9:647:9 | array | test.swift:647:9:647:9 | SSA def(array) | +| test.swift:647:17:647:26 | [...] | test.swift:647:9:647:9 | array | +| test.swift:648:9:648:9 | SSA def(f) | test.swift:649:30:649:30 | f | +| test.swift:648:9:648:9 | f | test.swift:648:9:648:9 | SSA def(f) | +| test.swift:648:13:648:22 | #keyPath(...) | test.swift:648:9:648:9 | f | +| test.swift:648:13:648:22 | enter #keyPath(...) | test.swift:648:20:648:22 | KeyPathComponent | +| test.swift:653:7:653:7 | self | test.swift:653:7:653:7 | SSA def(self) | +| test.swift:655:3:655:3 | SSA def(self) | test.swift:656:5:656:5 | self | +| test.swift:655:3:655:3 | self | test.swift:655:3:655:3 | SSA def(self) | +| test.swift:655:8:655:12 | SSA def(s) | test.swift:656:14:656:14 | s | +| test.swift:655:8:655:12 | s | test.swift:655:8:655:12 | SSA def(s) | +| test.swift:656:5:656:5 | [post] self | test.swift:655:3:657:3 | self[return] | +| test.swift:656:5:656:5 | self | test.swift:655:3:657:3 | self[return] | +| test.swift:661:9:661:9 | SSA def(s) | test.swift:662:29:662:29 | s | +| test.swift:661:9:661:9 | s | test.swift:661:9:661:9 | SSA def(s) | +| test.swift:661:13:661:26 | call to S.init(x:) | test.swift:661:9:661:9 | s | +| test.swift:662:9:662:9 | SSA def(s2) | test.swift:664:15:664:15 | s2 | +| test.swift:662:9:662:9 | s2 | test.swift:662:9:662:9 | SSA def(s2) | +| test.swift:662:14:662:30 | call to S2_Optional.init(s:) | test.swift:662:9:662:9 | s2 | +| test.swift:663:9:663:9 | SSA def(f) | test.swift:664:27:664:27 | f | +| test.swift:663:9:663:9 | f | test.swift:663:9:663:9 | SSA def(f) | +| test.swift:663:13:663:29 | #keyPath(...) | test.swift:663:9:663:9 | f | +| test.swift:663:13:663:29 | enter #keyPath(...) | test.swift:663:26:663:26 | KeyPathComponent | +| test.swift:668:9:668:9 | SSA def(x) | test.swift:672:9:672:9 | x | +| test.swift:668:9:668:9 | x | test.swift:668:9:668:9 | SSA def(x) | +| test.swift:668:13:668:20 | call to source() | test.swift:668:9:668:9 | x | +| test.swift:669:9:669:9 | SSA def(y) | test.swift:673:9:673:9 | y | +| test.swift:669:9:669:9 | y | test.swift:669:9:669:9 | SSA def(y) | +| test.swift:669:13:669:13 | 0 | test.swift:669:9:669:9 | y | +| test.swift:670:9:670:12 | ... as ... | test.swift:670:9:670:9 | t | +| test.swift:672:5:672:9 | SSA def(t) | test.swift:674:9:674:9 | t | +| test.swift:672:9:672:9 | x | test.swift:672:5:672:9 | SSA def(t) | +| test.swift:673:5:673:9 | SSA def(x) | test.swift:675:15:675:15 | x | +| test.swift:673:9:673:9 | y | test.swift:673:5:673:9 | SSA def(x) | +| test.swift:674:5:674:9 | SSA def(y) | test.swift:676:15:676:15 | y | +| test.swift:674:9:674:9 | t | test.swift:674:5:674:9 | SSA def(y) | +| test.swift:678:5:678:16 | SSA def(x) | test.swift:680:11:680:11 | x | +| test.swift:678:9:678:16 | call to source() | test.swift:678:5:678:16 | SSA def(x) | +| test.swift:679:5:679:9 | SSA def(y) | test.swift:680:15:680:15 | y | +| test.swift:679:9:679:9 | 0 | test.swift:679:5:679:9 | SSA def(y) | +| test.swift:680:10:680:11 | &... | test.swift:681:15:681:15 | x | +| test.swift:680:11:680:11 | [post] x | test.swift:680:10:680:11 | &... | +| test.swift:680:11:680:11 | x | test.swift:680:10:680:11 | &... | +| test.swift:680:14:680:15 | &... | test.swift:682:15:682:15 | y | +| test.swift:680:15:680:15 | [post] y | test.swift:680:14:680:15 | &... | +| test.swift:680:15:680:15 | y | test.swift:680:14:680:15 | &... | +| test.swift:686:9:686:9 | SSA def(arr1) | test.swift:687:15:687:15 | arr1 | +| test.swift:686:9:686:9 | arr1 | test.swift:686:9:686:9 | SSA def(arr1) | +| test.swift:686:16:686:22 | [...] | test.swift:686:9:686:9 | arr1 | +| test.swift:687:15:687:15 | &... | test.swift:688:5:688:5 | arr1 | +| test.swift:687:15:687:15 | [post] arr1 | test.swift:687:15:687:15 | &... | +| test.swift:687:15:687:15 | arr1 | test.swift:687:15:687:15 | &... | +| test.swift:688:5:688:5 | &... | test.swift:689:15:689:15 | arr1 | +| test.swift:688:5:688:5 | [post] arr1 | test.swift:688:5:688:5 | &... | +| test.swift:688:5:688:5 | arr1 | test.swift:688:5:688:5 | &... | +| test.swift:689:15:689:15 | &... | test.swift:690:15:690:15 | arr1 | +| test.swift:689:15:689:15 | [post] arr1 | test.swift:689:15:689:15 | &... | +| test.swift:689:15:689:15 | arr1 | test.swift:689:15:689:15 | &... | +| test.swift:692:9:692:9 | SSA def(arr2) | test.swift:693:15:693:15 | arr2 | +| test.swift:692:9:692:9 | arr2 | test.swift:692:9:692:9 | SSA def(arr2) | +| test.swift:692:16:692:25 | [...] | test.swift:692:9:692:9 | arr2 | +| test.swift:693:15:693:15 | &... | test.swift:704:16:704:16 | arr2 | +| test.swift:693:15:693:15 | [post] arr2 | test.swift:693:15:693:15 | &... | +| test.swift:693:15:693:15 | arr2 | test.swift:693:15:693:15 | &... | +| test.swift:695:9:695:9 | SSA def(matrix) | test.swift:696:15:696:15 | matrix | +| test.swift:695:9:695:9 | matrix | test.swift:695:9:695:9 | SSA def(matrix) | +| test.swift:695:18:695:29 | [...] | test.swift:695:9:695:9 | matrix | +| test.swift:696:15:696:15 | &... | test.swift:697:15:697:15 | matrix | +| test.swift:696:15:696:15 | [post] matrix | test.swift:696:15:696:15 | &... | +| test.swift:696:15:696:15 | matrix | test.swift:696:15:696:15 | &... | +| test.swift:697:15:697:15 | [post] matrix | test.swift:697:15:697:15 | &... | +| test.swift:697:15:697:15 | matrix | test.swift:697:15:697:15 | &... | +| test.swift:697:15:697:23 | ...[...] | test.swift:697:15:697:23 | &... | +| test.swift:699:9:699:9 | SSA def(matrix2) | test.swift:700:5:700:5 | matrix2 | +| test.swift:699:9:699:9 | matrix2 | test.swift:699:9:699:9 | SSA def(matrix2) | +| test.swift:699:19:699:23 | [...] | test.swift:699:9:699:9 | matrix2 | +| test.swift:700:5:700:5 | &... | test.swift:701:15:701:15 | matrix2 | +| test.swift:700:5:700:5 | [post] matrix2 | test.swift:700:5:700:5 | &... | +| test.swift:700:5:700:5 | matrix2 | test.swift:700:5:700:5 | &... | +| test.swift:700:5:700:14 | ...[...] | test.swift:700:5:700:14 | &... | +| test.swift:701:15:701:15 | [post] matrix2 | test.swift:701:15:701:15 | &... | +| test.swift:701:15:701:15 | matrix2 | test.swift:701:15:701:15 | &... | +| test.swift:701:15:701:24 | ...[...] | test.swift:701:15:701:24 | &... | +| test.swift:703:9:703:9 | SSA def(arr3) | test.swift:704:23:704:23 | arr3 | +| test.swift:703:9:703:9 | arr3 | test.swift:703:9:703:9 | SSA def(arr3) | +| test.swift:703:16:703:18 | [...] | test.swift:703:9:703:9 | arr3 | +| test.swift:704:9:704:9 | SSA def(arr4) | test.swift:706:15:706:15 | arr4 | +| test.swift:704:9:704:9 | arr4 | test.swift:704:9:704:9 | SSA def(arr4) | +| test.swift:704:16:704:23 | ... .+(_:_:) ... | test.swift:704:9:704:9 | arr4 | +| test.swift:704:23:704:23 | arr3 | test.swift:705:15:705:15 | arr3 | +| test.swift:705:15:705:15 | [post] arr3 | test.swift:705:15:705:15 | &... | +| test.swift:705:15:705:15 | arr3 | test.swift:705:15:705:15 | &... | +| test.swift:706:15:706:15 | [post] arr4 | test.swift:706:15:706:15 | &... | +| test.swift:706:15:706:15 | arr4 | test.swift:706:15:706:15 | &... | +| test.swift:708:9:708:9 | SSA def(arr5) | test.swift:709:15:709:15 | arr5 | +| test.swift:708:9:708:9 | arr5 | test.swift:708:9:708:9 | SSA def(arr5) | +| test.swift:708:16:708:51 | call to Array.init(repeating:count:) | test.swift:708:9:708:9 | arr5 | +| test.swift:709:15:709:15 | [post] arr5 | test.swift:709:15:709:15 | &... | +| test.swift:709:15:709:15 | arr5 | test.swift:709:15:709:15 | &... | +| test.swift:711:9:711:9 | SSA def(arr6) | test.swift:712:5:712:5 | arr6 | +| test.swift:711:9:711:9 | arr6 | test.swift:711:9:711:9 | SSA def(arr6) | +| test.swift:711:16:711:22 | [...] | test.swift:711:9:711:9 | arr6 | +| test.swift:712:5:712:5 | &... | test.swift:713:15:713:15 | arr6 | +| test.swift:712:5:712:5 | [post] arr6 | test.swift:712:5:712:5 | &... | +| test.swift:712:5:712:5 | arr6 | test.swift:712:5:712:5 | &... | +| test.swift:713:15:713:15 | [post] arr6 | test.swift:713:15:713:15 | &... | +| test.swift:713:15:713:15 | arr6 | test.swift:713:15:713:15 | &... | +| test.swift:715:9:715:9 | SSA def(arr7) | test.swift:716:15:716:15 | arr7 | +| test.swift:715:9:715:9 | arr7 | test.swift:715:9:715:9 | SSA def(arr7) | +| test.swift:715:16:715:25 | [...] | test.swift:715:9:715:9 | arr7 | +| test.swift:716:15:716:34 | call to randomElement() | test.swift:716:15:716:35 | ...! | +| test.swift:720:9:720:9 | SSA def(set1) | test.swift:721:15:721:15 | set1 | +| test.swift:720:9:720:9 | set1 | test.swift:720:9:720:9 | SSA def(set1) | +| test.swift:720:9:720:15 | ... as ... | test.swift:720:9:720:9 | set1 | +| test.swift:720:21:720:27 | [...] | test.swift:720:9:720:15 | ... as ... | +| test.swift:721:15:721:15 | [post] set1 | test.swift:722:5:722:5 | set1 | +| test.swift:721:15:721:15 | set1 | test.swift:722:5:722:5 | set1 | +| test.swift:721:15:721:34 | call to randomElement() | test.swift:721:15:721:35 | ...! | +| test.swift:722:5:722:5 | &... | test.swift:723:15:723:15 | set1 | +| test.swift:722:5:722:5 | [post] set1 | test.swift:722:5:722:5 | &... | +| test.swift:722:5:722:5 | set1 | test.swift:722:5:722:5 | &... | +| test.swift:723:15:723:34 | call to randomElement() | test.swift:723:15:723:35 | ...! | +| test.swift:725:9:725:9 | SSA def(set2) | test.swift:726:15:726:15 | set2 | +| test.swift:725:9:725:9 | set2 | test.swift:725:9:725:9 | SSA def(set2) | +| test.swift:725:16:725:30 | call to Set.init(_:) | test.swift:725:9:725:9 | set2 | +| test.swift:726:15:726:34 | call to randomElement() | test.swift:726:15:726:35 | ...! | +| test.swift:729:8:729:8 | SSA def(self) | test.swift:729:8:729:8 | self[return] | +| test.swift:729:8:729:8 | self | test.swift:729:8:729:8 | SSA def(self) | +| test.swift:730:9:730:9 | self | test.swift:730:9:730:9 | SSA def(self) | +| test.swift:730:9:730:9 | self | test.swift:730:9:730:9 | SSA def(self) | +| test.swift:730:9:730:9 | self | test.swift:730:9:730:9 | SSA def(self) | +| test.swift:730:9:730:9 | value | test.swift:730:9:730:9 | SSA def(value) | +| test.swift:731:9:731:9 | self | test.swift:731:9:731:9 | SSA def(self) | +| test.swift:731:9:731:9 | self | test.swift:731:9:731:9 | SSA def(self) | +| test.swift:731:9:731:9 | self | test.swift:731:9:731:9 | SSA def(self) | +| test.swift:731:9:731:9 | value | test.swift:731:9:731:9 | SSA def(value) | +| test.swift:732:9:732:9 | self | test.swift:732:9:732:9 | SSA def(self) | +| test.swift:732:9:732:9 | self | test.swift:732:9:732:9 | SSA def(self) | +| test.swift:732:9:732:9 | self | test.swift:732:9:732:9 | SSA def(self) | +| test.swift:732:9:732:9 | value | test.swift:732:9:732:9 | SSA def(value) | +| test.swift:736:9:736:9 | SSA def(v1) | test.swift:742:5:742:5 | v1 | +| test.swift:736:9:736:9 | v1 | test.swift:736:9:736:9 | SSA def(v1) | +| test.swift:736:9:736:17 | ... as ... | test.swift:736:9:736:9 | v1 | +| test.swift:736:21:736:21 | 0 | test.swift:736:9:736:17 | ... as ... | +| test.swift:737:9:737:17 | ... as ... | test.swift:737:9:737:9 | v2 | +| test.swift:737:21:737:21 | 0 | test.swift:737:9:737:17 | ... as ... | +| test.swift:738:9:738:17 | ... as ... | test.swift:738:9:738:9 | v3 | +| test.swift:738:21:738:21 | 0 | test.swift:738:9:738:17 | ... as ... | +| test.swift:739:9:739:9 | SSA def(mo1) | test.swift:745:5:745:5 | mo1 | +| test.swift:739:9:739:9 | mo1 | test.swift:739:9:739:9 | SSA def(mo1) | +| test.swift:739:15:739:27 | call to MyOptionals.init() | test.swift:739:9:739:9 | mo1 | +| test.swift:740:9:740:9 | SSA def(mo2) | test.swift:748:5:748:5 | mo2 | +| test.swift:740:9:740:9 | mo2 | test.swift:740:9:740:9 | SSA def(mo2) | +| test.swift:740:9:740:26 | ... as ... | test.swift:740:9:740:9 | mo2 | +| test.swift:740:30:740:42 | call to MyOptionals.init() | test.swift:740:9:740:26 | ... as ... | +| test.swift:742:5:742:5 | v1 | test.swift:742:5:742:7 | ...! | +| test.swift:742:5:742:5 | v1 | test.swift:752:15:752:15 | v1 | +| test.swift:743:5:743:17 | SSA def(v2) | test.swift:753:15:753:15 | v2 | +| test.swift:743:10:743:17 | call to source() | test.swift:743:5:743:17 | SSA def(v2) | +| test.swift:744:5:744:17 | SSA def(v3) | test.swift:754:15:754:15 | v3 | +| test.swift:744:10:744:17 | call to source() | test.swift:744:5:744:17 | SSA def(v3) | +| test.swift:745:5:745:5 | [post] mo1 | test.swift:746:5:746:5 | mo1 | +| test.swift:745:5:745:5 | mo1 | test.swift:746:5:746:5 | mo1 | +| test.swift:745:5:745:9 | .v1 | test.swift:745:5:745:11 | ...! | +| test.swift:746:5:746:5 | [post] mo1 | test.swift:747:5:747:5 | mo1 | +| test.swift:746:5:746:5 | mo1 | test.swift:747:5:747:5 | mo1 | +| test.swift:747:5:747:5 | [post] mo1 | test.swift:755:15:755:15 | mo1 | +| test.swift:747:5:747:5 | mo1 | test.swift:755:15:755:15 | mo1 | +| test.swift:748:5:748:5 | mo2 | test.swift:748:5:748:8 | ...! | +| test.swift:748:5:748:5 | mo2 | test.swift:749:5:749:5 | mo2 | +| test.swift:748:5:748:10 | .v1 | test.swift:748:5:748:12 | ...! | +| test.swift:749:5:749:5 | mo2 | test.swift:749:5:749:8 | ...! | +| test.swift:749:5:749:5 | mo2 | test.swift:750:5:750:5 | mo2 | +| test.swift:750:5:750:5 | mo2 | test.swift:750:5:750:8 | ...! | +| test.swift:750:5:750:5 | mo2 | test.swift:758:15:758:15 | mo2 | +| test.swift:752:15:752:15 | v1 | test.swift:752:15:752:17 | ...! | +| test.swift:753:15:753:15 | v2 | test.swift:753:15:753:17 | ...! | +| test.swift:755:15:755:15 | [post] mo1 | test.swift:756:15:756:15 | mo1 | +| test.swift:755:15:755:15 | mo1 | test.swift:756:15:756:15 | mo1 | +| test.swift:755:15:755:19 | .v1 | test.swift:755:15:755:21 | ...! | +| test.swift:756:15:756:15 | [post] mo1 | test.swift:757:15:757:15 | mo1 | +| test.swift:756:15:756:15 | mo1 | test.swift:757:15:757:15 | mo1 | +| test.swift:756:15:756:19 | .v2 | test.swift:756:15:756:21 | ...! | +| test.swift:758:15:758:15 | mo2 | test.swift:758:15:758:18 | ...! | +| test.swift:758:15:758:15 | mo2 | test.swift:759:15:759:15 | mo2 | +| test.swift:758:15:758:20 | .v1 | test.swift:758:15:758:22 | ...! | +| test.swift:759:15:759:15 | mo2 | test.swift:759:15:759:18 | ...! | +| test.swift:759:15:759:15 | mo2 | test.swift:760:15:760:15 | mo2 | +| test.swift:759:15:759:20 | .v2 | test.swift:759:15:759:22 | ...! | +| test.swift:760:15:760:15 | mo2 | test.swift:760:15:760:18 | ...! | diff --git a/swift/ql/test/library-tests/dataflow/dataflow/test.swift b/swift/ql/test/library-tests/dataflow/dataflow/test.swift index e789964a54f..44001572dbd 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/test.swift +++ b/swift/ql/test/library-tests/dataflow/dataflow/test.swift @@ -365,6 +365,25 @@ func testTuples2() { sink(arg: c) } +func tupleShiftLeft1(_ t: (Int, Int)) -> (Int, Int) { + return (t.1, 0) +} + +func tupleShiftLeft2(_ t: (Int, Int)) -> (Int, Int) { return (0, 0) } // modelled flow + +func testTuples3() { + let t1 = (1, source()) + let t2 = tupleShiftLeft1(t1) + let t3 = tupleShiftLeft2(t1) + + sink(arg: t1.0) + sink(arg: t1.1) // $ flow=375 + sink(arg: t2.0) // $ flow=375 + sink(arg: t2.1) + sink(arg: t3.0) // $ flow=375 + sink(arg: t3.1) +} + indirect enum MyEnum { case myNone case mySingle(Int) @@ -406,7 +425,7 @@ func testEnums() { case .myNone: () case .mySingle(let a): - sink(arg: a) // $ flow=403 + sink(arg: a) // $ flow=422 case .myPair(let a, let b): sink(arg: a) sink(arg: b) @@ -415,7 +434,7 @@ func testEnums() { } if case .mySingle(let x) = a { - sink(arg: x) // $ flow=403 + sink(arg: x) // $ flow=422 } if case .myPair(let x, let y) = a { sink(arg: x) @@ -431,7 +450,7 @@ func testEnums() { sink(arg: a) case .myPair(let a, let b): sink(arg: a) - sink(arg: b) // $ flow=425 + sink(arg: b) // $ flow=444 case let .myCons(a, _): sink(arg: a) } @@ -441,7 +460,7 @@ func testEnums() { } if case .myPair(let x, let y) = a { sink(arg: x) - sink(arg: y) // $ flow=425 + sink(arg: y) // $ flow=444 } let b: MyEnum = .myCons(42, a) @@ -457,7 +476,7 @@ func testEnums() { case let .myCons(a, .myPair(b, c)): sink(arg: a) sink(arg: b) - sink(arg: c) // $ flow=425 + sink(arg: c) // $ flow=444 case let .myCons(a, _): sink(arg: a) } @@ -466,20 +485,20 @@ func testEnums() { sink(arg: x) } if case MyEnum.myPair(let x, let y) = .myPair(source(), 0) { - sink(arg: x) // $ flow=468 + sink(arg: x) // $ flow=487 sink(arg: y) } if case let .myCons(_, .myPair(_, c)) = b { - sink(arg: c) // $ flow=425 + sink(arg: c) // $ flow=444 } switch (a, b) { case let (.myPair(a, b), .myCons(c, .myPair(d, e))): sink(arg: a) - sink(arg: b) // $ flow=425 + sink(arg: b) // $ flow=444 sink(arg: c) sink(arg: d) - sink(arg: e) // $ flow=425 + sink(arg: e) // $ flow=444 default: () } @@ -491,11 +510,11 @@ func testEnums() { let c5 = mkMyEnum2(0) let c6 = mkMyEnum2(source()) if case MyEnum.mySingle(let d1) = c1 { sink(arg: d1) } - if case MyEnum.mySingle(let d2) = c2 { sink(arg: d2) } // $ flow=488 + if case MyEnum.mySingle(let d2) = c2 { sink(arg: d2) } // $ flow=507 if case MyEnum.mySingle(let d3) = c3 { sink(arg: d3) } - if case MyEnum.mySingle(let d4) = c4 { sink(arg: d4) } // $ flow=490 + if case MyEnum.mySingle(let d4) = c4 { sink(arg: d4) } // $ flow=509 if case MyEnum.mySingle(let d5) = c5 { sink(arg: d5) } - if case MyEnum.mySingle(let d6) = c6 { sink(arg: d6) } // $ flow=492 + if case MyEnum.mySingle(let d6) = c6 { sink(arg: d6) } // $ flow=511 let e1 = Optional.some(0) let e2 = Optional.some(source()) @@ -504,11 +523,11 @@ func testEnums() { let e5 = mkOptional2(0) let e6 = mkOptional2(source()) sink(arg: e1!) - sink(arg: e2!) // $ flow=501 + sink(arg: e2!) // $ flow=520 sink(arg: e3!) - sink(arg: e4!) // $ flow=503 + sink(arg: e4!) // $ flow=522 sink(arg: e5!) - sink(arg: e6!) // $ flow=505 + sink(arg: e6!) // $ flow=524 } func source2() -> (Int, Int)? { return nil } @@ -554,8 +573,8 @@ func testOptionalPropertyAccess(y: Int?) { } func testIdentityArithmetic() { - sink(arg: +source()) // $ flow=557 - sink(arg: (source())) // $ flow=558 + sink(arg: +source()) // $ flow=576 + sink(arg: (source())) // $ flow=577 } func sink(str: String) {} @@ -572,13 +591,13 @@ class MyClass { extension MyClass { convenience init(contentsOfFile: String) { self.init(s: source3()) - sink(str: str) // $ flow=574 + sink(str: str) // $ flow=593 } } func extensionInits(path: String) { - sink(str: MyClass(s: source3()).str) // $ flow=580 - sink(str: MyClass(contentsOfFile: path).str) // $ flow=574 + sink(str: MyClass(s: source3()).str) // $ flow=599 + sink(str: MyClass(contentsOfFile: path).str) // $ flow=593 } class InoutConstructorClass { @@ -603,10 +622,10 @@ struct S { func testKeyPath() { let s = S(x: source()) let f = \S.x - sink(arg: s[keyPath: f]) // $ flow=604 + sink(arg: s[keyPath: f]) // $ flow=623 let inferred : KeyPath = \.x - sink(arg: s[keyPath: inferred]) // $ flow=604 + sink(arg: s[keyPath: inferred]) // $ flow=623 } struct S2 { @@ -621,13 +640,13 @@ func testNestedKeyPath() { let s = S(x: source()) let s2 = S2(s: s) let f = \S2.s.x - sink(arg: s2[keyPath: f]) // $ flow=621 + sink(arg: s2[keyPath: f]) // $ flow=640 } func testArrayKeyPath() { let array = [source()] let f = \[Int].[0] - sink(arg: array[keyPath: f]) // $ flow=628 + sink(arg: array[keyPath: f]) // $ flow=647 } struct S2_Optional { @@ -642,7 +661,7 @@ func testOptionalKeyPath() { let s = S(x: source()) let s2 = S2_Optional(s: s) let f = \S2_Optional.s?.x - sink(opt: s2[keyPath: f]) // $ MISSING: flow=642 + sink(opt: s2[keyPath: f]) // $ MISSING: flow=661 } func testSwap() { @@ -654,42 +673,89 @@ func testSwap() { x = y y = t sink(arg: x) - sink(arg: y) // $ flow=649 + sink(arg: y) // $ flow=668 x = source() y = 0 swap(&x, &y) - sink(arg: x) // $ SPURIOUS: flow=659 - sink(arg: y) // $ flow=659 + sink(arg: x) // $ SPURIOUS: flow=678 + sink(arg: y) // $ flow=678 } func testArray() { var arr1 = [1,2,3] sink(arg: arr1[0]) arr1[1] = source() - sink(arg: arr1[0]) // $ flow=669 + sink(arg: arr1[0]) // $ flow=688 sink(arg: arr1) var arr2 = [source()] - sink(arg: arr2[0]) // $ flow=673 + sink(arg: arr2[0]) // $ flow=692 var matrix = [[source()]] sink(arg: matrix[0]) - sink(arg: matrix[0][0]) // $ flow=676 + sink(arg: matrix[0][0]) // $ flow=695 var matrix2 = [[1]] matrix2[0][0] = source() - sink(arg: matrix2[0][0]) // $ flow=681 + sink(arg: matrix2[0][0]) // $ flow=700 var arr3 = [1] var arr4 = arr2 + arr3 sink(arg: arr3[0]) - sink(arg: arr4[0]) // $ MISSING: flow=673 + sink(arg: arr4[0]) // $ MISSING: flow=692 var arr5 = Array(repeating: source(), count: 2) - sink(arg: arr5[0]) // $ MISSING: flow=689 + sink(arg: arr5[0]) // $ MISSING: flow=708 var arr6 = [1,2,3] arr6.insert(source(), at: 2) - sink(arg: arr6[0]) // $ flow=693 + sink(arg: arr6[0]) // $ flow=712 + + var arr7 = [source()] + sink(arg: arr7.randomElement()!) // $ flow=715 +} + +func testSetCollections() { + var set1: Set = [1,2,3] + sink(arg: set1.randomElement()!) + set1.insert(source()) + sink(arg: set1.randomElement()!) // $ flow=722 + + let set2 = Set([source()]) + sink(arg: set2.randomElement()!) // $ flow=725 +} + +struct MyOptionals { + var v1 : Int? = 0 + var v2 : Int? = 0 + var v3 : Int! = 0 +} + +func testWriteOptional() { + var v1 : Int? = 0 + var v2 : Int? = 0 + var v3 : Int! = 0 + var mo1 = MyOptionals() + var mo2 : MyOptionals! = MyOptionals() + + v1! = source() + v2 = source() + v3 = source() + mo1.v1! = source() + mo1.v2 = source() + mo1.v3 = source() + mo2!.v1! = source() + mo2!.v2 = source() + mo2!.v3 = source() + + sink(arg: v1!) // $ flow=742 + sink(arg: v2!) // $ flow=743 + sink(arg: v3) // $ flow=744 + sink(arg: mo1.v1!) // $ MISSING:flow=745 + sink(arg: mo1.v2!) // $ flow=746 + sink(arg: mo1.v3) // $ flow=747 + sink(arg: mo2!.v1!) // $ MISSING:flow=748 + sink(arg: mo2!.v2!) // $ MISSING:flow=749 + sink(arg: mo2!.v3) // $ MISSING:flow=750 } diff --git a/swift/ql/test/library-tests/dataflow/flowsources/CONSISTENCY/CfgConsistency.expected b/swift/ql/test/library-tests/dataflow/flowsources/CONSISTENCY/CfgConsistency.expected deleted file mode 100644 index ac216c004f2..00000000000 --- a/swift/ql/test/library-tests/dataflow/flowsources/CONSISTENCY/CfgConsistency.expected +++ /dev/null @@ -1,3 +0,0 @@ -deadEnd -| customurlschemes.swift:94:59:94:76 | options | -| customurlschemes.swift:133:59:133:76 | options | diff --git a/swift/ql/test/library-tests/dataflow/flowsources/uikit.swift b/swift/ql/test/library-tests/dataflow/flowsources/uikit.swift index 5d6ce674278..c3138f6b887 100644 --- a/swift/ql/test/library-tests/dataflow/flowsources/uikit.swift +++ b/swift/ql/test/library-tests/dataflow/flowsources/uikit.swift @@ -1,33 +1,82 @@ // --- stubs --- class NSObject { } -class NSAttributedString: NSObject {} -class UIResponder: NSObject {} -class UIView: UIResponder {} -class UIControl: UIView {} + +struct _NSRange { } + +typealias NSRange = _NSRange + +class NSAttributedString: NSObject { } + +class UIResponder: NSObject { } + +class UIView: UIResponder { } + +class UIControl: UIView { } + +class UITextRange : NSObject { +} + +protocol UITextInput { + func text(in range: UITextRange) -> String? + + func shouldChangeText(in range: UITextRange, replacementText text: String) -> Bool +} + class UITextField: UIControl { var text: String? { get { nil } set { } } + var attributedText: NSAttributedString? { get { nil } set { } } + var placeholder: String? { get { nil } set { } } } + class UISearchTextField : UITextField { } +protocol UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool +} + // --- tests --- -func testUITextField(textField: UITextField, searchTextField: UISearchTextField) { +func sink(arg: Any) { } + +class MyTextInput : UITextInput { + func text(in range: UITextRange) -> String? { return nil } + func harmless(in range: UITextRange) -> String? { return nil } + + func shouldChangeText(in range: UITextRange, replacementText text: String) -> Bool { // $ source=local + sink(arg: text) // $ tainted + + return true + } +} + +class MyUITextFieldDelegate : UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // $ source=local + sink(arg: string) // $ tainted + + return true + } +} + +func test(textField: UITextField, searchTextField: UISearchTextField, myTextInput: MyTextInput, range: UITextRange) { _ = textField.text // $ source=local _ = textField.attributedText // $ source=local _ = textField.placeholder // GOOD (not input) _ = textField.text?.uppercased() // $ source=local _ = searchTextField.text // $ source=local + + _ = myTextInput.text(in: range)! // $ source=local + _ = myTextInput.harmless(in: range)! // GOOD (not input) } diff --git a/swift/ql/test/library-tests/dataflow/taint/libraries/CONSISTENCY/CfgConsistency.expected b/swift/ql/test/library-tests/dataflow/taint/libraries/CONSISTENCY/CfgConsistency.expected deleted file mode 100644 index 34ad2196338..00000000000 --- a/swift/ql/test/library-tests/dataflow/taint/libraries/CONSISTENCY/CfgConsistency.expected +++ /dev/null @@ -1,2 +0,0 @@ -deadEnd -| url.swift:493:2:493:28 | call to sink(any:) | diff --git a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.expected b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.expected index dc37626854f..d4243b80f62 100644 --- a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.expected +++ b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.expected @@ -1,13 +1,19 @@ -| nominaltype.swift:35:6:35:6 | a | A | A | | -| nominaltype.swift:36:6:36:6 | a_alias | A_alias | A | getAliasedType:A | -| nominaltype.swift:37:6:37:6 | a_optional_alias | A_optional_alias | A? | getAliasedType:A? | -| nominaltype.swift:38:6:38:6 | b1 | B1 | B1 | getABaseType:A | -| nominaltype.swift:39:6:39:6 | b2 | B2 | B2 | getABaseType:A_alias | -| nominaltype.swift:40:6:40:6 | b1_alias | B1_alias | B1 | getABaseType:A, getAliasedType:B1 | -| nominaltype.swift:41:6:41:6 | b2_alias | B2_alias | B2 | getABaseType:A_alias, getAliasedType:B2 | -| nominaltype.swift:42:6:42:6 | p | P | P | | -| nominaltype.swift:43:6:43:6 | p_alias | P_alias | P_alias | | -| nominaltype.swift:44:6:44:6 | c1 | C1 | C1 | getABaseType:P | -| nominaltype.swift:45:6:45:6 | c2 | C2 | C2 | getABaseType:P_alias | -| nominaltype.swift:46:6:46:6 | c1_alias | C1_alias | C1 | getABaseType:P, getAliasedType:C1 | -| nominaltype.swift:47:6:47:6 | c2_alias | C2_alias | C2 | getABaseType:P_alias, getAliasedType:C2 | +| nominaltype.swift:54:6:54:6 | a | A | getFullName:A, getName:A, getUnderlyingType:A | +| nominaltype.swift:55:6:55:6 | a_alias | A_alias | getAliasedType:A, getFullName:A_alias, getName:A_alias, getUnderlyingType:A | +| nominaltype.swift:56:6:56:6 | a_optional_alias | A_optional_alias | getAliasedType:A?, getFullName:A_optional_alias, getName:A_optional_alias, getUnderlyingType:A? | +| nominaltype.swift:57:6:57:6 | b1 | B1 | getABaseType:A, getFullName:B1, getName:B1, getUnderlyingType:B1 | +| nominaltype.swift:58:6:58:6 | b2 | B2 | getABaseType:A_alias, getFullName:B2, getName:B2, getUnderlyingType:B2 | +| nominaltype.swift:59:6:59:6 | b1_alias | B1_alias | getABaseType:A, getAliasedType:B1, getFullName:B1_alias, getName:B1_alias, getUnderlyingType:B1 | +| nominaltype.swift:60:6:60:6 | b2_alias | B2_alias | getABaseType:A_alias, getAliasedType:B2, getFullName:B2_alias, getName:B2_alias, getUnderlyingType:B2 | +| nominaltype.swift:61:6:61:6 | p | P | getFullName:P, getName:P, getUnderlyingType:P | +| nominaltype.swift:62:6:62:6 | p_alias | P_alias | getFullName:P_alias, getName:P_alias, getUnderlyingType:P_alias | +| nominaltype.swift:63:6:63:6 | c1 | C1 | getABaseType:P, getFullName:C1, getName:C1, getUnderlyingType:C1 | +| nominaltype.swift:64:6:64:6 | c2 | C2 | getABaseType:P_alias, getFullName:C2, getName:C2, getUnderlyingType:C2 | +| nominaltype.swift:65:6:65:6 | c1_alias | C1_alias | getABaseType:P, getAliasedType:C1, getFullName:C1_alias, getName:C1_alias, getUnderlyingType:C1 | +| nominaltype.swift:66:6:66:6 | c2_alias | C2_alias | getABaseType:P_alias, getAliasedType:C2, getFullName:C2_alias, getName:C2_alias, getUnderlyingType:C2 | +| nominaltype.swift:67:6:67:6 | o | Outer | getFullName:Outer, getName:Outer, getUnderlyingType:Outer | +| nominaltype.swift:68:6:68:6 | oi | Outer.Inner | getFullName:Outer.Inner, getName:Inner, getUnderlyingType:Outer.Inner | +| nominaltype.swift:69:6:69:6 | oia | Outer.Inner.InnerAlias | getABaseType:FixedWidthInteger, getABaseType:SignedInteger, getABaseType:_ExpressibleByBuiltinIntegerLiteral, getAliasedType:Int, getFullName:Outer.Inner.InnerAlias, getName:InnerAlias, getUnderlyingType:Int | +| nominaltype.swift:70:6:70:6 | aa | Any? | getFullName:Any?, getName:Any?, getUnderlyingType:Any? | +| nominaltype.swift:71:6:71:6 | p1p2 | P1P2 | getFullName:P1P2, getName:P1P2, getUnderlyingType:P1P2 | +| nominaltype.swift:72:6:72:6 | boxInt | Box | getFullName:Box, getName:Box, getUnderlyingType:Box | diff --git a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.ql b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.ql index c0ab33423bf..9e6deb12b18 100644 --- a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.ql +++ b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.ql @@ -1,9 +1,15 @@ import swift string describe(Type t) { - result = "getAliasedType:" + t.(TypeAliasType).getAliasedType() + result = "getName:" + t.getName() or - result = "getABaseType:" + t.getABaseType() + result = "getFullName:" + t.getFullName() + or + result = "getUnderlyingType:" + t.getUnderlyingType().toString() + or + result = "getAliasedType:" + t.(TypeAliasType).getAliasedType().toString() + or + result = "getABaseType:" + t.getABaseType().toString() } from VarDecl v, Type t @@ -11,4 +17,4 @@ where v.getLocation().getFile().getBaseName() != "" and not v.getName() = "self" and t = v.getType() -select v, t.toString(), t.getUnderlyingType(), concat(describe(t), ", ") +select v, t.toString(), concat(describe(t), ", ") diff --git a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.swift b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.swift index 712abe27863..26f73c49f55 100644 --- a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.swift +++ b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltype.swift @@ -31,6 +31,25 @@ typealias C1_alias = C1 typealias C2_alias = C2 +class Outer { + class Inner { + typealias InnerAlias = Int + } +} + +protocol P1 { +} + +protocol P2 { +} + +typealias P1P2 = P1 & P2 + +class Box { +} + + + func test() { var a : A var a_alias : A_alias @@ -45,4 +64,10 @@ func test() { var c2 : C2 var c1_alias : C1_alias var c2_alias : C2_alias + var o : Outer + var oi : Outer.Inner + var oia : Outer.Inner.InnerAlias + var aa : Any? + var p1p2 : P1P2 + var boxInt : Box } diff --git a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.expected b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.expected index 612b031f54e..b62b789b1a8 100644 --- a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.expected +++ b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.expected @@ -1,11 +1,14 @@ -| nominaltype.swift:35:6:35:6 | a | A | | -| nominaltype.swift:36:6:36:6 | a_alias | A_alias | getAliasedType:A | -| nominaltype.swift:37:6:37:6 | a_optional_alias | A_optional_alias | getAliasedType:A? | -| nominaltype.swift:38:6:38:6 | b1 | B1 | getABaseType:A | -| nominaltype.swift:39:6:39:6 | b2 | B2 | getABaseType:A_alias | -| nominaltype.swift:40:6:40:6 | b1_alias | B1_alias | getAliasedType:B1 | -| nominaltype.swift:41:6:41:6 | b2_alias | B2_alias | getAliasedType:B2 | -| nominaltype.swift:44:6:44:6 | c1 | C1 | getABaseType:P | -| nominaltype.swift:45:6:45:6 | c2 | C2 | getABaseType:P_alias | -| nominaltype.swift:46:6:46:6 | c1_alias | C1_alias | getAliasedType:C1 | -| nominaltype.swift:47:6:47:6 | c2_alias | C2_alias | getAliasedType:C2 | +| nominaltype.swift:54:6:54:6 | a | A | getFullName:A, getName:A | +| nominaltype.swift:55:6:55:6 | a_alias | A_alias | getAliasedType:A, getFullName:A_alias, getName:A_alias | +| nominaltype.swift:56:6:56:6 | a_optional_alias | A_optional_alias | getAliasedType:A?, getFullName:A_optional_alias, getName:A_optional_alias | +| nominaltype.swift:57:6:57:6 | b1 | B1 | getABaseType:A, getFullName:B1, getName:B1 | +| nominaltype.swift:58:6:58:6 | b2 | B2 | getABaseType:A_alias, getFullName:B2, getName:B2 | +| nominaltype.swift:59:6:59:6 | b1_alias | B1_alias | getAliasedType:B1, getFullName:B1_alias, getName:B1_alias | +| nominaltype.swift:60:6:60:6 | b2_alias | B2_alias | getAliasedType:B2, getFullName:B2_alias, getName:B2_alias | +| nominaltype.swift:63:6:63:6 | c1 | C1 | getABaseType:P, getFullName:C1, getName:C1 | +| nominaltype.swift:64:6:64:6 | c2 | C2 | getABaseType:P_alias, getFullName:C2, getName:C2 | +| nominaltype.swift:65:6:65:6 | c1_alias | C1_alias | getAliasedType:C1, getFullName:C1_alias, getName:C1_alias | +| nominaltype.swift:66:6:66:6 | c2_alias | C2_alias | getAliasedType:C2, getFullName:C2_alias, getName:C2_alias | +| nominaltype.swift:67:6:67:6 | o | Outer | getFullName:Outer, getName:Outer | +| nominaltype.swift:68:6:68:6 | oi | Inner | getFullName:Outer.Inner, getName:Inner | +| nominaltype.swift:69:6:69:6 | oia | InnerAlias | getAliasedType:Int, getFullName:Outer.Inner.InnerAlias, getName:InnerAlias | diff --git a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.ql b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.ql index 917b27d6826..be1e4c58efa 100644 --- a/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.ql +++ b/swift/ql/test/library-tests/elements/type/nominaltype/nominaltypedecl.ql @@ -1,9 +1,13 @@ import swift string describe(TypeDecl td) { - result = "getAliasedType:" + td.(TypeAliasDecl).getAliasedType() + result = "getName:" + td.getName() or - result = "getABaseType:" + td.(NominalTypeDecl).getABaseType() + result = "getFullName:" + td.getFullName() + or + result = "getAliasedType:" + td.(TypeAliasDecl).getAliasedType().toString() + or + result = "getABaseType:" + td.(NominalTypeDecl).getABaseType().toString() } from VarDecl v, TypeDecl td