Skip to content
Open
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
44 changes: 44 additions & 0 deletions cpp/common/src/codingstandards/cpp/exclusions/cpp/Classes3.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
import cpp
import RuleMetadata
import codingstandards.cpp.exclusions.RuleMetadata

newtype Classes3Query =
TImproperlyProvidedSpecialMemberFunctionsQuery() or
TImproperlyProvidedSpecialMemberFunctionsAuditQuery()

predicate isClasses3QueryMetadata(Query query, string queryId, string ruleId, string category) {
query =
// `Query` instance for the `improperlyProvidedSpecialMemberFunctions` query
Classes3Package::improperlyProvidedSpecialMemberFunctionsQuery() and
queryId =
// `@id` for the `improperlyProvidedSpecialMemberFunctions` query
"cpp/misra/improperly-provided-special-member-functions" and
ruleId = "RULE-15-0-1" and
category = "required"
or
query =
// `Query` instance for the `improperlyProvidedSpecialMemberFunctionsAudit` query
Classes3Package::improperlyProvidedSpecialMemberFunctionsAuditQuery() and
queryId =
// `@id` for the `improperlyProvidedSpecialMemberFunctionsAudit` query
"cpp/misra/improperly-provided-special-member-functions-audit" and
ruleId = "RULE-15-0-1" and
category = "required"
}

