Merge branch 'main' into shared-taint-tracking

This commit is contained in:
Jeroen Ketema
2023-08-17 00:14:25 +02:00
committed by GitHub
524 changed files with 12240 additions and 7635 deletions

View File

@@ -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"
]
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -1,4 +0,0 @@
---
category: majorAnalysis
---
* The `PrintAST` library now also prints global and namespace variables and their initializers.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.8.1
lastReleaseVersion: 0.9.0

View File

@@ -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

View File

@@ -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

View File

@@ -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)
)
}

View File

@@ -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() }

View File

@@ -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
}

View File

@@ -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

View File

@@ -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<FloatDelta> {
private module Param implements Reason::ParamSig {
class TypeReasonImpl = Unit;
}
class SemReason = Reason::Make<Param>::SemReason;
class SemNoReason = Reason::Make<Param>::SemNoReason;
class SemCondReason = Reason::Make<Param>::SemCondReason;
class SemTypeReason = Reason::Make<Param>::SemTypeReason;
/**
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
*

View File

@@ -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" }
}
}

View File

@@ -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<FloatDelta> {
private module Param implements Reason::ParamSig {
class TypeReasonImpl extends Unit {
TypeReasonImpl() { none() }
}
}
class SemReason = Reason::Make<Param>::SemReason;
class SemNoReason = Reason::Make<Param>::SemNoReason;
class SemCondReason = Reason::Make<Param>::SemCondReason;
class SemTypeReason = Reason::Make<Param>::SemTypeReason;
/**
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
*

View File

@@ -113,37 +113,6 @@ signature module DeltaSig {
}
signature module LangSig<DeltaSig D> {
/** 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<D> Bounds, OverflowSig<D> OverflowParam, LangSig<D> LangParam,
UtilSig<D> 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,

View File

@@ -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<ParamSig Param> {
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) }
}
}

View File

@@ -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<SizeBarrierConfig>
module SizeBarrierFlow = DataFlow::Global<SizeBarrierConfig>;
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 {

View File

@@ -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<BarrierConfig>;
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<InvalidPointerToDerefConfig>
private import DataFlow::GlobalWithState<InvalidPointerToDerefConfig>
/**
* 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
)
}

View File

@@ -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`.
*

View File

@@ -1,3 +1,7 @@
## 0.7.2
No user-facing changes.
## 0.7.1
### Minor Analysis Improvements

View File

@@ -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() +

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.7.2
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.7.1
lastReleaseVersion: 0.7.2

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-queries
version: 0.7.2-dev
version: 0.7.3-dev
groups:
- cpp
- queries

View File

@@ -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 |

View File

@@ -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];
}

View File

@@ -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 | ... + ... | ... + ... |

View File

@@ -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
}
}

View File

@@ -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) {

View File

@@ -134,7 +134,7 @@ void pointer_test() {
sink(*p3); // $ ast,ir
*p3 = 0;
sink(*p3); // $ SPURIOUS: ast,ir
sink(*p3); // $ SPURIOUS: ast
}
// --- return values ---

View File

@@ -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 |

View File

@@ -1,4 +0,0 @@
import cpp
from Diagnostic d
select d

View File

@@ -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

View File

@@ -1,5 +1,5 @@
| float128.cpp:1:50:1:60 | _Complex128 | file://:0:0:0:0 | <error-type> |
| float128.cpp:2:41:2:49 | _Float128 | file://:0:0:0:0 | <error-type> |
| 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<T> | float128.cpp:10:8:10:17 | false_type |
| float128.cpp:14:19:14:51 | __is_floating_point_helper<float> | float128.cpp:11:8:11:16 | true_type |
| float128.cpp:15:19:15:52 | __is_floating_point_helper<double> | float128.cpp:11:8:11:16 | true_type |

View File

@@ -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"
}
}
}
}

View File

@@ -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. |

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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 = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|', ' ' };
/// <summary>
/// Appends the given argument to the command line.

View File

@@ -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
{
/// <summary>
/// Main implementation of the build analysis.
/// </summary>
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;
/// <summary>
/// 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.
/// </summary>
/// <param name="reference">The filename of the reference.</param>
private void UseReference(string reference)
{
usedReferences[reference] = true;
}
private void UseReference(string reference) => usedReferences[reference] = true;
/// <summary>
/// Store that a particular source file is used (by a project file).
/// </summary>
/// <param name="sourceFile">The source file.</param>
private void UseSource(FileInfo sourceFile)
{
sources[sourceFile.FullName] = sourceFile.Exists;
}
private void UseSource(FileInfo sourceFile) => sources[sourceFile.FullName] = sourceFile.Exists;
/// <summary>
/// The list of resolved reference files.
/// </summary>
public IEnumerable<string> ReferenceFiles => this.usedReferences.Keys;
public IEnumerable<string> ReferenceFiles => usedReferences.Keys;
/// <summary>
/// The list of source files used in projects.
@@ -242,7 +247,7 @@ namespace Semmle.BuildAnalyser
/// <summary>
/// List of assembly IDs which couldn't be resolved.
/// </summary>
public IEnumerable<string> UnresolvedReferences => this.unresolvedReferences.Select(r => r.Key);
public IEnumerable<string> UnresolvedReferences => unresolvedReferences.Select(r => r.Key);
/// <summary>
/// List of source files which were mentioned in project files but
@@ -256,12 +261,7 @@ namespace Semmle.BuildAnalyser
/// </summary>
/// <param name="id">The assembly ID.</param>
/// <param name="projectFile">The project file making the reference.</param>
private void UnresolvedReference(string id, string projectFile)
{
unresolvedReferences[id] = projectFile;
}
private readonly TemporaryDirectory packageDirectory;
private void UnresolvedReference(string id, string projectFile) => unresolvedReferences[id] = projectFile;
/// <summary>
/// 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<string> targets, string? pathToNugetConfig = null)
{
@@ -331,11 +329,9 @@ namespace Semmle.BuildAnalyser
}
}
private void DownloadMissingPackages(IEnumerable<string> restoreTargets)
{
var alreadyDownloadedPackages = Directory.GetDirectories(packageDirectory.DirInfo.FullName).Select(d => Path.GetFileName(d).ToLowerInvariant()).ToHashSet();
var notYetDownloadedPackages = new HashSet<string>();
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<char> 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("<PackageReference .*Include=\"(.*?)\".*/>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
private static partial Regex PackageReference();
public void Dispose() => packageDirectory?.Dispose();
}
}

View File

@@ -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<string> GetListedRuntimes();
IList<string> GetListedRuntimes();
}
/// <summary>
@@ -78,7 +78,8 @@ namespace Semmle.BuildAnalyser
public IList<string> 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<string>();
}
progressMonitor.LogInfo($"Found runtimes: {string.Join("\n", runtimes)}");
return runtimes;
}
}

View File

@@ -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
{
// <summary>
// 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.
// </summary>
internal partial class FileContent
{
private readonly ProgressMonitor progressMonitor;
private readonly IUnsafeFileReader unsafeFileReader;
private readonly Func<IEnumerable<string>> getFiles;
private readonly Func<HashSet<string>> getAlreadyDownloadedPackages;
private readonly HashSet<string> notYetDownloadedPackages = new HashSet<string>();
private readonly Initializer initialize;
public HashSet<string> NotYetDownloadedPackages
{
get
{
initialize.Run();
return notYetDownloadedPackages;
}
}
private bool useAspNetDlls = false;
/// <summary>
/// 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)
/// <Project Sdk="Microsoft.NET.Sdk.Web">
/// <FrameworkReference Include="Microsoft.AspNetCore.App"/>
/// </summary>
public bool UseAspNetDlls
{
get
{
initialize.Run();
return useAspNetDlls;
}
}
internal FileContent(Func<HashSet<string>> getAlreadyDownloadedPackages,
ProgressMonitor progressMonitor,
Func<IEnumerable<string>> 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<IEnumerable<string>> getFiles) : this(() => Directory.GetDirectories(packageDirectory.DirInfo.FullName)
.Select(d => Path.GetFileName(d)
.ToLowerInvariant())
.ToHashSet(), progressMonitor, getFiles, new UnsafeFileReader())
{ }
private static string GetGroup(ReadOnlySpan<char> 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<char> 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<char> 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("<PackageReference.*\\sInclude=\"(.*?)\".*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
private static partial Regex PackageReference();
[GeneratedRegex("<FrameworkReference.*\\sInclude=\"(.*?)\".*/?>", 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<string> ReadLines(string file);
}
internal class UnsafeFileReader : IUnsafeFileReader
{
public IEnumerable<string> ReadLines(string file)
{
using var sr = new StreamReader(file);
string? line;
while ((line = sr.ReadLine()) != null)
{
yield return line;
}
}
}

View File

@@ -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.");
}
}

View File

@@ -18,11 +18,17 @@ namespace Semmle.Extraction.CSharp.Standalone
private const string aspNetCoreApp = "Microsoft.AspNetCore.App";
private readonly IDotNet dotNet;
private readonly Lazy<Dictionary<string, RuntimeVersion>> newestRuntimes;
private Dictionary<string, RuntimeVersion> 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<RuntimeVersion>
internal record RuntimeVersion : IComparable<RuntimeVersion>
{
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();
/// <summary>
@@ -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<string> GetRuntimes()
/// <summary>
/// Gets the .NET runtime location to use for extraction.
/// </summary>
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;
}
/// <summary>
/// Gets the .NET runtime location to use for extraction
/// Gets the ASP.NET runtime location to use for extraction, if one exists.
/// </summary>
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;
}
}
}

View File

@@ -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<string> lines;
public UnsafeFileReaderStub(List<string> lines)
{
this.lines = lines;
}
public IEnumerable<string> ReadLines(string file)
{
foreach (var line in lines)
{
yield return line;
}
}
}
internal class TestFileContent : FileContent
{
public TestFileContent(List<string> lines) : base(() => new HashSet<string>(),
new ProgressMonitor(new LoggerStub()),
() => new List<string>() { "test1.cs" },
new UnsafeFileReaderStub(lines))
{ }
}
public class FileContentTests
{
[Fact]
public void TestFileContent1()
{
// Setup
var lines = new List<string>()
{
"<Project Sdk=\"Microsoft.NET.Sdk\">",
"<PackageReference Include=\"DotNetAnalyzers.DocumentationAnalyzers\" Version=\"1.0.0-beta.59\" PrivateAssets=\"all\" />",
"<PackageReference Version=\"7.0.0\" Include=\"Microsoft.CodeAnalysis.NetAnalyzers\"PrivateAssets=\"all\" />",
"<PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.2.0-beta.406\">",
"<FrameworkReference Include=\"My.Framework\"/>"
};
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<string>()
{
"<Project Sdk=\"Microsoft.NET.Sdk.Web\">",
"<FrameworkReference Include=\"My.Framework\"/>",
"<PackageReference Version=\"7.0.0\" Include=\"Microsoft.CodeAnalysis.NetAnalyzers\"PrivateAssets=\"all\" />",
"<PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.2.0-beta.406\">"
};
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);
}
}
}

View File

@@ -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<string>
{
@"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));
}
}
}

View File

@@ -22,9 +22,9 @@ namespace Semmle.Util
/// <summary>
/// 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.
/// </summary>
public static void AddOrUpdate<T1, T2>(this Dictionary<T1, T2> dict, T1 key, T2 value) where T1 : notnull where T2 : IComparable<T2>
public static void AddOrUpdateToLatest<T1, T2>(this Dictionary<T1, T2> dict, T1 key, T2 value) where T1 : notnull where T2 : IComparable<T2>
{
if (!dict.TryGetValue(key, out var existing) || existing.CompareTo(value) < 0)
{

View File

@@ -0,0 +1,28 @@
using System;
namespace Semmle.Util
{
/// <summary>
/// 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.
/// </summary>
public class Initializer
{
private readonly Lazy<bool> doInit;
public Initializer(Action action)
{
doInit = new Lazy<bool>(() =>
{
action();
return true;
});
}
public void Run()
{
var _ = doInit.Value;
}
}
}

View File

@@ -1,3 +1,7 @@
## 1.6.2
No user-facing changes.
## 1.6.1
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 1.6.2
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.6.1
lastReleaseVersion: 1.6.2

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-all
version: 1.6.2-dev
version: 1.6.3-dev
groups:
- csharp
- solorigate

View File

@@ -1,3 +1,7 @@
## 1.6.2
No user-facing changes.
## 1.6.1
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 1.6.2
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.6.1
lastReleaseVersion: 1.6.2

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-queries
version: 1.6.2-dev
version: 1.6.3-dev
groups:
- csharp
- solorigate

View File

@@ -0,0 +1 @@
| Program.cs:0:0:0:0 | Program.cs |

View File

@@ -0,0 +1,5 @@
import csharp
from File f
where f.fromSource()
select f

View File

@@ -0,0 +1 @@
var dummy = "dummy";

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<Target Name="DeleteBinObjFolders" BeforeTargets="Clean">
<RemoveDir Directories=".\bin" />
<RemoveDir Directories=".\obj" />
</Target>
</Project>

View File

@@ -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"])

View File

@@ -1,3 +1,7 @@
## 0.7.2
No user-facing changes.
## 0.7.1
### New Features

View File

@@ -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<T>`.
*/
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 |

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.7.2
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.7.1
lastReleaseVersion: 0.7.2

View File

@@ -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

View File

@@ -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)
)
}
}

View File

@@ -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)).*")
)

View File

@@ -1,3 +1,7 @@
## 0.7.2
No user-facing changes.
## 0.7.1
No user-facing changes.

View File

@@ -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(...)'."

View File

@@ -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(...)'.",

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1,3 @@
## 0.7.2
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.7.1
lastReleaseVersion: 0.7.2

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-queries
version: 0.7.2-dev
version: 0.7.3-dev
groups:
- csharp
- queries

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections;
using System.Collections.Generic;
class MissedCastOpportunity
{
public void M1(List<Animal> animals)
{
// BAD: Can be replaced with animals.Cast<Dog>().
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<Dog>().
foreach (Animal animal in animals)
{
Dog d = (Dog)animal;
d.Woof();
}
}
public void M4(Array animals)
{
// BAD: Can be replaced with animals.Cast<Dog>().
foreach (Animal animal in animals)
{
Dog d = (Dog)animal;
d.Woof();
}
}
public void M5(IEnumerable animals)
{
// BAD: Can be replaced with animals.Cast<Dog>().
foreach (object animal in animals)
{
Dog d = (Dog)animal;
d.Woof();
}
}
public class NonEnumerableClass
{
public IEnumerator<Animal> 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 + ".");
}
}
}

View File

@@ -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 |

View File

@@ -0,0 +1 @@
Linq/MissedCastOpportunity.ql

View File

@@ -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

View File

@@ -0,0 +1,83 @@
using System;
using System.Linq;
using System.Collections.Generic;
class MissedWhereOpportunity
{
public void M1(List<int> 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<int> 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<int> GetEnumerator() => throw null;
}
}

View File

@@ -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 |

View File

@@ -0,0 +1 @@
Linq/MissedWhereOpportunity.ql

View File

@@ -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

View File

@@ -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 |

View File

@@ -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

View File

@@ -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 |

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -310,3 +310,4 @@ and the CodeQL library pack ``codeql/swift-all`` (`changelog <https://github.com
`SQLite3 <https://sqlite.org/index.html>`__, Database
`SQLite.swift <https://github.com/stephencelis/SQLite.swift>`__, Database
`WebKit <https://developer.apple.com/documentation/webkit>`__, User interface library
`UIKit <https://developer.apple.com/documentation/uikit>`__, User interface library

View File

@@ -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()
}

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More