Skip to content

Declarations5 cpp misra 2023#1101

Open
knewbury01 wants to merge 7 commits intogithub:mainfrom
knewbury01:knewbury01/cpp-misra2023-declarations5
Open

Declarations5 cpp misra 2023#1101
knewbury01 wants to merge 7 commits intogithub:mainfrom
knewbury01:knewbury01/cpp-misra2023-declarations5

Conversation

@knewbury01
Copy link
Copy Markdown
Collaborator

@knewbury01 knewbury01 commented Apr 2, 2026

Description

Declarations 5

Change request type

  • Release or process automation (GitHub workflows, internal scripts)
  • Internal documentation
  • External documentation
  • Query files (.ql, .qll, .qls or unit tests)
  • External scripts (analysis report or other code shipped as part of a release)

Rules with added or modified queries

  • No rules added
  • Queries have been added for the following rules:
    • RULE-6-9-1
    • RULE-6-8-4
  • Queries have been modified for the following rules:
    • rule number here

Release change checklist

A change note (development_handbook.md#change-notes) is required for any pull request which modifies:

  • The structure or layout of the release artifacts.
  • The evaluation performance (memory, execution time) of an existing query.
  • The results of an existing query in any circumstance.

If you are only adding new rule queries, a change note is not required.

Author: Is a change note required?

  • Yes
  • No

🚨🚨🚨
Reviewer: Confirm that format of shared queries (not the .qll file, the
.ql file that imports it) is valid by running them within VS Code.

  • Confirmed

Reviewer: Confirm that either a change note is not required or the change note is required and has been added.

  • Confirmed

Query development review checklist

For PRs that add new queries or modify existing queries, the following checklist should be completed by both the author and reviewer:

Author

  • Have all the relevant rule package description files been checked in?
  • Have you verified that the metadata properties of each new query is set appropriately?
  • Do all the unit tests contain both "COMPLIANT" and "NON_COMPLIANT" cases?
  • Are the alert messages properly formatted and consistent with the style guide?
  • Have you run the queries on OpenPilot and verified that the performance and results are acceptable?
    As a rule of thumb, predicates specific to the query should take no more than 1 minute, and for simple queries be under 10 seconds. If this is not the case, this should be highlighted and agreed in the code review process.
  • Does the query have an appropriate level of in-query comments/documentation?
  • Have you considered/identified possible edge cases?
  • Does the query not reinvent features in the standard library?
  • Can the query be simplified further (not golfed!)

Reviewer

  • Have all the relevant rule package description files been checked in?
  • Have you verified that the metadata properties of each new query is set appropriately?
  • Do all the unit tests contain both "COMPLIANT" and "NON_COMPLIANT" cases?
  • Are the alert messages properly formatted and consistent with the style guide?
  • Have you run the queries on OpenPilot and verified that the performance and results are acceptable?
    As a rule of thumb, predicates specific to the query should take no more than 1 minute, and for simple queries be under 10 seconds. If this is not the case, this should be highlighted and agreed in the code review process.
  • Does the query have an appropriate level of in-query comments/documentation?
  • Have you considered/identified possible edge cases?
  • Does the query not reinvent features in the standard library?
  • Can the query be simplified further (not golfed!)

@knewbury01 knewbury01 self-assigned this Apr 2, 2026
@knewbury01 knewbury01 marked this pull request as ready for review April 7, 2026 18:19
Copilot AI review requested due to automatic review settings April 7, 2026 18:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new C++ MISRA-C++-2023 “Declarations5” rule package and introduces two new rule queries (RULE-6-8-4 and RULE-6-9-1), wiring them into rule metadata/exclusions and adding unit tests.

Changes:

  • Add new CodeQL queries for RULE-6-8-4 and RULE-6-9-1 under the Declarations5 package.
  • Add/route rule package metadata (rules.csv + rule_packages/cpp/Declarations5.json) and integrate the new package into exclusions metadata.
  • Add unit tests (.qlref, .expected, test.cpp) for both new queries.
Show a summary per file
File Description
rules.csv Re-points RULE-6-8-4 and RULE-6-9-1 to the new Declarations5 package.
rule_packages/cpp/Declarations5.json Defines rule-package metadata and query metadata for the two new rules.
cpp/misra/src/rules/RULE-6-8-4/MemberFunctionsRefqualified.ql Implements the RULE-6-8-4 query logic.
cpp/misra/src/rules/RULE-6-9-1/TypeAliasesDeclaration.ql Implements the RULE-6-9-1 query logic.
cpp/misra/test/rules/RULE-6-8-4/test.cpp Adds test cases for ref-qualification rule behavior.
cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.qlref Connects tests to the production RULE-6-8-4 query.
cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.expected Adds expected results for RULE-6-8-4 tests.
cpp/misra/test/rules/RULE-6-9-1/test.cpp Adds test cases for type-alias consistency across redeclarations.
cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.qlref Connects tests to the production RULE-6-9-1 query.
cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.expected Adds expected results for RULE-6-9-1 tests.
cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll Registers the new Declarations5 package in the C++ exclusions metadata dispatcher.
cpp/common/src/codingstandards/cpp/exclusions/cpp/Declarations5.qll Adds autogenerated metadata mappings and query constructors for the new package.

Copilot's findings

Comments suppressed due to low confidence (1)

cpp/misra/src/rules/RULE-6-8-4/MemberFunctionsRefqualified.ql:56

  • MembersReturningSubObject currently matches return field; even when the function returns the field by value. This causes false positives (for example, an && overload that returns a subobject by value to avoid dangling references). The rule description is about returning references/pointers; please add a return-type guard so only reference/pointer returns are considered violations.
class MembersReturningSubObject extends MembersReturningObjectOrSubobject {
  MembersReturningSubObject() {
    exists(ReturnStmt r, FieldSubObjectDeclaration field |
      r.getEnclosingFunction() = this and
      (
        r.getAChild*() = field.(Field).getAnAccess()
        or
        exists(PointerDereferenceExpr p |
          p.getAChild*() = field.(Field).getAnAccess() and
          r.getAChild*() = p
        )
      ) and
      field.(Field).getDeclaringType() = this.getDeclaringType()
    )
  }
  • Files reviewed: 12/12 changed files
  • Comments generated: 6

Comment thread cpp/misra/src/rules/RULE-6-8-4/MemberFunctionsRefqualified.ql
Comment thread cpp/misra/src/rules/RULE-6-8-4/MemberFunctionsRefqualified.ql
Comment on lines +6 to +7
| test.cpp:42:16:42:16 | Members returning object or subobject | Member function is not properly ref qualified. |
| test.cpp:42:16:42:16 | Members returning object or subobject | Member function is not properly ref qualified. |
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The .expected output contains three identical rows for the same location (test.cpp:42:16). This indicates the query is emitting duplicates; tests should generally not codify duplicate results. Once the query is de-duplicated, update this expected file accordingly.

Suggested change
| test.cpp:42:16:42:16 | Members returning object or subobject | Member function is not properly ref qualified. |
| test.cpp:42:16:42:16 | Members returning object or subobject | Member function is not properly ref qualified. |

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +5
INT i;
extern int i; // NON_COMPLIANT
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The expected results for this rule report the primary location on INT i; (line 4), but the test marks extern int i; (line 5) as NON_COMPLIANT. Please align the NON_COMPLIANT annotation(s) with the location(s) the query reports (either move/add the annotation to line 4, or adjust the query to report line 5 as the primary location).

Suggested change
INT i;
extern int i; // NON_COMPLIANT
INT i; // NON_COMPLIANT
extern int i;

Copilot uses AI. Check for mistakes.
Comment thread cpp/misra/src/rules/RULE-6-9-1/TypeAliasesDeclaration.ql
Comment thread cpp/misra/test/rules/RULE-6-8-4/test.cpp
Copy link
Copy Markdown
Collaborator

@MichaelRFairhurst MichaelRFairhurst left a comment

Choose a reason for hiding this comment

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

There are definitely some tricky nuanced scenarios here, but the good news is I think we have some prior art that can handle them, and is still fairly simple. Here's my thoughts.

Consider making getASubobjectAccessOf public in CppObjects.qll and using that here, so that we can essentially write:

predicate isSubobjectReferenceToThis(ThisExpr root, Expr access) {
  // something like this:
  access.(AddressOfExpr).getAnOperand() = getASubobjectAccessOf(root)
  or
  // ... handle the case where we take a reference to a subobject instead of its address
}

We likely want to extend getASubobjectAccessOf in two ways for C++ (it's still a copy of the c version right now):

  • A. to unwrap the this pointer, and
  • B. to exclude reference accesses

A. would be something like:

Expr getASubobjectAccessOfPointee(Expr e) {
  e.getParent() instanceof AddressOfExpr and
  result = getASubobjectAccessOf(e.getParent())
  or
  e.getParent() instanceof PointerFieldAcess() and
  ....
}

B. excluding reference accesses, would be something like:

Expr getASubobjectAccessOf(Expr e) {
  ...
  or
  result.(DotFieldAccess).getQualifier() = getASubobjectAccessOf(e) and
  // new part:
  not result.(FieldAccess).getField().getUnderlyingType() instanceof ReferenceType
  or
  ...
}

Note that there's overlap between A. and B., because this->x.y may be a subobject (if x and y are not references) or not (if either x or y is a reference).

Something beyond A. and B. that we probably don't want to handle right now, but rather file an issue for, would be to add support for simple getter functions, especially for pointer-like types such as unique_ptr<T> and iterator types. Definitely reasonable to file in the issue tracker as a TODO.

class C {
  Member m;
  Member &get_member() { return m; }
  Member& operator*() { return m; }
  Member *operator->() { return &m; }
}

void f() {
  C c;
  c.get_member(); // accesses member
  *c; // accesses member
  c->bar; // accesses member

Hopefully this makes some sense, feel free to ping questions of course!

exists(ReturnStmt r, ThisExpr t |
r.getEnclosingFunction() = this and
(
r.getAChild*() = t
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm usually not a fan of getAChild*(), because it is typically a bigger hammer than we want. For instance, this will return FPs for return ref ? 1 : 2, or return ref_to_vector.get_size(), etc.

Can we use localFlow instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I have now opted to direct explicit references only... I do agree localFlow within the statement might make the most sense, but it would maybe be overkill? let me know though if that seems like it would miss reasonable things and I will add it!

*/
class FieldSubObjectDeclaration extends Declaration {
FieldSubObjectDeclaration() {
not this.getADeclarationEntry().getType() instanceof ReferenceType and
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This isn't going to do exactly what we probably want, because we have to worry again about pointers and pointees.

Returning a pointer by value is allowed, and dereferencing that pointer should also be allowed because the result could be any object -- not guaranteed to be a subobject of this. And returning an address or reference to a field that is a pointer should be flagged as well.

class C {
  int i;
  int *ip;
  int **ip2;

  int *f() {
    return &i; // non compliant
    return ip; // compliant -- copies pointer
    return *ip2; // compliant -- reads pointer
  }

  int **f2() {
    return &ip; // non compliant
    return ip2; // compliant -- copies pointer
  }

  int &f3() {
    return i; // non compliant
    return *ip; // compliant -- reads pointer
    return **ip2; // compliant -- reads pointer
  }

  int *&f4() {
    return &p; // won't compile
    return ip; // non compliant
    return *ip2; // compliant -- reads pointer
  }

  int **&f5() {
    return &ip; // won't compile
    return ip; // non compliant
  }
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

added this test, as well as incorporated feedback from overall review comment , some FPs remain, I think I need a second set of eyes on my expansion of cppobjects at this point

Copy link
Copy Markdown
Collaborator

@MichaelRFairhurst MichaelRFairhurst left a comment

Choose a reason for hiding this comment

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

This is really not far off! It's using basically all the right pieces in all the right places, just looks like a couple cases are subtly flipped around I'd say!

f.getAnAccess().(FieldAccess) = result.getAChild*()
) and
(
result = e
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm....I think a field of reference type should still be a subobject of itself. So this should be outside of the above condition.

Expr getASubobjectAccessOf(Expr e) {
exists(Field f |
not f.getUnderlyingType() instanceof ReferenceType and
f.getAnAccess().(FieldAccess) = result.getAChild*()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like using getAChild*() finds too many results here.

For example:

my_var                         // 1
  .get_a_field_of_non_ref_type // 2
  .get_a_field_of_ref_type     // 3

Here, 3 is not a subobject of 1. However, 2 is a child of 3, and 2 is a field access of non-ref type, so it satisfies f. So this will give the wrong answer here.

I think the correct implementation is simpler:

result = e
or
result.(DotFieldAccess).getQualifier = getASubobjectAccessOf(e) and
not result.(DotFieldAccess).getTarget().getUnderlyingType() instanceof ReferenceType
or
....

}

/**
* gets an access where the pointee is the subobject
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Might be clearer to write with a more detailed comment, which I'm happy to help fill in!

Based on the name, I'd say this predicate takes an expression e with pointer type pointer, and finds subobjects of the thing that e points to. Doing so is just a matter of dereferencing e, and then potentially getting subobject accesses of that result.

The expression e may be dereferenced either with *e, getting the pointee itself, or with e->x, immediately getting a subobject of the pointee. Any subobject access of these expressions (e.g. (*e).x, or e->x.y[0].b... are subobject accesses of the pointee of expression e.

This predicate is especially useful for finding subobject accesses of *this.

* gets an access where the pointee is the subobject
*/
Expr getASubobjectAccessOfPointee(Expr e) {
e.getParent() instanceof AddressOfExpr and
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I may have put the wrong code here in my earlier review, and if so I'm sorry!

We don't want to find &x, we want to find *x, which is a PointerDereferenceExpr

or
//or one level of indirection
exists(PointerDereferenceExpr p |
p.getAChild() = t and
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Here we can say

r.getAChild() = getASubobjectAccessOf(t).

That will handle: return *this, return this->x, return (*this)[4], etc.

We'll need to say or r.getAChild() = t to handle return this.

or
// the accessed field is a pointer to a subobject
e.getParent().(PointerFieldAccess).getTarget().getUnspecifiedType() instanceof PointerType and
result = getASubobjectAccessOf(e.getParent())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Mostly right but subtle difference!

Given e1->e2, we want the result to be e2 when the parameter is e1. (or a subobject access of e2).

r.getAChild() = e and
(
//pointer or reference to pointer subobject returned
e = getASubobjectAccessOfPointee(access) and
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In this case we don't want to use getASubobjectAccessOfPointee on the access.

It looks like this case is handling:

return my_member;
return my_member.subobject;
return my_member.subobject[n];
// etc

So in this case, we can say e = getASubobjectAccessOf(access). But we should also handle return &my_member too.

(
//pointer or reference to pointer subobject returned
e = getASubobjectAccessOfPointee(access) and
(e.getType() instanceof PointerType or e.getType() instanceof ReferenceType)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So, in these two branches here, we don't necessarily care if e has a pointer type or e has a reference type so long as e = getASubobjectAccessOf(access).

The two cases that we need to handle, though, are:

  • pointers to subobjects, and
  • references to subobjects

So if e is a subobject, then:

  • returning the address of e is not allowed (as in return &...)
  • returning e directly is not allowed if the method returns by reference (as in int &get_ref() { return e; })

I do have to apologize though because there's one more caveat that makes this confusing 😅. I think it's the last thing. We have to not only check that e = getASubobjectAccessOf(access), but we also have to check if access is not a reference type. That's just because an access of reference type isn't a subobject in the first place, if that makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants