Skip to content

RFC: C++ Support non type template parameter values#2153

Merged
nickrolfe merged 17 commits intogithub:masterfrom
matt-gretton-dann:cpp-447-support-non-type-template-parameters
Nov 6, 2019
Merged

RFC: C++ Support non type template parameter values#2153
nickrolfe merged 17 commits intogithub:masterfrom
matt-gretton-dann:cpp-447-support-non-type-template-parameters

Conversation

@matt-gretton-dann
Copy link
Contributor

This is an initial PR adding support for values from non-type template parameter values. It is not yet ready for merging as it doesn't have stats or complete testing added.

Questions:

  • Is the Database Schema taking the right approach
  • What else needs to be added to the libraries to give proper support for this functionality?

@matt-gretton-dann matt-gretton-dann added C++ depends on internal PR This PR should only be merged in sync with an internal Semmle PR WIP This is a work-in-progress, do not merge yet! labels Oct 18, 2019
@matt-gretton-dann matt-gretton-dann requested a review from a team as a code owner October 18, 2019 10:34
@matt-gretton-dann
Copy link
Contributor Author

Copy link
Contributor

@jbj jbj left a comment

Choose a reason for hiding this comment

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

I think it would be helpful for discussing my comments if you add some direct tests of the new getTemplateArgumentValue predicate.

#-----| 0: [Parameter] p#0
#-----| Type = [RValueReferenceType] S &&
# 9| [MemberFunction] int Bad::S::MemberFunction(int)
# 9| [FunctionTemplateInstantiation,MemberFunction] int Bad::S::MemberFunction<int>(int)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd expect to see <6> here instead of <int>. I suppose the function signature pretty-printer needs to learn about getTemplateArgumentValue to find the 6, but where is the int coming from?

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'm still working on getting this tidied-up

Copy link
Contributor

Choose a reason for hiding this comment

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

If it causes you trouble, feel free to throw it over the fence to the analysis team.

@dave-bartolomeo
Copy link
Contributor

I think the overall approach seems reasonable, but I have a few high-level questions/comments:

  1. Is support for template template parameters planned as a follow-up? I think it's lower priority than non-type template parameters, but we'll need it eventually.
  2. I'd really like to see tests to cover some of the more exotic cases, like non-type template parameters whose type is a pointer, reference, or pointer-to-member. Another case to test would be where the type of the non-type template parameter is dependent on a previous type template parameter, e.g. template<typename T, T* p> class Foo;.
  3. Given template<typename A, int B> class Foo; Foo<float, 5> bar;, does getTemplateArgument(0) return float, getTemplateArgument(1) return int, and getTemplateArgumentValue(1) return 5?

@matt-gretton-dann
Copy link
Contributor Author

I think the overall approach seems reasonable, but I have a few high-level questions/comments:

  1. Is support for template template parameters planned as a follow-up? I think it's lower priority than non-type template parameters, but we'll need it eventually.

It wasn't but can be :-)

  1. I'd really like to see tests to cover some of the more exotic cases, like non-type template parameters whose type is a pointer, reference, or pointer-to-member. Another case to test would be where the type of the non-type template parameter is dependent on a previous type template parameter, e.g. template<typename T, T* p> class Foo;.

I will add these

  1. Given template<typename A, int B> class Foo; Foo<float, 5> bar;, does getTemplateArgument(0) return float, getTemplateArgument(1) return int, and getTemplateArgumentValue(1) return 5?

You will get two Declarations one for the template declaration, and one for the instantiation.

For the declaration you will get: getTemplateArgument(0) returns A, getTemplateArgument(1) returns int and getTemplateArgumentValue(1) returns B.

For the instantiation you are correct with the results.


static const int one1 = 1, one2 = 1;
C<one1> c = C<one2>();
C<one1 + one2> e;
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 front-end presents the extractor with the integer constant '2' here with no further information - not even a link back to the source code so we can see how '2' was written. There is currently no way I can find to tell the extractor about one1 + one2.

| test.cpp:16:8:16:8 | E<T, X> | file://:0:0:0:0 | T * | file://:0:0:0:0 | X |
| test.cpp:16:8:16:8 | E<T, X> | test.cpp:15:19:15:19 | T | file://:0:0:0:0 | X |
| test.cpp:16:8:16:8 | E<int, (int *)nullptr> | file://:0:0:0:0 | int | file://:0:0:0:0 | 0 |
| test.cpp:16:8:16:8 | E<int, (int *)nullptr> | file://:0:0:0:0 | int * | file://:0:0:0:0 | 0 |
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 do not understand why getATemplateArgument() is returning two types here (int and int*) for the same parameter. The TRAP looks correct: just one class_template_argument() tuple for index 1 which has a type T*. Is this something to do with how the underlying and unresolved element QL lib code works>

#-----| 0: [Parameter] p#0
#-----| Type = [RValueReferenceType] S &&
# 9| [MemberFunction] int Bad::S::MemberFunction(int)
# 9| [FunctionTemplateInstantiation,MemberFunction] int Bad::S::MemberFunction<int>(int)
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'm still working on getting this tidied-up

@@ -0,0 +1,19 @@
// semmle-extractor-options: --edg --trap_container=folder --edg --trap-compression=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.

@dbartol: This should cover some of the test cases you wanted to see.

@matt-gretton-dann matt-gretton-dann force-pushed the cpp-447-support-non-type-template-parameters branch from 05a2bd4 to 3c026fc Compare October 29, 2019 14:38
@matt-gretton-dann
Copy link
Contributor Author

I think I have now got the DB side of this in good shape - I still need to generate the stats file update, but apart from that the DB and tests look good.

@jbj: I have made changes upgrades to the help. I'm still not sure they are good enough. I think we could do with a conversation about what should be put there and what is missing.

*/
override Expr getTemplateArgumentValue(int i) {
class_template_argument_value(underlyingElement(this), i, unresolveElement(result))
}
Copy link
Member

Choose a reason for hiding this comment

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

Would it be better to just generalise

Type getTemplateArgument(int i)

to

Element getTemplateArgument(int i)

(and similarly in the dbscheme)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the QL-level API you proposed there is more intuitive than what's proposed in this PR, and it would also cut down on the number of similar-looking predicates that are added. Unfortunately it's a breaking change to change the return type because it would no longer be possible to write getTemplateArgument(0).getName(). It might be a breaking change we're willing to make since I don't expect too many external queries to use this API at all.

We also currently get the declared type of a non-type template parameter extracted and accessible via getTemplateArgument (the myIntAlias in template<myIntAlias i> ...). If we want to keep that, we'd also need an accessor predicate for it. I'd call it the kind of the template argument, but maybe there's a more appropriate C++-specific word for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My initial design of the interface had the goal to not break anything, and to keep the changes small. I agree its not necessarily intuitive, and therefore am happy to change it.

However, I think having a predicate which returns either a Type or Expr depending on the template parameter kind is not good either as this means that those who want to know the type of Expr need to use another predicate to get that type.

I think there should be two predicates:

  • One to get the type of a template argument (either explicitly specified by the user, or deduced by the compiler). This has a value for every template argument.
  • One to get the value of the template argument. Only has a value for non-type templates.

(This is actually the current interface - although it could have much better names).

Maybe a better approach would be something like the following (which I think is closer to how the C++ Standard views the world):

Consider the following:

template<typename T, T X> T foo() { T y = X; return y + y; } 
int x = foo<int, 10>(); 

Currently. the trap we produce for the instantiation of foo<int, 10> currently does not reference the type T anywhere - instead the return type and type of y are directly int. Similarly, X is replaced with 10.

Maybe instead we should expose T as a typedef and X as a variable declaration, and use those instead. In the example above this would mean the return type of the instantiation of foo() is T which is a typedef for int.