module Classes3Package {
Query improperlyProvidedSpecialMemberFunctionsQuery() {
//autogenerate `Query` type
result =
// `Query` type for `improperlyProvidedSpecialMemberFunctions` query
TQueryCPP(TClasses3PackageQuery(TImproperlyProvidedSpecialMemberFunctionsQuery()))
}

Query improperlyProvidedSpecialMemberFunctionsAuditQuery() {
//autogenerate `Query` type
result =
// `Query` type for `improperlyProvidedSpecialMemberFunctionsAudit` query
TQueryCPP(TClasses3PackageQuery(TImproperlyProvidedSpecialMemberFunctionsAuditQuery()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import BannedSyntax
import BannedTypes
import Classes
import Classes2
import Classes3
import Classes4
import Comments
import Concurrency
Expand Down Expand Up @@ -126,6 +127,7 @@ newtype TCPPQuery =
TBannedTypesPackageQuery(BannedTypesQuery q) or
TClassesPackageQuery(ClassesQuery q) or
TClasses2PackageQuery(Classes2Query q) or
TClasses3PackageQuery(Classes3Query q) or
TClasses4PackageQuery(Classes4Query q) or
TCommentsPackageQuery(CommentsQuery q) or
TConcurrencyPackageQuery(ConcurrencyQuery q) or
Expand Down Expand Up @@ -234,6 +236,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat
isBannedTypesQueryMetadata(query, queryId, ruleId, category) or
isClassesQueryMetadata(query, queryId, ruleId, category) or
isClasses2QueryMetadata(query, queryId, ruleId, category) or
isClasses3QueryMetadata(query, queryId, ruleId, category) or
isClasses4QueryMetadata(query, queryId, ruleId, category) or
isCommentsQueryMetadata(query, queryId, ruleId, category) or
isConcurrencyQueryMetadata(query, queryId, ruleId, category) or
Expand Down
258 changes: 258 additions & 0 deletions cpp/misra/src/rules/RULE-15-0-1/AnalyzableClass.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/**
* Provides predicates and classes to perform a precursor analysis of which classes the rule can
* potentially analyze, or should be excluded and instead selected by the audit query.
*
* For example, the class `AnalyzableClass` resolves all of the special member functions that we
* must have in order to determine rule compliance.
*
* Part of what this module does is perform a very approximate analysis of which classes will
* produce a value of true in `std::is_(copy|move)_(constructible|assignable)_v`.
*
* Fully predicting these standard type traits requires performing a very thorough overload
* resolution analysis, including value category propagation and reference binding and user defined
* conversion operators and standard conversions and promotions and ranking viable candidates and
* properly handling ambiguous overloads.
*/

import cpp

Comment thread
MichaelRFairhurst marked this conversation as resolved.
/**
* For `std::is_(copy|move)_(constructible|assignable)_v` to return true for a given class, the
* member must exist, it must not be deleted, and it must be publicly accessible.
*
* This is a very coarse approximation of true behavior of the standard type traits.
*/
private predicate isUsable(MemberFunction f) {
not f.isDeleted() and
f.isPublic()
}

/**
* Holds if the member function `f` has been "customized" by the user, e.g., they explicitly wrote
* the implementation of the function.
*/
private predicate isMemberCustomized(MemberFunction f) {
exists(f.getDefinition()) and
not f.isDefaulted() and
not f.isDeleted() and
not f.isCompilerGenerated()
}

/**
* Holds if the user declared the member function `f`, as opposed to it being implicitly declared
* by the compiler.
*
* Note that `T(T&&) = default` and `T(T&&) = delete` are both user declared. This is not to be
* confused with "user defined."
*/
private predicate isUserDeclared(MemberFunction f) { not f.isCompilerGenerated() }

/**
* Holds if the implicit move constructor or move assignment operator of the class `c` will not be
* declared.
*
* Specified in [class.copy.ctor]/8 and [class.copy.assign]/4.
*/
private predicate implicitMoveIsSuppressed(Class c) {
isUserDeclared(c.getAConstructor().(CopyConstructor))
or
isUserDeclared(c.getAMemberFunction().(CopyAssignmentOperator))
or
isUserDeclared(c.getDestructor())
Copy link
Copy Markdown
Collaborator

@jeongsoolee09 jeongsoolee09 Apr 30, 2026

Choose a reason for hiding this comment

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

It may be missing a branch.

Suggested change
isUserDeclared(c.getDestructor())
isUserDeclared(c.getDestructor())
or
isUserDeclared(c.getAMemberFunction().(MoveAssignmentOperator))

}

/**
* Returns the move constructor of the class `c` if it exists, or the copy constructor if it does
* not exist and the implicit definition was suppressed by the compiler.
*
* For example:
* ```cpp
* class OnlyCopyCtor {
* public:
* OnlyCopyCtor(const OnlyCopyCtor &) = default;
* };
*
* static_assert(std::is_copy_constructible_v<OnlyCopyCtor>); // Succeeds
* static_assert(std::is_move_constructible_v<OnlyCopyCtor>); // Also succeeds
* ```
*
* Note that without the declared copy constructor, the compiler may define an implicit move
* constructor.
*
* Additionally note that if the move constructor was declared as `= delete;`, then the second
* assertion in the above example would fail.
*/
private Constructor getMoveConstructor(Class c) {
if
not exists(MoveConstructor mc | mc = c.getAConstructor() and isUserDeclared(mc)) and
implicitMoveIsSuppressed(c)
then result = c.getAConstructor().(CopyConstructor)
else result = c.getAConstructor().(MoveConstructor)
}

/**
* Returns the move assignment operator of the class `c` if it exists, or the copy assignment
* operator if it does not exist and the implicit definition was suppressed by the compiler.
*
* For example:
* ```cpp
* class OnlyCopyAssign {
* public:
* OnlyCopyAssign& operator=(const OnlyCopyAssign &) = default;
* };
*
* static_assert(std::is_copy_assignable_v<OnlyCopyAssign>); // Succeeds
* static_assert(std::is_move_assignable_v<OnlyCopyAssign>); // Also succeeds
* ```
*
* Note that without the declared copy assignment operator, the compiler may define an implicit move
* assignment operator.
*
* Additionally note that if the move assignment operator was declared as `= delete;`, then the second
* assertion in the above example would fail.
*/
private Operator getMoveAssign(Class c) {
if
not exists(MoveAssignmentOperator mc | mc = c.getAMemberFunction() and isUserDeclared(mc)) and
implicitMoveIsSuppressed(c)
then result = c.getAMemberFunction().(CopyAssignmentOperator)
else result = c.getAMemberFunction().(MoveAssignmentOperator)
}

/**
* The types of special member functions that the `AnalyzableClass` tracks and analyzes.
*/
newtype TSpecialMember =
TMoveConstructor() or
TMoveAssignmentOperator() or
TCopyConstructor() or
TCopyAssignmentOperator() or
TDestructor()

/**
* A class for which we can see all special member functions, including implicitly declared ones,
* and therefore we can attempt to analyze it in the current rule.
*
* If one of the special member functions cannot be found, we cannot know if it is missing because
* it should not have been generated, or if EDG did not emit a definition for it. For instance, EDG
* may not generate these functions if they are trivial, or if they are delete, or not ODR used. We,
* the authors of this project, do not know the exact conditions we have to consider in this case.
*
* Determining for ourselves whether a certain constructor would be implicitly declared, and with
* what signature, and whether it is deleted, requires implementing a significant portion of the C++
* language rules regarding special member function generation, including a significant portion of
* C++ overload resolution rules which are non-trivial.
*
* Therefore we must find a definition for each special member in the database to proceed. The only
* exception we allow is certain missing `MoveConstructor` or `MoveAssignmentOperator` members; if
* the class defines copy operations or the destructor, we expect these to be missing, and typically
* this means the corresponding copy operation acts in place of the equivalent move.
*
* The last difficulty in analysis that this class attempts to handle is the values of the type
* traits `std::is_(copy|move)_(constructible|assignable)`. These type traits are defined as true if
* certain C++ expressions, such as `T(declval<T>())` or `declval<T>() = declval<T>()`, are
* well-formed. We cannot correctly determine this in all cases without implementing a significant
* portion of the C++ language rules for reference binding and overload resolution.
*
* To handle these type traits, we take a very rough approximation. If the corresponding special
* member function is public and not deleted, then we assume the type trait will evaluate to true.
* We also handle the case where a user declared copy operation suppresses the implicit move
* operations, which typically means overload resolution selects the copy operation. (This is not
* the case when the move operations are declared as deleted). We handle this by treating the copy
* operation as effectively acting in place of the move operation for the purposes of evaluating
* the type traits.
*/
class AnalyzableClass extends Class {
CopyConstructor copyCtor;
// The move constructor may be suppressed, and the copy constructor may be used during moves.
Comment thread
MichaelRFairhurst marked this conversation as resolved.
Constructor moveCtor;
CopyAssignmentOperator copyAssign;
// The move assignment operator may be suppressed, and the copy assignment operator may be used during moves.
Operator moveAssign;
Destructor dtor;

AnalyzableClass() {
copyCtor = this.getAConstructor() and
moveCtor = getMoveConstructor(this) and
copyAssign = this.getAMemberFunction() and
moveAssign = getMoveAssign(this) and
dtor = this.getDestructor()
}

/**
* Holds `std::is_move_constructible_v<T>` is likely true for this class.
*
* Specifically this holds if there's a non-deleted public move constructor available for this
* class, or if there is a non-deleted public copy constructor that acts as the move constructor.
*/
predicate moveConstructible() { isUsable(moveCtor) }

/**
* Holds `std::is_copy_constructible_v<T>` is likely true for this class.
*
* Specifically this holds if there's a non-deleted public copy constructor available for this
* class.
*/
predicate copyConstructible() { isUsable(copyCtor) }

/**
* Holds `std::is_move_assignable_v<T>` is likely true for this class.
*
* Specifically this holds if there's a non-deleted public move assignment operator available for
* this class, or if there is a non-deleted public copy assignment operator that acts as the move
* assignment operator.
*/
predicate moveAssignable() { isUsable(moveAssign) }

/**
* Holds `std::is_copy_assignable_v<T>` is likely true for this class.
*
* Specifically this holds if there's a non-deleted public copy assignment operator available for
* this class.
*/
predicate copyAssignable() { isUsable(copyAssign) }

/**
* Holds if the given special member function `s` is customized for this class.
*
* For most cases, this checks that the given special member function `s` has a user-provided
* body (other than `= default;` or `= delete;`).
*
* If the class has copy operations that act in place of the move operations, that means the
* corresponding move operation was not declared, so we say this predicate does not hold for the
* given move operation `s`.
*/
predicate isCustomized(TSpecialMember s) {
s instanceof TMoveConstructor and
isMemberCustomized(moveCtor) and
declaresMoveConstructor()
or
s instanceof TMoveAssignmentOperator and
isMemberCustomized(moveAssign) and
declaresMoveAssignmentOperator()
or
s instanceof TCopyConstructor and isMemberCustomized(copyCtor)
or
s instanceof TCopyAssignmentOperator and isMemberCustomized(copyAssign)
or
s instanceof TDestructor and isMemberCustomized(dtor)
}

/**
* Holds if this class declares a move constructor.
*
* This will be true if move constructor resolution found a non-implicit constructor that is not
* the copy constructor masquerading as a move constructor.
*/
predicate declaresMoveConstructor() { not moveCtor = copyCtor and isUserDeclared(moveCtor) }

/**
* Holds if this class declares a move assignment operator.
*
* This will be true if move assignment resolution found a non-implicit operator that is not
* the copy assignment operator masquerading as a move assignment operator.
*/
predicate declaresMoveAssignmentOperator() {
not moveAssign = copyAssign and isUserDeclared(moveAssign)
}
}
Loading
Loading