|
39 | 39 | #include <utility> |
40 | 40 |
|
41 | 41 | // CWE ids used |
| 42 | +static const struct CWE uncheckedErrorConditionCWE(391U); |
42 | 43 | static const struct CWE CWE398(398U); // Indicator of Poor Code Quality |
43 | 44 | static const struct CWE CWE570(570U); // Expression is Always False |
44 | 45 | static const struct CWE CWE571(571U); // Expression is Always True |
@@ -1494,63 +1495,103 @@ void CheckCondition::alwaysTrueFalseError(const Token *tok, const ValueFlow::Val |
1494 | 1495 |
|
1495 | 1496 | void CheckCondition::checkInvalidTestForOverflow() |
1496 | 1497 | { |
| 1498 | + // Interesting blogs: |
| 1499 | + // https://www.airs.com/blog/archives/120 |
| 1500 | + // https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-enables.html |
| 1501 | + // https://research.checkpoint.com/2020/optout-compiler-undefined-behavior-optimizations/ |
| 1502 | + |
| 1503 | + // x + c < x -> false |
| 1504 | + // x + c <= x -> false |
| 1505 | + // x + c > x -> true |
| 1506 | + // x + c >= x -> true |
| 1507 | + |
| 1508 | + // x + y < x -> y < 0 |
| 1509 | + |
| 1510 | + |
1497 | 1511 | if (!mSettings->isEnabled(Settings::WARNING)) |
1498 | 1512 | return; |
1499 | 1513 |
|
1500 | | - const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
1501 | | - for (const Scope * scope : symbolDatabase->functionScopes) { |
| 1514 | + for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
| 1515 | + if (!Token::Match(tok, "<|<=|>=|>") || !tok->isBinaryOp()) |
| 1516 | + continue; |
1502 | 1517 |
|
1503 | | - for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { |
1504 | | - if (!tok->isComparisonOp() || !tok->astOperand1() || !tok->astOperand2()) |
1505 | | - continue; |
| 1518 | + const Token *lhsTokens[2] = {tok->astOperand1(), tok->astOperand2()}; |
| 1519 | + for (const Token *lhs: lhsTokens) { |
| 1520 | + std::string cmp = tok->str(); |
| 1521 | + if (lhs == tok->astOperand2()) |
| 1522 | + cmp[0] = (cmp[0] == '<') ? '>' : '<'; |
1506 | 1523 |
|
1507 | | - const Token *calcToken, *exprToken; |
1508 | | - bool result; |
1509 | | - if (Token::Match(tok, "<|>=") && tok->astOperand1()->str() == "+") { |
1510 | | - calcToken = tok->astOperand1(); |
1511 | | - exprToken = tok->astOperand2(); |
1512 | | - result = (tok->str() == ">="); |
1513 | | - } else if (Token::Match(tok, ">|<=") && tok->astOperand2()->str() == "+") { |
1514 | | - calcToken = tok->astOperand2(); |
1515 | | - exprToken = tok->astOperand1(); |
1516 | | - result = (tok->str() == "<="); |
1517 | | - } else |
| 1524 | + if (!Token::Match(lhs, "[+-]") || !lhs->isBinaryOp()) |
1518 | 1525 | continue; |
1519 | 1526 |
|
1520 | | - // Only warn for signed integer overflows and pointer overflows. |
1521 | | - if (!(calcToken->valueType() && (calcToken->valueType()->pointer || calcToken->valueType()->sign == ValueType::Sign::SIGNED))) |
1522 | | - continue; |
1523 | | - if (!(exprToken->valueType() && (exprToken->valueType()->pointer || exprToken->valueType()->sign == ValueType::Sign::SIGNED))) |
| 1527 | + const bool isSignedInteger = lhs->valueType() && lhs->valueType()->isIntegral() && lhs->valueType()->sign == ValueType::Sign::SIGNED; |
| 1528 | + const bool isPointer = lhs->valueType() && lhs->valueType()->pointer > 0; |
| 1529 | + if (!isSignedInteger && !isPointer) |
1524 | 1530 | continue; |
1525 | 1531 |
|
1526 | | - const Token *termToken; |
1527 | | - if (isSameExpression(mTokenizer->isCPP(), true, exprToken, calcToken->astOperand1(), mSettings->library, true, false)) |
1528 | | - termToken = calcToken->astOperand2(); |
1529 | | - else if (isSameExpression(mTokenizer->isCPP(), true, exprToken, calcToken->astOperand2(), mSettings->library, true, false)) |
1530 | | - termToken = calcToken->astOperand1(); |
1531 | | - else |
1532 | | - continue; |
| 1532 | + const Token *exprTokens[2] = {lhs->astOperand1(), lhs->astOperand2()}; |
| 1533 | + for (const Token *expr: exprTokens) { |
| 1534 | + if (lhs->str() == "-" && expr == lhs->astOperand2()) |
| 1535 | + continue; // TODO? |
1533 | 1536 |
|
1534 | | - if (!termToken) |
1535 | | - continue; |
| 1537 | + if (expr->hasKnownIntValue()) |
| 1538 | + continue; |
| 1539 | + |
| 1540 | + if (!isSameExpression(mTokenizer->isCPP(), |
| 1541 | + true, |
| 1542 | + expr, |
| 1543 | + lhs->astSibling(), |
| 1544 | + mSettings->library, |
| 1545 | + true, |
| 1546 | + false)) |
| 1547 | + continue; |
| 1548 | + |
| 1549 | + const Token * const other = expr->astSibling(); |
| 1550 | + |
| 1551 | + // x [+-] c cmp x |
| 1552 | + if ((other->isNumber() && other->getKnownIntValue() > 0) || |
| 1553 | + (!other->isNumber() && other->valueType() && other->valueType()->isIntegral() && other->valueType()->sign == ValueType::Sign::UNSIGNED)) { |
| 1554 | + bool result; |
| 1555 | + if (lhs->str() == "+") |
| 1556 | + result = (cmp == ">" || cmp == ">="); |
| 1557 | + else |
| 1558 | + result = (cmp == "<" || cmp == "<="); |
| 1559 | + invalidTestForOverflow(tok, lhs->valueType(), result ? "true" : "false"); |
| 1560 | + continue; |
| 1561 | + } |
1536 | 1562 |
|
1537 | | - // Only warn when termToken is always positive |
1538 | | - if (termToken->valueType() && termToken->valueType()->sign == ValueType::Sign::UNSIGNED) |
1539 | | - invalidTestForOverflow(tok, result); |
1540 | | - else if (termToken->isNumber() && MathLib::isPositive(termToken->str())) |
1541 | | - invalidTestForOverflow(tok, result); |
| 1563 | + // x + y cmp x |
| 1564 | + if (lhs->str() == "+" && other->varId() > 0) { |
| 1565 | + const std::string result = other->str() + cmp + "0"; |
| 1566 | + invalidTestForOverflow(tok, lhs->valueType(), result); |
| 1567 | + continue; |
| 1568 | + } |
| 1569 | + |
| 1570 | + // x - y cmp x |
| 1571 | + if (lhs->str() == "-" && other->varId() > 0) { |
| 1572 | + std::string cmp2 = cmp; |
| 1573 | + cmp2[0] = (cmp[0] == '<') ? '>' : '<'; |
| 1574 | + const std::string result = other->str() + cmp2 + "0"; |
| 1575 | + invalidTestForOverflow(tok, lhs->valueType(), result); |
| 1576 | + continue; |
| 1577 | + } |
| 1578 | + } |
1542 | 1579 | } |
1543 | 1580 | } |
1544 | 1581 | } |
1545 | 1582 |
|
1546 | | -void CheckCondition::invalidTestForOverflow(const Token* tok, bool result) |
| 1583 | +void CheckCondition::invalidTestForOverflow(const Token* tok, const ValueType *valueType, const std::string &replace) |
1547 | 1584 | { |
1548 | | - const std::string errmsg = "Invalid test for overflow '" + |
1549 | | - (tok ? tok->expressionString() : std::string("x + u < x")) + |
1550 | | - "'. Condition is always " + |
1551 | | - std::string(result ? "true" : "false") + |
1552 | | - " unless there is overflow, and overflow is undefined behaviour."; |
1553 | | - reportError(tok, Severity::warning, "invalidTestForOverflow", errmsg, (result ? CWE571 : CWE570), false); |
| 1585 | + const std::string expr = (tok ? tok->expressionString() : std::string("x + c < x")); |
| 1586 | + const std::string overflow = (valueType && valueType->pointer) ? "pointer overflow" : "signed integer overflow"; |
| 1587 | + |
| 1588 | + std::string errmsg = |
| 1589 | + "Invalid test for overflow '" + expr + "'; " + overflow + " is undefined behavior."; |
| 1590 | + if (replace == "false" || replace == "true") |
| 1591 | + errmsg += " Some mainstream compilers remove such overflow tests when optimising the code and assume it's always " + replace + "."; |
| 1592 | + else |
| 1593 | + errmsg += " Some mainstream compilers removes handling of overflows when optimising the code and change the code to '" + replace + "'."; |
| 1594 | + reportError(tok, Severity::warning, "invalidTestForOverflow", errmsg, uncheckedErrorConditionCWE, false); |
1554 | 1595 | } |
1555 | 1596 |
|
1556 | 1597 |
|
|
0 commit comments