From 98e981d2f66f413faa26d80b1ac030bd61fda2f1 Mon Sep 17 00:00:00 2001 From: Mike Fairhurst Date: Wed, 3 Dec 2025 14:11:40 -0800 Subject: [PATCH 1/3] Add new library, backported to a small handful of queries, for resolving types. Expands upon `PossiblySpecified` module, which was nothing but a renaming of `.stripSpeciifers` that was intended to be clearer (and not resolve typedefs). However, there is almost never a reason not to resolve _certain_ typedefs. Assume we are writing a query to detect usages of a typedef such as `uint8_t`. If we have a candidate type and wish to check if it is a `uint8_t`, we may do so via `candidate.(TypedefType).hasName("uint8_t")`. However, this will not match `const uint8_t`. The type methods such as `stripSpecifiers`, `getUnderlyingType`, can be used but are sometimes vague, and some implicitly resolve typedefs while others do not, and some even have incorrect documentation. Furthermore, if a user has declared `typedef uint8_t uchar_t` or `typedef const uint8_t uint8_const_t`, then we _must_ resolve typedefs to find these usages of `uint8_t` in the project. However, `candidate.resolveTypedefs()` will also unwrap the `uint8_t`. In C++ the problem is worse too, as we would need to detect cases such as `const uint8_t&`, and decltypes. This project needs, IMO, a better API for incrementally resolving typedefs, decltypes, specifiers, and references. This PR adds what is hopefully a decent starting point for this. Instead of writing `candidate.resolveTypedefs() instanceof FooType` or `candidate.stripTopLevelSpecifiers() instanceof FooType` or `candidate.getUnderlyingType() instanceof FooType`, the `cpp.types.Resolve` import now allows `candidate instanceof ResolvesTo::Exactly` to only resolve typedefs and decltypes, or `::IgnoringSpecifiers` to resolve typedefs, decltypes, and unwrap `SpecifiedType`s as well. These types have a `.resolve()` member predicate to unwrap everything and get the resolved `FooType`. The API basically intends to define an interterface for how to unwrap type A in search of a type B. Types are like linked lists, with flags on certain links. We really just want a means of traversing different kinds of links in different contexts, sometimes requiring the presence of a certain kind of link, and sometimes aggregating properties about what links were traversed. We also want an API that provides some guard rails and funnels people towards correct usage of itself. Hopefully `::Exactly` comes across as too strict, and hopefully `CvConst` makes it clear that a type may be `const` or `const volatile`. Also added some types that compliment this API well such as `PointerTo::Type` and `ReferenceOf::Type`. Also integrated this library into a few example queries so the PR includes example usage. --- ...tingAPointerToIntegerOrIntegerToPointer.ql | 28 +- .../NonstandardUseOfThreadingObject.ql | 6 +- ...readingObjectWithInvalidStorageDuration.ql | 4 +- .../MutexNotInitializedBeforeUse.ql | 6 +- ...-12-03-type-resolution-tracking-changes.md | 6 + .../src/codingstandards/cpp/types/Resolve.qll | 273 ++++++++++++++++++ .../codingstandards/cpp/types/Specifiers.qll | 28 ++ .../src/codingstandards/cpp/types/Type.qll | 24 -- .../cpp/types/Resolve/ResolveTest.expected | 0 .../cpp/types/Resolve/ResolveTest.ql | 68 +++++ .../cpp/types/Resolve/test.cpp | 72 +++++ .../library/codingstandards/cpp/types/options | 1 + .../LegacyForStatementsShouldBeSimple.ql | 10 +- 13 files changed, 481 insertions(+), 45 deletions(-) create mode 100644 change_notes/2025-12-03-type-resolution-tracking-changes.md create mode 100644 cpp/common/src/codingstandards/cpp/types/Resolve.qll create mode 100644 cpp/common/src/codingstandards/cpp/types/Specifiers.qll create mode 100644 cpp/common/test/library/codingstandards/cpp/types/Resolve/ResolveTest.expected create mode 100644 cpp/common/test/library/codingstandards/cpp/types/Resolve/ResolveTest.ql create mode 100644 cpp/common/test/library/codingstandards/cpp/types/Resolve/test.cpp create mode 100644 cpp/common/test/library/codingstandards/cpp/types/options diff --git a/c/cert/src/rules/INT36-C/ConvertingAPointerToIntegerOrIntegerToPointer.ql b/c/cert/src/rules/INT36-C/ConvertingAPointerToIntegerOrIntegerToPointer.ql index 1cbdcc4e12..a209afca6d 100644 --- a/c/cert/src/rules/INT36-C/ConvertingAPointerToIntegerOrIntegerToPointer.ql +++ b/c/cert/src/rules/INT36-C/ConvertingAPointerToIntegerOrIntegerToPointer.ql @@ -17,6 +17,7 @@ import cpp import codingstandards.c.cert +import codingstandards.cpp.types.Resolve class LiteralZero extends Literal { LiteralZero() { this.getValue() = "0" } @@ -37,21 +38,30 @@ class StdIntIntPtrType extends Type { } } +class ResolvesToStdIntIntPtrType = ResolvesTo::IgnoringSpecifiers; + +class ResolvesToVoidPointerType = ResolvesTo::IgnoringSpecifiers; + /** * Casting a pointer value to integer, excluding literal 0. * Includes implicit conversions made during declarations or assignments. */ predicate conversionBetweenPointerAndInteger(Cast cast, string message) { /* Ensure that `int` has different size than that of pointers */ - exists(IntType intType, PointerType ptrType | intType.getSize() < ptrType.getSize() | - cast.getExpr().getUnderlyingType() = intType and - cast.getUnderlyingType() = ptrType and + exists( + ResolvesTo::IgnoringSpecifiers intType, + ResolvesTo::IgnoringSpecifiers ptrType + | + intType.getSize() < ptrType.getSize() + | + cast.getExpr().getType() = intType and + cast.getType() = ptrType and if cast.isCompilerGenerated() then message = "Integer expression " + cast.getExpr() + " is implicitly cast to a pointer type." else message = "Integer expression " + cast.getExpr() + " is cast to a pointer type." or - cast.getExpr().getUnderlyingType() = ptrType and - cast.getUnderlyingType() = intType and + cast.getExpr().getType() = ptrType and + cast.getType() = intType and if cast.isCompilerGenerated() then message = "Pointer expression " + cast.getExpr() + " is implicitly cast to an integer type." @@ -61,11 +71,11 @@ predicate conversionBetweenPointerAndInteger(Cast cast, string message) { not cast.getExpr() instanceof LiteralZero and /* Compliant exception 2: variable's declared type is (u)intptr_t */ not ( - cast.getType() instanceof StdIntIntPtrType and - cast.getExpr().getType() instanceof VoidPointerType + cast.getType() instanceof ResolvesToStdIntIntPtrType and + cast.getExpr().getType() instanceof ResolvesToVoidPointerType or - cast.getType() instanceof VoidPointerType and - cast.getExpr().getType() instanceof StdIntIntPtrType + cast.getType() instanceof ResolvesToVoidPointerType and + cast.getExpr().getType() instanceof ResolvesToStdIntIntPtrType ) } diff --git a/c/misra/src/rules/RULE-22-12/NonstandardUseOfThreadingObject.ql b/c/misra/src/rules/RULE-22-12/NonstandardUseOfThreadingObject.ql index d92b4ccea6..85bcba272a 100644 --- a/c/misra/src/rules/RULE-22-12/NonstandardUseOfThreadingObject.ql +++ b/c/misra/src/rules/RULE-22-12/NonstandardUseOfThreadingObject.ql @@ -16,9 +16,11 @@ import cpp import codingstandards.c.misra import codingstandards.cpp.Concurrency -import codingstandards.cpp.Type +import codingstandards.cpp.types.Resolve -predicate isThreadingObject(Type t) { t instanceof PossiblySpecified::Type } +predicate isThreadingObject(Type t) { + t instanceof ResolvesTo::IgnoringSpecifiers +} predicate validUseOfStdThreadObject(Expr e) { e.getParent() instanceof AddressOfExpr diff --git a/c/misra/src/rules/RULE-22-13/ThreadingObjectWithInvalidStorageDuration.ql b/c/misra/src/rules/RULE-22-13/ThreadingObjectWithInvalidStorageDuration.ql index 066cf3c295..5afd9f4547 100644 --- a/c/misra/src/rules/RULE-22-13/ThreadingObjectWithInvalidStorageDuration.ql +++ b/c/misra/src/rules/RULE-22-13/ThreadingObjectWithInvalidStorageDuration.ql @@ -17,7 +17,7 @@ import cpp import codingstandards.c.misra import codingstandards.c.Objects import codingstandards.cpp.Concurrency -import codingstandards.cpp.Type +import codingstandards.cpp.types.Resolve from ObjectIdentity obj, StorageDuration storageDuration, Type type where @@ -25,7 +25,7 @@ where storageDuration = obj.getStorageDuration() and not storageDuration.isStatic() and type = obj.getASubObjectType() and - type instanceof PossiblySpecified::Type + type instanceof ResolvesTo::IgnoringSpecifiers select obj, "Object of type '" + obj.getType().getName() + "' has invalid storage duration type '" + storageDuration.getStorageTypeName() + "'." diff --git a/c/misra/src/rules/RULE-22-14/MutexNotInitializedBeforeUse.ql b/c/misra/src/rules/RULE-22-14/MutexNotInitializedBeforeUse.ql index f78c25f981..08f4374b62 100644 --- a/c/misra/src/rules/RULE-22-14/MutexNotInitializedBeforeUse.ql +++ b/c/misra/src/rules/RULE-22-14/MutexNotInitializedBeforeUse.ql @@ -17,7 +17,7 @@ import cpp import codingstandards.c.misra import codingstandards.c.Objects import codingstandards.cpp.Concurrency -import codingstandards.cpp.Type +import codingstandards.cpp.types.Resolve import codingstandards.c.initialization.GlobalInitializationAnalysis module MutexInitializationConfig implements GlobalInitializationAnalysisConfigSig { @@ -68,8 +68,8 @@ where ) and ( if - obj.getType() instanceof PossiblySpecified::Type or - obj.getType() instanceof PossiblySpecified::Type + obj.getType() instanceof ResolvesTo::IgnoringSpecifiers or + obj.getType() instanceof ResolvesTo::IgnoringSpecifiers then description = typeString else description = typeString + " in object" ) diff --git a/change_notes/2025-12-03-type-resolution-tracking-changes.md b/change_notes/2025-12-03-type-resolution-tracking-changes.md new file mode 100644 index 0000000000..5e5e9e4f1d --- /dev/null +++ b/change_notes/2025-12-03-type-resolution-tracking-changes.md @@ -0,0 +1,6 @@ + - `INT36-C` - `ConvertingAPointerToIntegerOrIntegerToPointer.ql`: + - Integrated new type resolution modules to fully handle typedefs and ignore cv-qualifiers during type comparisons, such as in detecting int types, pointer types, (u)intptr_t types, and void pointer types. + - `RULE-22-12`, `RULE-22-13`, `RULE-22-14` - `NonstandardUseOfThreadingObject.ql`, `ThreadingObjectWithInvalidStorageDuration.ql`, `MutexNotInitializedBeforeUse.ql`: + - Integrated new type resolution modules to handle typedefs when identifying threading object types. + - `RULE-9-5-1` - `LegacyForStatementsShouldBeSimple.ql`: + - Refactor to integrate new type resolution, no change in functionality expected. \ No newline at end of file diff --git a/cpp/common/src/codingstandards/cpp/types/Resolve.qll b/cpp/common/src/codingstandards/cpp/types/Resolve.qll new file mode 100644 index 0000000000..d47dbb915d --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/types/Resolve.qll @@ -0,0 +1,273 @@ +private import cpp +private import qtil.Qtil +private import codingstandards.cpp.types.Specifiers +private import codeql.util.Boolean + +Type typeResolvesToTypeStep(Type type) { + result = type.(Decltype).getBaseType() + or + result = type.(TypedefType).getBaseType() +} + +module PointerTo::Type PointeeType> { + /** + * A pointer type that points to a type that resolves to the module's `PointeeType` type parameter. + */ + class Type extends Qtil::Final::Type { + Type() { getBaseType() instanceof PointeeType } + } +} + +module ReferenceOf::Type ReferencedType> { + /** + * A reference type that refers to a type that resolves to the module's `ReferencedType` type + * parameter. + */ + class Type extends Qtil::Final::Type { + Type() { getBaseType() instanceof ReferencedType } + } +} + +/** + * A module for handling complex type resolution in c++, such as a decltype of a const typedef + * of a struct type, which resolves to a const struct type. + * + * Basic usage: + * - `ResolvesTo::Exactly` is the set of class types, and typedefs/decltypes that resolve + * to class types exactly (without specifiers). + * - `resolvedType.resolve()` gets the fully resolved class type for the above. + * - `ResolvesTo::Specified` is the set of types that resolve to a specified class type. + * - `ResolvesTo::Ref` is the set of types that resolve to a reference to a class type. + * - `ResolvesTo::Const` is the set of types that resolve to a const class type. + * - `ResolvesTo::IgnoringSpecifiers` is the set of types that resolve to a class type + * ignoring specifiers. + * - `ResolvesTo::ExactlyOrRef` is the set of types that resolve to a class type and + * unwraps references. + * + * These module classes are preferred to the member predicates on `Type` such as `resolveTypedefs`, + * `stripSpecifiers`, `getUnderlyingType()`, etc, for the following reasons: + * - Hopefully the API is clearer and easier to use correctly. + * - Unlike `resolveTypedefs`, these classes can find types that resolve to other typedefs (via + * `ResolvesType::Exactly` etc.), instead of always resolving all typedefs. + * - The member predicates on `Type` have cases with no result. For instance, if there's a const + * typedef `const T = F`, but `const F` is never explicitly written in the code, then there is no + * matching extracted `Type` for `resolveTypedefs` to return, and it will have no result. + */ +module ResolvesTo::Type ResolvedType> { + private import cpp as cpp + + final class CppType = cpp::Type; + + /** + * A type that resolve exactly to the module's `ResolvedType` type parameter. + * + * For example, `ResolvesTo::Type` is the set of all `FooType`s and types that resolve + * (through typedefs * and/or decltypes) to `FooType`s. This does _not_ include types that resolve + * to a const `FooType` (though `FooType` itself may be const). To perform type resolution and + * check or strip specifiers, see module classes `IgnoringSpecifiers`, `Specified`, `Const`, etc. + * + * ``` + * // Example `ResolvesTo::Exactly` types: + * FooType f; // matches (a FooType) + * decltype(f); // matches (a decltype of FooType) + * typedef FooType FT; // matches (a typedef of FooType) + * FT f2; // matches (a typedef of FooType) + * decltype(f2); // matches (a decltype of typedef of FooType) + * typedef FT FT2; // matches (a typedef of typedef of FooType) + * + * // Examples types that are not `ResolvesTo::Exactly` types: + * const FooType cf; // does not match (specified FooTypes) + * FooType& rf = f; // does not match (references to FooTypes) + * NotFooType nf; // does not match (non FooTypes) + * ``` + */ + class Exactly extends CppType { + ResolvedType resolvedType; + + Exactly() { resolvedType = typeResolvesToTypeStep*(this) } + + ResolvedType resolve() { result = resolvedType } + } + + /** + * A type that resolves to a const type that in turn resolves to the module's `ResolvedType` type. + * + * For example, `ResolvesTo::CvConst` is the set of all const `FooType`s and types that + * resolve (through typedefs and/or decltypes) to const `FooType`s, including cases involving + * `const` typedefs, etc. + * + * Volatile specifiers are ignored, since const volatile types are still const. + * + * For matching both const and non-const types that resolve to `FooType`, see + * `IgnoringSpecifiers`. + * + * ``` + * // Note that this does NOT match `FooType`s that are not const. + * FooType f; // does not match (non-const) + * decltype(f) df; // does not match (non-const) + * typedef TF = FooType; // does not match (non-const) + * + * // Example `ResolvesTo::Const` types: + * const FooType cf; // matches (a const FooType) + * const volatile FooType cf; // matches (a const FooType, volatile is allowed and ignored) + * decltype(cf); // matches (a decltype of a const FooType) + * const decltype(f); // matches (a const decltype of FooType) + * const decltype(cf); // matches (a const decltype of a const FooType) + * typedef const FooType CFT; // matches (a typedef of const FooType) + * const TF ctf; // matches (a const typedef of FooType) + * + * // Additional examples types that are not `ResolvesTo::Const` types: + * const FooType &f; // does not match (reference to const FooType) + * ``` + */ + class CvConst extends Specified { + CvConst() { getASpecifier() instanceof ConstSpecifier } + } + + /** + * A type that resolves to a cv-qualified (or otherwise specified) type that in turn resolves to + * the module's `ResolvedType` type parameter. + * + * For example, `ResolvesTo::Specified` is the set of all specified `FooType`s and types + * that resolve (through typedefs and/or decltypes) to specified `FooType`s, including cases + * involving `const` and `volatile` typedefs, etc. + * + * ``` + * // Note that this does NOT match `FooType`s that are not specified. + * FooType f; // does not match (not specified) + * decltype(f) df; // does not match (not specified) + * typedef TF = FooType; // does not match (not specified) + * + * // Example `ResolvesTo::Specified` types: + * const FooType cf; // matches (a const FooType) + * volatile FooType vf; // matches (a volatile FooType) + * const volatile FooType cvf; // matches (a const volatile FooType) + * decltype(cf); // matches (a decltype of a const FooType) + * volatile decltype(f); // matches (a volatile decltype of FooType) + * const decltype(vf); // matches (a const decltype of volatile FooType) + * const decltype(cf); // matches (a const decltype of const FooType) + * typedef const FooType CFT; // matches (a typedef of const FooType) + * const TF ctf; // matches (a const typedef of FooType) + * volatile TF ctf; // matches (a volatile typedef of FooType) + * + * // Additional examples types that are not `ResolvesTo::Specified` types: + * const FooType &f; // does not match (reference to const FooType) + * ``` + */ + class Specified extends CppType { + ResolvedType resolved; + + Specified() { + resolved = typeResolvesToTypeStep*(this).(SpecifiedType).getBaseType().(Exactly).resolve() + } + + ResolvedType resolve() { result = resolved } + } + + /** + * A class that resolves to the module's `ResolvedType` type parameter, ignoring specifiers. + */ + class IgnoringSpecifiers extends CppType { + ResolvedType resolved; + + IgnoringSpecifiers() { + resolved = this.(Specified).resolve() + or + resolved = this.(Exactly).resolve() + } + + ResolvedType resolve() { result = resolved } + } + + /** + * A type that resolves to a reference to that resolves to the module's `ResolvedType` type + * parameter. + * + * For example, `ResolvesTo::Ref` is the set of all references to `FooType`s and types + * that resolve (through typedefs and/or decltypes) to references to `FooType`s. + * + * ``` + * // Example `ResolvesTo::Ref` types: + * FooType &f; // matches (a reference to FooType) + * decltype(f); // matches (a decltype of reference to FooType) + * typedef FooType &FT; // matches (a typedef of ref to FooType) + * FT f2; // matches (a typedef of ref to FooType) + * decltype(f2); // matches (a decltype of typedef of ref to FooType) + * typedef FT FT2; // matches (a typedef of typedef of ref to FooType) + * + * // Examples types that are not `ResolvesTo::Ref` types: + * const FooType &cf; // does not match (specified references to FooTypes) + * FooType rf = f; // does not match (non-rerefence FooTypes) + * NotFooType &nf; // does not match (non FooTypes) + * ``` + */ + class Ref extends CppType { + ResolvedType resolved; + + Ref() { + // Note that the extractor appears to perform reference collapsing, so cases like + // `const T& &` are treated as `const T&`. + resolved = typeResolvesToTypeStep*(this).(ReferenceType).getBaseType().(Exactly).resolve() + } + + ResolvedType resolve() { result = resolved } + } + + //class Ref = + //Impl::ResolveRefType; + /** + * A type that resolves to a const reference of (or reference to const of) the module's + * `ResolvedType` type parameter. + * + * For example, `ResolvesTo::CvConstRef` is the set of all const references to `FooType`s + * and types that resolve (through typedefs and/or decltypes) to const references to `FooType`s. + * + * Volatile specifiers are ignored, since const volatile types are still const. + * + * ``` + * FooType &f; // does not match (not const) + * const FooType f; // does not match (not a reference) + * + * // Example `ResolvesTo::CvConstRef` types: + * const FooType &cf; // matches (a const reference to FooType) + * const volatile FooType &cf; // matches (a const reference to FooType, volatile is ignored) + * const decltype(f) cdf; // matches (a const decltype of reference to FooType) + * decltype(f)& dcf; // matches (a decltype of const reference to FooType) + * typedef const FooType &CFT; // matches (a typedef of const ref to FooType) + * const CFT ctf; // matches due to const collapse + * CFT &ctf; // matches due to reference collapse + * ``` + */ + class CvConstRef extends CppType { + ResolvedType resolved; + + CvConstRef() { + exists(ReferenceType refType | + // A type can be a reference to const, but a const type cannot contain a reference. + // Therefore, we only need to find reference types that resolve to const types. + // Note that the extractor appears to perform reference collapsing, so cases like + // `const T& &` are treated as `const T&`. + refType = typeResolvesToTypeStep*(this) and + refType.getBaseType() instanceof CvConst + ) + } + + ResolvedType resolve() { result = resolved } + } + + /** + * A type that resolves to either a reference to that resolves to the module's `ResolvedType` type + * parameter, or exactly to the `ResolvedType`. + */ + class ExactlyOrRef extends CppType { + ResolvedType resolved; + + ExactlyOrRef() { + resolved = this.(Ref).resolve() + or + resolved = this.(Exactly).resolve() + } + + ResolvedType resolve() { result = resolved } + } +} diff --git a/cpp/common/src/codingstandards/cpp/types/Specifiers.qll b/cpp/common/src/codingstandards/cpp/types/Specifiers.qll new file mode 100644 index 0000000000..187709f8c1 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/types/Specifiers.qll @@ -0,0 +1,28 @@ +import cpp + +class ConstSpecifier extends Specifier { + ConstSpecifier() { this.hasName("const") } +} + +/** + * A SpecifiedType with a `const` specifier. + * + * Note that this does *not* find all const types, as it does not resolve typedefs etc. + */ +class RawConstType extends SpecifiedType { + RawConstType() { this.getASpecifier() instanceof ConstSpecifier } +} + +/** + * Any type that is const, using the `.isConst()` member predicate. + */ +class ConstType extends Type { + ConstType() { this.isConst() } +} + +/** + * Any type that is not const, using the `.isConst()` member predicate. + */ +class NonConstType extends Type { + NonConstType() { not this.isConst() } +} diff --git a/cpp/common/src/codingstandards/cpp/types/Type.qll b/cpp/common/src/codingstandards/cpp/types/Type.qll index 0e55b3ad58..137a01c239 100644 --- a/cpp/common/src/codingstandards/cpp/types/Type.qll +++ b/cpp/common/src/codingstandards/cpp/types/Type.qll @@ -60,30 +60,6 @@ Type stripSpecifiers(Type type) { else result = type } -signature class PossiblySpecifiedBaseType extends Type; - -/** - * This module defines a class `Type` which holds for types `T` and `const/volatile T` etc. - * - * Similar to `getUnspecifiedType()`, but does not resolve typedefs. Useful for matching - * potentially qualified versions of standard typedef types, such as `const mtx_t`. - * - * Example usage: `someType.(PossiblySpecified::Type).strip()` - */ -module PossiblySpecified { - import cpp as cpp - - final class CppType = cpp::Type; - - class Type extends CppType { - BaseType baseType; - - Type() { baseType = stripSpecifiers(this) } - - BaseType strip() { result = baseType } - } -} - /** * Get the precision of an integral type, where precision is defined as the number of bits * that can be used to represent the numeric value. diff --git a/cpp/common/test/library/codingstandards/cpp/types/Resolve/ResolveTest.expected b/cpp/common/test/library/codingstandards/cpp/types/Resolve/ResolveTest.expected new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cpp/common/test/library/codingstandards/cpp/types/Resolve/ResolveTest.ql b/cpp/common/test/library/codingstandards/cpp/types/Resolve/ResolveTest.ql new file mode 100644 index 0000000000..ab8953f7b2 --- /dev/null +++ b/cpp/common/test/library/codingstandards/cpp/types/Resolve/ResolveTest.ql @@ -0,0 +1,68 @@ +import cpp +import utils.test.InlineExpectationsTest +import codingstandards.cpp.types.Resolve + +class FooType extends Class { + FooType() { this.hasName("Foo") } +} + +class TDFoo extends TypedefType { + TDFoo() { this.hasName("TDFoo") } +} + +class DeclFoo extends Decltype { + DeclFoo() { this.getBaseType() instanceof FooType } +} + +class ExactFoo = ResolvesTo::Exactly; + +class ConstFoo = ResolvesTo::CvConst; + +class SpecFoo = ResolvesTo::Specified; + +class MaybeSpecFoo = ResolvesTo::IgnoringSpecifiers; + +class RefFoo = ResolvesTo::Ref; + +class ConstRefFoo = ResolvesTo::CvConstRef; + +class ExactTDFoo = ResolvesTo::Exactly; + +class ExactDeclFoo = ResolvesTo::Exactly; + +predicate hasRelevantType(Type type, string s) { + type instanceof ExactFoo and s = "Foo" + or + type instanceof ConstFoo and s = "ConstFoo" + or + type instanceof SpecFoo and s = "SpecFoo" + or + type instanceof MaybeSpecFoo and s = "MaybeSpecFoo" + or + type instanceof RefFoo and s = "RefFoo" + or + type instanceof ConstRefFoo and s = "ConstRefFoo" + or + type instanceof ExactTDFoo and s = "TDFoo" + or + type instanceof ExactDeclFoo and s = "DeclFoo" +} + +module MyTest implements TestSig { + string getARelevantTag() { result = "type" } + + predicate hasActualResult(Location l, string element, string tag, string value) { + exists(Variable var, Type type | + var.getLocation().getFile().getBaseName() = "test.cpp" and + not var.isFromTemplateInstantiation(_) and + not var.isFromUninstantiatedTemplate(_) and + var.getLocation() = l and + element = var.toString() and + tag = getARelevantTag() and + type = var.getType() and + value = concat(string typestr | hasRelevantType(type, typestr) | typestr, ",") + ) + } +} + +import MakeTest diff --git a/cpp/common/test/library/codingstandards/cpp/types/Resolve/test.cpp b/cpp/common/test/library/codingstandards/cpp/types/Resolve/test.cpp new file mode 100644 index 0000000000..c0bd914ad2 --- /dev/null +++ b/cpp/common/test/library/codingstandards/cpp/types/Resolve/test.cpp @@ -0,0 +1,72 @@ +#include + +class Foo {}; + +Foo l1; // $ type=Foo,MaybeSpecFoo +Foo &l2 = l1; // $ type=RefFoo +const Foo l3; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +const Foo &l4 = l3; // $ type=ConstRefFoo +volatile Foo l5; // $ type=MaybeSpecFoo,SpecFoo +volatile Foo &l6 = l5; // $ type= +const volatile Foo l7; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +const volatile Foo &l8 = l7; // $ type=ConstRefFoo + +typedef Foo TDFoo; +typedef Foo &TDFooRef; +typedef const Foo TDFooConst; +typedef const Foo &TDFooConstRef; +typedef volatile Foo TDFooVol; +typedef const volatile Foo TDFooVolConst; + +// Plain typedefs +TDFoo l9; // $ type=Foo,MaybeSpecFoo,TDFoo +TDFooConst l10; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +TDFooVol l11; // $ type=MaybeSpecFoo,SpecFoo +TDFooVolConst l12; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +const TDFoo l13; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +volatile TDFoo l15; // $ type=MaybeSpecFoo,SpecFoo + +// Ref types +TDFoo &l17 = l1; // $ type=RefFoo +TDFooRef l18 = l1; // $ type=RefFoo +TDFooConstRef l19 = l3; // $ type=ConstRefFoo + +// const collapse +const TDFooConstRef l21 = l3; // $ type=ConstRefFoo +const TDFooConst l14; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +const volatile TDFoo l16; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo + +// Ref collapse +TDFooRef &l22 = l1; // $ type=RefFoo +const TDFooRef &l23 = l1; // $ type=RefFoo +TDFooConstRef &l24 = l3; // $ type=ConstRefFoo + +// Cannot const qualify a ref type, const is ignored [8.3.2/1] +TDFooRef const l20 = l1; // $ type=RefFoo + +template class Declval { +public: + T type; +}; + +decltype(l1) l25 = l1; // $ type=DeclFoo,Foo,MaybeSpecFoo +decltype(Declval::type) l26; // $ type=DeclFoo,Foo,MaybeSpecFoo +decltype(Declval::type) l27 = l1; // $ type=RefFoo +decltype(Declval::type) l28; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +decltype(Declval::type) l29 = l1; // $ type=ConstRefFoo +decltype(l25) l30; // $ type=DeclFoo,Foo,MaybeSpecFoo +decltype(Declval::type) l31; // $ type=DeclFoo,Foo,MaybeSpecFoo +decltype(Declval::type) l32 = l1; // $ type=RefFoo +decltype(Declval::type) l33 = l1; // $ type=ConstRefFoo +decltype(Declval::type) l34; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo + +const decltype(Declval::type) l35; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +volatile decltype(Declval::type) l36; // $ type=MaybeSpecFoo,SpecFoo +const volatile decltype(Declval::type) + l37; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo +decltype(Declval::type) &l38 = l1; // $ type=RefFoo +decltype(Declval::type) &l39 = l1; // $ type=RefFoo +decltype(Declval::type) &l40 = l1; // $ type=ConstRefFoo +decltype(Declval::type) &l41 = l1; // $ type=ConstRefFoo +const decltype(Declval::type) + l42; // $ type=ConstFoo,MaybeSpecFoo,SpecFoo \ No newline at end of file diff --git a/cpp/common/test/library/codingstandards/cpp/types/options b/cpp/common/test/library/codingstandards/cpp/types/options new file mode 100644 index 0000000000..17bcdd919b --- /dev/null +++ b/cpp/common/test/library/codingstandards/cpp/types/options @@ -0,0 +1 @@ +semmle-extractor-options:--clang -std=c++17 -nostdinc++ -I../../../../../../../common/test/includes/standard-library -I../../../../common/test/includes/custom-library \ No newline at end of file diff --git a/cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql b/cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql index 9525d1f5f8..eb7759b1d8 100644 --- a/cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql +++ b/cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql @@ -22,6 +22,8 @@ import codingstandards.cpp.Call import codingstandards.cpp.Loops import codingstandards.cpp.ast.Increment import codingstandards.cpp.misra.BuiltInTypeRules::MisraCpp23BuiltInTypes +import codingstandards.cpp.types.Resolve +import codingstandards.cpp.types.Specifiers Variable getDeclaredVariableInForLoop(ForStmt forLoop) { result = forLoop.getADeclaration().getADeclarationEntry().(VariableDeclarationEntry).getVariable() @@ -85,13 +87,11 @@ Expr getLoopStepOfForStmt(ForStmt forLoop) { predicate loopVariableAssignedToNonConstPointerOrReferenceType( ForStmt forLoop, VariableAccess loopVariableAccessInCondition, Expr assignmentRhs ) { - exists(Type targetType, DerivedType strippedType | + exists(Type targetType | isAssignment(assignmentRhs, targetType, _) and - strippedType = targetType.stripTopLevelSpecifiers() and - not strippedType.getBaseType().isConst() and ( - strippedType instanceof PointerType or - strippedType instanceof ReferenceType + targetType instanceof ResolvesTo::Type>::IgnoringSpecifiers or + targetType instanceof ResolvesTo::Type>::IgnoringSpecifiers ) | assignmentRhs.getEnclosingStmt().getParent*() = forLoop.getStmt() and From 3d0722e8ff16a77bfdc2db44cebe65994d1dfae7 Mon Sep 17 00:00:00 2001 From: Mike Fairhurst Date: Wed, 3 Dec 2025 19:44:02 -0800 Subject: [PATCH 2/3] Add qlpack, qlpack locks for qtil --- c/cert/src/codeql-pack.lock.yml | 2 ++ c/cert/test/codeql-pack.lock.yml | 2 ++ c/common/src/codeql-pack.lock.yml | 2 ++ c/common/test/codeql-pack.lock.yml | 2 ++ c/misra/src/codeql-pack.lock.yml | 2 ++ c/misra/test/codeql-pack.lock.yml | 2 ++ cpp/autosar/src/codeql-pack.lock.yml | 2 ++ cpp/autosar/test/codeql-pack.lock.yml | 2 ++ cpp/cert/src/codeql-pack.lock.yml | 2 ++ cpp/cert/test/codeql-pack.lock.yml | 2 ++ cpp/common/src/codeql-pack.lock.yml | 2 ++ cpp/common/src/qlpack.yml | 1 + cpp/common/test/codeql-pack.lock.yml | 2 ++ cpp/misra/src/codeql-pack.lock.yml | 2 ++ cpp/misra/test/codeql-pack.lock.yml | 2 ++ 15 files changed, 29 insertions(+) diff --git a/c/cert/src/codeql-pack.lock.yml b/c/cert/src/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/c/cert/src/codeql-pack.lock.yml +++ b/c/cert/src/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/c/cert/test/codeql-pack.lock.yml b/c/cert/test/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/c/cert/test/codeql-pack.lock.yml +++ b/c/cert/test/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/c/common/src/codeql-pack.lock.yml b/c/common/src/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/c/common/src/codeql-pack.lock.yml +++ b/c/common/src/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/c/common/test/codeql-pack.lock.yml b/c/common/test/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/c/common/test/codeql-pack.lock.yml +++ b/c/common/test/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/c/misra/src/codeql-pack.lock.yml b/c/misra/src/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/c/misra/src/codeql-pack.lock.yml +++ b/c/misra/src/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/c/misra/test/codeql-pack.lock.yml b/c/misra/test/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/c/misra/test/codeql-pack.lock.yml +++ b/c/misra/test/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/autosar/src/codeql-pack.lock.yml b/cpp/autosar/src/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/autosar/src/codeql-pack.lock.yml +++ b/cpp/autosar/src/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/autosar/test/codeql-pack.lock.yml b/cpp/autosar/test/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/autosar/test/codeql-pack.lock.yml +++ b/cpp/autosar/test/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/cert/src/codeql-pack.lock.yml b/cpp/cert/src/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/cert/src/codeql-pack.lock.yml +++ b/cpp/cert/src/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/cert/test/codeql-pack.lock.yml b/cpp/cert/test/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/cert/test/codeql-pack.lock.yml +++ b/cpp/cert/test/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/common/src/codeql-pack.lock.yml b/cpp/common/src/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/common/src/codeql-pack.lock.yml +++ b/cpp/common/src/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/common/src/qlpack.yml b/cpp/common/src/qlpack.yml index 21663a5186..222a096067 100644 --- a/cpp/common/src/qlpack.yml +++ b/cpp/common/src/qlpack.yml @@ -3,5 +3,6 @@ version: 2.52.0-dev license: MIT dependencies: codeql/cpp-all: 4.0.3 + advanced-security/qtil: "0.0.3" dataExtensions: - ext/*.model.yml diff --git a/cpp/common/test/codeql-pack.lock.yml b/cpp/common/test/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/common/test/codeql-pack.lock.yml +++ b/cpp/common/test/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/misra/src/codeql-pack.lock.yml b/cpp/misra/src/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/misra/src/codeql-pack.lock.yml +++ b/cpp/misra/src/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: diff --git a/cpp/misra/test/codeql-pack.lock.yml b/cpp/misra/test/codeql-pack.lock.yml index a45ea8f438..9a7d19bc0a 100644 --- a/cpp/misra/test/codeql-pack.lock.yml +++ b/cpp/misra/test/codeql-pack.lock.yml @@ -1,6 +1,8 @@ --- lockVersion: 1.0.0 dependencies: + advanced-security/qtil: + version: 0.0.3 codeql/cpp-all: version: 4.0.3 codeql/dataflow: From d20e6a5ca0405f2c8436eb4283c0eb795241e507 Mon Sep 17 00:00:00 2001 From: Mike Fairhurst Date: Wed, 3 Dec 2025 20:01:01 -0800 Subject: [PATCH 3/3] Fix typos & CvConstRef.resolve() issue detected by copilot code review --- cpp/common/src/codingstandards/cpp/types/Resolve.qll | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cpp/common/src/codingstandards/cpp/types/Resolve.qll b/cpp/common/src/codingstandards/cpp/types/Resolve.qll index d47dbb915d..3fd2f7db78 100644 --- a/cpp/common/src/codingstandards/cpp/types/Resolve.qll +++ b/cpp/common/src/codingstandards/cpp/types/Resolve.qll @@ -38,7 +38,7 @@ module ReferenceOf::Type ReferencedType> { * - `resolvedType.resolve()` gets the fully resolved class type for the above. * - `ResolvesTo::Specified` is the set of types that resolve to a specified class type. * - `ResolvesTo::Ref` is the set of types that resolve to a reference to a class type. - * - `ResolvesTo::Const` is the set of types that resolve to a const class type. + * - `ResolvesTo::CvConst` is the set of types that resolve to a const class type. * - `ResolvesTo::IgnoringSpecifiers` is the set of types that resolve to a class type * ignoring specifiers. * - `ResolvesTo::ExactlyOrRef` is the set of types that resolve to a class type and @@ -59,7 +59,7 @@ module ResolvesTo::Type ResolvedType> { final class CppType = cpp::Type; /** - * A type that resolve exactly to the module's `ResolvedType` type parameter. + * A type that resolves exactly to the module's `ResolvedType` type parameter. * * For example, `ResolvesTo::Type` is the set of all `FooType`s and types that resolve * (through typedefs * and/or decltypes) to `FooType`s. This does _not_ include types that resolve @@ -180,7 +180,7 @@ module ResolvesTo::Type ResolvedType> { } /** - * A type that resolves to a reference to that resolves to the module's `ResolvedType` type + * A type that resolves to a reference that resolves to the module's `ResolvedType` type * parameter. * * For example, `ResolvesTo::Ref` is the set of all references to `FooType`s and types @@ -213,8 +213,6 @@ module ResolvesTo::Type ResolvedType> { ResolvedType resolve() { result = resolved } } - //class Ref = - //Impl::ResolveRefType; /** * A type that resolves to a const reference of (or reference to const of) the module's * `ResolvedType` type parameter. @@ -248,7 +246,7 @@ module ResolvesTo::Type ResolvedType> { // Note that the extractor appears to perform reference collapsing, so cases like // `const T& &` are treated as `const T&`. refType = typeResolvesToTypeStep*(this) and - refType.getBaseType() instanceof CvConst + resolved = refType.getBaseType().(CvConst).resolve() ) } @@ -256,7 +254,7 @@ module ResolvesTo::Type ResolvedType> { } /** - * A type that resolves to either a reference to that resolves to the module's `ResolvedType` type + * A type that resolves to either a reference that resolves to the module's `ResolvedType` type * parameter, or exactly to the `ResolvedType`. */ class ExactlyOrRef extends CppType {