|
| 1 | +/** |
| 2 | + * @id c/misra/possible-misuse-of-undetected-nan |
| 3 | + * @name DIR-4-15: Evaluation of floating-point expressions shall not lead to the undetected generation of NaNs |
| 4 | + * @description Evaluation of floating-point expressions shall not lead to the undetected generation |
| 5 | + * of NaNs. |
| 6 | + * @kind path-problem |
| 7 | + * @precision low |
| 8 | + * @problem.severity warning |
| 9 | + * @tags external/misra/id/dir-4-15 |
| 10 | + * correctness |
| 11 | + * external/misra/c/2012/amendment3 |
| 12 | + * external/misra/obligation/required |
| 13 | + */ |
| 14 | + |
| 15 | +import cpp |
| 16 | +import codeql.util.Boolean |
| 17 | +import codingstandards.c.misra |
| 18 | +import codingstandards.cpp.RestrictedRangeAnalysis |
| 19 | +import codingstandards.cpp.FloatingPoint |
| 20 | +import codingstandards.cpp.AlertReporting |
| 21 | +import semmle.code.cpp.controlflow.Guards |
| 22 | +import semmle.code.cpp.dataflow.new.DataFlow |
| 23 | +import semmle.code.cpp.dataflow.new.TaintTracking |
| 24 | +import semmle.code.cpp.controlflow.Dominance |
| 25 | + |
| 26 | +abstractclassPotentiallyNaNExprextendsExpr{ |
| 27 | +abstractstringgetReason(); |
| 28 | +} |
| 29 | + |
| 30 | +classDomainErrorFunctionCallextendsFunctionCall,PotentiallyNaNExpr{ |
| 31 | +stringreason; |
| 32 | + |
| 33 | +DomainErrorFunctionCall(){ RestrictedDomainError::hasDomainError(this,reason)} |
| 34 | + |
| 35 | +overridestringgetReason(){result=reason} |
| 36 | +} |
| 37 | + |
| 38 | +// IEEE 754-1985 Section 7.1 invalid operations |
| 39 | +classInvalidOperationExprextendsBinaryOperation,PotentiallyNaNExpr{ |
| 40 | +stringreason; |
| 41 | + |
| 42 | +InvalidOperationExpr(){ |
| 43 | +// Usual arithmetic conversions in both languages mean that if either operand is a floating |
| 44 | +// type, the other one is converted to a floating type as well. |
| 45 | +getAnOperand().getFullyConverted().getType()instanceofFloatingPointTypeand |
| 46 | +( |
| 47 | +// 7.1.1 propagates signaling NaNs, we rely on flow analysis and/or assume quiet NaNs, so we |
| 48 | +// intentionally do not cover this case. |
| 49 | +// 7.1.2: magnitude subtraction of infinities, such as +Inf + -Inf |
| 50 | +getOperator()="+"and |
| 51 | +exists(Booleansign| |
| 52 | +exprMayEqualInfinity(getLeftOperand(),sign)and |
| 53 | +exprMayEqualInfinity(getRightOperand(),sign.booleanNot()) |
| 54 | +)and |
| 55 | +reason="from addition of infinity and negative infinity" |
| 56 | +or |
| 57 | +// 7.1.2 continued |
| 58 | +getOperator()="-"and |
| 59 | +exists(Booleansign| |
| 60 | +exprMayEqualInfinity(getLeftOperand(),sign)and |
| 61 | +exprMayEqualInfinity(getRightOperand(),sign) |
| 62 | +)and |
| 63 | +reason="from subtraction of an infinity from itself" |
| 64 | +or |
| 65 | +// 7.1.3: multiplication of zero by infinity |
| 66 | +getOperator()="*"and |
| 67 | +exists(ExprzeroOp,ExprinfinityOp| |
| 68 | +zeroOp=getAnOperand()and |
| 69 | +infinityOp=getAnOperand()and |
| 70 | +notzeroOp=infinityOpand |
| 71 | +exprMayEqualZero(zeroOp)and |
| 72 | +exprMayEqualInfinity(infinityOp, _) |
| 73 | +)and |
| 74 | +reason="from multiplication of zero by infinity" |
| 75 | +or |
| 76 | +// 7.1.4: Division of zero by zero, or infinity by infinity |
| 77 | +getOperator()="/"and |
| 78 | +exprMayEqualZero(getLeftOperand())and |
| 79 | +exprMayEqualZero(getRightOperand())and |
| 80 | +reason="from division of zero by zero" |
| 81 | +or |
| 82 | +// 7.1.4 continued |
| 83 | +getOperator()="/"and |
| 84 | +exprMayEqualInfinity(getLeftOperand(), _)and |
| 85 | +exprMayEqualInfinity(getRightOperand(), _)and |
| 86 | +reason="from division of infinity by infinity" |
| 87 | +or |
| 88 | +// 7.1.5: x % y where y is zero or x is infinite |
| 89 | +getOperator()="%"and |
| 90 | +exprMayEqualInfinity(getLeftOperand(), _)and |
| 91 | +reason="from modulus of infinity" |
| 92 | +or |
| 93 | +// 7.1.5 continued |
| 94 | +getOperator()="%"and |
| 95 | +exprMayEqualZero(getRightOperand())and |
| 96 | +reason="from modulus by zero" |
| 97 | +// 7.1.6 handles the sqrt function, not covered here. |
| 98 | +// 7.1.7 declares exceptions during invalid conversions, which we catch as sinks in our flow |
| 99 | +// analysis. |
| 100 | +// 7.1.8 declares exceptions for unordered comparisons, which we catch as sinks in our flow |
| 101 | +// analysis. |
| 102 | +) |
| 103 | +} |
| 104 | + |
| 105 | +overridestringgetReason(){result=reason} |
| 106 | +} |
| 107 | + |
| 108 | +module InvalidNaNUsageimplements DataFlow::ConfigSig{ |
| 109 | +/** |
| 110 | + * An expression which has non-NaN inputs and may produce a NaN output. |
| 111 | + */ |
| 112 | +predicateisSource(DataFlow::Nodenode){ |
| 113 | +potentialSource(node)and |
| 114 | +notexists(DataFlow::Nodeprior| |
| 115 | +isAdditionalFlowStep(prior,node)and |
| 116 | +potentialSource(prior) |
| 117 | +) |
| 118 | +} |
| 119 | + |
| 120 | +/** |
| 121 | + * An expression which may produce a NaN output. |
| 122 | + */ |
| 123 | +additionalpredicatepotentialSource(DataFlow::Nodenode){ |
| 124 | +node.asExpr()instanceofPotentiallyNaNExpr |
| 125 | +} |
| 126 | + |
| 127 | +predicateisBarrierOut(DataFlow::Nodenode){ |
| 128 | +guardedNotFPClass(node.asExpr(),TNaN()) |
| 129 | +or |
| 130 | +exists(Expre| |
| 131 | +e.getType()instanceofIntegralTypeand |
| 132 | +e=node.asConvertedExpr() |
| 133 | +) |
| 134 | +} |
| 135 | + |
| 136 | +/** |
| 137 | + * Add an additional flow step to handle NaN propagation through floating point operations. |
| 138 | + */ |
| 139 | +predicateisAdditionalFlowStep(DataFlow::Nodesource, DataFlow::Nodesink){ |
| 140 | +exists(Operationo| |
| 141 | +o.getAnOperand()=source.asExpr()and |
| 142 | +o=sink.asExpr()and |
| 143 | +o.getType()instanceofFloatingPointType |
| 144 | +) |
| 145 | +} |
| 146 | + |
| 147 | +predicateisSink(DataFlow::Nodenode){ |
| 148 | +notguardedNotFPClass(node.asExpr(),TNaN())and |
| 149 | +nodeinstanceofInvalidNaNUsage |
| 150 | +} |
| 151 | +} |
| 152 | + |
| 153 | +classInvalidNaNUsageextends DataFlow::Node{ |
| 154 | +stringdescription; |
| 155 | + |
| 156 | +InvalidNaNUsage(){ |
| 157 | +// Case 1: NaNs shall not be compared, except to themselves |
| 158 | +exists(ComparisonOperationcmp| |
| 159 | +this.asExpr()=cmp.getAnOperand()and |
| 160 | +nothashCons(cmp.getLeftOperand())=hashCons(cmp.getRightOperand())and |
| 161 | +description="comparison, which would always evaluate to false." |
| 162 | +) |
| 163 | +or |
| 164 | +// Case 2: NaNs and infinities shall not be cast to integers |
| 165 | +exists(Conversionc| |
| 166 | +this.asExpr()=c.getUnconverted()and |
| 167 | +c.getExpr().getType()instanceofFloatingPointTypeand |
| 168 | +c.getType()instanceofIntegralTypeand |
| 169 | +description="a cast to integer." |
| 170 | +) |
| 171 | +} |
| 172 | + |
| 173 | +stringgetDescription(){result=description} |
| 174 | +} |
| 175 | + |
| 176 | +module InvalidNaNFlow= DataFlow::Global<InvalidNaNUsage>; |
| 177 | + |
| 178 | +import InvalidNaNFlow::PathGraph |
| 179 | + |
| 180 | +from |
| 181 | +Elementelem, InvalidNaNFlow::PathNodesource, InvalidNaNFlow::PathNodesink, |
| 182 | +InvalidNaNUsageusage,ExprsourceExpr,stringsourceString,Functionfunction, |
| 183 | +stringcomputedInFunction |
| 184 | +where |
| 185 | +not InvalidNaNFlow::PathGraph::edges(_,source, _, _)and |
| 186 | +not InvalidNaNFlow::PathGraph::edges(sink, _, _, _)and |
| 187 | +notsourceExpr.isFromTemplateInstantiation(_)and |
| 188 | +notusage.asExpr().isFromTemplateInstantiation(_)and |
| 189 | +elem= MacroUnwrapper<Expr>::unwrapElement(sink.getNode().asExpr())and |
| 190 | +usage=sink.getNode()and |
| 191 | +sourceExpr=source.getNode().asExpr()and |
| 192 | +sourceString=source.getNode().asExpr().(PotentiallyNaNExpr).getReason()and |
| 193 | +function=sourceExpr.getEnclosingFunction()and |
| 194 | + InvalidNaNFlow::flow(source.getNode(),usage)and |
| 195 | +( |
| 196 | +ifnotsourceExpr.getEnclosingFunction()=usage.asExpr().getEnclosingFunction() |
| 197 | +thencomputedInFunction="computed in function $@ " |
| 198 | +elsecomputedInFunction="" |
| 199 | +) |
| 200 | +selectelem,source,sink, |
| 201 | +"Possible NaN value $@ "+computedInFunction+"flows to "+usage.getDescription(),sourceExpr, |
| 202 | +sourceString,function,function.getName() |