Merge pull request #654 from geoffw0/lossyresultcast

CPP: Work on Lossy function result cast query
This commit is contained in:
Jonas Jensen
2019-01-17 17:07:29 +01:00
committed by GitHub
7 changed files with 233 additions and 5 deletions

View File

@@ -0,0 +1,7 @@
double getWidth();
void f() {
int width = getWidth();
// ...
}

View File

@@ -0,0 +1,28 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>This rule finds function calls whose result type is a floating point type, which are implicitly cast to an integral type. Such code may not behave as intended when the floating point return value has a fractional part, or takes an extreme value outside the range that can be represented by the integer type.</p>
</overview>
<recommendation>
<p>Consider changing the surrounding expression to match the floating point type. If rounding is intended, explicitly round using a standard function such as `trunc`, `floor` or `round`.</p>
</recommendation>
<example><sample src="LossyFunctionResultCast.cpp" />
<p>In this example, the result of the call to <code>getWidth()</code> is implicitly cast to <code>int</code>, resulting in an unintended loss of accuracy. To fix this, the type of variable <code>width</code> could be changed from <code>int</code> to <code>double</code>.</p>
</example>
<references>
<li>
Microsoft Visual C++ Documentation: <a href="https://docs.microsoft.com/en-us/cpp/cpp/type-conversions-and-type-safety-modern-cpp?view=vs-2017">Type Conversions and Type Safety (Modern C++)</a>.
</li>
<li>
Cplusplus.com: <a href="http://www.cplusplus.com/doc/tutorial/typecasting/">Type conversions</a>.
</li>
</references>
</qhelp>

View File

@@ -1,18 +1,68 @@
/**
* @name Lossy function result cast
* @description Finds function calls whose result type is a floating point type, and which are casted to an integral type.
* Includes only expressions with implicit cast and excludes function calls to ceil, floor and round. {This is a gcc check; doesn't seem wildly useful.}
* Includes only expressions with implicit cast and excludes function calls to ceil, floor and round.
* @kind problem
* @id cpp/lossy-function-result-cast
* @problem.severity warning
* @precision medium
* @tags correctness
*/
import cpp
import semmle.code.cpp.dataflow.DataFlow
predicate whitelist(Function f) {
exists(string fName |
fName = f.getName() and
(
fName = "ceil" or
fName = "ceilf" or
fName = "ceill" or
fName = "floor" or
fName = "floorf" or
fName = "floorl" or
fName = "nearbyint" or
fName = "nearbyintf" or
fName = "nearbyintl" or
fName = "rint" or
fName = "rintf" or
fName = "rintl" or
fName = "round" or
fName = "roundf" or
fName = "roundl" or
fName = "trunc" or
fName = "truncf" or
fName = "truncl" or
fName.matches("__builtin_%")
)
)
}
predicate whitelistPow(FunctionCall fc) {
(
fc.getTarget().getName() = "pow" or
fc.getTarget().getName() = "powf" or
fc.getTarget().getName() = "powl"
) and exists(float value |
value = fc.getArgument(0).getValue().toFloat() and
(value.floor() - value).abs() < 0.001
)
}
predicate whiteListWrapped(FunctionCall fc) {
whitelist(fc.getTarget()) or
whitelistPow(fc) or
exists(Expr e, ReturnStmt rs |
whiteListWrapped(e) and
DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(rs.getExpr())) and
fc.getTarget() = rs.getEnclosingFunction()
)
}
from FunctionCall c, FloatingPointType t1, IntegralType t2
where t1 = c.getTarget().getType().getUnderlyingType() and
t2 = c.getActualType() and
c.hasImplicitConversion() and
not c.getTarget().getName() = "ceil" and
not c.getTarget().getName() = "floor" and
not c.getTarget().getName() = "round"
select c
not whiteListWrapped(c)
select c, "Return value of type " + t1.toString() + " is implicitly converted to " + t2.toString() + " here."

View File

@@ -0,0 +1,9 @@
| test.cpp:33:6:33:13 | call to getFloat | Return value of type float is implicitly converted to bool here. |
| test.cpp:35:13:35:20 | call to getFloat | Return value of type float is implicitly converted to int here. |
| test.cpp:38:6:38:14 | call to getDouble | Return value of type double is implicitly converted to bool here. |
| test.cpp:40:13:40:21 | call to getDouble | Return value of type double is implicitly converted to int here. |
| test.cpp:43:6:43:12 | call to getMyLD | Return value of type long double is implicitly converted to bool here. |
| test.cpp:45:13:45:19 | call to getMyLD | Return value of type long double is implicitly converted to int here. |
| test.cpp:101:10:101:12 | call to pow | Return value of type double is implicitly converted to int here. |
| test.cpp:103:10:103:12 | call to pow | Return value of type double is implicitly converted to int here. |
| test.cpp:105:10:105:12 | call to pow | Return value of type double is implicitly converted to int here. |

View File

@@ -0,0 +1 @@
Likely Bugs/Conversion/LossyFunctionResultCast.ql

View File

@@ -0,0 +1,131 @@
typedef long double MYLD;
bool getBool();
int getInt();
float getFloat();
double getDouble();
MYLD getMyLD();
float *getFloatPtr();
float &getFloatRef();
const float &getConstFloatRef();
void setPosInt(int x);
void setPosFloat(float x);
double round(double x);
float roundf(float x);
void test1()
{
// simple
if (getBool())
{
setPosInt(getBool());
setPosFloat(getBool());
}
if (getInt())
{
setPosInt(getInt());
setPosFloat(getInt());
}
if (getFloat()) // BAD
{
setPosInt(getFloat()); // BAD
setPosFloat(getFloat());
}
if (getDouble()) // BAD
{
setPosInt(getDouble()); // BAD
setPosFloat(getDouble());
}
if (getMyLD()) // BAD
{
setPosInt(getMyLD()); // BAD
setPosFloat(getMyLD());
}
if (getFloatPtr())
{
// ...
}
if (getFloatRef()) // BAD [NOT DETECTED]
{
setPosInt(getFloatRef()); // BAD [NOT DETECTED]
setPosFloat(getFloatRef());
}
if (getConstFloatRef()) // BAD [NOT DETECTED]
{
setPosInt(getConstFloatRef()); // BAD [NOT DETECTED]
setPosFloat(getConstFloatRef());
}
// explicit cast
if ((bool)getInt())
{
setPosInt(getInt());
setPosFloat((float)getInt());
}
if ((bool)getFloat())
{
setPosInt((int)getFloat());
setPosFloat(getFloat());
}
// explicit rounding
if (roundf(getFloat()))
{
setPosInt(roundf(getFloat()));
setPosFloat(roundf(getFloat()));
}
if (round(getDouble()))
{
setPosInt(round(getDouble()));
setPosFloat(round(getDouble()));
}
}
double pow(double x, double y);
int test2(double v, double w, int n)
{
switch (n)
{
case 1:
return pow(2, v); // GOOD
case 2:
return pow(10, v); // GOOD
case 3:
return pow(2.5, v); // BAD
case 4:
return pow(v, 2); // BAD
case 5:
return pow(v, w); // BAD
};
}
double myRound1(double v)
{
return round(v);
}
double myRound2(double v)
{
double result = round(v);
return result;
}
double myRound3(double v)
{
return (v > 0) ? round(v) : 0;
}
void test3()
{
int i = myRound1(1.5); // GOOD
int j = myRound2(2.5); // GOOD
int k = myRound3(3.5); // GOOD
}