Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++: Add support for C++ requires expressions #17740

Merged
merged 1 commit into from
Oct 15, 2024
Merged

Conversation

jketema
Copy link
Contributor

@jketema jketema commented Oct 11, 2024

Pull Request checklist

All query authors

Internal query authors only

  • Autofixes generated based on these changes are valid, only needed if this PR makes significant changes to .ql, .qll, or .qhelp files. See the documentation (internal access required).
  • Changes are validated at scale (internal access required).
  • Adding a new query? Consider also adding the query to autofix.

@jketema jketema added the depends on internal PR This PR should only be merged in sync with an internal Semmle PR label Oct 11, 2024
@jketema jketema force-pushed the requires branch 8 times, most recently from ed70f88 to db98433 Compare October 12, 2024 12:18
@jketema jketema marked this pull request as ready for review October 14, 2024 05:58
@jketema jketema requested a review from a team as a code owner October 14, 2024 05:58
@calumgrant
Copy link
Contributor

What is the IR generated for requires expressions? My feeling is that we should exclude requires from IR because it isn't code that is actually executed, and any alerts that are surfaced in the requires block are likely to be of a completely different character. It doesn't have any kind of memory representation for example.

@jketema
Copy link
Contributor Author

jketema commented Oct 14, 2024

What is the IR generated for requires expressions?

There isn't any, because these are constant expressions, and they also have a value child. The value will occur in the IR, because that may be assigned to something.

@jketema
Copy link
Contributor Author

jketema commented Oct 14, 2024

Note that the IR for the test I added in #17693 is not changed by this PR.

Copy link
Contributor

@calumgrant calumgrant left a comment

Choose a reason for hiding this comment

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

A few minor things but overall LGTM.

@@ -1909,6 +1912,10 @@ case @expr.kind of
| @istriviallyrelocatable
;

compound_requirement_is_noexcept(
unique int expr: @compound_requirement ref
Copy link
Contributor

Choose a reason for hiding this comment

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

unique is implicit here. Could an expression be possible here, similar to fun_decl_noexcept? In that case, this predicate is compound_requirement_empty_noexcept.

Another strategy could be to unite this predicate with fun_decl_noexcept/fun_decl_empty_noexcept, but that might be more work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

unique is implicit here

Do you mean that I can omit it?

Could an expression be possible here, similar to fun_decl_noexcept?

No, that's not allowed. It's just a keyword without any arguments.

Copy link
Contributor

Choose a reason for hiding this comment

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

unique is implicit here

Do you mean that I can omit it?

Yes I would think so. You can't have duplicated rows in a tuple.

Could an expression be possible here, similar to fun_decl_noexcept?

No, that's not allowed. It's just a keyword without any arguments.

Ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dropped the unique.

@@ -2168,11 +2175,11 @@ stmt_decl_entry_bind(
int decl_entry: @element ref
);

@functionorblock = @function | @stmt_block;
@functionorblockorrequiresexpr = @function | @stmt_block | @requires_expr;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: could rename this to @block_parent or something descriptive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that "block" has a special meaning here: https://clang.llvm.org/docs/BlockLanguageSpec.html

I'm open to a more sensible name though, just not sure what it should be.

Copy link
Contributor Author

@jketema jketema Oct 14, 2024

Choose a reason for hiding this comment

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

How about parameterized_element?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced by parameterized_element

int index;

Parameter() { params(this, function, index, _) }
Parameter() { params(this, _, _, _) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this line needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess not. I mostly simplified this, because ql-for-ql started explaining, but didn't think beyond that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dropped.


override string getAPrimaryQlClass() { result = "RequiresExpr" }

/**
* Gets a parameter of this requires expression.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Gets a parameter of this requires expression.
* Gets a parameter of this requires expression, if any.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


/** Gets the number of parameters of this requires expression. */
int getNumberOfParameters() { result = count(this.getAParameter()) }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would expect there to be a predicate to access the body as well. - getBlock()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, good point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getRequirement/1 getARequirement/0 and getNumberOfRequirements/0 added.

}

/**
* A C++ concept id expression
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* A C++ concept id expression
* A C++ concept id expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

#-----| Type = [BoolType] bool
#-----| Value = [RequiresExpr] 1
#-----| ValueCategory = prvalue
#-----| <params>:
# 2692| getChild(0): [GTExpr,SimpleRequirementExpr] ... > ...
Copy link
Contributor

@calumgrant calumgrant Oct 14, 2024

Choose a reason for hiding this comment

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

Accessing the body via getBlock() might be nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, see above for the actual predicate name I used.

Expr getExpr() { result = this.getChild(0) }

/**
* Gets the return type requirement from the compound requirement (if any).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Gets the return type requirement from the compound requirement (if any).
* Gets the return type requirement from the compound requirement, if any.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

---
category: feature
---
* Added classes `RequiresExpr`, `SimpleRequirementExpr`, `TypeRequirementExpr`, `CompoundRequirementExpr`, and `NestedRequirementExpr` to represent C++20 requires expressions and the simple, type, compound, and nested requirements that can occur in requires expressions.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Added classes `RequiresExpr`, `SimpleRequirementExpr`, `TypeRequirementExpr`, `CompoundRequirementExpr`, and `NestedRequirementExpr` to represent C++20 requires expressions and the simple, type, compound, and nested requirements that can occur in requires expressions.
* Added classes `RequiresExpr`, `SimpleRequirementExpr`, `TypeRequirementExpr`, `CompoundRequirementExpr`, and `NestedRequirementExpr` to represent C++20 requires expressions and the simple, type, compound, and nested requirements that can occur in `require` expressions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I assume you mean "requires expressions"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

override AstNode getChildInternal(int childIndex) {
result.getAst() = expr.getChild(childIndex)
override PrintAstNode getChildInternal(int childIndex) {
result.(AstNode).getAst() = expr.getChild(childIndex)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Consider adding a predicatePrintAst::getAst() { none() }.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This in consistent with how this is done in other places in this library, so I'm going to leave this as-is for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Left as-is.

*/
class RequiresExpr extends Expr, @requires_expr {
override string toString() { result = "requires ..." }
override string toString() { result = "requires(...) { ... }" }
Copy link
Contributor

@calumgrant calumgrant Oct 14, 2024

Choose a reason for hiding this comment

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

Nit: Consider something like if exists(this.getAParameter()) then result = "requires(...) { ... }" else result = "requires { ... }" }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I thought about that. However, I cannot really differentiate between empty and missing parameter lists, so I wasn't really sure about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@jketema jketema force-pushed the requires branch 2 times, most recently from 3acd474 to 0df5305 Compare October 14, 2024 18:36
@jketema
Copy link
Contributor Author

jketema commented Oct 14, 2024

Apologies for force pushing the fixes. The schema changes made it very difficult to do something else and still end up with a reasonable history.

Comment on lines -10 to +11
* A C/C++ function parameter or catch block parameter. For example the
* function parameter `p` and the catch block parameter `e` in the following
* A C/C++ function parameter, catch block parameter, or requires expression parameter.
* For example the function parameter `p` and the catch block parameter `e` in the following
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that these changes are new.

Comment on lines -23 to +24
* For catch block parameters, there is a one-to-one correspondence between
* the `Parameter` and its `ParameterDeclarationEntry`.
* For catch block parameters and expression , there is a one-to-one
* correspondence between the `Parameter` and its `VariableDeclarationEntry`.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These changes are new.

Copy link
Contributor

@calumgrant calumgrant left a comment

Choose a reason for hiding this comment

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

Great work!

@jketema jketema merged commit 50ec254 into github:main Oct 15, 2024
15 of 16 checks passed
@jketema jketema deleted the requires branch October 15, 2024 12:32
@jketema jketema mentioned this pull request Oct 16, 2024
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C++ depends on internal PR This PR should only be merged in sync with an internal Semmle PR documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants