diff --git a/cpp/ql/lib/change-notes/2026-10-15-bitwidth.md b/cpp/ql/lib/change-notes/2026-10-15-bitwidth.md new file mode 100644 index 000000000000..7d1b3b4248c4 --- /dev/null +++ b/cpp/ql/lib/change-notes/2026-10-15-bitwidth.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added a new predicate `getBitWidth` to the `Type` class, which yields the bit-width of the type. diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/extensions/ConstantShiftExprRange.qll b/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/extensions/ConstantShiftExprRange.qll index 3f300d7aa8d6..0f7e1ce5ed40 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/extensions/ConstantShiftExprRange.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/extensions/ConstantShiftExprRange.qll @@ -21,7 +21,7 @@ bindingset[val] private predicate isValidShiftExprShift(float val, Expr l) { val >= 0 and // We use getFullyConverted because the spec says to use the *promoted* left operand - val < (l.getFullyConverted().getUnderlyingType().getSize() * 8) + val < l.getFullyConverted().getUnderlyingType().getBitWidth() } bindingset[val, shift, max_val] diff --git a/cpp/ql/lib/semmle/code/cpp/Enum.qll b/cpp/ql/lib/semmle/code/cpp/Enum.qll index 38263dacf7ac..6b30d7921157 100644 --- a/cpp/ql/lib/semmle/code/cpp/Enum.qll +++ b/cpp/ql/lib/semmle/code/cpp/Enum.qll @@ -55,6 +55,12 @@ class Enum extends UserType, IntegralOrEnumType { not exists(UserType.super.getSize()) and result = 4 } + override int getBitWidth() { + result = UserType.super.getBitWidth() + or + not exists(UserType.super.getBitWidth()) and result = 32 + } + /** See `Type.isDeeplyConst` and `Type.isDeeplyConstBelow`. Internal. */ override predicate isDeeplyConstBelow() { any() } // No subparts diff --git a/cpp/ql/lib/semmle/code/cpp/Type.qll b/cpp/ql/lib/semmle/code/cpp/Type.qll index 023aa1b6c3d4..78367dbf7be4 100644 --- a/cpp/ql/lib/semmle/code/cpp/Type.qll +++ b/cpp/ql/lib/semmle/code/cpp/Type.qll @@ -123,6 +123,16 @@ class Type extends Locatable, @type { usertypesize(underlyingElement(this), result, _) } + /** + * Gets the bit width of this type. + */ + int getBitWidth() { + not bit_precise_int_types(underlyingElement(this), _) and + result = this.getSize() * 8 + or + bit_precise_int_types(underlyingElement(this), result) + } + /** * Gets the alignment of this type in bytes. */ @@ -1076,9 +1086,6 @@ class Char32Type extends IntegralType { class BitPreciseIntType extends IntegralType { BitPreciseIntType() { builtintypes(underlyingElement(this), _, [67, 68, 69], _, _, _) } - /** Get the bit-with of the bit-precise integer. */ - int getBitWidth() { bit_precise_int_types(underlyingElement(this), result) } - override string getAPrimaryQlClass() { result = "BitPreciseIntType" } } @@ -1182,6 +1189,8 @@ class Decltype extends Type, NameQualifyingElement { override int getSize() { result = this.getBaseType().getSize() } + override int getBitWidth() { result = this.getBaseType().getBitWidth() } + override int getAlignment() { result = this.getBaseType().getAlignment() } override int getPointerIndirectionLevel() { @@ -1242,6 +1251,8 @@ class TypeofType extends Type { override int getSize() { result = this.getBaseType().getSize() } + override int getBitWidth() { result = this.getBaseType().getBitWidth() } + override int getAlignment() { result = this.getBaseType().getAlignment() } override int getPointerIndirectionLevel() { @@ -1387,6 +1398,8 @@ class IntrinsicTransformedType extends Type { override int getSize() { result = this.getBaseType().getSize() } + override int getBitWidth() { result = this.getBaseType().getBitWidth() } + override int getAlignment() { result = this.getBaseType().getAlignment() } override int getPointerIndirectionLevel() { @@ -1512,6 +1525,8 @@ class SpecifiedType extends DerivedType { override int getSize() { result = this.getBaseType().getSize() } + override int getBitWidth() { result = this.getBaseType().getBitWidth() } + override int getAlignment() { result = this.getBaseType().getAlignment() } override int getPointerIndirectionLevel() { @@ -1587,6 +1602,8 @@ class ArrayType extends DerivedType { */ override int getSize() { result = this.getByteSize() } + override int getBitWidth() { result = this.getByteSize() * 8 } + override string explain() { if exists(this.getArraySize()) then diff --git a/cpp/ql/lib/semmle/code/cpp/TypedefType.qll b/cpp/ql/lib/semmle/code/cpp/TypedefType.qll index 69eb8f881d29..70aa933d6a2d 100644 --- a/cpp/ql/lib/semmle/code/cpp/TypedefType.qll +++ b/cpp/ql/lib/semmle/code/cpp/TypedefType.qll @@ -26,6 +26,8 @@ class TypedefType extends UserType { override int getSize() { result = this.getBaseType().getSize() } + override int getBitWidth() { result = this.getBaseType().getBitWidth() } + override int getAlignment() { result = this.getBaseType().getAlignment() } override int getPointerIndirectionLevel() { diff --git a/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll b/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll index 624465761c2c..dfda2f4796c5 100644 --- a/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll +++ b/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll @@ -1193,7 +1193,7 @@ class FormatLiteral extends Literal instanceof StringLiteral { // Subtract one in the exponent because one bit is for the sign. // Add 1 to account for the possible sign in the output. typeBasedBound = - 1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1)) and + 1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getBitWidth() - 1)) and // The second case uses range analysis to deduce a length that's shorter than the length // of the number -2^31. exists(Expr arg, float lower, float upper | @@ -1227,7 +1227,7 @@ class FormatLiteral extends Literal instanceof StringLiteral { // e.g. 2^32 - 1 = "4294967295" exists(float typeBasedBound, float valueBasedBound | // The first case handles length sub-specifiers - typeBasedBound = lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8) - 1) and + typeBasedBound = lengthInBase10(2.pow(this.getIntegralDisplayType(n).getBitWidth()) - 1) and // The second case uses range analysis to deduce a length that's shorter than // the length of the number 2^31 - 1. exists(Expr arg, float lower, float upper | @@ -1239,7 +1239,7 @@ class FormatLiteral extends Literal instanceof StringLiteral { lengthInBase10(max(float cand | // If lower can be negative we use `(unsigned)-1` as the candidate value. lower < 0 and - cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8) + cand = 2.pow(any(IntType t | t.isUnsigned()).getBitWidth()) or cand = upper )) and @@ -1275,7 +1275,7 @@ class FormatLiteral extends Literal instanceof StringLiteral { lengthInBase16(max(float cand | // If lower can be negative we use `(unsigned)-1` as the candidate value. lower < 0 and - cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8) + cand = 2.pow(any(IntType t | t.isUnsigned()).getBitWidth()) or cand = upper )) and @@ -1304,12 +1304,12 @@ class FormatLiteral extends Literal instanceof StringLiteral { exists(int sizeBits, int baseLen | sizeBits = min(int bits | - bits = this.getIntegralDisplayType(n).getSize() * 8 + bits = this.getIntegralDisplayType(n).getBitWidth() or exists(IntegralType t | t = this.getUse().getConversionArgument(n).getType().getUnderlyingType() | - t.isUnsigned() and bits = t.getSize() * 8 + t.isUnsigned() and bits = t.getBitWidth() ) ) and baseLen = (sizeBits / 3.0).ceil() and diff --git a/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll b/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll index 84e96fa9c468..5329fb92a5a4 100644 --- a/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll +++ b/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll @@ -344,12 +344,12 @@ private int convertIntToType(int val, IntegralType t) { then if val = 0 then result = 0 else result = 1 else if t.isUnsigned() - then val >= 0 and val.bitShiftRight(t.getSize() * 8) = 0 and result = val + then val >= 0 and val.bitShiftRight(t.getBitWidth()) = 0 and result = val else - if val >= 0 and val.bitShiftRight(t.getSize() * 8 - 1) = 0 + if val >= 0 and val.bitShiftRight(t.getBitWidth() - 1) = 0 then result = val else ( - (-(val + 1)).bitShiftRight(t.getSize() * 8 - 1) = 0 and result = val + (-(val + 1)).bitShiftRight(t.getBitWidth() - 1) = 0 and result = val ) } diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeAnalysisUtils.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeAnalysisUtils.qll index 03dbc56dc99c..3b316f5bce5c 100644 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeAnalysisUtils.qll +++ b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeAnalysisUtils.qll @@ -406,7 +406,7 @@ predicate cmpWithLinearBound( */ private predicate typeBounds0(ArithmeticType t, float lb, float ub) { exists(IntegralType integralType, float limit | - integralType = t and limit = 2.pow(8 * integralType.getSize()) + integralType = t and limit = 2.pow(integralType.getBitWidth()) | if integralType instanceof BoolType then lb = 0 and ub = 1 @@ -438,7 +438,7 @@ private IntegralType getUnderlyingTypeForEnum(Enum e) { not e.hasExplicitUnderlyingType() and result.isSigned() and exists(IntType intType | - if max(e.getAnEnumConstant().getValue().toFloat()) >= 2.pow(8 * intType.getSize() - 1) + if max(e.getAnEnumConstant().getValue().toFloat()) >= 2.pow(intType.getBitWidth() - 1) then result instanceof LongType else result = intType ) diff --git a/cpp/ql/src/Likely Bugs/AmbiguouslySignedBitField.ql b/cpp/ql/src/Likely Bugs/AmbiguouslySignedBitField.ql index 1fabf449ab35..3244d6748e9f 100644 --- a/cpp/ql/src/Likely Bugs/AmbiguouslySignedBitField.ql +++ b/cpp/ql/src/Likely Bugs/AmbiguouslySignedBitField.ql @@ -29,7 +29,7 @@ where // GLib's gboolean is a typedef for a type representing BoolType. not bf.getType().hasName("gboolean") and // If this is true, then there cannot be unsigned sign extension or overflow. - not bf.getDeclaredNumBits() = bf.getType().getSize() * 8 and + not bf.getDeclaredNumBits() = bf.getType().getBitWidth() and not bf.isAnonymous() and not bf.isFromUninstantiatedTemplate(_) select bf, diff --git a/cpp/ql/src/Likely Bugs/Conversion/ConversionChangesSign.ql b/cpp/ql/src/Likely Bugs/Conversion/ConversionChangesSign.ql index c89c71fb50b7..35843512ac05 100644 --- a/cpp/ql/src/Likely Bugs/Conversion/ConversionChangesSign.ql +++ b/cpp/ql/src/Likely Bugs/Conversion/ConversionChangesSign.ql @@ -23,7 +23,7 @@ where not ( e1.isConstant() and 0 <= e1.getValue().toInt() and - e1.getValue().toInt() <= ((it2.getSize() * 8 - 1) * 2.log()).exp() + e1.getValue().toInt() <= ((it2.getBitWidth() - 1) * 2.log()).exp() ) and not e1.isConstant() select e1, diff --git a/cpp/ql/src/Likely Bugs/Conversion/ImplicitDowncastFromBitfield.ql b/cpp/ql/src/Likely Bugs/Conversion/ImplicitDowncastFromBitfield.ql index 237d20c8f628..5f883e651ddb 100644 --- a/cpp/ql/src/Likely Bugs/Conversion/ImplicitDowncastFromBitfield.ql +++ b/cpp/ql/src/Likely Bugs/Conversion/ImplicitDowncastFromBitfield.ql @@ -20,8 +20,8 @@ where then fct = va.getFullyConverted().getType().(ReferenceType).getBaseType() else fct = va.getFullyConverted().getType() ) and - fi.getNumBits() > fct.getSize() * 8 and - va.getExplicitlyConverted().getType().getSize() > fct.getSize() and + fi.getNumBits() > fct.getBitWidth() and + va.getExplicitlyConverted().getType().getBitWidth() > fct.getBitWidth() and va.getTarget() = fi and not fct.getUnspecifiedType() instanceof BoolType select va, "Implicit downcast of bitfield $@.", fi, fi.toString() diff --git a/cpp/ql/src/Likely Bugs/Memory Management/Padding/Suboptimal64BitType.ql b/cpp/ql/src/Likely Bugs/Memory Management/Padding/Suboptimal64BitType.ql index f2cf4367d86d..5393c17b3ea3 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/Padding/Suboptimal64BitType.ql +++ b/cpp/ql/src/Likely Bugs/Memory Management/Padding/Suboptimal64BitType.ql @@ -14,7 +14,7 @@ from PaddedType t, Architecture arch, WideCharType wc, int holes, int size, int percentage, int optimum where arch.pointerSize() = 64 and // Select 64-bit architecture - arch.wideCharSize() = (wc.getSize() * 8) and // Select Windows(sizeof(wchar_t == 2)) or non-Windows(sizeof(wchar_t == 4)) + arch.wideCharSize() = wc.getBitWidth() and // Select Windows(sizeof(wchar_t == 2)) or non-Windows(sizeof(wchar_t == 4)) t.isPrecise() and optimum = t.optimalSize(arch) and size = arch.paddedSize(t) and diff --git a/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.ql b/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.ql index cd08362ad35a..87086db9e9a9 100644 --- a/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.ql +++ b/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.ql @@ -22,7 +22,7 @@ where not bf.getUnderlyingType() instanceof Enum and not bf.getUnspecifiedType() instanceof BoolType and not bf.getType().hasName("BOOL") and // At least for C programs on Windows, BOOL is a common typedef for a type representing BoolType. - not bf.getDeclaredNumBits() = bf.getType().getSize() * 8 and // If this is true, then there cannot be unsigned sign extension or overflow. + not bf.getDeclaredNumBits() = bf.getType().getBitWidth() and // If this is true, then there cannot be unsigned sign extension or overflow. not bf.isAnonymous() select bf, "Bit field " + bf.getName() + " of type " + bf.getUnderlyingType().getName() + diff --git a/cpp/ql/src/jsf/4.23 Type Conversions/AV Rule 180.ql b/cpp/ql/src/jsf/4.23 Type Conversions/AV Rule 180.ql index 16727add3fff..7e03caef0b91 100644 --- a/cpp/ql/src/jsf/4.23 Type Conversions/AV Rule 180.ql +++ b/cpp/ql/src/jsf/4.23 Type Conversions/AV Rule 180.ql @@ -61,10 +61,10 @@ class ImplicitConversion extends Expr { int getSourceSize() { if this instanceof IntConstantExpr then result = this.(IntConstantExpr).getRequiredSizeBits() - else result = this.getUnderlyingSourceType().getSize() * 8 + else result = this.getUnderlyingSourceType().getBitWidth() } - int getTargetSize() { result = this.getUnderlyingTargetType().getSize() * 8 } + int getTargetSize() { result = this.getUnderlyingTargetType().getBitWidth() } predicate isSourceSignedInt() { if this instanceof IntConstantExpr