Then in the template declaration we have getTemplateParameter() which return the names T, X (in this example) and provide a link to the types in the instantiation. (This final bit isn't clearly thought out :-) ).

My quick scan of the C/C++ Front-End source suggests that it holds enough info in the IL for this to be workable.

This is definitely a breaking change, and much more complicated to implement, but would be the more natural view of the world. However, does what we're currently doing provide enough for end users?

Copy link
Member

@igfoo igfoo Oct 31, 2019

Choose a reason for hiding this comment

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

I think the most natural interface would be, if inst is your foo<int, 10> instantiation,

inst.getTemplateArgumentKind(0) = type // currently not representable
inst.getTemplateArgument(0) = int
inst.getTemplate().getTemplateArgumentKind(0) = typename // currently not representable
inst.getTemplate().getTemplateArgument(0) = T

inst.getTemplateArgumentKind(1) = int
inst.getTemplateArgument(1) = 10
inst.getTemplate().getTemplateArgumentKind(1) = T
inst.getTemplate().getTemplateArgument(1) = X

Copy link
Contributor

Choose a reason for hiding this comment

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

I like your idea of using typedefs and variables. With C++11 template<...> using declarations, I think this will even generalise to template template parameters. But for non-type template parameters, it's unclear to me how we'd map the variable to its value. If an instantiated template has variable X for a non-type template parameter, should the Initializer of that variable then be the expression that holds its value (10, in your example)? What if the same template instantiation is found in multiple source locations? One location might have foo<int, 10>, and another might have foo<int, 5+5>.

The interface currently implemented in this PR is not great, but I don't think it's an interface that will be used much, so I'm fine with merging it as it is as long as there is extensive QLDoc about all the surprising behaviour we've discussed.

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 did - updated :-).

I could imagine writing something like:

template<class T, T::size_type initial_size> array

where I would want to know the underlying type of T::size_type (4 or 8 bytes?).

This is where treating the parameters as typedefs and variable decls would be more natural because initial_size would be a variable of type T::size_type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Anyway where I think we've got to is the need for two predicates on Declaration:

  1. Element getTemplateArgument(int i) which returns the user-supplied i-th template argument.
  2. Type getTemplateArgumentKind(int i) which returns the type of the i`-th template argument if that is a non-type argument, and has no result otherwise.

Correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And that the above is a breaking change - but we do not expect it to cause many people issues (if any).

Copy link
Member

Choose a reason for hiding this comment

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

Even if it currently only returns Types, getTemplateArgumentKind(int) should have result type Element. If a predicate is only going to have results of type Type, then its name should contain Type rather than Kind.

I'm not particularly wedded to the name Kind, incidentally; I was more talking about functionality than naming. I don't have a better suggestion, though.

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've implemented the above change - although the return type of both getTemplateArgument and getTemplateArgumentKind is now Locatable as that is the common-parent of Type & Expr.

@matt-gretton-dann matt-gretton-dann force-pushed the cpp-447-support-non-type-template-parameters branch from 45bd6be to c468b8f Compare November 1, 2019 14:36
Copy link
Contributor

@jbj jbj left a comment

Choose a reason for hiding this comment

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

These breaking changes will require an entry in change-notes/1.23/analysis-cpp.md.

Matthew Gretton-Dann added 6 commits November 5, 2019 11:39
Note that Declaration::getTemplateArgumentType() and
Declaration::getTemplateArgumentValue() need to be public so that they
can be overriden in derived classes.
@matt-gretton-dann matt-gretton-dann force-pushed the cpp-447-support-non-type-template-parameters branch from c468b8f to 8eef953 Compare November 5, 2019 11:41
@matt-gretton-dann
Copy link
Contributor Author

These breaking changes will require an entry in change-notes/1.23/analysis-cpp.md.

Done. Please review.

Copy link
Contributor

@nickrolfe nickrolfe left a comment

Choose a reason for hiding this comment

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

Jonas said, "I'm happy enough with the QL that any follow-up changes won't have to block the PR."

@nickrolfe nickrolfe merged commit 5b00b21 into github:master Nov 6, 2019
jbj added a commit to jbj/ql that referenced this pull request Nov 11, 2019
This test output must have been wrong because I produced it with an
extractor that didn't have github#2153 applied.
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 WIP This is a work-in-progress, do not merge yet!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants