Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 2.1k
C#: Instantiate shared Guards and shared ControlFlowReachability and replace nullness#20558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
64caae5ca7d560c01ac30b52a9a8449059fc2d21e96cfadbf587901b64810f6df6172bb392767613c789fad49ff84a65bec44e6fcf172e367d0e4f5File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| category: majorAnalysis | ||
| --- | ||
| * The representation of the C# control-flow graph has been significantly changed. This has minor effects on a wide range of queries including both minor improvements and minor regressions, for example, improved precision has been observed for `cs/inefficient-containskey` and `cs/stringbuilder-creation-in-loop`. Two queries stand out as being significantly affected with great improvements: `cs/dereferenced-value-may-be-null` has been completely rewritten which removes a very significant number of false positives. Furthermore, `cs/constant-condition` has been updated to report many new results - these new results are primarily expected to be true positives, but a few new false positives are expected as well. As part of these changes, `cs/dereferenced-value-may-be-null` has been changed from a `path-problem` query to a `problem` query, so paths are no longer reported for this query. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -251,6 +251,9 @@ module ControlFlow { | ||
| } | ||
| } | ||
| /** A control flow node indicating normal termination of a callable. */ | ||
| class NormalExitNode extends AnnotatedExitNode instanceof Impl::NormalExitNode { } | ||
hvitved marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /** A node for a callable exit point. */ | ||
| class ExitNode extends Node instanceof Impl::ExitNode { | ||
| /** Gets the callable that this exit applies to. */ | ||
| @@ -292,13 +295,7 @@ module ControlFlow { | ||
| class Split = Splitting::Split; | ||
| class FinallySplit = Splitting::FinallySplitting::FinallySplit; | ||
| class ExceptionHandlerSplit = Splitting::ExceptionHandlerSplitting::ExceptionHandlerSplit; | ||
| class BooleanSplit = Splitting::BooleanSplitting::BooleanSplit; | ||
| class LoopSplit = Splitting::LoopSplitting::LoopSplit; | ||
| } | ||
| class BasicBlock = BBs::BasicBlock; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /** | ||
| * Provides an implementation of local (intraprocedural) control flow reachability. | ||
| */ | ||
| import csharp | ||
| private import codeql.controlflow.ControlFlow | ||
| private import semmle.code.csharp.controlflow.BasicBlocks | ||
| private import semmle.code.csharp.controlflow.Guards as Guards | ||
| private import semmle.code.csharp.ExprOrStmtParent | ||
| private module ControlFlowInput implements | ||
| InputSig<Location, ControlFlow::Node, ControlFlow::BasicBlock> | ||
| { | ||
| private import csharp as CS | ||
Check warningCode scanning / CodeQL Names only differing by case Warning
CS is only different by casing from Cs that is used elsewhere for modules.
| ||
| AstNode getEnclosingAstNode(ControlFlow::Node node) { | ||
| node.getAstNode() = result | ||
| or | ||
| not exists(node.getAstNode()) and result = node.getEnclosingCallable() | ||
| } | ||
| class AstNode = ExprOrStmtParent; | ||
| AstNode getParent(AstNode node) { result = node.getParent() } | ||
| class FinallyBlock extends AstNode { | ||
| FinallyBlock() { any(TryStmt try).getFinally() = this } | ||
| } | ||
| class Expr = CS::Expr; | ||
| class SourceVariable = Ssa::SourceVariable; | ||
| class SsaDefinition = Ssa::Definition; | ||
| class SsaWriteDefinition extends SsaDefinition instanceof Ssa::ExplicitDefinition { | ||
| Expr getDefinition() { result = super.getADefinition().getSource() } | ||
| } | ||
| class SsaPhiNode = Ssa::PhiNode; | ||
| class SsaUncertainDefinition = Ssa::UncertainDefinition; | ||
| class GuardValue = Guards::GuardValue; | ||
| predicate ssaControlsBranchEdge(SsaDefinition def, BasicBlock bb1, BasicBlock bb2, GuardValue v) { | ||
| Guards::Guards::ssaControlsBranchEdge(def, bb1, bb2, v) | ||
| } | ||
| predicate ssaControls(SsaDefinition def, BasicBlock bb, GuardValue v) { | ||
| Guards::Guards::ssaControls(def, bb, v) | ||
| } | ||
| import Guards::Guards::InternalUtil | ||
| } | ||
| module ControlFlowReachability = Make<Location, Cfg, ControlFlowInput>; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -13,6 +13,284 @@ | ||
| private import semmle.code.csharp.frameworks.system.Linq | ||
| private import semmle.code.csharp.frameworks.system.Collections | ||
| private import semmle.code.csharp.frameworks.system.collections.Generic | ||
| private import codeql.controlflow.Guards as SharedGuards | ||
| private module GuardsInput implements | ||
| SharedGuards::InputSig<Location, ControlFlow::Node, ControlFlow::BasicBlock> | ||
| { | ||
| private import csharp as CS | ||
Check warningCode scanning / CodeQL Names only differing by case Warning
CS is only different by casing from Cs that is used elsewhere for modules.
| ||
| class NormalExitNode = ControlFlow::Nodes::NormalExitNode; | ||
| class AstNode = ControlFlowElement; | ||
| class Expr = CS::Expr; | ||
| private newtype TConstantValue = | ||
| TStringValue(string value) { any(StringLiteral s).getValue() = value } | ||
| class ConstantValue extends TConstantValue { | ||
| string toString() { this = TStringValue(result) } | ||
| } | ||
| abstract class ConstantExpr extends Expr { | ||
| predicate isNull() { none() } | ||
| boolean asBooleanValue() { none() } | ||
| int asIntegerValue() { none() } | ||
| ConstantValue asConstantValue() { none() } | ||
| } | ||
| private class NullConstant extends ConstantExpr { | ||
| NullConstant() { nullValueImplied(this) } | ||
| override predicate isNull() { any() } | ||
| } | ||
| private class BooleanConstant extends ConstantExpr instanceof BoolLiteral { | ||
| override boolean asBooleanValue() { result = super.getBoolValue() } | ||
| } | ||
| private predicate intConst(Expr e, int i) { | ||
| e.getValue().toInt() = i and | ||
| ( | ||
| e.getType() instanceof Enum | ||
| or | ||
| e.getType() instanceof IntegralType | ||
| ) | ||
| } | ||
| private class IntegerConstant extends ConstantExpr { | ||
| IntegerConstant() { intConst(this, _) } | ||
| override int asIntegerValue() { intConst(this, result) } | ||
| } | ||
| private class EnumConst extends ConstantExpr { | ||
| EnumConst() { this.getType() instanceof Enum and this.hasValue() } | ||
| override int asIntegerValue() { result = this.getValue().toInt() } | ||
| } | ||
| private class StringConstant extends ConstantExpr instanceof StringLiteral { | ||
| override ConstantValue asConstantValue() { result = TStringValue(this.getValue()) } | ||
| } | ||
| class NonNullExpr extends Expr { | ||
| NonNullExpr() { nonNullValueImplied(this) } | ||
| } | ||
| class Case extends AstNode instanceof CS::Case { | ||
| Expr getSwitchExpr() { super.getExpr() = result } | ||
| predicate isDefaultCase() { this instanceof DefaultCase } | ||
| ConstantExpr asConstantCase() { super.getPattern() = result } | ||
| private predicate hasEdge(BasicBlock bb1, BasicBlock bb2, MatchingCompletion c) { | ||
| exists(PatternExpr pe | | ||
| super.getPattern() = pe and | ||
| c.isValidFor(pe) and | ||
| bb1.getLastNode() = pe.getAControlFlowNode() and | ||
| bb1.getASuccessor(c.getAMatchingSuccessorType()) = bb2 | ||
| ) | ||
| } | ||
| predicate matchEdge(BasicBlock bb1, BasicBlock bb2) { | ||
| exists(MatchingCompletion c | this.hasEdge(bb1, bb2, c) and c.isMatch()) | ||
| } | ||
| predicate nonMatchEdge(BasicBlock bb1, BasicBlock bb2) { | ||
| exists(MatchingCompletion c | this.hasEdge(bb1, bb2, c) and c.isNonMatch()) | ||
| } | ||
| } | ||
| abstract private class BinExpr extends BinaryOperation { } | ||
| class AndExpr extends BinExpr { | ||
| AndExpr() { | ||
| this instanceof LogicalAndExpr or | ||
| this instanceof BitwiseAndExpr | ||
| } | ||
| } | ||
| class OrExpr extends BinExpr { | ||
| OrExpr() { | ||
| this instanceof LogicalOrExpr or | ||
| this instanceof BitwiseOrExpr | ||
| } | ||
| } | ||
| class NotExpr = LogicalNotExpr; | ||
| class IdExpr extends Expr { | ||
| IdExpr() { this instanceof AssignExpr or this instanceof CastExpr } | ||
| Expr getEqualChildExpr() { | ||
| result = this.(AssignExpr).getRValue() | ||
| or | ||
| result = this.(CastExpr).getExpr() | ||
hvitved marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| } | ||
| predicate equalityTest(Expr eqtest, Expr left, Expr right, boolean polarity) { | ||
| exists(ComparisonTest ct | | ||
| ct.getExpr() = eqtest and | ||
| ct.getFirstArgument() = left and | ||
| ct.getSecondArgument() = right | ||
| | | ||
| ct.getComparisonKind().isEquality() and polarity = true | ||
| or | ||
| ct.getComparisonKind().isInequality() and polarity = false | ||
| ) | ||
| or | ||
| exists(IsExpr ie, PatternExpr pat | | ||
| ie = eqtest and | ||
| ie.getExpr() = left and | ||
| ie.getPattern() = pat | ||
| | | ||
| right = pat.(ConstantPatternExpr) and | ||
| polarity = true | ||
| or | ||
| right = pat.(NotPatternExpr).getPattern().(ConstantPatternExpr) and | ||
| polarity = false | ||
| ) | ||
| } | ||
| class ConditionalExpr = CS::ConditionalExpr; | ||
| class Parameter = CS::Parameter; | ||
| private int parameterPosition() { result in [-1, any(Parameter p).getPosition()] } | ||
| class ParameterPosition extends int { | ||
| ParameterPosition() { this = parameterPosition() } | ||
| } | ||
| class ArgumentPosition extends int { | ||
| ArgumentPosition() { this = parameterPosition() } | ||
| } | ||
| pragma[inline] | ||
| predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos } | ||
| final private class FinalCallable = Callable; | ||
| class NonOverridableMethod extends FinalCallable { | ||
| NonOverridableMethod() { not this.(Overridable).isOverridableOrImplementable() } | ||
| Parameter getParameter(ParameterPosition ppos) { | ||
| super.getParameter(ppos) = result and | ||
| not result.isParams() | ||
| } | ||
| Expr getAReturnExpr() { this.canReturn(result) } | ||
| } | ||
| class NonOverridableMethodCall extends Expr instanceof Call { | ||
| NonOverridableMethod getMethod() { super.getTarget().getUnboundDeclaration() = result } | ||
| Expr getArgument(ArgumentPosition apos) { | ||
| result = super.getArgumentForParameter(any(Parameter p | p.getPosition() = apos)) | ||
| } | ||
| } | ||
| } | ||
| private module GuardsImpl = SharedGuards::Make<Location, Cfg, GuardsInput>; | ||
| class GuardValue = GuardsImpl::GuardValue; | ||
| private module LogicInput implements GuardsImpl::LogicInputSig { | ||
| class SsaDefinition extends Ssa::Definition { | ||
| Expr getARead() { super.getARead() = result } | ||
| } | ||
| class SsaWriteDefinition extends SsaDefinition instanceof Ssa::ExplicitDefinition { | ||
| Expr getDefinition() { result = super.getADefinition().getSource() } | ||
| } | ||
| class SsaPhiNode extends SsaDefinition instanceof Ssa::PhiNode { | ||
| predicate hasInputFromBlock(SsaDefinition inp, BasicBlock bb) { | ||
| super.hasInputFromBlock(inp, bb) | ||
| } | ||
| } | ||
| predicate parameterDefinition(Parameter p, SsaDefinition def) { | ||
| def.(Ssa::ImplicitParameterDefinition).getParameter() = p | ||
| } | ||
| predicate additionalNullCheck(GuardsImpl::PreGuard guard, GuardValue val, Expr e, boolean isNull) { | ||
| // Comparison with a non-`null` value, for example `x?.Length > 0` | ||
| exists(ComparisonTest ct, ComparisonKind ck, Expr arg | ct.getExpr() = guard | | ||
| e instanceof DereferenceableExpr and | ||
| ct.getAnArgument() = e and | ||
| ct.getAnArgument() = arg and | ||
| arg = any(NullValue nv | nv.isNonNull()).getAnExpr() and | ||
| ck = ct.getComparisonKind() and | ||
| e != arg and | ||
| isNull = false and | ||
| not ck.isEquality() and | ||
| not ck.isInequality() and | ||
| val.asBooleanValue() = true | ||
| ) | ||
| or | ||
| // Call to `string.IsNullOrEmpty()` or `string.IsNullOrWhiteSpace()` | ||
| exists(MethodCall mc, string name | guard = mc | | ||
| mc.getTarget() = any(SystemStringClass c).getAMethod(name) and | ||
| name.regexpMatch("IsNullOr(Empty|WhiteSpace)") and | ||
| mc.getArgument(0) = e and | ||
| val.asBooleanValue() = false and | ||
| isNull = false | ||
| ) | ||
| or | ||
| guard = | ||
| any(PatternMatch pm | | ||
| e instanceof DereferenceableExpr and | ||
| e = pm.getExpr() and | ||
| ( | ||
| val.asBooleanValue().booleanNot() = patternMatchesNull(pm.getPattern()) and | ||
| isNull = false | ||
| or | ||
| exists(TypePatternExpr tpe | | ||
| // E.g. `x is string` where `x` has type `string` | ||
| typePattern(guard, tpe, tpe.getCheckedType()) and | ||
| val.asBooleanValue() = false and | ||
| isNull = true | ||
| ) | ||
| ) | ||
| ) | ||
| or | ||
| e.(DereferenceableExpr).hasNullableType() and | ||
| guard = | ||
| any(PropertyAccess pa | | ||
| pa.getQualifier() = e and | ||
| pa.getTarget().hasName("HasValue") and | ||
| val.asBooleanValue().booleanNot() = isNull | ||
| ) | ||
| } | ||
| predicate additionalImpliesStep( | ||
| GuardsImpl::PreGuard g1, GuardValue v1, GuardsImpl::PreGuard g2, GuardValue v2 | ||
| ) { | ||
| g1 instanceof DereferenceableExpr and | ||
| g1 = getNullEquivParent(g2) and | ||
| v1.isNullness(_) and | ||
| v2 = v1 | ||
| or | ||
| g1 instanceof DereferenceableExpr and | ||
| g2 = getANullImplyingChild(g1) and | ||
| v1.isNonNullValue() and | ||
| v2 = v1 | ||
| or | ||
| g2 = g1.(NullCoalescingExpr).getAnOperand() and | ||
| v1.isNullValue() and | ||
| v2 = v1 | ||
| } | ||
| } | ||
| module Guards = GuardsImpl::Logic<LogicInput>; | ||
| /** An expression whose value may control the execution of another element. */ | ||
| class Guard extends Expr { | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.