diff --git a/.github/workflows/CI-cygwin.yml b/.github/workflows/CI-cygwin.yml index 6b0ad2cc5bd..86111e063fb 100644 --- a/.github/workflows/CI-cygwin.yml +++ b/.github/workflows/CI-cygwin.yml @@ -44,7 +44,6 @@ jobs: - name: Set up Cygwin uses: cygwin/cygwin-install-action@v6 with: - site: https://mirrors.cicku.me/cygwin/ platform: ${{ matrix.platform }} packages: ${{ matrix.packages }} diff --git a/Makefile b/Makefile index e51cd58ced7..883f5d985b9 100644 --- a/Makefile +++ b/Makefile @@ -675,7 +675,7 @@ $(libcppdir)/templatesimplifier.o: lib/templatesimplifier.cpp lib/checkers.h lib $(libcppdir)/timer.o: lib/timer.cpp lib/config.h lib/timer.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/timer.cpp -$(libcppdir)/token.o: lib/token.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/tokenrange.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/token.o: lib/token.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/tokenrange.h lib/utils.h lib/valueflow.h lib/vf_common.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/token.cpp $(libcppdir)/tokenlist.o: lib/tokenlist.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/keywords.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h @@ -690,7 +690,7 @@ $(libcppdir)/vf_analyzers.o: lib/vf_analyzers.cpp lib/analyzer.h lib/astutils.h $(libcppdir)/vf_common.o: lib/vf_common.cpp lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_common.cpp -$(libcppdir)/vf_settokenvalue.o: lib/vf_settokenvalue.cpp lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h +$(libcppdir)/vf_settokenvalue.o: lib/vf_settokenvalue.cpp lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_settokenvalue.cpp $(libcppdir)/vfvalue.o: lib/vfvalue.cpp lib/config.h lib/errortypes.h lib/mathlib.h lib/smallvector.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h @@ -957,7 +957,7 @@ test/testutils.o: test/testutils.cpp lib/check.h lib/checkers.h lib/color.h lib/ test/testvaarg.o: test/testvaarg.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkvaarg.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvaarg.cpp -test/testvalueflow.o: test/testvalueflow.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testvalueflow.o: test/testvalueflow.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/infer.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueptr.h lib/vf_common.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvalueflow.cpp test/testvarid.o: test/testvarid.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h diff --git a/lib/checkbufferoverrun.cpp b/lib/checkbufferoverrun.cpp index bdd18ee21d2..89a59e1eb19 100644 --- a/lib/checkbufferoverrun.cpp +++ b/lib/checkbufferoverrun.cpp @@ -355,10 +355,77 @@ void CheckBufferOverrunImpl::arrayIndex() arrayIndexError(tok, dimensions, indexValues); } + const auto isNonnegativeInductionVariable = [&](const Token* indexToken) { + const Variable* const variable = indexToken->variable(); + if (!variable || !indexToken->varId()) + return false; + + for (const Scope* loop = indexToken->scope(); loop; loop = loop->nestedIn) { + if (loop->type != ScopeType::eFor || !loop->classDef || !loop->bodyStart || !loop->bodyEnd) + continue; + const Token* const forExpressions = loop->classDef->next(); + if (!forExpressions || !forExpressions->astOperand2()) + continue; + const Token* const clauses = forExpressions->astOperand2(); + const Token* const init = clauses->astOperand1(); + const Token* const remainingClauses = clauses->astOperand2(); + if (!init || !remainingClauses || !remainingClauses->astOperand1() || !remainingClauses->astOperand2()) + continue; + const Token* const step = remainingClauses->astOperand2(); + if (!Token::simpleMatch(init, "=") || !init->astOperand1() || + init->astOperand1()->varId() != indexToken->varId() || !Token::simpleMatch(step, "++")) + continue; + const Token* stepVariable = step->astOperand1(); + if (!stepVariable) + stepVariable = step->astOperand2(); + if (!stepVariable || stepVariable->varId() != indexToken->varId()) + continue; + + const Token* const initialValue = init->astOperand2(); + const ValueType* const initialType = initialValue ? initialValue->valueType() : nullptr; + const ValueType* const indexType = indexToken->valueType(); + if (!initialValue || !initialType || !indexType || !initialType->isIntegral() || !indexType->isIntegral() || + initialType->pointer || indexType->pointer || initialType->sign != indexType->sign || + initialType->getSizeOf(mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer) > + indexType->getSizeOf(mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer)) + continue; + const bool hasNonnegativeLowerBound = std::any_of(initialValue->values().cbegin(), + initialValue->values().cend(), + [](const ValueFlow::Value& value) { + return value.isIntValue() && !value.isImpossible() && !value.isInconclusive() && value.intvalue >= 0 && + (value.bound == ValueFlow::Value::Bound::Lower || + (value.bound == ValueFlow::Value::Bound::Point && value.isKnown())); + }); + if (!hasNonnegativeLowerBound) + continue; + const Token* const condition = remainingClauses->astOperand1(); + if (findVariableChanged(previousBeforeAstLeftmostLeaf(condition)->next(), + nextAfterAstRightmostLeaf(condition), + /*indirect*/ 0, + variable->declarationId(), + variable->isGlobal(), + mSettings)) + continue; + if (findVariableChanged(loop->bodyStart->next(), + loop->bodyEnd, + /*indirect*/ 1, + variable->declarationId(), + variable->isGlobal(), + mSettings)) + continue; + return true; + } + return false; + }; + // Negative index bool neg = false; std::vector negativeIndexes; for (const Token * indexToken : indexTokens) { + if (isNonnegativeInductionVariable(indexToken)) { + negativeIndexes.emplace_back(ValueFlow::Value::unknown()); + continue; + } const ValueFlow::Value *negativeValue = indexToken->getValueLE(-1, mSettings); if (negativeValue) { negativeIndexes.emplace_back(*negativeValue); diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index 48b2a1ae4bf..ffda2670769 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -1995,117 +1995,206 @@ void CheckConditionImpl::checkCompareValueOutOfTypeRange() if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("compareValueOutOfTypeRangeError")) return; - if (mSettings.platform.type == Platform::Type::Unspecified) - return; logChecker("CheckCondition::checkCompareValueOutOfTypeRange"); // style,platform - const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); - for (const Scope * scope : symbolDatabase->functionScopes) { + const auto getBits = [&](const ValueType& type) { + if (type.type == ValueType::Type::BOOL) + return std::size_t{1}; + return type.getSizeOf(mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointee) * + mSettings.platform.char_bit; + }; + const auto getSignedBounds = [](std::size_t bits, MathLib::bigint& minimum, MathLib::bigint& maximum) { + if (bits == 0 || bits > 64) + return false; + if (bits == 64) { + minimum = std::numeric_limits::min(); + maximum = std::numeric_limits::max(); + } else { + const MathLib::bigint halfRange = MathLib::bigint{1} << (bits - 1); + minimum = -halfRange; + maximum = halfRange - 1; + } + return true; + }; + const auto getUnsignedMaximum = [](std::size_t bits, MathLib::biguint& maximum) { + const std::size_t carrierBits = std::numeric_limits::digits; + if (bits == 0 || bits > carrierBits) + return false; + maximum = bits == carrierBits ? std::numeric_limits::max() + : (MathLib::biguint{1} << bits) - 1; + return true; + }; + const auto toUnsigned = [](MathLib::bigint value, std::size_t bits) { + auto converted = static_cast(value); + const std::size_t carrierBits = std::numeric_limits::digits; + if (bits < carrierBits) + converted &= (MathLib::biguint{1} << bits) - 1; + return converted; + }; + + const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); + for (const Scope* scope : symbolDatabase->functionScopes) { for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { if (!tok->isComparisonOp() || !tok->isBinaryOp()) continue; for (int i = 0; i < 2; ++i) { - const Token * const valueTok = (i == 0) ? tok->astOperand1() : tok->astOperand2(); - const Token * const typeTok = valueTok->astSibling(); - if (!valueTok->hasKnownIntValue() || !typeTok->valueType() || typeTok->valueType()->pointer) + const Token* const valueTok = (i == 0) ? tok->astOperand1() : tok->astOperand2(); + const Token* const typeTok = valueTok->astSibling(); + const ValueType* const valueType = valueTok->valueType(); + const ValueType* const type = typeTok ? typeTok->valueType() : nullptr; + if (!valueTok->hasKnownIntValue() || !valueType || !type || valueType->pointer || type->pointer || + !valueType->isIntegral() || !type->isIntegral() || typeTok->isLiteral()) continue; - if (valueTok->getKnownIntValue() < 0 && valueTok->valueType() && valueTok->valueType()->sign != ValueType::Sign::SIGNED) + + const MathLib::bigint knownValue = valueTok->getKnownIntValue(); + if (knownValue == 0) + continue; // prevent overlap with TestOther::unsignedPositive/unsignedLessThanZero + + const ValueType commonType = ValueType::getCommonIntegralType(type, valueType, mSettings); + if (!commonType.isIntegral() || commonType.type == ValueType::Type::UNKNOWN_INT || + commonType.sign == ValueType::Sign::UNKNOWN_SIGN) continue; - if (typeTok->isLiteral()) + + const std::size_t sourceBits = getBits(*type); + const std::size_t commonBits = getBits(commonType); + if (sourceBits == 0 || sourceBits > 64 || commonBits == 0 || commonBits > 64) continue; - std::uint8_t bits = 0; - switch (typeTok->valueType()->type) { - case ValueType::Type::BOOL: - bits = 1; - break; - case ValueType::Type::CHAR: - bits = mSettings.platform.char_bit; - break; - case ValueType::Type::SHORT: - bits = mSettings.platform.short_bit; - break; - case ValueType::Type::INT: - bits = mSettings.platform.int_bit; - break; - case ValueType::Type::LONG: - bits = mSettings.platform.long_bit; - break; - case ValueType::Type::LONGLONG: - bits = mSettings.platform.long_long_bit; - break; - default: - break; + + bool commonUnsigned = commonType.sign == ValueType::Sign::UNSIGNED; + bool inImage = false; + bool below = false; + bool above = false; + bool atMinimum = false; + bool atMaximum = false; + + if (commonUnsigned) { + MathLib::biguint unsignedValue{}; + MathLib::biguint unsignedMinimum{}; + MathLib::biguint unsignedMaximum{}; + if (!getUnsignedMaximum(commonBits, unsignedMaximum)) + continue; + MathLib::biguint intervalLower[2]{}; + MathLib::biguint intervalUpper[2]{}; + std::size_t intervalCount = 0; + const bool sourceUnsigned = type->sign == ValueType::Sign::UNSIGNED || + type->type == ValueType::Type::BOOL; + if (sourceUnsigned) { + MathLib::biguint sourceMaximum{}; + if (sourceBits > commonBits || !getUnsignedMaximum(sourceBits, sourceMaximum)) + continue; + intervalLower[0] = 0; + intervalUpper[0] = sourceMaximum; + intervalCount = 1; + } else if (type->sign == ValueType::Sign::SIGNED) { + MathLib::bigint sourceMinimum{}, sourceMaximum{}; + if (sourceBits > commonBits || !getSignedBounds(sourceBits, sourceMinimum, sourceMaximum)) + continue; + if (sourceBits == commonBits) { + intervalLower[0] = 0; + intervalUpper[0] = unsignedMaximum; + intervalCount = 1; + } else { + const MathLib::biguint negativeCount = MathLib::biguint{1} << (sourceBits - 1); + intervalLower[0] = 0; + intervalUpper[0] = static_cast(sourceMaximum); + intervalLower[1] = unsignedMaximum - negativeCount + 1; + intervalUpper[1] = unsignedMaximum; + intervalCount = 2; + } + } else { + continue; + } + + unsignedValue = toUnsigned(knownValue, commonBits); + unsignedMinimum = intervalLower[0]; + unsignedMaximum = intervalUpper[intervalCount - 1]; + for (std::size_t interval = 0; interval < intervalCount; ++interval) { + if (unsignedValue >= intervalLower[interval] && unsignedValue <= intervalUpper[interval]) { + inImage = true; + break; + } + } + below = unsignedValue < unsignedMinimum; + above = unsignedValue > unsignedMaximum; + atMinimum = unsignedValue == unsignedMinimum; + atMaximum = unsignedValue == unsignedMaximum; + } else { + MathLib::bigint signedValue{}; + MathLib::bigint signedMinimum{}; + MathLib::bigint signedMaximum{}; + if (!getSignedBounds(commonBits, signedMinimum, signedMaximum)) + continue; + if (type->type == ValueType::Type::BOOL) { + signedMinimum = 0; + signedMaximum = 1; + } else if (type->sign == ValueType::Sign::SIGNED) { + if (sourceBits > commonBits || !getSignedBounds(sourceBits, signedMinimum, signedMaximum)) + continue; + } else if (type->sign == ValueType::Sign::UNSIGNED) { + MathLib::biguint sourceMaximum{}; + if (sourceBits >= commonBits || !getUnsignedMaximum(sourceBits, sourceMaximum) || + sourceMaximum > static_cast(signedMaximum)) + continue; + signedMinimum = 0; + signedMaximum = static_cast(sourceMaximum); + } else { + continue; + } + if (valueType->sign == ValueType::Sign::UNSIGNED && knownValue < 0) + continue; + signedValue = knownValue; + below = signedValue < signedMinimum; + above = signedValue > signedMaximum; + inImage = !below && !above; + atMinimum = signedValue == signedMinimum; + atMaximum = signedValue == signedMaximum; } - if (bits == 0 || bits >= 63) - continue; - const auto typeMinValue = (typeTok->valueType()->sign == ValueType::Sign::UNSIGNED) ? 0 : (-(1LL << (bits-1))); - const auto unsignedTypeMaxValue = (1LL << bits) - 1LL; - long long typeMaxValue; - if (typeTok->valueType()->sign != ValueType::Sign::SIGNED) - typeMaxValue = unsignedTypeMaxValue; - else if (bits >= mSettings.platform.int_bit && (!valueTok->valueType() || valueTok->valueType()->sign != ValueType::Sign::SIGNED)) - typeMaxValue = unsignedTypeMaxValue; - else - typeMaxValue = unsignedTypeMaxValue / 2; + if (tok->hasKnownIntValue() && inImage && sourceBits == commonBits) + continue; bool result{}; - const auto kiv = valueTok->getKnownIntValue(); - if (kiv == 0) - continue; // prevent overlap with TestOther::unsignedPositive/unsignedLessThanZero - if (tok->str() == "==") - result = false; - else if (tok->str() == "!=") - result = true; - else if (tok->str()[0] == '>' && i == 0) - // num > var - result = (kiv > 0); - else if (tok->str()[0] == '>' && i == 1) - // var > num - result = (kiv < 0); - else if (tok->str()[0] == '<' && i == 0) - // num < var - result = (kiv < 0); - else if (tok->str()[0] == '<' && i == 1) - // var < num - result = (kiv > 0); - bool error = false; - if (kiv < typeMinValue || kiv > typeMaxValue) { + const std::string& op = tok->str(); + if (op == "==" || op == "!=") { + error = !inImage; + result = op == "!="; + } else if (below || above) { + error = true; + if (i == 0) + result = below ? (op == "<" || op == "<=") : (op == ">" || op == ">="); + else + result = below ? (op == ">" || op == ">=") : (op == "<" || op == "<="); + } else if (i == 0 && atMinimum && op == "<=") { + error = true; + result = true; + } else if (i == 0 && atMinimum && op == ">") { + error = true; + } else if (i == 0 && atMaximum && op == ">=") { + error = true; + result = true; + } else if (i == 0 && atMaximum && op == "<") { + error = true; + } else if (i == 1 && atMinimum && op == ">=") { + error = true; + result = true; + } else if (i == 1 && atMinimum && op == "<") { + error = true; + } else if (i == 1 && atMaximum && op == "<=") { + error = true; + result = true; + } else if (i == 1 && atMaximum && op == ">") { error = true; - } else { - switch (i) { - case 0: // num cmp var - if (kiv == typeMinValue) { - if (tok->str() == "<=") { - result = true; - error = true; - } else if (tok->str() == ">") - error = true; - } - else if (kiv == typeMaxValue && (tok->str() == ">=" || tok->str() == "<")) { - error = true; - } - break; - case 1: // var cmp num - if (kiv == typeMinValue) { - if (tok->str() == ">=") { - result = true; - error = true; - } else if (tok->str() == "<") - error = true; - } - else if (kiv == typeMaxValue && (tok->str() == "<=" || tok->str() == ">")) { - error = true; - } - break; - } } + if (!error || diag(tok)) continue; - compareValueOutOfTypeRangeError(valueTok, typeTok->valueType()->str(), kiv, result); + compareValueOutOfTypeRangeError(valueTok, + type->str(), + knownValue, + result); } } } diff --git a/lib/checktype.cpp b/lib/checktype.cpp index 447a8924360..5da6a90186d 100644 --- a/lib/checktype.cpp +++ b/lib/checktype.cpp @@ -98,12 +98,49 @@ void CheckTypeImpl::checkTooBigBitwiseShift() else continue; - // Get biggest rhs value. preferably a value which doesn't have 'condition'. - const ValueFlow::Value * value = tok->astOperand2()->getValueGE(lhsbits, mSettings); + // Get the biggest feasible rhs value, preferring a value without a condition. + const Token* const shiftCount = tok->astOperand2(); + const auto isUnreachableTernaryValue = [&](const ValueFlow::Value& candidate) { + const Token* child = shiftCount; + for (const Token* parent = child->astParent(); parent; child = parent, parent = parent->astParent()) { + if (parent->str() != ":" || !Token::simpleMatch(parent->astParent(), "?")) + continue; + const bool inTrueBranch = child == parent->astOperand1(); + const bool inFalseBranch = child == parent->astOperand2(); + if (!inTrueBranch && !inFalseBranch) + continue; + const Token* condition = parent->astParent()->astOperand1(); + bool canEnterBranch = false; + bool canEnterOtherBranch = false; + for (const ValueFlow::Value& value : condition->values()) { + if (!value.isIntValue() || value.isImpossible() || value.bound != ValueFlow::Value::Bound::Point || + (value.path != 0 && (candidate.path == 0 || value.path != candidate.path))) + continue; + if ((value.intvalue != 0) == inTrueBranch) + canEnterBranch = true; + else + canEnterOtherBranch = true; + } + return canEnterOtherBranch && !canEnterBranch; + } + return false; + }; + const auto getShiftCountValueGE = [&](MathLib::bigint threshold) -> const ValueFlow::Value* { + return ValueFlow::findValue(shiftCount->values(), mSettings, [&](const ValueFlow::Value& candidate) { + if (candidate.isImpossible() || !candidate.isIntValue() || candidate.intvalue < threshold || + isUnreachableTernaryValue(candidate)) + return false; + return std::none_of(shiftCount->values().cbegin(), shiftCount->values().cend(), [&](const ValueFlow::Value& bound) { + return bound.isImpossible() && bound.isIntValue() && bound.bound == ValueFlow::Value::Bound::Lower && + bound.intvalue <= threshold && (bound.path == 0 || bound.path == candidate.path); + }); + }); + }; + const ValueFlow::Value * value = getShiftCountValueGE(lhsbits); if (value && mSettings.isEnabled(value, false)) tooBigBitwiseShiftError(tok, lhsbits, *value); else if (lhstype->sign == ValueType::Sign::SIGNED) { - value = tok->astOperand2()->getValueGE(lhsbits-1, mSettings); + value = getShiftCountValueGE(lhsbits - 1); if (value && mSettings.isEnabled(value, false)) tooBigSignedBitwiseShiftError(tok, lhsbits, *value); } @@ -493,15 +530,18 @@ void CheckTypeImpl::checkFloatToIntegerOverflow(const Token *tok, const ValueTyp return; for (const ValueFlow::Value &f : floatValues) { - if (f.valueType != ValueFlow::Value::ValueType::FLOAT) + if (f.valueType != ValueFlow::Value::ValueType::FLOAT || f.isImpossible()) continue; if (!mSettings.isEnabled(&f, false)) continue; - if (f.floatValue >= std::exp2(mSettings.platform.long_long_bit)) + + const bool positiveEvidence = f.bound != ValueFlow::Value::Bound::Upper; + const bool negativeEvidence = f.bound != ValueFlow::Value::Bound::Lower; + if (positiveEvidence && f.floatValue >= std::exp2(mSettings.platform.long_long_bit)) floatToIntegerOverflowError(tok, f); - else if ((-f.floatValue) > std::exp2(mSettings.platform.long_long_bit - 1)) + else if (negativeEvidence && (-f.floatValue) > std::exp2(mSettings.platform.long_long_bit - 1)) floatToIntegerOverflowError(tok, f); - else if (mSettings.platform.type != Platform::Type::Unspecified) { + else if (positiveEvidence && mSettings.platform.type != Platform::Type::Unspecified) { int bits = 0; if (vtint->type == ValueType::Type::CHAR) bits = mSettings.platform.char_bit; diff --git a/lib/infer.cpp b/lib/infer.cpp index cad0761f68b..1d7d8083400 100644 --- a/lib/infer.cpp +++ b/lib/infer.cpp @@ -237,12 +237,32 @@ namespace { }; } -static void addToErrorPath(ValueFlow::Value& value, const std::vector& refs) +static bool addToErrorPath(ValueFlow::Value& value, const std::vector& refs) { std::unordered_set locations; + const Token* condition = nullptr; + MathLib::bigint path = 0; + nonneg int varId = 0; + MathLib::bigint varvalue = 0; + bool conflictingVariable = false; for (const ValueFlow::Value* ref : refs) { - if (ref->condition && !value.condition) - value.condition = ref->condition; + if (ref->path < 0 || (ref->path > 0 && path > 0 && ref->path != path)) + return false; + if (ref->path > 0) + path = ref->path; + if (ref->condition && !condition) + condition = ref->condition; + if (ref->varId) { + if (varId && (varId != ref->varId || varvalue != ref->varvalue)) + conflictingVariable = true; + else { + varId = ref->varId; + varvalue = ref->varvalue; + } + } + value.conditional = value.conditional || ref->conditional; + value.defaultArg = value.defaultArg || ref->defaultArg; + value.safe = value.safe || ref->safe; std::copy_if(ref->errorPath.cbegin(), ref->errorPath.cend(), std::back_inserter(value.errorPath), @@ -256,6 +276,16 @@ static void addToErrorPath(ValueFlow::Value& value, const std::vector& refs) @@ -270,17 +300,26 @@ static void setValueKind(ValueFlow::Value& value, const std::vector 0) value.setPossible(); else value.setKnown(); } -static bool inferNotEqual(const std::list& values, MathLib::bigint x) +static const ValueFlow::Value* findImpossiblePoint(const ValuePtr& model, + const std::list& values, + const std::vector& scalarRefs, + MathLib::bigint x) { - return std::any_of(values.cbegin(), values.cend(), [&](const ValueFlow::Value& value) { - return value.isImpossible() && value.intvalue == x; + const auto it = std::find_if(values.cbegin(), values.cend(), [&](const ValueFlow::Value& value) { + if (!model->match(value) || !value.isImpossible() || value.bound != ValueFlow::Value::Bound::Point || + value.intvalue != x) + return false; + return std::any_of(scalarRefs.cbegin(), scalarRefs.cend(), [&](const ValueFlow::Value* ref) { + return model->match(*ref) && value.equalValue(*ref); + }); }); + return it == values.cend() ? nullptr : &*it; } std::vector infer(const ValuePtr& model, @@ -288,75 +327,138 @@ std::vector infer(const ValuePtr& model, std::list lhsValues, std::list rhsValues) { - std::vector result; auto notMatch = [&](const ValueFlow::Value& value) { return !model->match(value); }; lhsValues.remove_if(notMatch); if (lhsValues.empty()) - return result; + return {}; rhsValues.remove_if(notMatch); if (rhsValues.empty()) - return result; - - Interval lhs = Interval::fromValues(lhsValues); - Interval rhs = Interval::fromValues(rhsValues); - - if (op == "-") { - Interval diff = lhs - rhs; - if (diff.isScalar()) { - std::vector refs = diff.getScalarRef(); - ValueFlow::Value value(diff.getScalar()); - addToErrorPath(value, refs); - setValueKind(value, refs); - result.push_back(std::move(value)); - } else { - if (!diff.minvalue.empty()) { - ValueFlow::Value value(diff.minvalue.front() - 1); - value.setImpossible(); - value.bound = ValueFlow::Value::Bound::Upper; - addToErrorPath(value, diff.minRef); - result.push_back(std::move(value)); + return {}; + + lhsValues.remove_if([](const ValueFlow::Value& value) { + return value.path < 0; + }); + rhsValues.remove_if([](const ValueFlow::Value& value) { + return value.path < 0; + }); + if (lhsValues.empty() || rhsValues.empty()) + return {}; + + const auto inferIntervals = [&](const std::list& lhsValues2, + const std::list& rhsValues2) { + std::vector inferred; + Interval lhs = Interval::fromValues(lhsValues2); + Interval rhs = Interval::fromValues(rhsValues2); + + const ValueFlow::Value* excludedPoint = nullptr; + std::vector excludedRefs; + if (op == "!=" || op == "==") { + if (lhs.isScalar()) { + std::vector scalarRefs = lhs.getScalarRef(); + excludedPoint = findImpossiblePoint(model, rhsValues2, scalarRefs, lhs.getScalar()); + if (excludedPoint) + excludedRefs = std::move(scalarRefs); } - if (!diff.maxvalue.empty()) { - ValueFlow::Value value(diff.maxvalue.front() + 1); - value.setImpossible(); - value.bound = ValueFlow::Value::Bound::Lower; - addToErrorPath(value, diff.maxRef); - result.push_back(std::move(value)); + if (!excludedPoint && rhs.isScalar()) { + std::vector scalarRefs = rhs.getScalarRef(); + excludedPoint = findImpossiblePoint(model, lhsValues2, scalarRefs, rhs.getScalar()); + if (excludedPoint) + excludedRefs = std::move(scalarRefs); } + if (excludedPoint) + excludedRefs.push_back(excludedPoint); } - } else if ((op == "!=" || op == "==") && lhs.isScalarOrEmpty() && rhs.isScalarOrEmpty()) { - if (lhs.isScalar() && rhs.isScalar()) { - std::vector refs = Interval::merge(lhs.getScalarRef(), rhs.getScalarRef()); - ValueFlow::Value value(calculate(op, lhs.getScalar(), rhs.getScalar())); - addToErrorPath(value, refs); - setValueKind(value, refs); - result.push_back(std::move(value)); + if (op == "-") { + Interval diff = lhs - rhs; + if (diff.isScalar()) { + std::vector refs = diff.getScalarRef(); + ValueFlow::Value value(diff.getScalar()); + if (addToErrorPath(value, refs)) { + setValueKind(value, refs); + inferred.push_back(std::move(value)); + } + } else { + if (!diff.minvalue.empty()) { + ValueFlow::Value value(diff.minvalue.front() - 1); + value.setImpossible(); + value.bound = ValueFlow::Value::Bound::Upper; + if (addToErrorPath(value, diff.minRef)) + inferred.push_back(std::move(value)); + } + if (!diff.maxvalue.empty()) { + ValueFlow::Value value(diff.maxvalue.front() + 1); + value.setImpossible(); + value.bound = ValueFlow::Value::Bound::Lower; + if (addToErrorPath(value, diff.maxRef)) + inferred.push_back(std::move(value)); + } + } + } else if (excludedPoint) { + ValueFlow::Value value(op == "!="); + if (addToErrorPath(value, excludedRefs)) { + setValueKind(value, excludedRefs); + inferred.push_back(std::move(value)); + } + } else if ((op == "!=" || op == "==") && lhs.isScalarOrEmpty() && rhs.isScalarOrEmpty()) { + if (lhs.isScalar() && rhs.isScalar()) { + std::vector refs = Interval::merge(lhs.getScalarRef(), rhs.getScalarRef()); + ValueFlow::Value value(calculate(op, lhs.getScalar(), rhs.getScalar())); + if (addToErrorPath(value, refs)) { + setValueKind(value, refs); + inferred.push_back(std::move(value)); + } + } } else { std::vector refs; - if (lhs.isScalar() && inferNotEqual(rhsValues, lhs.getScalar())) - refs = lhs.getScalarRef(); - else if (rhs.isScalar() && inferNotEqual(lhsValues, rhs.getScalar())) - refs = rhs.getScalarRef(); - if (!refs.empty()) { - ValueFlow::Value value(op == "!="); - addToErrorPath(value, refs); - setValueKind(value, refs); - result.push_back(std::move(value)); + std::vector r = Interval::compare(op, lhs, rhs, &refs); + if (!r.empty()) { + ValueFlow::Value value(static_cast(r.front())); + if (addToErrorPath(value, refs)) { + setValueKind(value, refs); + inferred.push_back(std::move(value)); + } } } - } else { - std::vector refs; - std::vector r = Interval::compare(op, lhs, rhs, &refs); - if (!r.empty()) { - ValueFlow::Value value(static_cast(r.front())); - addToErrorPath(value, refs); - setValueKind(value, refs); - result.push_back(std::move(value)); - } + return inferred; + }; + + std::vector paths; + const auto addPath = [&](MathLib::bigint path) { + if (std::find(paths.cbegin(), paths.cend(), path) == paths.cend()) + paths.push_back(path); + }; + const auto hasCompatibleValue = [](const std::list& values, MathLib::bigint path) { + return std::any_of(values.cbegin(), values.cend(), [path](const ValueFlow::Value& value) { + return value.path == 0 || value.path == path; + }); + }; + if (hasCompatibleValue(lhsValues, 0) && hasCompatibleValue(rhsValues, 0)) + addPath(0); + for (const ValueFlow::Value& value : lhsValues) { + if (value.path > 0 && hasCompatibleValue(rhsValues, value.path)) + addPath(value.path); + } + for (const ValueFlow::Value& value : rhsValues) { + if (value.path > 0 && hasCompatibleValue(lhsValues, value.path)) + addPath(value.path); } + std::vector result; + if (paths.size() == 1 && paths.front() == 0) + return inferIntervals(lhsValues, rhsValues); + for (MathLib::bigint path : paths) { + std::list lhsCompatible; + std::list rhsCompatible; + const auto compatible = [path](const ValueFlow::Value& value) { + return path == 0 ? value.path == 0 : (value.path == 0 || value.path == path); + }; + std::copy_if(lhsValues.cbegin(), lhsValues.cend(), std::back_inserter(lhsCompatible), compatible); + std::copy_if(rhsValues.cbegin(), rhsValues.cend(), std::back_inserter(rhsCompatible), compatible); + std::vector inferred = inferIntervals(lhsCompatible, rhsCompatible); + std::move(inferred.begin(), inferred.end(), std::back_inserter(result)); + } return result; } diff --git a/lib/infer.h b/lib/infer.h index 8d66d7da016..ecd93baca74 100644 --- a/lib/infer.h +++ b/lib/infer.h @@ -43,10 +43,10 @@ struct InferModel { InferModel() = default; }; -std::vector infer(const ValuePtr& model, - const std::string& op, - std::list lhsValues, - std::list rhsValues); +CPPCHECKLIB std::vector infer(const ValuePtr& model, + const std::string& op, + std::list lhsValues, + std::list rhsValues); std::vector infer(const ValuePtr& model, const std::string& op, @@ -61,7 +61,7 @@ std::vector infer(const ValuePtr& model, CPPCHECKLIB std::vector getMinValue(const ValuePtr& model, const std::list& values); std::vector getMaxValue(const ValuePtr& model, const std::list& values); -ValuePtr makeIntegralInferModel(); +CPPCHECKLIB ValuePtr makeIntegralInferModel(); ValueFlow::Value inferCondition(const std::string& op, const Token* varTok, MathLib::bigint val); diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index c2ce2db812b..b05bd05fd5d 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -7426,10 +7426,17 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const return; } - if (vt1->isTypeEqual(vt2)) { + if (vt1->isTypeEqual(vt2) && vt1->sign == vt2->sign) { setValueType(parent, *vt1); return; } + + if (vt1->pointer == 0U && vt2 && vt2->pointer == 0U && vt1->isIntegral() && vt2->isIntegral()) { + const ValueType commonType = ValueType::getCommonIntegralType(vt1, vt2, mSettings); + if (commonType.type != ValueType::Type::UNKNOWN_TYPE) + setValueType(parent, commonType); + return; + } } if (vt1->pointer != 0U) { @@ -7469,37 +7476,93 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const if (vt1->isIntegral() && vt1->pointer == 0U && (!vt2 || (vt2->isIntegral() && vt2->pointer == 0U)) && (ternary || parent->isArithmeticalOp() || parent->tokType() == Token::eBitOp || parent->tokType() == Token::eIncDecOp || parent->isAssignmentOp())) { - - ValueType vt; - if (!vt2 || vt1->type > vt2->type) { - vt.type = vt1->type; - vt.sign = vt1->sign; - vt.originalTypeName = vt1->originalTypeName; - } else if (vt1->type == vt2->type) { - vt.type = vt1->type; - if (vt1->sign == ValueType::Sign::UNSIGNED || vt2->sign == ValueType::Sign::UNSIGNED) - vt.sign = ValueType::Sign::UNSIGNED; - else if (vt1->sign == ValueType::Sign::UNKNOWN_SIGN || vt2->sign == ValueType::Sign::UNKNOWN_SIGN) - vt.sign = ValueType::Sign::UNKNOWN_SIGN; - else - vt.sign = ValueType::Sign::SIGNED; - vt.originalTypeName = (vt1->originalTypeName.empty() ? vt2 : vt1)->originalTypeName; - } else { - vt.type = vt2->type; - vt.sign = vt2->sign; - vt.originalTypeName = vt2->originalTypeName; + if (parent->isAssignmentOp()) { + setValueType(parent, *vt1); + return; } - if (vt.type < ValueType::Type::INT && !(ternary && vt.type==ValueType::Type::BOOL)) { - vt.type = ValueType::Type::INT; - vt.sign = ValueType::Sign::SIGNED; - vt.originalTypeName.clear(); + if (parent->tokType() == Token::eIncDecOp) { + setValueType(parent, *vt1); + return; } - - setValueType(parent, vt); + if (parent->tokType() == Token::eBitOp && Token::Match(parent, "<<|>>")) { + setValueType(parent, ValueType::getCommonIntegralType(vt1, nullptr, mSettings)); + return; + } + const ValueType vt = ValueType::getCommonIntegralType(vt1, vt2, mSettings); + if (vt.type != ValueType::Type::UNKNOWN_TYPE) + setValueType(parent, vt); return; } } +ValueType ValueType::getCommonIntegralType(const ValueType* lhs, const ValueType* rhs, const Settings& settings) +{ + if (!lhs || !lhs->isIntegral() || lhs->pointer || (rhs && (!rhs->isIntegral() || rhs->pointer))) + return {}; + + const auto getBits = [&](const ValueType& type) { + return type.getSizeOf(settings, Accuracy::ExactOrZero, SizeOf::Pointee) * settings.platform.char_bit; + }; + const auto promote = [&](const ValueType& type) { + if (type.type == Type::BOOL) + return ValueType(Sign::SIGNED, Type::INT, 0); + if (type.type != Type::CHAR && type.type != Type::SHORT && type.type != Type::WCHAR_T) + return type; + + ValueType result(Sign::UNKNOWN_SIGN, Type::INT, 0); + const std::size_t sourceBits = getBits(type); + const std::size_t intBits = settings.platform.int_bit; + if (sourceBits == 0 || intBits == 0) + return result; + if (type.sign == Sign::SIGNED) + result.sign = sourceBits <= intBits ? Sign::SIGNED : Sign::UNSIGNED; + else if (type.sign == Sign::UNSIGNED) + result.sign = sourceBits < intBits ? Sign::SIGNED : Sign::UNSIGNED; + else if (sourceBits < intBits) + result.sign = Sign::SIGNED; + return result; + }; + + const ValueType first = promote(*lhs); + if (!rhs) + return first; + const ValueType second = promote(*rhs); + if (first.type == Type::UNKNOWN_INT || second.type == Type::UNKNOWN_INT) { + if (first.type == second.type && first.sign == second.sign) + return first; + const Sign commonSign = first.sign == second.sign ? first.sign : Sign::UNKNOWN_SIGN; + return ValueType(commonSign, Type::UNKNOWN_INT, 0); + } + const ValueType* const higherRank = first.type >= second.type ? &first : &second; + ValueType result(Sign::UNKNOWN_SIGN, higherRank->type, 0); + result.originalTypeName = higherRank->originalTypeName; + + if (first.sign == second.sign) { + result.sign = first.sign; + return result; + } + if (first.sign == Sign::UNKNOWN_SIGN || second.sign == Sign::UNKNOWN_SIGN) + return result; + + const ValueType& unsignedType = first.sign == Sign::UNSIGNED ? first : second; + const ValueType& signedType = first.sign == Sign::SIGNED ? first : second; + if (unsignedType.type >= signedType.type) { + result.type = unsignedType.type; + result.sign = Sign::UNSIGNED; + result.originalTypeName = unsignedType.originalTypeName; + } else if (getBits(signedType) > getBits(unsignedType)) { + result.type = signedType.type; + result.sign = Sign::SIGNED; + result.originalTypeName = signedType.originalTypeName; + } else { + result.type = signedType.type; + result.sign = Sign::UNSIGNED; + result.originalTypeName.clear(); + } + return result; +} + + static ValueType::Type getEnumType(const Scope* scope, const Platform& platform) // TODO: also determine sign? { ValueType::Type type = ValueType::Type::INT; diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 08c5382929e..d6c6231abbb 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -1333,6 +1333,7 @@ class CPPCHECKLIB ValueType { Pointee, }; size_t getSizeOf(const Settings& settings, Accuracy accuracy, SizeOf sizeOf, int maxRecursion = 0) const; + static ValueType getCommonIntegralType(const ValueType* lhs, const ValueType* rhs, const Settings& settings); /// Check if type is the same ignoring const and references bool isTypeEqual(const ValueType* that) const; diff --git a/lib/token.cpp b/lib/token.cpp index eac69decee7..49c96d13d44 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -28,6 +28,7 @@ #include "utils.h" #include "tokenrange.h" #include "valueflow.h" +#include "vf_common.h" #include #include @@ -1928,8 +1929,11 @@ const ValueFlow::Value * Token::getValueLE(const MathLib::bigint val, const Sett { if (!mImpl->mValues) return nullptr; + const bool unsignedMayWrap = ValueFlow::isUnsignedArithmeticResult(this) && + !ValueFlow::isUnsignedArithmeticNoWrap(this, settings, true); return ValueFlow::findValue(*mImpl->mValues, settings, [&](const ValueFlow::Value& v) { - return !v.isImpossible() && v.isIntValue() && v.intvalue <= val; + return !v.isImpossible() && v.isIntValue() && v.intvalue <= val && + (!unsignedMayWrap || v.bound != ValueFlow::Value::Bound::Lower); }); } diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index c021d1d296b..b7a3f2476e1 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -113,6 +113,7 @@ #include #include #include +#include #include #include #include @@ -420,10 +421,12 @@ void ValueFlow::combineValueProperties(const ValueFlow::Value &value1, const Val if (value1.bound == ValueFlow::Value::Bound::Lower || value2.bound == ValueFlow::Value::Bound::Lower) result.bound = ValueFlow::Value::Bound::Lower; } - if (value1.path != value2.path) - result.path = -1; - else + if (value1.path == 0) + result.path = value2.path; + else if (value2.path == 0 || value1.path == value2.path) result.path = value1.path; + else + result.path = -1; } static void valueFlowNumber(TokenList &tokenlist, const Settings& settings) @@ -1025,7 +1028,7 @@ static void valueFlowRightShift(TokenList& tokenList, const Settings& settings) } } -static std::vector minUnsignedValue(const Token* tok, int depth = 8) +static std::vector minUnsignedValue(const Token* tok, const Settings& settings, int depth = 8) { std::vector result; if (!tok) @@ -1034,10 +1037,11 @@ static std::vector minUnsignedValue(const Token* tok, int depth return result; if (const ValueFlow::Value* v = tok->getKnownValue(ValueFlow::Value::ValueType::INT)) { result = {v->intvalue}; - } else if (!Token::Match(tok, "-|%|&|^") && tok->isConstOp() && tok->astOperand1() && tok->astOperand2()) { - std::vector op1 = minUnsignedValue(tok->astOperand1(), depth - 1); + } else if (!Token::Match(tok, "-|%|&|^") && tok->isConstOp() && tok->astOperand1() && tok->astOperand2() && + (!ValueFlow::isUnsignedArithmeticResult(tok) || ValueFlow::isUnsignedArithmeticNoWrap(tok, settings, true))) { + std::vector op1 = minUnsignedValue(tok->astOperand1(), settings, depth - 1); if (!op1.empty()) { - std::vector op2 = minUnsignedValue(tok->astOperand2(), depth - 1); + std::vector op2 = minUnsignedValue(tok->astOperand2(), settings, depth - 1); if (!op2.empty()) { result = calculate>(tok->str(), op1.front(), op2.front()); } @@ -1087,8 +1091,26 @@ static void valueFlowImpossibleValues(TokenList& tokenList, const Settings& sett upper.bound = ValueFlow::Value::Bound::Lower; upper.setImpossible(); setTokenValue(tok, std::move(upper), settings); + } else if (tok->isCast() && tok->valueType() && tok->valueType()->isIntegral() && !tok->valueType()->pointer) { + MathLib::bigint minValue; + MathLib::bigint maxValue; + if (!ValueFlow::getMinMaxValues(tok->valueType(), settings.platform, minValue, maxValue)) + continue; + + if (minValue > std::numeric_limits::min()) { + ValueFlow::Value lower{minValue - 1}; + lower.bound = ValueFlow::Value::Bound::Upper; + lower.setImpossible(); + setTokenValue(tok, std::move(lower), settings); + } + if (maxValue < std::numeric_limits::max()) { + ValueFlow::Value upper{maxValue + 1}; + upper.bound = ValueFlow::Value::Bound::Lower; + upper.setImpossible(); + setTokenValue(tok, std::move(upper), settings); + } } else if (astIsUnsigned(tok) && !astIsPointer(tok)) { - std::vector minvalue = minUnsignedValue(tok); + std::vector minvalue = minUnsignedValue(tok, settings); if (minvalue.empty()) continue; ValueFlow::Value value{std::max(0, minvalue.front()) - 1}; @@ -4097,41 +4119,82 @@ static std::list truncateValues(std::list va const ValueType* src, const Settings& settings) { - if (!dst || !dst->isIntegral()) + if (!dst || !dst->isIntegral() || dst->pointer) return values; - const size_t sz = dst->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); - - if (src) { - const size_t osz = src->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); - if (osz >= sz && dst->sign == ValueType::Sign::SIGNED && src->sign == ValueType::Sign::UNSIGNED) { - values.remove_if([&](const ValueFlow::Value& value) { - if (!value.isIntValue()) - return false; - if (!value.isImpossible()) - return false; - if (value.bound != ValueFlow::Value::Bound::Upper) - return false; - if (osz == sz && value.intvalue < 0) - return true; - if (osz > sz) - return true; - return false; - }); - } - } - - for (ValueFlow::Value &value : values) { - // Don't truncate impossible values since those can be outside of the valid range - if (value.isImpossible()) + const int bits = static_cast(dst->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit); + for (auto it = values.begin(); it != values.end();) { + ValueFlow::Value& value = *it; + if (value.isSymbolicValue() || value.isImpossible() || value.bound != ValueFlow::Value::Bound::Point) { + const ValueType* const conversionSource = value.isSymbolicValue() && value.tokvalue && value.tokvalue->valueType() + ? value.tokvalue->valueType() + : src; + if (value.isSymbolicValue() && dst->type == ValueType::Type::BOOL && conversionSource && + conversionSource->pointer > 0 && value.isKnown() && value.bound == ValueFlow::Value::Bound::Point && + value.intvalue == 0) { + ++it; + continue; + } + if (value.isFloatValue()) { + if (!ValueFlow::castValue(value, dst->sign, bits)) + it = values.erase(it); + else + ++it; + } else if (!ValueFlow::isValueRangePreservingConversion(conversionSource, dst, settings, &value)) { + it = values.erase(it); + } else if (value.isImpossible() && value.isIntValue() && + value.bound == ValueFlow::Value::Bound::Point && src && + src->sign == ValueType::Sign::SIGNED && dst->sign == ValueType::Sign::UNSIGNED && + dst->type != ValueType::Type::BOOL && !ValueFlow::castValue(value, dst->sign, bits)) { + it = values.erase(it); + } else { + ++it; + } continue; - if (value.isFloatValue()) { - value.intvalue = static_cast(value.floatValue); + } + if (dst->type == ValueType::Type::BOOL && (value.isIntValue() || value.isFloatValue())) { + value.intvalue = value.isIntValue() ? value.intvalue != 0 : std::fpclassify(value.floatValue) != FP_ZERO; value.valueType = ValueFlow::Value::ValueType::INT; + // Keep target-specific point results possible when plain char signedness is unknown. + } else if (dst->type == ValueType::Type::CHAR && dst->sign == ValueType::Sign::UNKNOWN_SIGN && + value.isIntValue() && value.bound == ValueFlow::Value::Bound::Point) { + const MathLib::bigint originalValue = value.intvalue; + ValueFlow::Value unsignedValue = value; + if (!ValueFlow::castValue(unsignedValue, ValueType::Sign::UNSIGNED, bits)) { + it = values.erase(it); + continue; + } + ValueType signedCharType(ValueType::Sign::SIGNED, ValueType::Type::CHAR, 0); + MathLib::bigint minValue{}, maxValue{}; + const bool signedRepresentable = ValueFlow::getMinMaxValues(&signedCharType, settings.platform, minValue, maxValue) && + originalValue >= minValue && originalValue <= maxValue; + if (signedRepresentable && unsignedValue.intvalue == originalValue) { + ++it; + continue; + } + auto next = it; + ++next; + value = std::move(unsignedValue); + value.setPossible(); + if (signedRepresentable) { + ValueFlow::Value signedValue = value; + signedValue.intvalue = originalValue; + signedValue.setPossible(); + values.insert(next, std::move(signedValue)); + } + it = next; + continue; + // Zero is representable in every integral type, even when its size is unknown. + } else if ((value.isIntValue() || value.isFloatValue()) && + !(value.isIntValue() && value.intvalue == 0) && + !ValueFlow::isValueRangePreservingConversion(src, dst, settings, &value) && + !ValueFlow::castValue(value, dst->sign, bits)) { + it = values.erase(it); + continue; } - - if (value.isIntValue() && sz > 0 && sz < sizeof(MathLib::biguint)) - value.intvalue = ValueFlow::truncateIntValue(value.intvalue, sz, dst->sign); + ++it; } return values; } @@ -4213,16 +4276,15 @@ static void valueFlowAfterAssign(const TokenList &tokenlist, std::list values = truncateValues( rhs->values(), tok->astOperand1()->valueType(), rhs->valueType(), settings); - // Remove known values - std::set types; - if (tok->astOperand1()->hasKnownValue()) { - for (const ValueFlow::Value& value:tok->astOperand1()->values()) { - if (value.isKnown() && !value.isSymbolicValue()) - types.insert(value.valueType); - } - } + // Remove only values already represented by an identical known value on the LHS. values.remove_if([&](const ValueFlow::Value& value) { - return types.count(value.valueType) > 0; + if (!value.isKnown() || value.isSymbolicValue()) + return false; + return std::any_of(tok->astOperand1()->values().cbegin(), + tok->astOperand1()->values().cend(), + [&](const ValueFlow::Value& knownValue) { + return knownValue.isKnown() && !knownValue.isSymbolicValue() && knownValue.equalValue(value); + }); }); // Remove container size if its not a container - unless the size records its container // and flows into a pointer to the container data (e.g. p = v.data()) @@ -4420,10 +4482,12 @@ struct ConditionHandler { return it->path; } - MathLib::bigint getPath() const - { - assert(std::abs(findPath(true_values) - findPath(false_values)) == 0); - return findPath(true_values) | findPath(false_values); + MathLib::bigint getPath() const { + const MathLib::bigint truePath = findPath(true_values); + const MathLib::bigint falsePath = findPath(false_values); + assert(true_values.empty() || false_values.empty() || + std::abs(truePath - falsePath) == 0); + return truePath | falsePath; } Token* getContextAndValues(Token* condTok, @@ -4522,7 +4586,7 @@ struct ConditionHandler { continue; if (cond.vartok->hasKnownIntValue()) continue; - if (cond.true_values.empty() || cond.false_values.empty()) + if (cond.true_values.empty() && cond.false_values.empty()) continue; if (!isConstExpression(cond.vartok, settings.library)) continue; @@ -4895,7 +4959,8 @@ struct ConditionHandler { bailBlock = i; changeKnownToPossible(values); } - if (changeBlock >= 0 && !Token::simpleMatch(top->previous(), "while (")) { + if (changeBlock >= 0 && + (!Token::simpleMatch(top->previous(), "while (") || astIsFloat(cond.vartok, false))) { if (settings.debugwarnings) bailout(tokenlist, errorLogger, @@ -5074,6 +5139,39 @@ struct SimpleConditionHandler : ConditionHandler { if (!conds.empty()) return conds; + if (tok->isComparisonOp() && tok->astOperand1() && tok->astOperand2() && + (tok->str() == ">=" || tok->str() == "<=")) { + for (bool lhs : {true, false}) { + const Token* vartok = lhs ? tok->astOperand1() : tok->astOperand2(); + const Token* valuetok = lhs ? tok->astOperand2() : tok->astOperand1(); + if (!astIsFloat(vartok, false) || !valuetok->isLiteral()) + continue; + const ValueFlow::Value* value = valuetok->getKnownValue(ValueFlow::Value::ValueType::FLOAT); + if (!value || !value->isKnown()) + continue; + + const bool lower = lhs == (tok->str() == ">="); + const ValueFlow::Value::Bound bound = lower ? ValueFlow::Value::Bound::Lower + : ValueFlow::Value::Bound::Upper; + Condition cond; + cond.vartok = vartok; + cond.impossible = false; + + ValueFlow::Value trueValue = *value; + trueValue.bound = bound; + trueValue.assumeCondition(tok); + trueValue.setPossible(); + cond.true_values.push_back(std::move(trueValue)); + + ValueFlow::Value falseValue = *value; + falseValue.bound = bound; + falseValue.assumeCondition(tok); + falseValue.setImpossible(); + cond.false_values.push_back(std::move(falseValue)); + return {std::move(cond)}; + } + } + const Token* vartok = getConditionVariable(tok); if (!vartok) return {}; @@ -5158,8 +5256,25 @@ static void valueFlowInferCondition(TokenList& tokenlist, const Settings& settin } } } else if (isIntegralOrPointer(tok->astOperand1()) && isIntegralOrPointer(tok->astOperand2())) { - std::vector result = - infer(makeIntegralInferModel(), tok->str(), tok->astOperand1()->values(), tok->astOperand2()->values()); + const bool isUnsignedArith = ValueFlow::isUnsignedArithmeticResult(tok); + const bool mayWrap = isUnsignedArith && !ValueFlow::isUnsignedArithmeticNoWrap(tok, settings, false); + std::vector result; + const auto isIntegralRange = [](const ValueFlow::Value& value) { + return value.bound != ValueFlow::Value::Bound::Point && + (value.isIntValue() || value.isSymbolicValue()); + }; + if (mayWrap) { + std::list lhsValues = tok->astOperand1()->values(); + std::list rhsValues = tok->astOperand2()->values(); + lhsValues.remove_if(isIntegralRange); + rhsValues.remove_if(isIntegralRange); + result = infer(makeIntegralInferModel(), tok->str(), std::move(lhsValues), std::move(rhsValues)); + } else { + result = infer(makeIntegralInferModel(), + tok->str(), + tok->astOperand1()->values(), + tok->astOperand2()->values()); + } for (ValueFlow::Value& value : result) { setTokenValue(tok, std::move(value), settings); } diff --git a/lib/vf_common.cpp b/lib/vf_common.cpp index f9eecc20fb9..ed3502c9455 100644 --- a/lib/vf_common.cpp +++ b/lib/vf_common.cpp @@ -34,8 +34,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -46,7 +48,7 @@ namespace ValueFlow if (!vt || !vt->isIntegral() || vt->pointer) return false; - std::uint8_t bits; + std::size_t bits; switch (vt->type) { case ValueType::Type::BOOL: bits = 1; @@ -66,10 +68,16 @@ namespace ValueFlow case ValueType::Type::LONGLONG: bits = platform.long_long_bit; break; + case ValueType::Type::WCHAR_T: + bits = platform.sizeof_wchar_t * platform.char_bit; + break; default: return false; } + if (bits == 0) { + return false; + } if (bits == 1) { minValue = 0; maxValue = 1; @@ -77,24 +85,747 @@ namespace ValueFlow if (vt->sign == ValueType::Sign::UNSIGNED) { minValue = 0; maxValue = (1LL << bits) - 1; - } else { + } else if (vt->sign == ValueType::Sign::SIGNED) { minValue = -(1LL << (bits - 1)); maxValue = (1LL << (bits - 1)) - 1; - } + } else + return false; } else if (bits == 64) { if (vt->sign == ValueType::Sign::UNSIGNED) { minValue = 0; - maxValue = LLONG_MAX; // todo max unsigned value - } else { + maxValue = LLONG_MAX; // MathLib::bigint cannot represent ULLONG_MAX; conservative max for unsigned 64-bit + } else if (vt->sign == ValueType::Sign::SIGNED) { minValue = LLONG_MIN; maxValue = LLONG_MAX; - } + } else + return false; } else { return false; } return true; } + static bool convertFloatToInteger(MathLib::bigint& result, + double value, + ValueType::Sign sign, + nonneg int bit) + { + if (!std::isfinite(value) || bit <= 0 || sign == ValueType::Sign::UNKNOWN_SIGN) + return false; + const double truncatedValue = std::trunc(value); + const int destinationBits = sign == ValueType::Sign::SIGNED ? bit - 1 : bit; + const int carrierBits = std::numeric_limits::digits; + const double upperExclusive = std::ldexp(1.0, std::min(destinationBits, carrierBits)); + if (sign == ValueType::Sign::SIGNED) { + if (truncatedValue < -upperExclusive || truncatedValue >= upperExclusive) + return false; + } else if (truncatedValue < 0.0 || truncatedValue >= upperExclusive) { + return false; + } + result = static_cast(truncatedValue); + return true; + } + + + bool isValueRangePreservingConversion(const ValueType* source, + const ValueType* destination, + const Settings& settings, + const Value* range) + { + // Non-null pointers convert to true; other pointer-to-integer mappings are not injective. + if (source && source->pointer > 0 && destination && destination->type == ValueType::Type::BOOL && + !destination->pointer && range && range->isIntValue() && range->isImpossible() && + range->bound == Value::Bound::Point && range->intvalue == 0) + return true; + if (!source || !destination || !destination->isIntegral() || source->pointer || destination->pointer) + return false; + if (source->isFloat()) { + if (!range || !range->isFloatValue() || range->isImpossible() || + range->bound == Value::Bound::Point || destination->type == ValueType::Type::BOOL) + return false; + const std::size_t destinationBits = destination->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit; + MathLib::bigint converted{}; + return destinationBits > 0 && destinationBits <= static_cast(std::numeric_limits::max()) && + convertFloatToInteger(converted, range->floatValue, destination->sign, static_cast(destinationBits)); + } + if (!source->isIntegral()) + return false; + if (source->isTypeEqual(destination) && source->sign == destination->sign) + return true; + + if (range && range->isIntValue() && range->bound != Value::Bound::Point && !range->isInconclusive()) { + MathLib::bigint sourceMin{}, sourceMax{}, destinationMin{}, destinationMax{}; + if (getMinMaxValues(source, settings.platform, sourceMin, sourceMax) && + getMinMaxValues(destination, settings.platform, destinationMin, destinationMax)) { + MathLib::bigint lower = sourceMin; + MathLib::bigint upper = sourceMax; + bool upperBoundedByFact = false; + if (range->isImpossible()) { + if (range->bound == Value::Bound::Upper) { + if (range->intvalue == std::numeric_limits::max()) + return false; + lower = std::max(lower, range->intvalue + 1); + } else if (range->bound == Value::Bound::Lower) { + if (range->intvalue == std::numeric_limits::min()) + return false; + upper = std::min(upper, range->intvalue - 1); + upperBoundedByFact = true; + } + } else if (range->bound == Value::Bound::Lower) { + lower = std::max(lower, range->intvalue); + } else if (range->bound == Value::Bound::Upper) { + upper = std::min(upper, range->intvalue); + upperBoundedByFact = true; + } + + const std::size_t sourceBits = source->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit; + const bool sourceHasRepresentableMaximum = source->sign != ValueType::Sign::UNSIGNED || + sourceBits <= std::numeric_limits::digits; + if (lower <= upper && (sourceHasRepresentableMaximum || upperBoundedByFact) && + lower >= destinationMin && upper <= destinationMax) + return true; + } + } + + if (source->type == ValueType::Type::BOOL || destination->type == ValueType::Type::BOOL) { + MathLib::bigint sourceMin{}, sourceMax{}, destinationMin{}, destinationMax{}; + return getMinMaxValues(source, settings.platform, sourceMin, sourceMax) && + getMinMaxValues(destination, settings.platform, destinationMin, destinationMax) && + sourceMin >= destinationMin && sourceMax <= destinationMax; + } + + if (destination->sign == ValueType::Sign::UNKNOWN_SIGN) + return false; + + const std::size_t sourceBits = source->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit; + const std::size_t destinationBits = destination->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit; + if (sourceBits == 0 || destinationBits == 0) + return false; + if (source->sign == ValueType::Sign::UNKNOWN_SIGN) + return destination->sign == ValueType::Sign::SIGNED && sourceBits < destinationBits; + + if (source->sign == destination->sign) + return sourceBits <= destinationBits; + if (range && range->isIntValue() && range->isImpossible() && range->bound == Value::Bound::Point && + source->sign == ValueType::Sign::SIGNED && destination->sign == ValueType::Sign::UNSIGNED) { + MathLib::bigint sourceMin{}, sourceMax{}; + if (!getMinMaxValues(source, settings.platform, sourceMin, sourceMax) || range->intvalue < sourceMin || + range->intvalue > sourceMax) + return false; + return sourceBits <= destinationBits; + } + return source->sign == ValueType::Sign::UNSIGNED && destination->sign == ValueType::Sign::SIGNED && + sourceBits < destinationBits; + } + + + bool isUnsignedArithmeticResult(const Token* tok) + { + if (!Token::Match(tok, "+|-|*")) + return false; + const ValueType* vt = tok->valueType(); + return vt && vt->isIntegral() && vt->pointer == 0 && vt->sign == ValueType::Sign::UNSIGNED && + (vt->type == ValueType::Type::INT || + vt->type == ValueType::Type::LONG || + vt->type == ValueType::Type::LONGLONG || + vt->type == ValueType::Type::UNKNOWN_INT); + } + + namespace { + struct UnsignedArithmeticRange { + MathLib::biguint lower{}; + MathLib::biguint upper{}; + bool hasUpper{}; + const Token* symbolicUpper{}; + MathLib::bigint symbolicUpperOffset{}; + bool symbolicUpperConditional{}; + }; + + bool getUnsignedTypeMaximum(const ValueType* valueType, + std::size_t bits, + MathLib::biguint& maximum) + { + if (valueType->type == ValueType::Type::BOOL) { + maximum = 1; + return true; + } + const std::size_t carrierBits = std::numeric_limits::digits; + if (valueType->sign != ValueType::Sign::UNSIGNED || bits == 0 || bits > carrierBits) + return false; + maximum = bits == carrierBits ? std::numeric_limits::max() + : (MathLib::biguint{1} << bits) - 1; + return true; + } + + MathLib::biguint truncateUnsignedValue(MathLib::biguint value, std::size_t bits) + { + const std::size_t carrierBits = std::numeric_limits::digits; + if (bits < carrierBits) + value &= (MathLib::biguint{1} << bits) - 1; + return value; + } + + MathLib::biguint convertToUnsigned(MathLib::bigint value, std::size_t bits) + { + return truncateUnsignedValue(static_cast(value), bits); + } + + const Token* statementEnd(const Token* start) + { + if (!start) + return nullptr; + if (start->str() == "{") + return start->link(); + if (Token::Match(start, "if|for|while|switch|do|try|catch")) + return nullptr; + for (const Token* tok = start; tok; tok = tok->next()) { + if (Token::Match(tok, "(|[|{")) { + if (!tok->link()) + return nullptr; + tok = tok->link(); + continue; + } + if (tok->str() == ";") + return tok; + if (tok->str() == "}") + return nullptr; + } + return nullptr; + } + + bool containsToken(const Token* start, const Token* end, const Token* target) + { + return start && end && target && start->index() <= target->index() && target->index() <= end->index(); + } + + bool followsToken(const Token* end, const Token* target) + { + return end && target && end->index() < target->index(); + } + + bool exitsUnconditionally(const Token* start) + { + if (Token::simpleMatch(start, "{")) + start = start->next(); + return Token::Match(start, "return|throw|break|continue"); + } + + const Scope* enclosingFunction(const Scope* scope) + { + while (scope && scope->type != ScopeType::eFunction && scope->type != ScopeType::eLambda) + scope = scope->nestedIn; + return scope; + } + bool isInScope(const Scope* scope, const Scope* ancestor) + { + for (; scope; scope = scope->nestedIn) { + if (scope == ancestor) + return true; + } + return false; + } + + bool conditionBranchAtUse(const Token* conditionTop, const Token* use, bool& branchIsTrue) + { + if (!conditionTop || !use) + return false; + const Scope* const functionScope = enclosingFunction(conditionTop->scope()); + if (!functionScope || functionScope != enclosingFunction(use->scope())) + return false; + const Token* control = conditionTop->previous(); + if (!Token::Match(control, "if|while|for (")) + return false; + const Token* bodyStart = conditionTop->link() ? conditionTop->link()->next() : nullptr; + const Token* bodyEnd = statementEnd(bodyStart); + if (!bodyEnd) + return false; + if (containsToken(bodyStart, bodyEnd, use)) { + branchIsTrue = true; + return true; + } + if (control->str() != "if") + return false; + const auto useAfterIfIsDominated = [&]() { + for (const Scope* scope = bodyStart->scope(); scope; scope = scope->nestedIn) { + if (scope->type == ScopeType::eIf && scope->classDef == control) + return isInScope(use->scope(), scope->nestedIn); + } + return false; + }; + + const Token* elseToken = bodyEnd->next(); + if (Token::simpleMatch(elseToken, "else")) { + const Token* elseStart = elseToken->next(); + const Token* elseEnd = statementEnd(elseStart); + if (!elseEnd) + return false; + if (containsToken(elseStart, elseEnd, use)) { + branchIsTrue = false; + return true; + } + if (!followsToken(elseEnd, use) || !useAfterIfIsDominated()) + return false; + const bool thenExits = exitsUnconditionally(bodyStart); + const bool elseExits = exitsUnconditionally(elseStart); + if (thenExits == elseExits) + return false; + branchIsTrue = !thenExits; + return true; + } + + if (followsToken(bodyEnd, use) && exitsUnconditionally(bodyStart) && useAfterIfIsDominated()) { + branchIsTrue = false; + return true; + } + return false; + } + + bool sameValueExpression(const Token* lhs, const Token* rhs) + { + if (!lhs || !rhs) + return false; + if (lhs == rhs || (lhs->exprId() != 0 && lhs->exprId() == rhs->exprId())) + return true; + return lhs->varId() != 0 && lhs->varId() == rhs->varId(); + } + + bool isConditionFactActiveAtUse(const Value& value, const Token* valueTok, const Token* use) + { + if (!value.condition) + return true; + if (value.path > 0) + return true; + if (value.bound == Value::Bound::Point || !Token::Match(value.condition, "<|<=|>|>=")) + return false; + + const Token* const condition = value.condition; + const bool valueIsLhs = sameValueExpression(condition->astOperand1(), valueTok); + const bool valueIsRhs = sameValueExpression(condition->astOperand2(), valueTok); + if (valueIsLhs == valueIsRhs) + return false; + const bool conditionTrueMeansGreater = valueIsLhs ? Token::Match(condition, ">|>=") + : Token::Match(condition, "<|<="); + const bool factMeansGreater = (value.bound == Value::Bound::Lower) != value.isImpossible(); + bool requiredConditionValue = factMeansGreater == conditionTrueMeansGreater; + + const Token* const conditionTop = condition->astTop(); + const Token* const conditionRoot = conditionTop ? conditionTop->astOperand2() : nullptr; + if (!conditionTop || !conditionRoot) + return false; + bool branchIsTrue = false; + if (!conditionBranchAtUse(conditionTop, use, branchIsTrue)) + return false; + + for (const Token* node = condition; node != conditionRoot;) { + const Token* const parent = node->astParent(); + if (!parent) + return false; + if (parent->str() == "(") { + node = parent; + continue; + } + if (parent->str() == "!") { + requiredConditionValue = !requiredConditionValue; + } else if (Token::simpleMatch(parent, "&&")) { + if (!requiredConditionValue) + return false; + requiredConditionValue = true; + } else if (Token::simpleMatch(parent, "||")) { + if (requiredConditionValue) + return false; + requiredConditionValue = false; + } else if (!(parent->isCast() && astIsBool(parent))) { + return false; + } + node = parent; + } + return branchIsTrue == requiredConditionValue; + } + + bool getUnsignedArithmeticRange(const Token* tok, + MathLib::bigint path, + const ValueType* resultType, + MathLib::biguint resultMax, + const Settings& settings, + UnsignedArithmeticRange& range, + const Token* useTok, + bool contextual, + bool includeSymbolic, + int depth = 8) + { + if (depth < 0) + return false; + const ValueType* const valueType = tok ? tok->valueType() : nullptr; + if (!valueType || !valueType->isIntegral() || valueType->pointer) + return false; + + const std::size_t valueSize = valueType->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointer); + const std::size_t valueBits = valueSize * settings.platform.char_bit; + const std::size_t resultSize = resultType->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointer); + const std::size_t resultBits = resultSize * settings.platform.char_bit; + if (valueBits == 0 || valueBits > 64 || resultBits == 0 || resultBits > 64) + return false; + + const bool unsignedValue = valueType->sign == ValueType::Sign::UNSIGNED || + valueType->type == ValueType::Type::BOOL; + MathLib::bigint signedLower{}; + MathLib::bigint signedUpper{}; + if (unsignedValue) { + if (!getUnsignedTypeMaximum(valueType, valueBits, range.upper)) + return false; + range.lower = 0; + range.hasUpper = true; + } else { + if (valueType->sign != ValueType::Sign::SIGNED || + !getMinMaxValues(valueType, settings.platform, signedLower, signedUpper)) + return false; + } + + const Token* symbolicSource = nullptr; + bool symbolicSourceConflict = false; + bool symbolicUpperConflict = false; + bool hasKnownPoint = false; + MathLib::bigint knownSignedPoint{}; + MathLib::biguint knownUnsignedPoint{}; + for (const Value& value : tok->values()) { + if (value.path < 0) + return false; + if (value.path != 0 && value.path != path) + continue; + if (!contextual && (value.condition || value.conditional)) + continue; + if (contextual && value.path == 0 && value.condition && + !isConditionFactActiveAtUse(value, tok, useTok)) + continue; + if (contextual && value.conditional && !value.isSymbolicValue()) + continue; + if (value.isSymbolicValue() && !includeSymbolic) + continue; + if (value.isSymbolicValue()) { + if (value.isImpossible()) + continue; + const bool isConditionalUpper = value.isPossible() && value.conditional && + value.bound == Value::Bound::Upper; + if (!value.tokvalue || value.tokvalue == tok) + continue; + if (value.bound == Value::Bound::Point && value.isKnown() && value.intvalue == 0) { + if (symbolicSource && symbolicSource != value.tokvalue) { + symbolicSource = nullptr; + symbolicSourceConflict = true; + } else if (!symbolicSourceConflict) { + symbolicSource = value.tokvalue; + } + } else if (isConditionalUpper && !symbolicUpperConflict) { + if (range.symbolicUpper && + (range.symbolicUpper != value.tokvalue || range.symbolicUpperOffset != value.intvalue)) { + range.symbolicUpper = nullptr; + range.symbolicUpperConditional = false; + symbolicUpperConflict = true; + } else { + range.symbolicUpperConditional = range.symbolicUpper + ? range.symbolicUpperConditional && value.conditional + : value.conditional; + range.symbolicUpper = value.tokvalue; + range.symbolicUpperOffset = value.intvalue; + } + } + continue; + } + + if (!value.isIntValue() || value.isInconclusive()) + continue; + if (value.bound == Value::Bound::Point) { + if (!value.isKnown()) + continue; + if (unsignedValue) { + const MathLib::biguint point = truncateUnsignedValue(static_cast(value.intvalue), valueBits); + if (hasKnownPoint && point != knownUnsignedPoint) + return false; + knownUnsignedPoint = point; + } else { + if (hasKnownPoint && value.intvalue != knownSignedPoint) + return false; + knownSignedPoint = value.intvalue; + } + hasKnownPoint = true; + continue; + } + + if (unsignedValue) { + if (value.intvalue < 0) { + if (value.isImpossible()) { + if (value.bound == Value::Bound::Lower) + return false; + } else if (value.bound == Value::Bound::Upper) { + return false; + } + continue; + } + const auto endpoint = static_cast(value.intvalue); + if (value.isImpossible()) { + if (value.bound == Value::Bound::Upper) { + if (endpoint == std::numeric_limits::max()) + return false; + range.lower = std::max(range.lower, endpoint + 1); + } else if (value.bound == Value::Bound::Lower) { + if (endpoint == 0) + return false; + range.upper = std::min(range.upper, endpoint - 1); + } + } else if (value.bound == Value::Bound::Lower) { + range.lower = std::max(range.lower, endpoint); + } else if (value.bound == Value::Bound::Upper) { + range.upper = std::min(range.upper, endpoint); + } + } else if (value.isImpossible()) { + if (value.bound == Value::Bound::Upper) { + if (value.intvalue == std::numeric_limits::max()) + return false; + signedLower = std::max(signedLower, value.intvalue + 1); + } else if (value.bound == Value::Bound::Lower) { + if (value.intvalue == std::numeric_limits::min()) + return false; + signedUpper = std::min(signedUpper, value.intvalue - 1); + } + } else if (value.bound == Value::Bound::Lower) { + signedLower = std::max(signedLower, value.intvalue); + } else if (value.bound == Value::Bound::Upper) { + signedUpper = std::min(signedUpper, value.intvalue); + } + } + + if (unsignedValue) { + if (range.lower > range.upper) + return false; + if (hasKnownPoint) { + if (knownUnsignedPoint < range.lower || knownUnsignedPoint > range.upper) + return false; + range.lower = truncateUnsignedValue(knownUnsignedPoint, resultBits); + range.upper = range.lower; + } + } else { + if (signedLower > signedUpper) + return false; + if (hasKnownPoint) { + if (knownSignedPoint < signedLower || knownSignedPoint > signedUpper) + return false; + range.lower = convertToUnsigned(knownSignedPoint, resultBits); + range.upper = range.lower; + range.hasUpper = true; + } else { + if (signedLower < 0) + return false; + range.lower = static_cast(signedLower); + range.upper = static_cast(signedUpper); + range.hasUpper = true; + } + } + + if (symbolicSource && !hasKnownPoint) { + const ValueType* const sourceType = symbolicSource->valueType(); + if (sourceType && sourceType->isIntegral() && !sourceType->pointer && + sourceType->sign == valueType->sign) { + const std::size_t sourceSize = sourceType->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointer); + if (sourceSize > 0 && sourceSize <= valueSize) { + UnsignedArithmeticRange sourceRange; + if (getUnsignedArithmeticRange(symbolicSource, + path, + resultType, + resultMax, + settings, + sourceRange, + useTok, + contextual, + includeSymbolic, + depth - 1)) { + range.lower = std::max(range.lower, sourceRange.lower); + range.upper = std::min(range.upper, sourceRange.upper); + if (range.upper < range.lower) + return false; + if (sourceRange.symbolicUpper && !symbolicUpperConflict) { + if (range.symbolicUpper && + (range.symbolicUpper != sourceRange.symbolicUpper || + range.symbolicUpperOffset != sourceRange.symbolicUpperOffset)) { + range.symbolicUpper = nullptr; + range.symbolicUpperConditional = false; + } else { + range.symbolicUpperConditional = range.symbolicUpper + ? range.symbolicUpperConditional && sourceRange.symbolicUpperConditional + : sourceRange.symbolicUpperConditional; + range.symbolicUpper = sourceRange.symbolicUpper; + range.symbolicUpperOffset = sourceRange.symbolicUpperOffset; + } + } + } + } + } + } + + return range.lower <= range.upper && range.upper <= resultMax; + } + } + + bool isUnsignedArithmeticNoWrap(const Token* tok, const Settings& settings, bool requireGlobalContext) + { + if (!isUnsignedArithmeticResult(tok) || !tok->astOperand1() || !tok->astOperand2()) + return false; + + const ValueType* const resultType = tok->valueType(); + const std::size_t resultSize = resultType->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointer); + const std::size_t resultBits = resultSize * settings.platform.char_bit; + if (resultBits == 0 || resultBits > 64) + return false; + MathLib::biguint resultMax{}; + if (!getUnsignedTypeMaximum(resultType, resultBits, resultMax)) + return false; + + std::vector paths; + const Token* const lhs = tok->astOperand1(); + const Token* const rhs = tok->astOperand2(); + const auto hasPath = [](const Token* operand, MathLib::bigint path) { + if (operand->values().empty()) + return true; + return std::any_of(operand->values().cbegin(), operand->values().cend(), [path](const Value& value) { + return value.path == 0 || value.path == path; + }); + }; + const auto addPath = [&](MathLib::bigint path) { + if (std::find(paths.cbegin(), paths.cend(), path) == paths.cend()) + paths.push_back(path); + }; + const auto hasUnconditionalPath = [](const Token* operand) { + return operand->values().empty() || + std::any_of(operand->values().cbegin(), operand->values().cend(), [](const Value& value) { + return value.path == 0; + }); + }; + for (const Value& value : lhs->values()) { + if (value.path < 0) + return false; + } + for (const Value& value : rhs->values()) { + if (value.path < 0) + return false; + } + if (requireGlobalContext) { + addPath(0); + } else { + if (hasUnconditionalPath(lhs) && hasUnconditionalPath(rhs)) + addPath(0); + for (const Value& value : lhs->values()) { + if (value.path > 0 && hasPath(rhs, value.path)) + addPath(value.path); + } + for (const Value& value : rhs->values()) { + if (value.path > 0 && hasPath(lhs, value.path)) + addPath(value.path); + } + } + if (paths.empty()) + return false; + + const bool contextual = !requireGlobalContext; + return std::all_of(paths.cbegin(), paths.cend(), [&](MathLib::bigint path) { + UnsignedArithmeticRange lhsRange; + UnsignedArithmeticRange rhsRange; + if (tok->str() == "-") { + UnsignedArithmeticRange rhsPoint; + if (getUnsignedArithmeticRange(rhs, path, resultType, resultMax, settings, + rhsPoint, tok, contextual, false) && + rhsPoint.hasUpper && rhsPoint.lower == rhsPoint.upper) { + if (rhsPoint.upper == 0) + return true; + if (getUnsignedArithmeticRange(lhs, path, resultType, resultMax, settings, + lhsRange, tok, contextual, false) && + lhsRange.lower >= rhsPoint.upper) + return true; + } + } + if (!getUnsignedArithmeticRange(lhs, path, resultType, resultMax, settings, lhsRange, tok, contextual, true) || + !getUnsignedArithmeticRange(rhs, path, resultType, resultMax, settings, rhsRange, tok, contextual, true)) + return false; + + if (tok->str() == "-") { + if (!rhsRange.hasUpper || lhsRange.lower < rhsRange.upper) + return false; + } else if (tok->str() == "+") { + const auto isSafeRelativeAddition = [&](const UnsignedArithmeticRange& boundedRange, + const UnsignedArithmeticRange& addendRange, + const Token* boundedOperand) { + if (!boundedRange.symbolicUpper || !boundedRange.symbolicUpperConditional || + !addendRange.hasUpper || !boundedOperand || !boundedOperand->variable() || + !boundedRange.symbolicUpper->variable() || + addendRange.upper > static_cast(std::numeric_limits::max())) + return false; + const auto addendUpper = static_cast(addendRange.upper); + if (boundedRange.symbolicUpperOffset > -addendUpper) + return false; + const ValueType* const upperType = boundedRange.symbolicUpper->valueType(); + if (!upperType || !upperType->isIntegral() || upperType->pointer || + upperType->sign != ValueType::Sign::UNSIGNED) + return false; + const std::size_t upperSize = upperType->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointer); + if (upperSize == 0 || upperSize * settings.platform.char_bit > resultBits) + return false; + for (const Scope* loop = boundedOperand->scope(); loop; loop = loop->nestedIn) { + if (loop->type != ScopeType::eFor || !loop->classDef) + continue; + const Token* const forExpressions = loop->classDef->next(); + if (!forExpressions || !forExpressions->astOperand2()) + continue; + const Token* const clauses = forExpressions->astOperand2(); + const Token* const init = clauses->astOperand1(); + const Token* const remainingClauses = clauses->astOperand2(); + if (!init || !remainingClauses || !remainingClauses->astOperand1() || + !remainingClauses->astOperand2()) + continue; + const Token* const condition = remainingClauses->astOperand1(); + const Token* const step = remainingClauses->astOperand2(); + if (!Token::simpleMatch(condition, "<") || !condition->astOperand1() || + !condition->astOperand2() || condition->astOperand1()->varId() != boundedOperand->varId() || + condition->astOperand2()->varId() != boundedRange.symbolicUpper->varId() || + !Token::simpleMatch(init, "=") || !init->astOperand1() || + init->astOperand1()->varId() != boundedOperand->varId() || !Token::simpleMatch(step, "++")) + continue; + const Token* stepVariable = step->astOperand1(); + if (!stepVariable) + stepVariable = step->astOperand2(); + if (stepVariable && stepVariable->varId() == boundedOperand->varId()) + return true; + } + return false; + }; + const bool numericSafe = lhsRange.hasUpper && rhsRange.hasUpper && + lhsRange.upper <= resultMax - rhsRange.upper; + if (!numericSafe && !isSafeRelativeAddition(lhsRange, rhsRange, lhs) && + !isSafeRelativeAddition(rhsRange, lhsRange, rhs)) + return false; + } else if (tok->str() == "*") { + if (!lhsRange.hasUpper || !rhsRange.hasUpper || + (lhsRange.upper != 0 && rhsRange.upper > resultMax / lhsRange.upper)) + return false; + } + return true; + }); + } MathLib::bigint truncateIntValue(MathLib::bigint value, size_t value_size, const ValueType::Sign dst_sign) { @@ -346,24 +1077,48 @@ namespace ValueFlow return tok->next(); } - Value castValue(Value value, const ValueType::Sign sign, nonneg int bit) + bool castValue(Value& value, const ValueType::Sign sign, nonneg int bit) { + if (bit <= 0 || sign == ValueType::Sign::UNKNOWN_SIGN) + return false; + if (value.isFloatValue()) { - value.valueType = Value::ValueType::INT; - if (value.floatValue >= std::numeric_limits::min() && value.floatValue <= std::numeric_limits::max()) { - value.intvalue = static_cast(value.floatValue); - } else { // don't perform UB - value.intvalue = 0; + if (value.bound != Value::Bound::Point) { + if (value.isImpossible()) + return false; + MathLib::bigint converted{}; + if (!convertFloatToInteger(converted, value.floatValue, sign, bit)) + return false; + value.intvalue = converted; + value.valueType = Value::ValueType::INT; + return true; } + if (value.isImpossible()) + return false; + MathLib::bigint converted{}; + if (!convertFloatToInteger(converted, value.floatValue, sign, bit)) + return false; + value.intvalue = converted; + value.valueType = Value::ValueType::INT; + return true; } + + if (value.bound != Value::Bound::Point || (value.isImpossible() && !value.isIntValue())) + return false; + + if (!value.isIntValue()) + return false; if (bit < MathLib::bigint_bits) { - constexpr MathLib::biguint one = 1; - value.intvalue &= (one << bit) - 1; - if (sign == ValueType::Sign::SIGNED && value.intvalue & (one << (bit - 1))) { - value.intvalue |= ~((one << bit) - 1ULL); - } + const MathLib::biguint mask = (MathLib::biguint{1} << bit) - 1; + const MathLib::biguint raw = static_cast(value.intvalue) & mask; + if (sign == ValueType::Sign::SIGNED && (raw & (MathLib::biguint{1} << (bit - 1)))) + value.intvalue = -1 - static_cast((~raw) & mask); + else + value.intvalue = static_cast(raw); + } else if (sign == ValueType::Sign::UNSIGNED && bit >= MathLib::bigint_bits && value.intvalue < 0) { + return false; } - return value; + return true; } std::string debugString(const Value& v) diff --git a/lib/vf_common.h b/lib/vf_common.h index aca438a0dff..affe4f47cee 100644 --- a/lib/vf_common.h +++ b/lib/vf_common.h @@ -39,13 +39,20 @@ namespace ValueFlow namespace ValueFlow { - bool getMinMaxValues(const ValueType* vt, const Platform& platform, MathLib::bigint& minValue, MathLib::bigint& maxValue); + CPPCHECKLIB bool getMinMaxValues(const ValueType* vt, const Platform& platform, MathLib::bigint& minValue, MathLib::bigint& maxValue); + CPPCHECKLIB bool isValueRangePreservingConversion(const ValueType* source, + const ValueType* destination, + const Settings& settings, + const Value* range = nullptr); + + bool isUnsignedArithmeticResult(const Token* tok); + bool isUnsignedArithmeticNoWrap(const Token* tok, const Settings& settings, bool requireGlobalContext); MathLib::bigint truncateIntValue(MathLib::bigint value, size_t value_size, ValueType::Sign dst_sign); Token * valueFlowSetConstantValue(Token *tok, const Settings &settings); - Value castValue(Value value, ValueType::Sign sign, nonneg int bit); + CPPCHECKLIB bool castValue(Value& value, ValueType::Sign sign, nonneg int bit); std::string debugString(const Value& v); diff --git a/lib/vf_settokenvalue.cpp b/lib/vf_settokenvalue.cpp index 1d6179fb9af..09107e36004 100644 --- a/lib/vf_settokenvalue.cpp +++ b/lib/vf_settokenvalue.cpp @@ -21,6 +21,7 @@ #include "astutils.h" #include "calculate.h" #include "config.h" +#include "infer.h" #include "library.h" #include "mathlib.h" #include "platform.h" @@ -29,12 +30,14 @@ #include "token.h" #include "utils.h" #include "valueflow.h" +#include "valueptr.h" #include "vfvalue.h" #include "vf_common.h" #include #include +#include #include #include #include @@ -64,35 +67,69 @@ namespace ValueFlow return Library::Container::Yield::NO_YIELD; } - static Value truncateImplicitConversion(Token* parent, const Value& value, const Settings& settings) + static bool truncateImplicitConversion(Token* parent, const Token* sourceTok, Value& value, const Settings& settings) { - if (!value.isIntValue() && !value.isFloatValue()) - return value; - if (!parent) - return value; - if (!parent->isBinaryOp()) - return value; - if (!parent->isConstOp() && !parent->isAssignmentOp()) - return value; - if (!astIsIntegral(parent->astOperand1(), false)) - return value; - if (!astIsIntegral(parent->astOperand2(), false)) - return value; - const ValueType* vt1 = parent->astOperand1()->valueType(); - const ValueType* vt2 = parent->astOperand2()->valueType(); - // If the sign is the same there is no truncation - if (vt1->sign == vt2->sign) - return value; - const size_t n1 = vt1->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); - const size_t n2 = vt2->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); - ValueType::Sign sign = ValueType::Sign::UNSIGNED; - if (n1 < n2) - sign = vt2->sign; - else // (n1 >= n2) - sign = vt1->sign; - Value v = castValue(value, sign, std::max(n1, n2) * 8); - v.wideintvalue = value.intvalue; - return v; + if (!parent || (!value.isIntValue() && + !(value.isSymbolicValue() && value.bound != Value::Bound::Point))) + return true; + + const Token* lhs = parent->astOperand1(); + const Token* rhs = parent->astOperand2(); + const ValueType* destination = nullptr; + ValueType commonType; + if (parent->str() == ":" && parent->astParent() && parent->astParent()->str() == "?") { + destination = parent->astParent()->valueType(); + } else if (parent->str() == "=") { + return true; + } else if ((parent->isConstOp() || parent->isAssignmentOp()) && lhs && rhs && lhs->valueType() && rhs->valueType()) { + if (Token::Match(parent, "<<|>>")) { + const Token* operand = sourceTok == lhs ? lhs : rhs; + commonType = ValueType::getCommonIntegralType(operand->valueType(), nullptr, settings); + } else { + commonType = ValueType::getCommonIntegralType(lhs->valueType(), rhs->valueType(), settings); + } + destination = &commonType; + } else { + return true; + } + + if (!destination || !destination->isIntegral() || destination->pointer) + return true; + const ValueType* const source = sourceTok ? sourceTok->valueType() : nullptr; + if (value.isImpossible() && value.isIntValue() && value.bound == Value::Bound::Point) { + if (!isValueRangePreservingConversion(source, destination, settings, &value)) + return false; + if (source && source->sign == ValueType::Sign::SIGNED && destination->sign == ValueType::Sign::UNSIGNED && + destination->type != ValueType::Type::BOOL) { + const MathLib::bigint originalValue = value.intvalue; + const int bits = static_cast(destination->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit); + if (!castValue(value, destination->sign, bits)) + return false; + value.wideintvalue = originalValue; + } + return true; + } + if (value.isImpossible() || value.bound != Value::Bound::Point || value.isSymbolicValue()) + return isValueRangePreservingConversion(source, destination, settings, &value); + if (destination->type == ValueType::Type::BOOL) { + value.intvalue = value.intvalue != 0; + return true; + } + if (isValueRangePreservingConversion(source, destination, settings, &value)) + return true; + if (destination->sign == ValueType::Sign::UNKNOWN_SIGN) + return source && source->isTypeEqual(destination) && source->sign == destination->sign; + + const int bits = static_cast(destination->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit); + const MathLib::bigint originalValue = value.intvalue; + if (!castValue(value, destination->sign, bits)) + return false; + value.wideintvalue = originalValue; + return true; } static const Token *getCastTypeStartToken(const Token *parent, const Library& library) @@ -126,21 +163,57 @@ namespace ValueFlow return value.isIntValue() || value.isFloatValue(); } - static void setTokenValueCast(Token *parent, const ValueType &valueType, Value value, const Settings &settings) + static void setTokenValueCast(Token* parent, + const ValueType& valueType, + const ValueType* sourceType, + Value value, + const Settings& settings) { - if (valueType.pointer || value.isImpossible()) - setTokenValue(parent,std::move(value),settings); - else if (valueType.type == ValueType::Type::CHAR) - setTokenValue(parent, castValue(std::move(value), valueType.sign, settings.platform.char_bit), settings); - else if (valueType.type == ValueType::Type::SHORT) - setTokenValue(parent, castValue(std::move(value), valueType.sign, settings.platform.short_bit), settings); - else if (valueType.type == ValueType::Type::INT) - setTokenValue(parent, castValue(std::move(value), valueType.sign, settings.platform.int_bit), settings); - else if (valueType.type == ValueType::Type::LONG) - setTokenValue(parent, castValue(std::move(value), valueType.sign, settings.platform.long_bit), settings); - else if (valueType.type == ValueType::Type::LONGLONG) - setTokenValue(parent, castValue(std::move(value), valueType.sign, settings.platform.long_long_bit), settings); - else if (valueType.isFloat() && isNumeric(value)) { + if (valueType.type == ValueType::Type::BOOL && sourceType && sourceType->pointer > 0 && + value.isSymbolicValue() && value.isKnown() && value.bound == Value::Bound::Point && value.intvalue == 0) { + setTokenValue(parent, std::move(value), settings); + return; + } + if (valueType.pointer) + setTokenValue(parent, std::move(value), settings); + else if (valueType.isIntegral() && (isNumeric(value) || value.isSymbolicValue())) { + if (value.isImpossible() || value.bound != Value::Bound::Point || value.isSymbolicValue()) { + if (value.isFloatValue()) { + const int bits = static_cast(valueType.getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit); + if (!castValue(value, valueType.sign, bits)) + return; + } else { + if (!isValueRangePreservingConversion(sourceType, &valueType, settings, &value)) + return; + if (value.isImpossible() && value.isIntValue() && value.bound == Value::Bound::Point && sourceType && + sourceType->sign == ValueType::Sign::SIGNED && valueType.sign == ValueType::Sign::UNSIGNED && + valueType.type != ValueType::Type::BOOL) { + const int bits = static_cast(valueType.getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit); + if (!castValue(value, valueType.sign, bits)) + return; + } + } + setTokenValue(parent, std::move(value), settings); + return; + } + if (valueType.type == ValueType::Type::BOOL) { + value.intvalue = value.isFloatValue() ? std::fpclassify(value.floatValue) != FP_ZERO : value.intvalue != 0; + value.valueType = Value::ValueType::INT; + } else { + const int bits = static_cast(valueType.getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee) * settings.platform.char_bit); + if (!castValue(value, valueType.sign, bits)) + return; + } + setTokenValue(parent, std::move(value), settings); + } else if (valueType.isFloat() && isNumeric(value)) { + if (value.isImpossible() || value.bound != Value::Bound::Point) + return; if (value.isIntValue()) value.floatValue = static_cast(value.intvalue); value.valueType = Value::ValueType::FLOAT; @@ -150,7 +223,7 @@ namespace ValueFlow const long long charMin = settings.platform.signedCharMin(); if (charMin <= value.intvalue && value.intvalue <= charMax) { // unknown type, but value is small so there should be no truncation etc - setTokenValue(parent,std::move(value),settings); + setTokenValue(parent, std::move(value), settings); } } } @@ -228,8 +301,8 @@ namespace ValueFlow >= sizeof(MathLib::bigint)) return; - if (!value.isImpossible() && value.isIntValue()) - value = truncateImplicitConversion(tok->astParent(), value, settings); + const bool canPropagate = !(value.isIntValue() || value.isSymbolicValue()) || + truncateImplicitConversion(tok->astParent(), tok, value, settings); // a container size value on a container expression belongs to that expression, while a // pointer or an iterator only transports the size of the container it was obtained from @@ -242,7 +315,7 @@ namespace ValueFlow if (!tok->addValue(value)) return; - if (value.path < 0) + if (!canPropagate || value.path < 0) return; Token *parent = tok->astParent(); @@ -398,12 +471,7 @@ namespace ValueFlow Token::simpleMatch(parent->astOperand1(), "dynamic_cast")) return; const ValueType &valueType = ValueType::parseDecl(castType, settings); - if (value.isImpossible() && value.isIntValue() && value.intvalue < 0 - && astIsUnsigned(tok) && valueType.sign == ValueType::SIGNED && tok->valueType() - && tok->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer) - >= valueType.getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer)) - return; - setTokenValueCast(parent, valueType, std::move(value), settings); + setTokenValueCast(parent, valueType, tok->valueType(), std::move(value), settings); } else if (parent->str() == ":" && Token::simpleMatch(parent->astParent(), "?")) { @@ -488,6 +556,9 @@ namespace ValueFlow return; } + const bool skipUnsignedRangeBounds = isUnsignedArithmeticResult(parent) && + !isUnsignedArithmeticNoWrap(parent, settings, false); + for (const Value &value1 : parent->astOperand1()->values()) { if (!isComputableValue(parent, value1)) continue; @@ -500,6 +571,11 @@ namespace ValueFlow continue; if (!isCompatibleValues(value1, value2)) continue; + const auto hasRangeBound = [](const Value& value) { + return (value.isIntValue() || value.isSymbolicValue()) && value.bound != Value::Bound::Point; + }; + if (skipUnsignedRangeBounds && (hasRangeBound(value1) || hasRangeBound(value2))) + continue; Value result(0); combineValueProperties(value1, value2, result); if (astIsFloat(parent, false)) { @@ -588,6 +664,19 @@ namespace ValueFlow } if (error) continue; + if (isUnsignedArithmeticResult(parent) && value1.bound == Value::Bound::Point && + value2.bound == Value::Bound::Point && result.isIntValue()) { + const ValueType* const resultType = parent->valueType(); + const size_t resultSize = resultType->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointer); + if (resultSize > 0 && resultSize < sizeof(MathLib::bigint)) { + result.wideintvalue = result.intvalue; + result.intvalue = ValueFlow::truncateIntValue(result.intvalue, + resultSize, + ValueType::Sign::UNSIGNED); + } + } // If the bound comes from the second value then invert the bound when subtracting if (Token::simpleMatch(parent, "-") && value2.bound == result.bound && value2.bound != Value::Bound::Point) @@ -639,6 +728,32 @@ namespace ValueFlow // unary minus else if (parent->isUnaryOp("-") && !astIsUnsigned(parent)) { for (const Value &val : tok->values()) { + if (val.isSymbolicValue() && val.isImpossible() && val.tokvalue && + val.bound != Value::Bound::Point) { + std::list symbolicValues; + std::copy_if(val.tokvalue->values().cbegin(), + val.tokvalue->values().cend(), + std::back_inserter(symbolicValues), + [&](const Value& symbolicValue) { + return symbolicValue.path == 0 || symbolicValue.path == val.path; + }); + const std::vector range = val.bound == Value::Bound::Upper + ? ::getMinValue(::makeIntegralInferModel(), symbolicValues) + : ::getMaxValue(::makeIntegralInferModel(), symbolicValues); + if (range.empty() || range.front() == std::numeric_limits::min()) + continue; + const MathLib::bigint negated = -range.front(); + if ((val.intvalue > 0 && negated < std::numeric_limits::min() + val.intvalue) || + (val.intvalue < 0 && negated > std::numeric_limits::max() + val.intvalue)) + continue; + Value v(val); + v.valueType = Value::ValueType::INT; + v.tokvalue = nullptr; + v.intvalue = negated - val.intvalue; + v.invertBound(); + setTokenValue(parent, std::move(v), settings); + continue; + } if (!val.isIntValue() && !val.isFloatValue()) continue; Value v(val); diff --git a/oss-fuzz/Makefile b/oss-fuzz/Makefile index 7413910834f..a00df42ac79 100644 --- a/oss-fuzz/Makefile +++ b/oss-fuzz/Makefile @@ -345,7 +345,7 @@ $(libcppdir)/templatesimplifier.o: ../lib/templatesimplifier.cpp ../lib/checkers $(libcppdir)/timer.o: ../lib/timer.cpp ../lib/config.h ../lib/timer.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/timer.cpp -$(libcppdir)/token.o: ../lib/token.cpp ../externals/simplecpp/simplecpp.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/tokenrange.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/token.o: ../lib/token.cpp ../externals/simplecpp/simplecpp.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/tokenrange.h ../lib/utils.h ../lib/valueflow.h ../lib/vf_common.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/token.cpp $(libcppdir)/tokenlist.o: ../lib/tokenlist.cpp ../externals/simplecpp/simplecpp.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/keywords.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h @@ -360,7 +360,7 @@ $(libcppdir)/vf_analyzers.o: ../lib/vf_analyzers.cpp ../lib/analyzer.h ../lib/as $(libcppdir)/vf_common.o: ../lib/vf_common.cpp ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_common.cpp -$(libcppdir)/vf_settokenvalue.o: ../lib/vf_settokenvalue.cpp ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h +$(libcppdir)/vf_settokenvalue.o: ../lib/vf_settokenvalue.cpp ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_settokenvalue.cpp $(libcppdir)/vfvalue.o: ../lib/vfvalue.cpp ../lib/config.h ../lib/errortypes.h ../lib/mathlib.h ../lib/smallvector.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h diff --git a/test/cli/other_test.py b/test/cli/other_test.py index aad4fec264e..ac176d4d34b 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -41,6 +41,31 @@ def test_missing_include(tmpdir): # #11283 assert stderr == '{}:2:2: information: Include file: "test.h" not found. [missingInclude]\n'.format(test_file) +@pytest.mark.parametrize("extension", [".c", ".cpp"]) +def test_floating_strict_bounds_do_not_mark_parameters_uninitialized(tmpdir, extension): + test_file = os.path.join(tmpdir, 'floating_bounds' + extension) + with open(test_file, 'wt') as f: + f.write(""" +float f1(float x) { if (x >= 1.0f) return 1; return x; } +float f5(float x) { if (x >= 1.0f) return 1; if (x < 1.0f) return 2; return x; } +long double g1(long double x) { if (x <= 2.0L) return 1; return x; } +float g2(float x) { if (1.0f <= x) return 1; return x * 2; } +double control(double x) { if (x >= 1.0) return 1; return x; } +float guarded_else(float x) { + if (x <= 0.0f) return 0; + else return x; +} +float real_uninitialized(void) { + float real_uninitialized; + return real_uninitialized; +} +""") + + exitcode, _, stderr = cppcheck(['-q', '--enable=style,warning', '--platform=unix64', '--template=simple', test_file]) + assert exitcode == 0 + assert stderr.count('[uninitvar]') == 1 + assert 'real_uninitialized [uninitvar]' in stderr + def __test_missing_include_check_config(tmpdir, use_j): test_file = os.path.join(tmpdir, 'test.c') with open(test_file, 'wt') as f: diff --git a/test/testbufferoverrun.cpp b/test/testbufferoverrun.cpp index f6a2e1a5ec1..ed1919c03fe 100644 --- a/test/testbufferoverrun.cpp +++ b/test/testbufferoverrun.cpp @@ -164,6 +164,7 @@ class TestBufferOverrun : public TestFixture { TEST_CASE(array_index_74); // #11088 TEST_CASE(array_index_75); TEST_CASE(array_index_76); + TEST_CASE(array_index_77); TEST_CASE(array_index_multidim); TEST_CASE(array_index_switch_in_for); TEST_CASE(array_index_for_in_for); // FP: #2634 @@ -200,6 +201,9 @@ class TestBufferOverrun : public TestFixture { TEST_CASE(array_index_container_data); // pointer from data()/c_str() carries the container size TEST_CASE(array_index_two_for_loops); TEST_CASE(array_index_new); // #7690 + TEST_CASE(array_index_guile); + TEST_CASE(array_index_abiword); + TEST_CASE(array_index_caller_inference); TEST_CASE(buffer_overrun_2_struct); TEST_CASE(buffer_overrun_3); @@ -2012,6 +2016,42 @@ class TestBufferOverrun : public TestFixture { errout_str()); } + void array_index_77() + { + // The loop index x is at least colsToTranslate, and the cast range of + // (int)cloudDx only provides the trivial floor of int. x - colsToTranslate + // must not be reported as a negative index. + check("static float cloudDx;\n" + "static int g_dst[400];\n" + "static int g_src[400];\n" + "void updateClouds(float elapsedTime) {\n" + " cloudDx += elapsedTime * 5;\n" + " if (cloudDx >= 1.0f) {\n" + " const int colsToTranslate = (int)cloudDx;\n" + " for (int x = colsToTranslate; x < 400; ++x) {\n" + " g_dst[x - colsToTranslate] = g_src[x];\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void negative_index(int idx) {\n" + " int buf[10];\n" + " if (idx < 0) return;\n" + " buf[idx - 1] = 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:13] -> [test.cpp:4:8]: (warning) Either the condition 'idx<0' is redundant or the array 'buf[10]' is accessed at index -1, which is out of bounds. [negativeIndex]\n", + errout_str()); + + // A genuinely negative literal index must still be reported + check("static int a[10];\n" + "void f() {\n" + " a[-1] = 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:4]: (error) Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", + errout_str()); + } + void array_index_multidim() { check("void f()\n" "{\n" @@ -2423,6 +2463,40 @@ class TestBufferOverrun : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:5:12]: (error) Array 'val[5]' accessed at index -9994, which is out of bounds. [negativeIndex]\n" "[test.cpp:5:23]: (error) Array 'val[5]' accessed at index -9995, which is out of bounds. [negativeIndex]\n", errout_str()); + + check("void f(void) {\n" + " int a[10];\n" + " for (int i = 0; i-- > -3; i++) {\n" + " a[i] = 0;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:10]: (error) Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); + + check("void f(void) {\n" + " int a[10];\n" + " for (int i = 0; (i = 0) < 1; i++) {\n" + " a[i] = 0;\n" + " break;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(void) {\n" + " int a[10];\n" + " for (int i = 0; --i > -3; i++) {\n" + " a[i] = 0;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:10]: (error) Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); + + check("void f(void) {\n" + " int n = 10;\n" + " int a[10];\n" + " for (int i = 0; i < n; i++) {\n" + " a[i] = 0;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } @@ -4008,6 +4082,22 @@ class TestBufferOverrun : public TestFixture { "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:4:14]: (portability) Undefined behaviour, when 'i' is -1 the pointer arithmetic 'x+i' is out of bounds. [pointerOutOfBoundsCond]\n", errout_str()); + check("void decimal_remainder(uint32_t i) {\n" + " char table[200];\n" + " while (i != 0U) {\n" + " uint32_t q = i / 100U;\n" + " uint32_t offset = (i - q * 100U) << 1;\n" + " char *p = table + offset;\n" + " (void)p;\n" + " i = q;\n" + " }\n" + "}\n" + "void actual_overrun(void) {\n" + " char table[200];\n" + " char *p = table + 201;\n" + "}\n", settings0_p); + ASSERT_EQUALS("[test.cpp:13:21]: (portability) Undefined behaviour, pointer arithmetic 'table+201' is out of bounds. [pointerOutOfBounds]\n", errout_str()); + check("void f() {\n" // #6350 - fp when there is cast of buffer " wchar_t buf[64];\n" " p = (unsigned char *) buf + sizeof (buf);\n" @@ -6063,6 +6153,77 @@ class TestBufferOverrun : public TestFixture { "}\n", settings); ASSERT_EQUALS("", errout_str()); } + void array_index_guile() { + check("#include \n" + "double f(double x) {\n" + " if (x <= -1.0) {\n" + " if (x == -1.0) return -100.0;\n" + " else return 0.0 / 0.0;\n" + " }\n" + " if (x < -0.5 || x > 1.0) return log(1.0 + x);\n" + " if (x == 0.0) return x;\n" + " double m = round(x * 256.0);\n" + " const double table[385] = {0};\n" + " return table[128 + (int)m];\n" + "}\n", dinit(CheckOptions, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + } + + void array_index_abiword() { + const Settings settingsUnix64 = settingsBuilder(settings0_i).platform(Platform::Type::Unix64).build(); + check("void f(unsigned int keyval) {\n" + " int table[256];\n" + " if (keyval > 0xFFFF) return;\n" + " if (keyval > 0xFF00) return;\n" + " if (keyval > 0xFE00) table[keyval - 0xFE00] = 0;\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:4:16] -> [test.cpp:5:31]: (warning) Either the condition 'keyval>0xFF00' is redundant or the array 'table[256]' is accessed at index 256, which is out of bounds. [arrayIndexOutOfBoundsCond]\n", errout_str()); + + check("void f(unsigned int keyval) {\n" + " int table[256];\n" + " if (keyval <= 0xFFFF && keyval <= 0xFF00 && keyval > 0xFE00)\n" + " table[keyval - 0xFE00] = 0;\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:3:36] -> [test.cpp:4:14]: (warning) Either the condition 'keyval<=0xFF00' is redundant or the array 'table[256]' is accessed at index 256, which is out of bounds. [arrayIndexOutOfBoundsCond]\n", errout_str()); + + check("void f(unsigned int keyval) {\n" + " int table[256];\n" + " if (0xFFFF < keyval || 0xFF00 < keyval) return;\n" + " if (0xFE00 >= keyval) return;\n" + " table[keyval - 0xFE00] = 0;\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:3:35] -> [test.cpp:5:10]: (warning) Either the condition '0xFF00 0xFFFF) return;\n" + " if (keyval >= 0xFF00) return;\n" + " if (keyval > 0xFE00) table[keyval - 0xFE00] = 0;\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("void f(unsigned int keyval) {\n" + " int table[257];\n" + " if (keyval > 0xFFFF) return;\n" + " if (keyval > 0xFF00) return;\n" + " if (keyval > 0xFE00) table[keyval - 0xFE00] = 0;\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + } + void array_index_caller_inference() + { + check("struct Scope { unsigned short sp; };\n" + "int buffer[4];\n" + "void pop_n(Scope *s, int n) {\n" + " if ((int)s->sp - n < 0)\n" + " buffer[(int)s->sp - n] = 1;\n" + "}\n" + "void caller_underflow(Scope *s) {\n" + " s->sp = 0;\n" + " pop_n(s, 1);\n" + "}\n", settings0_i); + ASSERT(errout_str().find("[negativeIndex]") != std::string::npos); + } }; REGISTER_TEST(TestBufferOverrun) diff --git a/test/testcondition.cpp b/test/testcondition.cpp index 93248837cb3..ef6129952c8 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -46,6 +46,7 @@ class TestCondition : public TestFixture { TEST_CASE(assignAndCompare); // assignment and comparison don't match TEST_CASE(mismatchingBitAnd); // overlapping bitmasks TEST_CASE(comparison); // CheckCondition::comparison test cases + TEST_CASE(unsignedArithmeticNoWrap); TEST_CASE(multicompare); // mismatching comparisons TEST_CASE(overlappingElseIfCondition); // overlapping conditions in if and else-if TEST_CASE(oppositeElseIfCondition); // opposite conditions in if and else-if @@ -124,6 +125,7 @@ class TestCondition : public TestFixture { TEST_CASE(checkAssignmentInCondition); TEST_CASE(compareOutOfTypeRange); + TEST_CASE(conditionInferenceProvenance); TEST_CASE(knownConditionCast); // #9976 TEST_CASE(knownConditionIncrementLoop); // #9808 TEST_CASE(knownConditionAfterBailout); // #12526 @@ -3693,14 +3695,14 @@ class TestCondition : public TestFixture { " if (handle) return 1;\n" " else return 0;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:9]: (style) Condition 'handle' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT(errout_str().find("Condition 'handle' is always true [knownConditionTrueFalse]") != std::string::npos); check("int f(void *handle) {\n" " if (handle == 0) return 0;\n" " if (handle) return 1;\n" " else return 0;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:3:9]: (style) Condition 'handle' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT(errout_str().find("Condition 'handle' is always true [knownConditionTrueFalse]") != std::string::npos); check("int f(void *handle) {\n" " if (handle != 0) return 0;\n" @@ -3788,7 +3790,7 @@ class TestCondition : public TestFixture { " if(array){}\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:5:12]: (style) Condition 'array' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT(errout_str().find("Condition 'array' is always true [knownConditionTrueFalse]") != std::string::npos); check("void f(int *array, int size ) {\n" " for(int i = 0; i < size; ++i) {\n" @@ -3797,7 +3799,7 @@ class TestCondition : public TestFixture { " else if(array){}\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:5:17]: (style) Condition 'array' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT(errout_str().find("Condition 'array' is always true [knownConditionTrueFalse]") != std::string::npos); // #9277 check("int f() {\n" @@ -4481,7 +4483,7 @@ class TestCondition : public TestFixture { "void S::f() {\n" " if ((p == NULL) || ((p) && (g() >= *p))) {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:7:26]: (style) Condition 'p' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT(errout_str().find("Condition 'p' is always true [knownConditionTrueFalse]") != std::string::npos); // #10749 check("struct Interface {\n" @@ -4680,7 +4682,7 @@ class TestCondition : public TestFixture { " if (o[1] == '\\0') {}\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:6:18]: (style) Condition 'o[1]=='\\0'' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT(errout_str().find("Condition 'o[1]=='\\0'' is always false [knownConditionTrueFalse]") != std::string::npos); check("void f(int x) {\n" // #11449 " int i = x;\n" @@ -4864,7 +4866,7 @@ class TestCondition : public TestFixture { " const S* sp2 = s.get2();\n" " if (sp2) {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:12:9]: (style) Condition 'sp2' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT(errout_str().find("Condition 'sp2' is always true [knownConditionTrueFalse]") != std::string::npos); check("struct S {\n" " void f(int i);\n" @@ -5477,7 +5479,7 @@ class TestCondition : public TestFixture { check("bool f(const int *p, const int *q) {\n" " return p != NULL && q != NULL && p == NULL;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:40]: (style) Condition 'p==NULL' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:14] -> [test.cpp:2:40]: (style) Condition 'p==NULL' is always false [knownConditionTrueFalse]\n", errout_str()); check("struct S {\n" // #11789 " std::vector v;\n" @@ -5586,7 +5588,7 @@ class TestCondition : public TestFixture { " return -1;\n" " return *it;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:4:12]: (style) Condition 'it!=vector.end()' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:14] -> [test.cpp:4:12]: (style) Condition 'it!=vector.end()' is always true [knownConditionTrueFalse]\n", errout_str()); // #11303 check("void f(int n) {\n" @@ -5595,7 +5597,7 @@ class TestCondition : public TestFixture { " buffer.back() == '\\n' ||\n" " buffer.back() == '\\0') {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:5:22]: (style) Condition 'buffer.back()=='\\0'' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:3:22] -> [test.cpp:5:22]: (style) Condition 'buffer.back()=='\\0'' is always false [knownConditionTrueFalse]\n", errout_str()); // #9353 check("struct X { std::string s; };\n" @@ -6633,6 +6635,108 @@ class TestCondition : public TestFixture { "[test.cpp:4:13]: (style) Comparing expression of type 'const unsigned int &' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); + // no false positive when unsigned arithmetic wraps around + check("void f(int x) {\n" + " if ((unsigned int)x - 1u == 0xFFFFFFFFu) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("void f(int x) {\n" + " if ((unsigned short)x - 1u == 0xFFFFFFFFu) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + // Signed result type after integer promotion must still warn + check("void f(int x) {\n" + " if ((unsigned short)x + 1 == 0) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:29]: (style) Condition '(unsigned short)x+1==0' is always false [knownConditionTrueFalse]\n", + errout_str()); + + // Direct cast bounds are preserved + check("void f(int x) {\n" + " if ((unsigned int)x > 4294967295ULL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:25]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + // Reproduce the original typedef-heavy Simulink-generated pattern from #15000. + check("void f(unsigned int x) {\n" + " unsigned long long tmp = ((unsigned long long)x) + 1ULL;\n" + " if (tmp > 4294967295ULL)\n" + " tmp = 4294967295ULL;\n" + " if ((((long long)((unsigned int)tmp)) - 1LL) < 0LL) {}\n" + " if ((((long long)((unsigned int)tmp)) - 1LL) > 4294967295LL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:6:50]: (style) Condition '(((long long)((unsigned int)tmp))-1LL)>4294967295LL' is always false [knownConditionTrueFalse]\n", + errout_str()); + check("void f(unsigned int x) {\n" + " unsigned long long tmp = ((unsigned long long)x) + 1ULL;\n" + " if (tmp > 4294967295ULL)\n" + " tmp = 4294967295ULL;\n" + " if ((((long long)((unsigned int)tmp)) - 1LL) < 0LL) {}\n" + " if ((((long long)((unsigned int)tmp)) - 1LL) > 4294967295LL) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:6:50]: (style) Condition '(((long long)((unsigned int)tmp))-1LL)>4294967295LL' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("void f(unsigned long long tmp) {\n" + " if (tmp > 4294967295ULL)\n" + " tmp = 4294967295ULL;\n" + " if (static_cast(static_cast(tmp)) - 1LL > 4294967295LL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:4:70]: (style) Condition 'static_cast(static_cast(tmp))-1LL>4294967295LL' is always false [knownConditionTrueFalse]\n", + errout_str()); + + // cast directly around variable: both the range-based and the declared-type + // analysis can prove the condition invariant; diag() must prevent duplicates + check("void f(unsigned char c) {\n" + " if ((unsigned char)c > 255) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:28]: (style) Comparing expression of type 'unsigned char' against value 255. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + check("void f(unsigned char c) {\n" + " if ((unsigned char)c == 256) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:29]: (style) Comparing expression of type 'unsigned char' against value 256. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + check("void f(unsigned int u) {\n" + " if ((unsigned int)u > 4294967295ULL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + check("void f(unsigned short s) {\n" + " if ((unsigned int)s > 4294967295ULL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + // wchar_t range is derived through ValueType::getSizeOf() + check("void f(wchar_t c) {\n" + " if ((wchar_t)c > 0x7fffffff) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:20]: (style) Condition '(wchar_t)c>0x7fffffff' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("void f(unsigned int x) {\n" + " if (-(signed char)x < -129) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:25]: (style) Condition '-(char)x<-129' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("void f(int x) {\n" + " if ((x) > 0) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("void f(unsigned int x) {\n" + " if ((unsigned int)x > 0) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check("void f() {\n" " long long ll = 1024 * 1024 * 1024;\n" " if (ll * 8 < INT_MAX) {}\n" @@ -6652,6 +6756,44 @@ class TestCondition : public TestFixture { "[test.cpp:2:28]: (style) Comparing expression of type 'signed int' against value 2147483647. Condition is always false. [compareValueOutOfTypeRangeError]\n" "[test.cpp:7:17]: (style) Comparing expression of type 'signed int' against value 2147483647. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); + const char signedToUnsigned[] = + "void gtkpod(void *p) {\n" + " unsigned int position = (int)(long)p;\n" + " if (position == -1) {}\n" + " if (position == 4294967295u) {}\n" + "}\n"; + check(signedToUnsigned, settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check(signedToUnsigned, settingsUnix64, false); + ASSERT_EQUALS("", errout_str()); + + const char narrowingSize[] = + "typedef unsigned long size_t;\n" + "void f(size_t len) {\n" + " if (len > 1) {\n" + " unsigned int end = (unsigned int)(len - 1);\n" + " if (end > 0) {}\n" + " }\n" + "}\n"; + check(narrowingSize, settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check(narrowingSize, settingsUnix64, false); + ASSERT_EQUALS("", errout_str()); + + const char wideningUnsigned[] = + "void f(unsigned int input) {\n" + " if (input == 0) return;\n" + " long long widened = (long long)input;\n" + " if (widened == 0) {}\n" + "}\n"; + check(wideningUnsigned, settingsUnix64); + const std::string wideningCpp = errout_str(); + ASSERT(wideningCpp.find("[test.cpp:2:15] -> [test.cpp:4:17]") != std::string::npos); + ASSERT(wideningCpp.find("Condition 'widened==0' is always false [knownConditionTrueFalse]") != std::string::npos); + check(wideningUnsigned, settingsUnix64, false); + const std::string wideningC = errout_str(); + ASSERT(wideningC.find("[test.c:2:15] -> [test.c:4:17]") != std::string::npos); + ASSERT(wideningC.find("Condition 'widened==0' is always false [knownConditionTrueFalse]") != std::string::npos); } void knownConditionCast() { @@ -6716,6 +6858,297 @@ class TestCondition : public TestFixture { ASSERT_EQUALS("[test.cpp:13:11] -> [test.cpp:18:13]: (style) Condition 'mS.b' is always false [knownConditionTrueFalse]\n", errout_str()); } + void conditionInferenceProvenance() { + const Settings settingsUnix64 = settingsBuilder().severity(Severity::style).platform(Platform::Type::Unix64).build(); + const Settings settingsUnix32 = settingsBuilder().severity(Severity::style).platform(Platform::Type::Unix32).build(); + + check("void poplast(); void use(unsigned);\n" + "struct Array {\n" + " unsigned length;\n" + " void remove(unsigned index) {\n" + " if (index == length - 1) poplast();\n" + " else if (index < length) use(index);\n" + " }\n" + "};\n" + "void call_remove(Array &a) { a.remove(0); }\n" + "void call_remove_any(Array &a, unsigned index) { a.remove(index); }\n" + "void call_remove_last(Array &a) { a.length = 1; a.remove(0); }\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("#define UINT_MAX 4294967295U\n" + "void gtkpod(void *p) {\n" + " unsigned int position = (int)(long)p;\n" + " if (position == -1) {}\n" + " if (position == UINT_MAX) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check("#define UINT_MAX 4294967295U\n" + "void gtkpod(void *p) {\n" + " unsigned int position = (int)(long)p;\n" + " if (position == -1) {}\n" + " if (position == UINT_MAX) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("", errout_str()); + check("#define UINT_MAX 4294967295U\n" + "void gtkpod32(void *p) {\n" + " unsigned int position = (int)(long)p;\n" + " if (position == -1) {}\n" + " if (position == UINT_MAX) {}\n" + "}\n", settingsUnix32); + ASSERT_EQUALS("", errout_str()); + check("#define UINT_MAX 4294967295U\n" + "void gtkpod32_c(void *p) {\n" + " unsigned int position = (int)(long)p;\n" + " if (position == -1) {}\n" + " if (position == UINT_MAX) {}\n" + "}\n", settingsUnix32, false); + ASSERT_EQUALS("", errout_str()); + check("void known_sentinel32() {\n" + " unsigned int position = (int)(long)-1;\n" + " if (position == -1) {}\n" + "}\n", settingsUnix32); + ASSERT_EQUALS("[test.cpp:3:18]: (style) Condition 'position==-1' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("void known_sentinel() {\n" + " unsigned int position = (int)(long)-1;\n" + " if (position == -1) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:3:18]: (style) Condition 'position==-1' is always true [knownConditionTrueFalse]\n", errout_str()); + check("void known_sentinel_c() {\n" + " unsigned int position = (int)(long)-1;\n" + " if (position == -1) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:3:18]: (style) Condition 'position==-1' is always true [knownConditionTrueFalse]\n", errout_str()); + const char pointExclusionCode[] = + "void excluded_signed_point(int value) {\n" + " if (value != -1) {\n" + " unsigned int converted = (unsigned int)value;\n" + " if (converted == 4294967295U) {}\n" + " }\n" + "}\n" + "void noninjective_exclusion(int value) {\n" + " if (value != -1) {\n" + " unsigned char narrowed = (unsigned char)value;\n" + " if (narrowed == 255) {}\n" + " }\n" + "}\n"; + check(pointExclusionCode, settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:15] -> [test.cpp:4:23]: (style) Condition 'converted==4294967295U' is always false [knownConditionTrueFalse]\n", errout_str()); + check(pointExclusionCode, settingsUnix64, false); + ASSERT_EQUALS("[test.c:2:15] -> [test.c:4:23]: (style) Condition 'converted==4294967295U' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("typedef unsigned long size_t;\n" + "void narrowed_size() {\n" + " size_t len = 4294967297ULL;\n" + " unsigned int position = (unsigned int)(len - 1);\n" + " if (position > 0) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:5:18]: (style) Condition 'position>0' is always false [knownConditionTrueFalse]\n", errout_str()); + check("typedef unsigned long size_t;\n" + "void narrowed_size_c() {\n" + " size_t len = 4294967297ULL;\n" + " unsigned int position = (unsigned int)(len - 1);\n" + " if (position > 0) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:5:18]: (style) Condition 'position>0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("typedef unsigned long size_t;\n" + "void unknown_narrowed_size(size_t len) {\n" + " unsigned int position = (unsigned int)(len - 1);\n" + " if (position > 0) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check("typedef unsigned long size_t;\n" + "void unknown_narrowed_size_c(size_t len) {\n" + " unsigned int position = (unsigned int)(len - 1);\n" + " if (position > 0) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("", errout_str()); + + check("void compare_ternary_lhs(int result) {\n" + " if (result == ((0) ? 315 : (-1-1u))) {}\n" + "}\n" + "void compare_ternary_rhs(int result) {\n" + " if (((0) ? 315 : (-1-1u)) == result) {}\n" + "}\n" + "void compare_ternary_swapped_lhs(int result) {\n" + " if (result == ((0) ? (-1-1u) : 315)) {}\n" + "}\n" + "void compare_ternary_swapped_rhs(int result) {\n" + " if (((0) ? (-1-1u) : 315) == result) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("void compare_ternary_c_lhs(int result) {\n" + " if (result == ((0) ? 315 : (-1-1u))) {}\n" + "}\n" + "void compare_ternary_c_rhs(int result) {\n" + " if (((0) ? 315 : (-1-1u)) == result) {}\n" + "}\n" + "void compare_ternary_c_swapped_lhs(int result) {\n" + " if (result == ((0) ? (-1-1u) : 315)) {}\n" + "}\n" + "void compare_ternary_c_swapped_rhs(int result) {\n" + " if (((0) ? (-1-1u) : 315) == result) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("", errout_str()); + + check("void compare_known_ternary() {\n" + " int result = -2;\n" + " if (result != ((0) ? 315 : (-1-1u))) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:3:16]: (style) Condition 'result!=((0)?315:(-1-1u))' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("void compare_known_ternary_c() {\n" + " int result = -2;\n" + " if (result != ((0) ? 315 : (-1-1u))) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:3:16]: (style) Condition 'result!=((0)?315:(-1-1u))' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("void plain_unsigned_expression() {\n" + " if (-1-1u == 4294967294U) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:15]: (style) Condition '-1-1u==4294967294U' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("void plain_unsigned_expression_c() {\n" + " if (-1-1u == 4294967294U) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:2:15]: (style) Condition '-1-1u==4294967294U' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("void s3backer_guard() {\n" + " if ((unsigned int)(4294967295U*.666666)==2863308666U) {}\n" + "}\n", settingsUnix64); + ASSERT(errout_str().find("always true [knownConditionTrueFalse]") != std::string::npos); + + check("void s3backer_guard_c() {\n" + " if ((unsigned int)(4294967295U*.666666)==2863308666U) {}\n" + "}\n", settingsUnix64, false); + ASSERT(errout_str().find("always true [knownConditionTrueFalse]") != std::string::npos); + + check("extern unsigned long getlength(); extern void use(int);\n" + "void f() {\n" + " int length = (int)getlength();\n" + " if (length != 1)\n" + " if (length == 1) use(length);\n" + "}\n" + "void g() {\n" + " int length = (int)getlength();\n" + " if (length != 1)\n" + " if (1 == length) use(length);\n" + "}\n" + "void h() {\n" + " int length = (int)getlength();\n" + " if (length != 1)\n" + " if (length != 1) use(length);\n" + "}\n" + "void j() {\n" + " int length = (int)getlength();\n" + " if (length != 1)\n" + " if (1 != length) use(length);\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:4:16] -> [test.cpp:5:20]: (style) Condition 'length==1' is always false [knownConditionTrueFalse]\n" + "[test.cpp:9:16] -> [test.cpp:10:15]: (style) Condition '1==length' is always false [knownConditionTrueFalse]\n" + "[test.cpp:14:16] -> [test.cpp:15:20]: (style) Condition 'length!=1' is always true [knownConditionTrueFalse]\n" + "[test.cpp:19:16] -> [test.cpp:20:15]: (style) Condition '1!=length' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("extern unsigned long getlength(); extern void use(int);\n" + "void oce() {\n" + " int length = (int)getlength();\n" + " if (length!=1)\n" + " if (getlength()!=0)\n" + " if ((length==1 && getlength()) || (length==2 && getlength()))\n" + " use(length);\n" + "}\n", settingsUnix64); + const std::string oceOutput = errout_str(); + ASSERT(oceOutput.find("Condition 'length==1' is always false [knownConditionTrueFalse]") != std::string::npos); + ASSERT(oceOutput.find("[test.cpp:4:") != std::string::npos); + ASSERT(oceOutput.find(" -> ") != std::string::npos); + check("extern unsigned long getlength(); extern void use(int);\n" + "void oce_reversed_exclusion() {\n" + " int length = (int)getlength();\n" + " if (length != 1) {\n" + " if (1 == length) use(length);\n" + " if (1 != length) use(length);\n" + " }\n" + "}\n", settingsUnix64); + const std::string reversedExclusionOutput = errout_str(); + ASSERT(reversedExclusionOutput.find("Condition '1==length' is always false [knownConditionTrueFalse]") != std::string::npos); + ASSERT(reversedExclusionOutput.find("Condition '1!=length' is always true [knownConditionTrueFalse]") != std::string::npos); + ASSERT(reversedExclusionOutput.find("[test.cpp:4:") != std::string::npos); + ASSERT(reversedExclusionOutput.find(" -> ") != std::string::npos); + + check("extern unsigned long getlength(); extern void use(int);\n" + "void range_without_exclusion() {\n" + " int length = (int)getlength();\n" + " if (length >= 0)\n" + " if (length == 1) use(length);\n" + "}\n" + "void exclude_a_different_point() {\n" + " int length = (int)getlength();\n" + " if (length != 2)\n" + " if (length == 1) use(length);\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("extern unsigned long getlength(); extern void use(int);\n" + "void mutation_invalidates_exclusion() {\n" + " int length = (int)getlength();\n" + " if (length != 1) {\n" + " length = 1;\n" + " if (length == 1) use(length);\n" + " }\n" + "}\n", settingsUnix64); + const std::string mutationOutput = errout_str(); + ASSERT(mutationOutput.find("Condition 'length==1' is always true [knownConditionTrueFalse]") != std::string::npos); + ASSERT_EQUALS(std::string::npos, mutationOutput.find("[test.cpp:4:16] ->")); + + check("extern unsigned long getlength(); extern void use(int);\n" + "void incompatible_paths(bool selector) {\n" + " int length = (int)getlength();\n" + " if (selector && length != 1) use(0);\n" + " if (!selector && length == 1) use(length);\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("void use(int);\n" + "struct Scope { unsigned short sp; };\n" + "void pop_n(Scope *s, int n) {\n" + " if ((int)s->sp-n < 0) use(1);\n" + " s->sp -= n;\n" + "}\n" + "void call_pop(Scope *s, int n) {\n" + " pop_n(s, n);\n" + " if (n == 0) use(0);\n" + "}\n" + "void caller_n0(Scope *s) { call_pop(s, 0); }\n" + "void caller_n1(Scope *s) { call_pop(s, 1); }\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("struct Scope { unsigned short sp; };\n" + "void use(int);\n" + "void local_n0(Scope *s, int n) {\n" + " if (n == 0)\n" + " if ((int)s->sp - n < 0) use(1);\n" + "}\n" + "void local_sp0_n1(Scope *s) {\n" + " s->sp = 0;\n" + " if ((int)s->sp - 1 < 0) use(1);\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:4:11] -> [test.cpp:5:28]: (style) Condition '(int)s->sp-n<0' is always false [knownConditionTrueFalse]\n" + "[test.cpp:9:24]: (style) Condition '(int)s->sp-1<0' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("void use(int);\n" + "struct Scope { unsigned short sp; };\n" + "void pop_n(Scope *s, int n) {\n" + " if ((int)s->sp-n < 0) use(1);\n" + "}\n" + "void caller_underflow(Scope *s) {\n" + " s->sp = 0;\n" + " pop_n(s, 1);\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + } + void knownConditionIncDecOperator() { check( "void f() {\n" @@ -6852,7 +7285,6 @@ class TestCondition : public TestFixture { ASSERT_EQUALS( "[test.cpp:3:12]: (style) Condition 'nf>+1.0' is always false [knownConditionTrueFalse]\n", errout_str()); - check("void foo() {\n" // #11200 " float f = 1.0;\n" " if (f > -1.0) {}\n" @@ -6904,6 +7336,143 @@ class TestCondition : public TestFixture { "", errout_str()); } + + void unsignedArithmeticNoWrap() + { + const Settings settingsUnix64 = settingsBuilder().severity(Severity::style).platform(Platform::Type::Unix64).build(); + const Settings settingsUnix32 = settingsBuilder().severity(Severity::style).platform(Platform::Type::Unix32).build(); + + check("int safe_subtraction(unsigned int count) {\n" + " if (count < 2) return 0;\n" + " if (count - 1 > 0) return 1;\n" + " return 0;\n" + "}\n"); + ASSERT(errout_str().find("Condition 'count-1>0' is always true") != std::string::npos); + check("void safe_subtraction_boundary(unsigned int x) {\n" + " if (x < 1u) return;\n" + " if (x - 1u == 4294967295u) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("void wrapping_subtraction_boundary(unsigned int x) {\n" + " if (x - 1u == 4294967295u) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check("void possible_upper_zero_wrap(unsigned int len) {\n" + " if (len > 0) return;\n" + " if (len - 1u > 100u) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check("void possible_upper_zero_wrap_c(unsigned int len) {\n" + " if (len > 0) return;\n" + " if (len - 1u > 100u) {}\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("", errout_str()); + check("void non_dominating_guard(bool flag, unsigned int x) {\n" + " if (flag) {\n" + " if (x < 1u) return;\n" + " }\n" + " if (x - 1u == 4294967295u) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("int safe_addition(unsigned int value) {\n" + " if (value < 10) {\n" + " if (value + 1 > 10) return 1;\n" + " }\n" + " return 0;\n" + "}\n"); + ASSERT(errout_str().find("Condition 'value+1>10' is always false") != std::string::npos); + + check("int tinyexr_range(unsigned long size) {\n" + " if (size < 6) return 0;\n" + " unsigned long offset = 2;\n" + " if (size - offset < 4) return 1;\n" + " return 0;\n" + "}\n"); + ASSERT(errout_str().find("Condition 'size-offset<4' is always false") != std::string::npos); + + check("void use(int);\n" + "void f6(unsigned char c) {\n" + " unsigned s = c;\n" + " if (s + 1u == 0u) use(4);\n" + "}\n" + "void f4(unsigned long n) {\n" + " for (unsigned long i = 0; i < n; i++) {\n" + " if (i + 1 == 0) use(3);\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:16]: (style) Condition 's+1u==0u' is always false [knownConditionTrueFalse]\n" + "[test.cpp:8:19]: (style) Condition 'i+1==0' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("void use(int);\n" + "void unguarded_assignment_wrap(unsigned long n) {\n" + " unsigned long i = n - 1;\n" + " if (i + 1 == 0) use(3);\n" + "}\n" + "void full_range_wrap(unsigned u) {\n" + " if (u + 1u == 0u) use(1);\n" + "}\n" + "void possible_subtraction_wrap(unsigned x, unsigned y) {\n" + " if (x < 10u && y < 20u && x - y > 100u) use(2);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void wrap_cases(int value) {\n" + " if ((unsigned int)value - 1u == 4294967295u) {}\n" + " if ((unsigned int)value + 1u == 0u) {}\n" + " if ((unsigned int)value * 2u > 1u) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("typedef unsigned long size_t;\n" + "int smenu(size_t len, char *str) {\n" + " size_t start = 0;\n" + " if (len > 1) {\n" + " while (start <= len-1 && (str[start]=='{' || str[start]=='}')) start++;\n" + " size_t end = len-1;\n" + " if (end > 0) return 1;\n" + " }\n" + " return 0;\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:4:13] -> [test.c:7:17]: (style) Condition 'end>0' is always true [knownConditionTrueFalse]\n", errout_str()); + check("typedef unsigned long size_t;\n" + "int smenu32(size_t len, char *str) {\n" + " size_t start = 0;\n" + " if (len > 1) {\n" + " while (start <= len-1 && (str[start]=='{' || str[start]=='}')) start++;\n" + " size_t end = len-1;\n" + " if (end > 0) return 1;\n" + " }\n" + " return 0;\n" + "}\n", settingsUnix32, false); + ASSERT_EQUALS("[test.c:4:13] -> [test.c:7:17]: (style) Condition 'end>0' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("typedef unsigned long size_t;\n" + "int no_loop(size_t len) {\n" + " if (len > 1) {\n" + " size_t end = len-1;\n" + " if (end > 0) return 1;\n" + " }\n" + " return 0;\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:3:13] -> [test.c:5:17]: (style) Condition 'end>0' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("typedef unsigned long size_t;\n" + "int narrow_size() {\n" + " size_t len = 4294967297ULL;\n" + " unsigned int end = (unsigned int)(len-1);\n" + " if (end > 0) return 1;\n" + " return 0;\n" + "}\n", settingsUnix64, false); + ASSERT_EQUALS("[test.c:5:13]: (style) Condition 'end>0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("void known_unsigned_point_wrap() {\n" + " if (0u - 1u == 4294967295U) {}\n" + "}\n", settingsUnix64); + ASSERT(errout_str().find("Condition '0u-1u==4294967295U' is always true [knownConditionTrueFalse]") != std::string::npos); + } }; REGISTER_TEST(TestCondition) diff --git a/test/testfunctions.cpp b/test/testfunctions.cpp index 4ebe3360aba..307991153f0 100644 --- a/test/testfunctions.cpp +++ b/test/testfunctions.cpp @@ -63,6 +63,8 @@ class TestFunctions : public TestFixture { // Math function usage TEST_CASE(mathfunctionCall_fmod); TEST_CASE(mathfunctionCall_sqrt); + TEST_CASE(mathfunctionCall_log_garli_reduction); + TEST_CASE(mathfunctionCall_log_guarded); TEST_CASE(mathfunctionCall_log); TEST_CASE(mathfunctionCall_acos); TEST_CASE(mathfunctionCall_asin); @@ -823,6 +825,90 @@ class TestFunctions : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void mathfunctionCall_log_guarded() { + check("double guarded_return(double x) {\n" + " if (x <= 0.0)\n" + " return 0.0;\n" + " return log(x);\n" + "}\n" + "double guarded_else(double x) {\n" + " if (x <= 0.0) {\n" + " return 0.0;\n" + " } else {\n" + " return log(x);\n" + " }\n" + "}\n" + "double guarded_short_circuit(double x) {\n" + " if (x <= 0.0 || x >= 2.0) return 0.0;\n" + " return log(x);\n" + "}\n" + "double guarded_subtract_short_circuit(double x) {\n" + " if (x >= 0.5 || x <= -1.0) return 0.0;\n" + " return log(0.5 - x);\n" + "}\n" + "double guarded_subtract(double x) {\n" + " if (x >= 0.5)\n" + " return 0.0;\n" + " return log(0.5 - x);\n" + "}\n" + "float guarded_float(float x) {\n" + " if (x <= 0.0f)\n" + " return 0.0f;\n" + " return logf(x);\n" + "}\n" + "float guarded_float_subtract(float x) {\n" + " if (x >= 0.5f)\n" + " return 0.0f;\n" + " return logf(0.5f - x);\n" + "}\n" + "long double guarded_long_double(long double x) {\n" + " if (x <= 0.0L)\n" + " return 0.0L;\n" + " return logl(x);\n" + "}\n" + "double guarded_after_assignment(double x) {\n" + " if (x <= 0.0)\n" + " x = 1.0;\n" + " return log(x);\n" + "}\n" + "double guarded_after_loop(double x) {\n" + " while (x <= 0.0)\n" + " x = 1.0;\n" + " return log(x);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("double invalid_true_branch(double x) {\n" + " if (x <= 0.0)\n" + " return log(x);\n" + " return 0.0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:20]: (warning) Either the condition 'x<=0.0' is redundant or log() argument nr 1 can have invalid value. The value is 0 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n", + errout_str()); + } + + void mathfunctionCall_log_garli_reduction() { + check("inline double LnGamma(double alp) {\n" + " double x = alp, f = 0.0, z;\n" + " if (x < 7) {\n" + " f = 1.0;\n" + " z = x - 1.0;\n" + " while (++z < 7.0)\n" + " f *= z;\n" + " x = z;\n" + " f = -log(f);\n" + " }\n" + " return f + (x - 0.5) * log(x) - x + 0.918938533204673;\n" + "}\n" + "double PointChi2(double p, double v) {\n" + " if (p < 0.000002 || p > 0.999998 || v <= 0.0)\n" + " return -1.0;\n" + " return LnGamma(v * 0.5);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + } + void mathfunctionCall_log() { // log,log10,logf,logl,log10f,log10l,log2,log2f,log2l,log1p,log1pf,log1pl check("void foo()\n" diff --git a/test/testother.cpp b/test/testother.cpp index 60c2a3bc528..dcbe5bc0632 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -10451,6 +10451,12 @@ class TestOther : public TestFixture { check("const int x = -1 >> 2;\n"); ASSERT_EQUALS("[test.cpp:1:18]: (portability) Shifting a negative value is technically undefined behaviour [shiftNegativeLHS]\n", errout_str()); + check("int negative_lhs(int value) {\n" + " if (value < -16 || value > 15) return 0;\n" + " return value << 10;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:18]: (portability) Shifting a negative value is technically undefined behaviour [shiftNegativeLHS]\n", errout_str()); + // #6383 - unsigned type check("const int x = (unsigned int)(-1) >> 2;\n"); ASSERT_EQUALS("", errout_str()); @@ -14106,7 +14112,7 @@ class TestOther : public TestFixture { " }\n" " return false;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:4:17]: (style) Pointer expression 'a.x' converted to bool is always true. [knownPointerToBool]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:4:17]: (style) Pointer expression 'a.x' converted to bool is always true. [knownPointerToBool]\n", errout_str()); check("void f(bool* b) { if (b) *b = true; }\n"); ASSERT_EQUALS("", errout_str()); diff --git a/test/teststl.cpp b/test/teststl.cpp index bb20d2d8182..af2600b5469 100644 --- a/test/teststl.cpp +++ b/test/teststl.cpp @@ -526,6 +526,42 @@ class TestStl : public TestFixture { " v.resize(entries + 2);\n" " v[0] = 1;\n" "}\n"); + ASSERT_EQUALS("[test.cpp:5:6]: warning: Out of bounds access in expression 'v[0]' because 'v' is empty. [containerOutOfBounds]\n", errout_str()); + + checkNormal("void f(void) {\n" + " std::vector v;\n" + " unsigned entries = 0xfffffffeU;\n" + " if (v.size() < entries + 2U)\n" + " v.resize(entries + 2U);\n" + " v[0] = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:6]: error: Out of bounds access in expression 'v[0]' because 'v' is empty. [containerOutOfBounds]\n", errout_str()); + + checkNormal("void f(void) {\n" + " std::vector v;\n" + " unsigned entries = 0xffffffffU;\n" + " if (v.size() < entries + 2U)\n" + " v.resize(entries + 2U);\n" + " v[0] = 1;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + checkNormal("void f(void) {\n" + " std::vector v;\n" + " unsigned entries = 4;\n" + " if (v.size() < entries + 2U)\n" + " v.resize(entries + 2U);\n" + " v[0] = 1;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + checkNormal("void f(unsigned entries) {\n" + " if (entries > 1000U) return;\n" + " std::vector v;\n" + " if (v.size() < entries + 2U)\n" + " v.resize(entries + 2U);\n" + " v[0] = 1;\n" + "}\n"); ASSERT_EQUALS("", errout_str()); checkNormal("void f(size_t entries) {\n" diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 93bc8861e3e..a2a6303766a 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -9682,6 +9682,8 @@ class TestSymbolDatabase : public TestFixture { $.platform.int_bit = 32, $.platform.long_bit = 64, $.platform.long_long_bit = 64); + const Settings settingsUnix64 = settingsBuilder().platform(Platform::Type::Unix64).build(); + const Settings settingsWin64 = settingsBuilder().platform(Platform::Type::Win64).build(); // numbers ASSERT_EQUALS("signed int", typeOf("1;\n", "1", false, &s)); @@ -9760,7 +9762,8 @@ class TestSymbolDatabase : public TestFixture { ASSERT_EQUALS("unsigned int", typeOf("(unsigned int)1 + (unsigned char)2;\n", "+")); ASSERT_EQUALS("unsigned int", typeOf("(unsigned int)1 + (signed int)2;\n", "+")); ASSERT_EQUALS("unsigned int", typeOf("(unsigned int)1 + (unsigned int)2;\n", "+")); - ASSERT_EQUALS("signed long", typeOf("(signed long)1 + (unsigned int)2;\n", "+")); + ASSERT_EQUALS("signed long", typeOf("(signed long)1 + (unsigned int)2;\n", "+", true, &settingsUnix64)); + ASSERT_EQUALS("unsigned long", typeOf("(signed long)1 + (unsigned int)2;\n", "+", true, &settingsWin64)); ASSERT_EQUALS("unsigned long", typeOf("(unsigned long)1 + (signed int)2;\n", "+")); // char @@ -9967,19 +9970,19 @@ class TestSymbolDatabase : public TestFixture { " \n" " \n" ""; - const Settings settingsWin64 = settingsBuilder().platform(Platform::Type::Win64).libraryxml(xmldata).build(); + const Settings settingsWin64PodType = settingsBuilder().platform(Platform::Type::Win64).libraryxml(xmldata).build(); ValueType vt; - ASSERT_EQUALS(true, vt.fromLibraryType("u32", settingsWin64)); - ASSERT_EQUALS(true, vt.fromLibraryType("xyz::x", settingsWin64)); + ASSERT_EQUALS(true, vt.fromLibraryType("u32", settingsWin64PodType)); + ASSERT_EQUALS(true, vt.fromLibraryType("xyz::x", settingsWin64PodType)); ASSERT_EQUALS(ValueType::Type::INT, vt.type); ValueType vt2; - ASSERT_EQUALS(true, vt2.fromLibraryType("podtype2", settingsWin64)); + ASSERT_EQUALS(true, vt2.fromLibraryType("podtype2", settingsWin64PodType)); ASSERT_EQUALS(ValueType::Type::INT, vt2.type); - ASSERT_EQUALS("unsigned int *", typeOf(";void *data = new u32[10];\n", "new", true, &settingsWin64)); - ASSERT_EQUALS("unsigned int *", typeOf(";void *data = new xyz::x[10];\n", "new", true, &settingsWin64)); - ASSERT_EQUALS("unsigned int", typeOf("; x = (xyz::x)12;\n", "(", true, &settingsWin64)); - ASSERT_EQUALS("unsigned int", typeOf(";u32(12);\n", "(", true, &settingsWin64)); - ASSERT_EQUALS("unsigned int", typeOf("x = u32(y[i]);\n", "(", true, &settingsWin64)); + ASSERT_EQUALS("unsigned int *", typeOf(";void *data = new u32[10];\n", "new", true, &settingsWin64PodType)); + ASSERT_EQUALS("unsigned int *", typeOf(";void *data = new xyz::x[10];\n", "new", true, &settingsWin64PodType)); + ASSERT_EQUALS("unsigned int", typeOf("; x = (xyz::x)12;\n", "(", true, &settingsWin64PodType)); + ASSERT_EQUALS("unsigned int", typeOf(";u32(12);\n", "(", true, &settingsWin64PodType)); + ASSERT_EQUALS("unsigned int", typeOf("x = u32(y[i]);\n", "(", true, &settingsWin64PodType)); } { // PlatformType @@ -10002,9 +10005,9 @@ class TestSymbolDatabase : public TestFixture { " \n" " \n" ""; - const Settings settingsWin64 = settingsBuilder().platform(Platform::Type::Win64).libraryxml(xmldata).build(); + const Settings settingsWin64PlatformType = settingsBuilder().platform(Platform::Type::Win64).libraryxml(xmldata).build(); ValueType vt; - ASSERT_EQUALS(true, vt.fromLibraryType("LPCTSTR", settingsWin64)); + ASSERT_EQUALS(true, vt.fromLibraryType("LPCTSTR", settingsWin64PlatformType)); ASSERT_EQUALS(ValueType::Type::WCHAR_T, vt.type); } { diff --git a/test/testtype.cpp b/test/testtype.cpp index 1b5b57f41ca..1876abdf711 100644 --- a/test/testtype.cpp +++ b/test/testtype.cpp @@ -593,6 +593,88 @@ class TestType : public TestFixture { "}\n", dinit(CheckPOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:4:24]: (error) Undefined behaviour: float () to integer conversion overflow. [floatConversionOverflow]\n", removeFloat(errout_str())); + check("void f(double rr) {\n" + " if (!(rr >= 0.0) || rr > 1.0e308)\n" + " return;\n" + " while (rr >= 1.0e119L)\n" + " rr *= 1.0e-100L;\n" + " while (rr >= 1.0e29L)\n" + " rr *= 1.0e-10L;\n" + " while (rr >= 1.0e19L)\n" + " rr *= 1.0e-1L;\n" + " unsigned long long value = (unsigned long long)rr;\n" + " (void)value;\n" + "}\n"); + ASSERT_EQUALS("", removeFloat(errout_str())); + + { + const Settings unix64 = settingsBuilder().platform(Platform::Type::Unix64).c(Standards::C11).cpp(Standards::CPP14).build(); + checkP("int f(double d) { if (d >= 1e10) return 0; if (d > 100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", errout_str()); + checkP("int f(double d) { if (d >= 1e10) return 0; if (d > 100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + checkP("int f(double d) { if (d >= 1e10 || d > 100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", errout_str()); + checkP("int f(double d) { if (d >= 1e10 || d > 100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + checkP("int f(double d) { if (1e10 <= d || d > 100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", errout_str()); + checkP("int f(double d) { if (1e10 <= d || d > 100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + checkP("int f(double d) { if (d <= -1e30) return 0; if (d < -100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", errout_str()); + checkP("int f(double d) { if (d <= -1e30) return 0; if (d < -100.0) return 0; return (int)d; }\n", dinit(CheckPOptions, $.settings = &unix64, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + checkP("int f(double d) { if (d >= 1e10) return 0; if (d > 100.0) return 0; return d; }\n", dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", errout_str()); + checkP("int f(double d) { if (d >= 1e10) return 0; if (d > 100.0) return 0; return d; }\n", dinit(CheckPOptions, $.settings = &unix64, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + checkP("int f(double d) { if (d >= 1e100) return (int)d; return 0; }\n", + dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("[test.cpp:1:25] -> [test.cpp:1:42]: (warning) Undefined behaviour: float () to integer conversion overflow. [floatConversionOverflow]\n", removeFloat(errout_str())); + checkP("int f(double d) { if (d <= -1e100) return (int)d; return 0; }\n", + dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("[test.cpp:1:25] -> [test.cpp:1:43]: (warning) Undefined behaviour: float () to integer conversion overflow. [floatConversionOverflow]\n", removeFloat(errout_str())); + checkP("unsigned int s3backer(unsigned int maxkeys) {\n" + " if (maxkeys >= (unsigned int)(4294967295U*.666666)-1) return 0;\n" + " return (unsigned int)(maxkeys/.666666)+1;\n" + "}\n", dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", removeFloat(errout_str())); + checkP("unsigned int s3backer(unsigned int maxkeys) {\n" + " if (maxkeys >= (unsigned int)(4294967295U*.666666)-1) return 0;\n" + " return (unsigned int)(maxkeys/.666666)+1;\n" + "}\n", dinit(CheckPOptions, $.settings = &unix64, $.cpp = false)); + ASSERT_EQUALS("", removeFloat(errout_str())); + const Settings unix32 = settingsBuilder().platform(Platform::Type::Unix32).c(Standards::C11).cpp(Standards::CPP14).build(); + checkP("unsigned int s3backer(unsigned int maxkeys) {\n" + " if (maxkeys >= (unsigned int)(4294967295U*.666666)-1) return 0;\n" + " return (unsigned int)(maxkeys/.666666)+1;\n" + "}\n", dinit(CheckPOptions, $.settings = &unix32, $.cpp = false)); + ASSERT_EQUALS("", removeFloat(errout_str())); + checkP("unsigned int s3backer_literal(unsigned int maxkeys) {\n" + " if (maxkeys >= 2863308665U) return 0;\n" + " return (unsigned int)(maxkeys/.666666)+1;\n" + "}\n", dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", removeFloat(errout_str())); + checkP("unsigned int s3backer_literal(unsigned int maxkeys) {\n" + " if (maxkeys >= 2863308665U) return 0;\n" + " return (unsigned int)(maxkeys/.666666)+1;\n" + "}\n", dinit(CheckPOptions, $.settings = &unix64, $.cpp = false)); + ASSERT_EQUALS("", removeFloat(errout_str())); + checkP("unsigned int f(double d) { if (d >= 2863308665U) return 0; return (unsigned int)d; }\n", + dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("", removeFloat(errout_str())); + + checkP("unsigned int f(void) { return (unsigned int)6442457383.46; }\n", + dinit(CheckPOptions, $.settings = &unix64)); + ASSERT_EQUALS("[test.cpp:1:31]: (error) Undefined behaviour: float () to integer conversion overflow. [floatConversionOverflow]\n", removeFloat(errout_str())); + } + } void integerOverflow() { // #11794 @@ -636,6 +718,72 @@ class TestType : public TestFixture { "};\n" "static B<64> b;\n"); ASSERT_EQUALS("", errout_str()); + + // FP shiftTooManyBits: guarded shift where the shift amount is + // conditionally derived from an unsigned subtraction; a possible value + // from an interprocedural caller must not collapse the interval + // inference on the unsigned arithmetic result + { + const Settings settings = settingsBuilder().platform(Platform::Type::Unix64).build(); + check("static unsigned int l[16];\n" + "void scalar_shift(unsigned int shift) {\n" + " unsigned int shiftlimbs = shift >> 5;\n" + " unsigned int shiftlow = shift & 0x1Fu;\n" + " unsigned int shifthigh = 32u - shiftlow;\n" + " unsigned int r = 0u;\n" + " r |= (shift < 448u && shiftlow ? (l[1 + shiftlimbs] << shifthigh) : 0u);\n" + " r |= (shift < 416u && shiftlow ? (l[2 + shiftlimbs] << shifthigh) : 0u);\n" + " (void)r;\n" + "}\n" + "int main(void) { scalar_shift(384u); return 0; }\n", + dinit(CheckOptions, $.settings = &settings)); + ASSERT_EQUALS("", errout_str()); + } + + check("int guarded_shift(int delta, unsigned int lag_depth, unsigned int lag_mask) {\n" + " if (lag_depth > 20) return 0;\n" + " if (delta < -100) return 0;\n" + " if (delta < -(int)lag_depth) return 0;\n" + " if (delta < 0) return lag_mask >> -delta;\n" + " return 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + { + const Settings c11unix64 = settingsBuilder().platform(Platform::Type::Unix64).c(Standards::C11).build(); + checkP("unsigned long f(unsigned long i) {\n" + " if (i < 64) return 0;\n" + " if (i < 96) return 1 << (i - 64);\n" + " return 0;\n" + "}\n", dinit(CheckPOptions, $.settings = &c11unix64, $.cpp = false)); + ASSERT_EQUALS("[test.c:3:11] -> [test.c:3:26]: (warning) Shifting signed 32-bit value by 31 bits is undefined behaviour. See condition at line 3. [shiftTooManyBitsSigned]\n", errout_str()); + + checkP("unsigned long f(unsigned long i) {\n" + " if (i < 64) return 0;\n" + " if (i < 96) return 1u << (i - 64);\n" + " return 0;\n" + "}\n", dinit(CheckPOptions, $.settings = &c11unix64, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + checkP("unsigned long f(unsigned long i) {\n" + " if (i < 64) return 0;\n" + " if (i < 95) return 1 << (i - 64);\n" + " return 0;\n" + "}\n", dinit(CheckPOptions, $.settings = &c11unix64, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + } + const Settings cpp14unix64 = settingsBuilder().platform(Platform::Type::Unix64).cpp(Standards::CPP14).build(); + checkP("unsigned long f(unsigned long i) {\n" + " if (i < 64) return 0;\n" + " if (i < 96) return 1 << (i - 64);\n" + " return 0;\n" + "}\n", dinit(CheckPOptions, $.settings = &cpp14unix64)); + ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:3:26]: (portability) Shifting signed 32-bit value by 31 bits is implementation-defined behaviour. See condition at line 3. [shiftTooManyBitsSigned]\n", errout_str()); + + check("unsigned int oversized_shift(unsigned int value) {\n" + " return value >> 32;\n" + "}\n"); + ASSERT(errout_str().find("[shiftTooManyBits]") != std::string::npos); } }; diff --git a/test/testuninitvar.cpp b/test/testuninitvar.cpp index 1874e020fa8..e110251bcfa 100644 --- a/test/testuninitvar.cpp +++ b/test/testuninitvar.cpp @@ -65,6 +65,7 @@ class TestUninitVar : public TestFixture { TEST_CASE(valueFlowUninit2_value); TEST_CASE(valueFlowUninit_uninitvar2); TEST_CASE(valueFlowUninit_functioncall); + TEST_CASE(valueFlowUninitFloatingPositiveControl); TEST_CASE(uninitStructMember); // struct members TEST_CASE(uninitvar2_while); TEST_CASE(uninitvar2_4494); // #4494 @@ -3555,7 +3556,8 @@ class TestUninitVar : public TestFixture { } void uninitvar2_value() { - checkUninitVar("void f() {\n" + checkUninitVar("#define ENOMEM 12\n" + "void f() {\n" " int i;\n" " if (x) {\n" " int y = -ENOMEM;\n" // assume constant ENOMEM is nonzero since it's negated @@ -3565,7 +3567,8 @@ class TestUninitVar : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - checkUninitVar("void f() {\n" + checkUninitVar("#define ENOMEM 12\n" + "void f() {\n" " int i, y;\n" " if (x) {\n" " y = -ENOMEM;\n" // assume constant ENOMEM is nonzero since it's negated @@ -3575,7 +3578,8 @@ class TestUninitVar : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - checkUninitVar("void f() {\n" + checkUninitVar("#define ENOMEM 12\n" + "void f() {\n" " int i, y;\n" " if (x) y = -ENOMEM;\n" // assume constant ENOMEM is nonzero since it's negated " else y = get_value(i);\n" @@ -3677,6 +3681,15 @@ class TestUninitVar : public TestFixture { (checkuninitvar.valueFlowUninit)(); } + void valueFlowUninitFloatingPositiveControl() { + valueFlowUninit("float initialized(float x) { if (x <= 0.0f) return 0.0f; return x; }\n", false); + ASSERT_EQUALS("", errout_str()); + valueFlowUninit("float uninitialized() { float x; return x; }\n", false); + ASSERT_EQUALS( + "[test.c:1:41]: (error) Uninitialized variable: x [uninitvar]\n", + errout_str()); + } + void uninitvar15() { // #13685 const char code[] = "int f() {\n" " int x;\n" @@ -3690,7 +3703,8 @@ class TestUninitVar : public TestFixture { void valueFlowUninit2_value() { - valueFlowUninit("void f() {\n" + valueFlowUninit("#define ENOMEM 12\n" + "void f() {\n" " int i;\n" " if (x) {\n" " int y = -ENOMEM;\n" // assume constant ENOMEM is nonzero since it's negated @@ -3700,7 +3714,8 @@ class TestUninitVar : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - valueFlowUninit("void f() {\n" + valueFlowUninit("#define ENOMEM 12\n" + "void f() {\n" " int i, y;\n" " if (x) {\n" " y = -ENOMEM;\n" // assume constant ENOMEM is nonzero since it's negated @@ -3710,7 +3725,8 @@ class TestUninitVar : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - valueFlowUninit("void f() {\n" + valueFlowUninit("#define ENOMEM 12\n" + "void f() {\n" " int i, y;\n" " if (x) y = -ENOMEM;\n" // assume constant ENOMEM is nonzero since it's negated " else y = get_value(i);\n" diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index f84f0ea0d89..81f79b93272 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -19,16 +19,20 @@ #include "errortypes.h" #include "fixture.h" #include "helpers.h" +#include "infer.h" #include "mathlib.h" #include "platform.h" #include "settings.h" #include "standards.h" #include "token.h" +#include "valueptr.h" +#include "vf_common.h" #include "vfvalue.h" #include #include #include +#include #include #include #include @@ -160,6 +164,7 @@ class TestValueFlow : public TestFixture { TEST_CASE(valueFlowSymbolicStrlen); TEST_CASE(valueFlowSmartPointer); TEST_CASE(valueFlowImpossibleMinMax); + TEST_CASE(inferPathCompatibility); TEST_CASE(valueFlowImpossibleIncDec); TEST_CASE(valueFlowImpossibleUnknownConstant); TEST_CASE(valueFlowContainerEqual); @@ -6377,7 +6382,8 @@ class TestValueFlow : public TestFixture { ASSERT_EQUALS(1, values.size()); ASSERT_EQUALS(true, values.front().isUninitValue()); - code = "void f(int x) {\n" + code = "#define ENOMEM 12\n" + "void f(int x) {\n" " int i;\n" " if (x > 0) {\n" " int y = -ENOMEM;\n" // assume constant ENOMEM is nonzero since it's negated @@ -8060,6 +8066,31 @@ class TestValueFlow : public TestFixture { " S(std::max_align_t T::* p) : m(p) {}\n" "};\n"; (void)valueOfTok(code, ":"); // don't crash + + const char libraryXml[] = "" + "" + "" + ""; + Settings charSettings = settingsBuilder().libraryxml(libraryXml).platform(Platform::Type::Unspecified).build(); + charSettings.checkUnknownFunctionReturn.insert("unknown_char"); + charSettings.checkUnknownFunctionReturn.insert("signed_char"); + const std::list signedReturnValues = tokenValues("x = signed_char();\n", "(", &charSettings); + ASSERT_EQUALS(2, signedReturnValues.size()); + ASSERT_EQUALS(-128, signedReturnValues.front().intvalue); + ASSERT_EQUALS(127, signedReturnValues.back().intvalue); + const std::list unknownReturnValues = tokenValues("x = unknown_char();\n", "(", &charSettings); + ASSERT(unknownReturnValues.empty() || + std::any_of(unknownReturnValues.cbegin(), unknownReturnValues.cend(), [](const ValueFlow::Value& value) { + return value.isIntValue() && value.intvalue >= 200; + })); + const char charCode[] = "char unknown_char(void);\n" + "void f() {\n" + " if (unknown_char() == 200) {}\n" + "}\n"; + const std::list comparisonValues = tokenValues(charCode, "==", &charSettings); + ASSERT(std::none_of(comparisonValues.cbegin(), comparisonValues.cend(), [](const ValueFlow::Value& value) { + return value.isKnown() && value.isIntValue() && value.intvalue == 0; + })); } void valueFlowUnknownFunctionReturnRand() { @@ -9063,6 +9094,40 @@ class TestValueFlow : public TestFixture { "}\n"; ASSERT_EQUALS(false, testValueOfXImpossible(code, 3U, 2)); ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, -1)); + + code = "int f() {\n" + " unsigned int source = 4294967295u;\n" + " unsigned int x = (source + 1u) % 16u;\n" + " return x;\n" + "}\n"; + ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 0)); + + code = "auto f(unsigned int i) {\n" + " auto x = (int)i;\n" + " return x;\n" + "}\n"; + SimpleTokenizer narrowingTokenizer(settings, *this); + ASSERT(narrowingTokenizer.tokenize(code)); + const Token* narrowingReturn = Token::findmatch(narrowingTokenizer.tokens(), "return x"); + ASSERT(narrowingReturn && narrowingReturn->next()); + const std::list& narrowedValues = narrowingReturn->next()->values(); + // Narrowing may add signed-destination bounds; it must not invent an excluded point. + ASSERT(std::none_of(narrowedValues.cbegin(), narrowedValues.cend(), [](const ValueFlow::Value& value) { + return value.isImpossible() && value.bound == ValueFlow::Value::Bound::Point; + })); + + code = "auto f(unsigned int i) {\n" + " auto x = (long long)i;\n" + " return x;\n" + "}\n"; + SimpleTokenizer wideningTokenizer(settings, *this); + ASSERT(wideningTokenizer.tokenize(code)); + const Token* wideningReturn = Token::findmatch(wideningTokenizer.tokens(), "return x"); + ASSERT(wideningReturn && wideningReturn->next()); + const std::list& widenedValues = wideningReturn->next()->values(); + ASSERT(std::any_of(widenedValues.cbegin(), widenedValues.cend(), [](const ValueFlow::Value& value) { + return value.isImpossible() && value.bound == ValueFlow::Value::Bound::Upper && value.intvalue == -1; + })); } void valueFlowMod() { @@ -9108,7 +9173,7 @@ class TestValueFlow : public TestFixture { code = "int f() {\n" " const int a[128] = {};\n" - " char b = -128;\n" + " signed char b = -128;\n" " return a[--b];\n" "}\n"; values = tokenValues(code, "--"); @@ -9121,13 +9186,13 @@ class TestValueFlow : public TestFixture { const char* code; code = "int f(const std::string &str) {\n" - " int x = str.c_str();\n" + " bool x = str.c_str();\n" " return x;\n" "}\n"; - ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, 0)); + ASSERT_EQUALS(true, testValueOfXKnown(code, 3U, 1)); code = "int f(const std::string_view &str) {\n" - " int x = str.c_str();\n" + " bool x = str.data();\n" " return x;\n" "}\n"; ASSERT_EQUALS(false, testValueOfXImpossible(code, 3U, 0)); @@ -9623,6 +9688,167 @@ class TestValueFlow : public TestFixture { "}\n"; ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, "a", -1)); ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, -1)); + + const Settings settingsUnix64 = settingsBuilder().platform(Platform::Type::Unix64).build(); + code = "void f(unsigned long long x) {\n" + " return (unsigned int)x;\n" + "}\n"; + SimpleTokenizer tokenizer(settingsUnix64, *this); + ASSERT(tokenizer.tokenize(code)); + const Token* returnTok = Token::findmatch(tokenizer.tokens(), "return ("); + ASSERT(returnTok && returnTok->next()); + const std::list& castValues = returnTok->next()->values(); + ASSERT(std::any_of(castValues.cbegin(), castValues.cend(), [](const ValueFlow::Value& value) { + return value.isImpossible() && value.intvalue == -1; + })); + ASSERT(std::any_of(castValues.cbegin(), castValues.cend(), [](const ValueFlow::Value& value) { + return value.isImpossible() && value.intvalue == 4294967296; + })); + + // Impossible bounds on a cast must not be propagated through + // arithmetic with unsigned result type, since wrap-around invalidates the bound + code = "void f(int x) {\n" + " return (unsigned int)x - 1u;\n" + "}\n"; + SimpleTokenizer tokenizer2(settingsUnix64, *this); + ASSERT(tokenizer2.tokenize(code)); + const Token* minusTok = Token::findsimplematch(tokenizer2.tokens(), "-"); + ASSERT(minusTok); + for (const ValueFlow::Value& value : minusTok->values()) { + ASSERT(!(value.isImpossible() && value.isIntValue() && + value.bound != ValueFlow::Value::Bound::Point && value.intvalue == 4294967295)); + } + + // Known values are still propagated through unsigned arithmetic + code = "void f(int x) {\n" + " unsigned int y = 5u;\n" + " return y - 1u;\n" + "}\n"; + SimpleTokenizer tokenizer3(settingsUnix64, *this); + ASSERT(tokenizer3.tokenize(code)); + const Token* minusTok3 = Token::findsimplematch(tokenizer3.tokens(), "-"); + ASSERT(minusTok3); + ASSERT(std::any_of(minusTok3->values().cbegin(), minusTok3->values().cend(), [](const ValueFlow::Value& value) { + return value.isKnown() && value.isIntValue() && value.intvalue == 4; + })); + + ValueType charType(ValueType::Sign::SIGNED, ValueType::Type::CHAR, 0); + MathLib::bigint minValue; + MathLib::bigint maxValue; + ASSERT(ValueFlow::getMinMaxValues(&charType, settingsUnix64.platform, minValue, maxValue)); + ASSERT_EQUALS(-128, minValue); + ASSERT_EQUALS(127, maxValue); + const ValueFlow::Value castInput(255); + ValueFlow::Value signedCast(castInput); + ASSERT(ValueFlow::castValue(signedCast, ValueType::Sign::SIGNED, 8)); + ASSERT_EQUALS(-1, signedCast.intvalue); + + charType.sign = ValueType::Sign::UNSIGNED; + ASSERT(ValueFlow::getMinMaxValues(&charType, settingsUnix64.platform, minValue, maxValue)); + ASSERT_EQUALS(0, minValue); + ASSERT_EQUALS(255, maxValue); + ValueFlow::Value unsignedCast(castInput); + ASSERT(ValueFlow::castValue(unsignedCast, ValueType::Sign::UNSIGNED, 8)); + ASSERT_EQUALS(255, unsignedCast.intvalue); + + ValueFlow::Value unsignedModulo(-1); + ASSERT(ValueFlow::castValue(unsignedModulo, ValueType::Sign::UNSIGNED, 8)); + ASSERT_EQUALS(255, unsignedModulo.intvalue); + ValueFlow::Value unrepresentableUnsigned64(-1); + ASSERT_EQUALS(false, ValueFlow::castValue(unrepresentableUnsigned64, ValueType::Sign::UNSIGNED, 64)); + ASSERT_EQUALS(-1, unrepresentableUnsigned64.intvalue); + + ValueFlow::Value validFloat; + validFloat.valueType = ValueFlow::Value::ValueType::FLOAT; + validFloat.floatValue = 2863308666.68847; + ASSERT(ValueFlow::castValue(validFloat, ValueType::Sign::UNSIGNED, 32)); + ASSERT_EQUALS(2863308666LL, validFloat.intvalue); + + ValueFlow::Value negativeFraction; + negativeFraction.valueType = ValueFlow::Value::ValueType::FLOAT; + negativeFraction.floatValue = -0.5; + ASSERT(ValueFlow::castValue(negativeFraction, ValueType::Sign::UNSIGNED, 32)); + ASSERT_EQUALS(0, negativeFraction.intvalue); + + ValueFlow::Value unsignedLimit; + unsignedLimit.valueType = ValueFlow::Value::ValueType::FLOAT; + unsignedLimit.floatValue = 4294967295.9; + ASSERT(ValueFlow::castValue(unsignedLimit, ValueType::Sign::UNSIGNED, 32)); + ASSERT_EQUALS(4294967295LL, unsignedLimit.intvalue); + + ValueFlow::Value signedMaximum; + signedMaximum.valueType = ValueFlow::Value::ValueType::FLOAT; + signedMaximum.floatValue = 2147483647.9; + ASSERT(ValueFlow::castValue(signedMaximum, ValueType::Sign::SIGNED, 32)); + ASSERT_EQUALS(2147483647LL, signedMaximum.intvalue); + + ValueFlow::Value unsignedBelow; + unsignedBelow.valueType = ValueFlow::Value::ValueType::FLOAT; + unsignedBelow.floatValue = -1.0; + ASSERT_EQUALS(false, ValueFlow::castValue(unsignedBelow, ValueType::Sign::UNSIGNED, 32)); + ASSERT(unsignedBelow.isFloatValue()); + + ValueFlow::Value signedBelow; + signedBelow.valueType = ValueFlow::Value::ValueType::FLOAT; + signedBelow.floatValue = -2147483649.0; + ASSERT_EQUALS(false, ValueFlow::castValue(signedBelow, ValueType::Sign::SIGNED, 32)); + ASSERT(signedBelow.isFloatValue()); + + ValueFlow::Value unsignedOverflow; + unsignedOverflow.valueType = ValueFlow::Value::ValueType::FLOAT; + unsignedOverflow.floatValue = 4294967296.0; + ASSERT_EQUALS(false, ValueFlow::castValue(unsignedOverflow, ValueType::Sign::UNSIGNED, 32)); + ASSERT(unsignedOverflow.isFloatValue()); + + ValueFlow::Value signedMinimum; + signedMinimum.valueType = ValueFlow::Value::ValueType::FLOAT; + signedMinimum.floatValue = -2147483648.9; + ASSERT(ValueFlow::castValue(signedMinimum, ValueType::Sign::SIGNED, 32)); + ASSERT_EQUALS(-2147483648LL, signedMinimum.intvalue); + + ValueFlow::Value signedOverflow; + signedOverflow.valueType = ValueFlow::Value::ValueType::FLOAT; + signedOverflow.floatValue = 2147483648.0; + ASSERT_EQUALS(false, ValueFlow::castValue(signedOverflow, ValueType::Sign::SIGNED, 32)); + ASSERT(signedOverflow.isFloatValue()); + + ValueFlow::Value infinite; + infinite.valueType = ValueFlow::Value::ValueType::FLOAT; + infinite.floatValue = std::numeric_limits::infinity(); + ASSERT_EQUALS(false, ValueFlow::castValue(infinite, ValueType::Sign::UNSIGNED, 32)); + ASSERT(infinite.isFloatValue()); + + ValueFlow::Value notANumber; + notANumber.valueType = ValueFlow::Value::ValueType::FLOAT; + notANumber.floatValue = std::numeric_limits::quiet_NaN(); + ASSERT_EQUALS(false, ValueFlow::castValue(notANumber, ValueType::Sign::UNSIGNED, 32)); + ASSERT(notANumber.isFloatValue()); + + ValueType doubleType(ValueType::Sign::UNKNOWN_SIGN, ValueType::Type::DOUBLE, 0); + ValueType intType(ValueType::Sign::SIGNED, ValueType::Type::INT, 0); + // A representable float endpoint maps monotonically and retains its bound kind. + ValueFlow::Value floatLower; + floatLower.valueType = ValueFlow::Value::ValueType::FLOAT; + floatLower.floatValue = 1.0; + floatLower.bound = ValueFlow::Value::Bound::Lower; + floatLower.setPossible(); + ASSERT(ValueFlow::isValueRangePreservingConversion(&doubleType, &intType, settingsUnix64, &floatLower)); + ASSERT(ValueFlow::castValue(floatLower, ValueType::Sign::SIGNED, 32)); + ASSERT(floatLower.isPossible()); + ASSERT(floatLower.isIntValue()); + ASSERT(floatLower.bound == ValueFlow::Value::Bound::Lower); + ASSERT_EQUALS(1, floatLower.intvalue); + + ValueFlow::Value excludedFloatLower; + excludedFloatLower.valueType = ValueFlow::Value::ValueType::FLOAT; + excludedFloatLower.floatValue = 1.0; + excludedFloatLower.bound = ValueFlow::Value::Bound::Lower; + excludedFloatLower.setImpossible(); + ASSERT_EQUALS(false, ValueFlow::isValueRangePreservingConversion(&doubleType, &intType, settingsUnix64, &excludedFloatLower)); + ASSERT_EQUALS(false, ValueFlow::castValue(excludedFloatLower, ValueType::Sign::SIGNED, 32)); + ASSERT(excludedFloatLower.isFloatValue()); + charType.sign = ValueType::UNKNOWN_SIGN; + ASSERT_EQUALS(false, ValueFlow::getMinMaxValues(&charType, settingsUnix64.platform, minValue, maxValue)); } void valueFlowImpossibleIncDec() @@ -9648,6 +9874,218 @@ class TestValueFlow : public TestFixture { "}\n"; ASSERT_EQUALS(true, testValueOfXImpossible(code, 7U, -1)); ASSERT_EQUALS(false, testValueOfXKnown(code, 7U, -1)); + + code = "int guarded_predecrement(unsigned int x) {\n" + " if (x > 0) {\n" + " --x;\n" + " if (x == 0) return 1;\n" + " }\n" + " return 0;\n" + "}\n"; + ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0)); + + code = "int guarded_postdecrement(unsigned int x) {\n" + " if (x > 0) {\n" + " x--;\n" + " if (x == 0) return 1;\n" + " }\n" + " return 0;\n" + "}\n"; + ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0)); + + code = "int decimal_dot(const char* current, const char* first, int dot) {\n" + " unsigned int digits = (unsigned int)(current - first);\n" + " if (digits > 0) {\n" + " if (dot) {\n" + " digits--;\n" + " if (digits == 0) return 1;\n" + " }\n" + " }\n" + " return 0;\n" + "}\n"; + ASSERT_EQUALS(false, testValueOfXImpossible(code, 6U, 0)); + + code = "int wrap_decrement(unsigned int x) {\n" + " x--;\n" + " if (x == 4294967295u) return 1;\n" + " return 0;\n" + "}\n"; + SimpleTokenizer wrapTokenizer(settings, *this); + ASSERT(wrapTokenizer.tokenize(code)); + const Token* wrappedValue = nullptr; + for (const Token* tok = wrapTokenizer.tokens(); tok; tok = tok->next()) { + if (tok->str() == "x" && tok->linenr() == 3) { + wrappedValue = tok; + break; + } + } + if (wrappedValue == nullptr) { + ASSERT(wrappedValue); + return; + } + ASSERT(std::none_of(wrappedValue->values().cbegin(), + wrappedValue->values().cend(), + [](const ValueFlow::Value& value) { + return value.isImpossible() && value.isIntValue() && value.intvalue == 4294967295LL; + })); + } + + void inferPathCompatibility() + { + SimpleTokenizer tokenizer(settings, *this); + ASSERT(tokenizer.tokenize("int f(int x) { if (x) return 0; if (x > 0) return 2; return 1; }\n")); + const Token* const condition = Token::findmatch(tokenizer.tokens(), "if"); + ASSERT(condition); + const Token* exclusionCondition = nullptr; + for (const Token* tok = condition->next(); tok; tok = tok->next()) { + if (tok->str() == "if") { + exclusionCondition = tok; + break; + } + } + ASSERT(exclusionCondition); + + ValueFlow::Value lhs(384); + lhs.setPossible(); + lhs.condition = condition; + lhs.path = 7; + lhs.conditional = true; + lhs.varId = 1; + lhs.varvalue = 384; + ValueFlow::Value rhs(32); + rhs.setKnown(); + + std::vector inferred = infer(makeIntegralInferModel(), "-", {lhs}, {rhs}); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT(inferred.front().isPossible()); + ASSERT_EQUALS(7, inferred.front().path); + ASSERT(inferred.front().condition == condition); + ASSERT(inferred.front().conditional); + ASSERT_EQUALS(1, inferred.front().varId); + ASSERT_EQUALS(384, inferred.front().varvalue); + std::list arithmeticValue{inferred.front()}; + + ValueFlow::Value rhsSamePath(32); + rhsSamePath.setPossible(); + rhsSamePath.condition = condition; + rhsSamePath.path = 7; + inferred = infer(makeIntegralInferModel(), "-", {lhs}, {rhsSamePath}); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT_EQUALS(7, inferred.front().path); + ASSERT(inferred.front().condition == condition); + + ValueFlow::Value rhsDifferentPath(32); + rhsDifferentPath.setPossible(); + rhsDifferentPath.path = 8; + inferred = infer(makeIntegralInferModel(), "-", {lhs}, {rhsDifferentPath}); + ASSERT(inferred.empty()); + + ValueFlow::Value knownLhs(9); + knownLhs.setKnown(); + ValueFlow::Value knownRhs(4); + knownRhs.setKnown(); + inferred = infer(makeIntegralInferModel(), "-", {knownLhs}, {knownRhs}); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT(inferred.front().isKnown()); + ASSERT_EQUALS(0, inferred.front().path); + ASSERT_EQUALS(5, inferred.front().intvalue); + ValueFlow::Value arithmeticExpected(352); + arithmeticExpected.setKnown(); + inferred = infer(makeIntegralInferModel(), "==", arithmeticValue, {arithmeticExpected}); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT(inferred.front().isPossible()); + ASSERT_EQUALS(7, inferred.front().path); + ASSERT_EQUALS(1, inferred.front().intvalue); + + ValueFlow::Value rhsLower(1); + rhsLower.setPossible(); + rhsLower.bound = ValueFlow::Value::Bound::Lower; + rhsLower.condition = condition; + rhsLower.path = 7; + ValueFlow::Value rhsUpper(10); + rhsUpper.setPossible(); + rhsUpper.bound = ValueFlow::Value::Bound::Upper; + rhsUpper.condition = condition; + rhsUpper.path = 7; + std::vector derivedBounds = infer(makeIntegralInferModel(), "-", {lhs}, {rhsLower, rhsUpper}); + ASSERT_EQUALS(2U, derivedBounds.size()); + ASSERT(std::all_of(derivedBounds.cbegin(), derivedBounds.cend(), [](const ValueFlow::Value& value) { + return value.isImpossible() && value.bound != ValueFlow::Value::Bound::Point && value.path == 7; + })); + ASSERT(std::any_of(derivedBounds.cbegin(), derivedBounds.cend(), [](const ValueFlow::Value& value) { + return value.bound == ValueFlow::Value::Bound::Upper; + })); + ASSERT(std::any_of(derivedBounds.cbegin(), derivedBounds.cend(), [](const ValueFlow::Value& value) { + return value.bound == ValueFlow::Value::Bound::Lower; + })); + std::list derivedValues(derivedBounds.begin(), derivedBounds.end()); + ValueFlow::Value intervalBoundary(384); + intervalBoundary.setKnown(); + inferred = infer(makeIntegralInferModel(), "==", derivedValues, {intervalBoundary}); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT(inferred.front().isPossible()); + ASSERT_EQUALS(7, inferred.front().path); + ASSERT_EQUALS(0, inferred.front().intvalue); + + lhs.path = 0; + rhsLower.path = 0; + rhsUpper.path = 0; + derivedBounds = infer(makeIntegralInferModel(), "-", {lhs}, {rhsLower, rhsUpper}); + std::list localDerivedValues(derivedBounds.begin(), derivedBounds.end()); + inferred = infer(makeIntegralInferModel(), "==", localDerivedValues, {intervalBoundary}); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT(inferred.front().isKnown()); + ASSERT_EQUALS(0, inferred.front().path); + ASSERT_EQUALS(0, inferred.front().intvalue); + + ValueFlow::Value scalarSupport(23); + scalarSupport.setPossible(); + scalarSupport.path = 7; + scalarSupport.condition = condition; + scalarSupport.errorPath.emplace_back(condition, "scalar support"); + ValueFlow::Value excludedPoint(23); + excludedPoint.setImpossible(); + excludedPoint.bound = ValueFlow::Value::Bound::Point; + excludedPoint.path = 7; + excludedPoint.condition = exclusionCondition; + excludedPoint.errorPath.emplace_back(exclusionCondition, "excluded point"); + ValueFlow::Value castLower(100); + castLower.setImpossible(); + castLower.bound = ValueFlow::Value::Bound::Lower; + castLower.path = 7; + ValueFlow::Value castUpper(0); + castUpper.setImpossible(); + castUpper.bound = ValueFlow::Value::Bound::Upper; + castUpper.path = 7; + const std::list castAndExclusionValues{castLower, excludedPoint, castUpper}; + inferred = infer(makeIntegralInferModel(), "==", {scalarSupport}, castAndExclusionValues); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT(inferred.front().isPossible()); + ASSERT(!inferred.front().isKnown()); + ASSERT_EQUALS(7, inferred.front().path); + ASSERT_EQUALS(0, inferred.front().intvalue); + ASSERT(inferred.front().condition == condition); + ASSERT(std::any_of(inferred.front().errorPath.cbegin(), inferred.front().errorPath.cend(), [&](const std::pair& item) { + return item.first == condition; + })); + ASSERT(std::any_of(inferred.front().errorPath.cbegin(), inferred.front().errorPath.cend(), [&](const std::pair& item) { + return item.first == exclusionCondition; + })); + + inferred = infer(makeIntegralInferModel(), "!=", {scalarSupport}, castAndExclusionValues); + ASSERT_EQUALS(1U, inferred.size()); + ASSERT(inferred.front().isPossible()); + ASSERT(!inferred.front().isKnown()); + ASSERT_EQUALS(7, inferred.front().path); + ASSERT_EQUALS(1, inferred.front().intvalue); + + scalarSupport.path = 7; + excludedPoint.path = 8; + castLower.path = 8; + castUpper.path = 8; + const std::list incompatibleValues{castLower, excludedPoint, castUpper}; + inferred = infer(makeIntegralInferModel(), "==", {scalarSupport}, incompatibleValues); + ASSERT(inferred.empty()); } void valueFlowImpossibleUnknownConstant() @@ -9660,7 +10098,8 @@ class TestValueFlow : public TestFixture { " if (x != 0) return;\n" " }\n" "}\n"; - ASSERT_EQUALS(true, testValueOfXImpossible(code, 4U, 0)); + // An untyped incomplete identifier cannot carry its exclusion through an int conversion. + ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0)); } void valueFlowContainerEqual()