RFC: C++ Support non type template parameter values#2153
Conversation
|
Internal PR: https://git.semmle.com/Semmle/code/pull/34800 |
jbj
left a comment
There was a problem hiding this comment.
I think it would be helpful for discussing my comments if you add some direct tests of the new getTemplateArgumentValue predicate.
cpp/upgrades/98a075d5495d7be7ede26557708cf22cfa3964ef/upgrade.properties
Outdated
Show resolved
Hide resolved
| #-----| 0: [Parameter] p#0 | ||
| #-----| Type = [RValueReferenceType] S && | ||
| # 9| [MemberFunction] int Bad::S::MemberFunction(int) | ||
| # 9| [FunctionTemplateInstantiation,MemberFunction] int Bad::S::MemberFunction<int>(int) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I'm still working on getting this tidied-up
There was a problem hiding this comment.
If it causes you trouble, feel free to throw it over the fence to the analysis team.
|
I think the overall approach seems reasonable, but I have a few high-level questions/comments:
|
It wasn't but can be :-)
I will add these
You will get two Declarations one for the template declaration, and one for the instantiation. For the declaration you will get: 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; |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
I'm still working on getting this tidied-up
| @@ -0,0 +1,19 @@ | |||
| // semmle-extractor-options: --edg --trap_container=folder --edg --trap-compression=none | |||
There was a problem hiding this comment.
@dbartol: This should cover some of the test cases you wanted to see.
05a2bd4 to
3c026fc
Compare
|
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. |
cpp/ql/src/semmle/code/cpp/Class.qll
Outdated
| */ | ||
| override Expr getTemplateArgumentValue(int i) { | ||
| class_template_argument_value(underlyingElement(this), i, unresolveElement(result)) | ||
| } |
There was a problem hiding this comment.
Would it be better to just generalise
Type getTemplateArgument(int i)
to
Element getTemplateArgument(int i)
(and similarly in the dbscheme)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Anyway where I think we've got to is the need for two predicates on Declaration:
Element getTemplateArgument(int i)which returns the user-suppliedi-th template argument.Type getTemplateArgumentKind(int i) which returns the type of thei`-th template argument if that is a non-type argument, and has no result otherwise.
Correct?
There was a problem hiding this comment.
And that the above is a breaking change - but we do not expect it to cause many people issues (if any).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
45bd6be to
c468b8f
Compare
jbj
left a comment
There was a problem hiding this comment.
These breaking changes will require an entry in change-notes/1.23/analysis-cpp.md.
We now support getting the name used for non-type template parameters
Note that Declaration::getTemplateArgumentType() and Declaration::getTemplateArgumentValue() need to be public so that they can be overriden in derived classes.
c468b8f to
8eef953
Compare
Done. Please review. |
nickrolfe
left a comment
There was a problem hiding this comment.
Jonas said, "I'm happy enough with the QL that any follow-up changes won't have to block the PR."
This test output must have been wrong because I produced it with an extractor that didn't have github#2153 applied.
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: