Skip to content

C++: Add missing parent scope cases #17776

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

Merged
merged 1 commit into from
Oct 16, 2024
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
21 changes: 15 additions & 6 deletions cpp/ql/lib/semmle/code/cpp/Element.qll
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class Element extends ElementBase {
* or certain kinds of `Statement`.
*/
Element getParentScope() {
// result instanceof class
// result instanceof Class
exists(Declaration m |
m = this and
result = m.getDeclaringType() and
Expand All @@ -138,31 +138,40 @@ class Element extends ElementBase {
or
exists(TemplateClass tc | this = tc.getATemplateArgument() and result = tc)
or
// result instanceof namespace
// result instanceof Namespace
exists(Namespace n | result = n and n.getADeclaration() = this)
or
exists(FriendDecl d, Namespace n | this = d and n.getADeclaration() = d and result = n)
or
exists(Namespace n | this = n and result = n.getParentNamespace())
or
// result instanceof stmt
// result instanceof Stmt
exists(LocalVariable v |
this = v and
exists(DeclStmt ds | ds.getADeclaration() = v and result = ds.getParent())
)
or
exists(Parameter p | this = p and result = p.getFunction())
exists(Parameter p |
this = p and
(
result = p.getFunction() or
result = p.getCatchBlock().getParent().(Handler).getParent().(TryStmt).getParent() or
Copy link
Contributor

Choose a reason for hiding this comment

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

@jketema Could you explain the rationale behind making the parent scope of a catch block parameter the parent scope of the TryStmt? Surely it should be the catch block itself?

This is what is breaking the Coding Standards IdentifierHiding.ql query. That query tries to find pairs of identifiers where the second hides the first. By considering to the parameter of a catch block to have a parent scope of the parent of the TryStmt we erroneously report cases like this:

  {
    int i; // COMPLIANT
  }

  try {

  } catch (int i) { // COMPLIANT
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you explain the rationale behind making the parent scope of a catch block parameter the parent scope of the TryStmt? Surely it should be the catch block itself?

This seemed to be most consistent with the other cases here. For example, for functions the parent scope is the whole function, and not the statement block that forms the body of the function. The latter would correspond with the catch block in the case of a try-catch.

Copy link
Contributor

Choose a reason for hiding this comment

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

For example, for functions the parent scope is the whole function

Just checking, but I presume you mean "for parameters the parent scope is the whole function"?

If so, I think it would be reasonable on that basis to report the Handler of the CatchBlock as the parent scope of the parameter. But reporting the parent of the TryStmt as the "scope" erroneously puts it in the same scope as other variables declared outside the TryStmt. Given the main purpose of this predicate is to support identifier hiding queries, this seems like it's not ideal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem is that Handler is not exposed anywhere else in the QL API, and it's only really visible in the AST, so that's far from ideal either.

result = p.getRequiresExpr().getEnclosingStmt().getParent()
)
)
or
exists(GlobalVariable g, Namespace n | this = g and n.getADeclaration() = g and result = n)
or
exists(TemplateVariable tv | this = tv.getATemplateArgument() and result = tv)
or
exists(EnumConstant e | this = e and result = e.getDeclaringEnum())
or
// result instanceof block|function
// result instanceof Block|Function
exists(BlockStmt b | this = b and blockscope(unresolveElement(b), unresolveElement(result)))
or
exists(TemplateFunction tf | this = tf.getATemplateArgument() and result = tf)
or
// result instanceof stmt
// result instanceof Stmt
exists(ControlStructure s | this = s and result = s.getParent())
or
using_container(unresolveElement(result), underlyingElement(this))
Expand Down
9 changes: 8 additions & 1 deletion cpp/ql/test/library-tests/scopes/parents/parents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ namespace foo {
}
}

template<typename T>
T var = 42;


int g() {
requires(int l) { l; };

return var<int>;
}

// semmle-extractor-options: -std=c++20
7 changes: 7 additions & 0 deletions cpp/ql/test/library-tests/scopes/parents/parents.expected
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
| 0 | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | __va_list_tag |
| 0 | file://:0:0:0:0 | (global namespace) | parents.cpp:2:11:2:13 | foo |
| 0 | file://:0:0:0:0 | (global namespace) | parents.cpp:18:3:18:3 | var |
| 0 | file://:0:0:0:0 | (global namespace) | parents.cpp:18:7:18:7 | var |
| 0 | file://:0:0:0:0 | (global namespace) | parents.cpp:20:5:20:5 | g |
| 1 | file://:0:0:0:0 | __va_list_tag | file://:0:0:0:0 | fp_offset |
| 1 | file://:0:0:0:0 | __va_list_tag | file://:0:0:0:0 | gp_offset |
| 1 | file://:0:0:0:0 | __va_list_tag | file://:0:0:0:0 | operator= |
Expand All @@ -14,7 +17,11 @@
| 1 | parents.cpp:4:10:4:10 | f | parents.cpp:4:19:13:5 | { ... } |
| 1 | parents.cpp:4:19:13:5 | { ... } | parents.cpp:5:11:5:11 | j |
| 1 | parents.cpp:4:19:13:5 | { ... } | parents.cpp:6:11:10:7 | { ... } |
| 1 | parents.cpp:4:19:13:5 | { ... } | parents.cpp:11:18:11:18 | e |
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels like the parent of e should be something associated with the catch block on line 11, rather than the entire function. I can see that this wouldn't fit the current definition of what getParentScope can return. And what you have is probably good enough for what getParentScope is used for.

Copy link
Contributor Author

@jketema jketema Oct 16, 2024

Choose a reason for hiding this comment

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

Yeah, agreed. I struggled a bit with that one initially. What I do now seems to be most in line with all the other cases that are there, and what we want when looking for shadowing.

| 1 | parents.cpp:4:19:13:5 | { ... } | parents.cpp:11:21:12:7 | { ... } |
| 1 | parents.cpp:6:11:10:7 | { ... } | parents.cpp:7:9:9:9 | for(...;...;...) ... |
| 1 | parents.cpp:6:11:10:7 | { ... } | parents.cpp:7:33:9:9 | { ... } |
| 1 | parents.cpp:7:33:9:9 | { ... } | parents.cpp:8:15:8:15 | k |
| 1 | parents.cpp:18:7:18:7 | var | parents.cpp:17:19:17:19 | T |
| 1 | parents.cpp:20:5:20:5 | g | parents.cpp:20:9:24:1 | { ... } |
| 1 | parents.cpp:20:9:24:1 | { ... } | parents.cpp:21:16:21:16 | l |
Loading