Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cpp/ql/lib/change-notes/2026-10-15-bitwidth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: feature
---
* Added a new predicate `getBitWidth` to the `Type` class, which yields the bit-width of the type.
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 6 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/Enum.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 20 additions & 3 deletions cpp/ql/lib/semmle/code/cpp/Type.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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" }
}

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/TypedefType.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
12 changes: 6 additions & 6 deletions cpp/ql/lib/semmle/code/cpp/commons/Printf.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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 |
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +349 to +352

@jketema jketema Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QL's int.bitShiftRight reduces the shift count modulo 32

I see no evidence of this in the documentation, but if this is the case, this was already a problem for larger types like __int128. So, won't fix here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think QL ints are 32-bit, so it's kind of right.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a weird way of phrasing things by Copilot then. In any case, pre-existing problem.

)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down
2 changes: 1 addition & 1 deletion cpp/ql/src/Likely Bugs/AmbiguouslySignedBitField.ql
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.ql
Original file line number Diff line number Diff line change
Expand Up @@ -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() +
Expand Down
4 changes: 2 additions & 2 deletions cpp/ql/src/jsf/4.23 Type Conversions/AV Rule 180.ql
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading