-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Fix Pragma: disallow prefixing operators with version ranges #13979
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
base: develop
Are you sure you want to change the base?
Conversation
|
Thank you for your contribution to the Solidity compiler! A team member will follow up shortly. If you haven't read our contributing guidelines and our review checklist before, please do it now, this makes the reviewing process and accepting your contribution smoother. If you have any questions or need our help, feel free to post them in the PR or talk to us directly on the #solidity-dev channel on Matrix. |
liblangutil/SemVerHandler.cpp
Outdated
|
|
||
| bool SemVerMatchExpressionParser::containPrefixingToken() const | ||
| { | ||
| if (std::find_if(m_tokens.begin(), m_tokens.end(), TokenTraits::isPragmaOp) != m_tokens.end()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (std::find_if(m_tokens.begin(), m_tokens.end(), TokenTraits::isPragmaOp) != m_tokens.end()) | |
| if (find_if(m_tokens.begin(), m_tokens.end(), TokenTraits::isPragmaOp) != m_tokens.end()) |
We already have using namespace std. On the other hand, we could use the range-v3 library, which has find_if working with the container itself, rather than its iterators. That would make the expression more easily readable.
Also, it would be better if this method directly returned the result of the comparison of the if block. Maybe, we could not have it at all and then just use the comparison directly.
| op == Token::Add || op == Token::Mul || op == Token::Equal || op == Token::NotEqual; } | ||
| constexpr bool isArithmeticOp(Token op) { return Token::Add <= op && op <= Token::Exp; } | ||
| constexpr bool isCompareOp(Token op) { return Token::Equal <= op && op <= Token::GreaterThanOrEqual; } | ||
| constexpr bool isPragmaOp(Token op) { return (Token::LessThan <= op && op <= Token::GreaterThanOrEqual) || op == Token::BitXor; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this should also include token =. Additionally, the naming of the method could be something like isAllowedInPragma or similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably, this function would never be used anywhere else... in that case, it would be better to not have it here. We could just use isCompareOp combined with the equality check for Token::BitXor as a lambda function in the local context where needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think fundamentally you are in the right direction, but I think that the current implementation does not correctly solve the problem.
I haven't checked, but I expect the proposed solution would throw an error when ranges are combined with a disjunction, for example:
pragma solidity 0.5.0 - 0.55 || >=0.7 <0.9;That's because it is checking all of the pragma tokens (m_tokens), instead of only those in the components of the range (range.components).
|
You should also add some tests. |
|
I agree with @matheusaaguiar's comments about using |
|
Thank you so much for the feedback, I agree with all you said. |
|
It looks like this PR is still in progress, so we're converting it back to a draft. |
Add 3 invalid tests and 2 valid tests
|
I modified the code to match with the feedback, I had to adapt some things: 1- I deleted the function containPrefixingToken() and added an inline if to detect the prefix. |
|
Also I don't know if I should do it but I updated the code to the last develop version before the commit. Waiting for feedback, thank you! |
cameel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR is missing a changelog entry.
| if (TokenTraits::isPragmaOp(range.components[0].prefix) || TokenTraits::isPragmaOp(range.components[1].prefix)) | ||
| { | ||
| solThrow( | ||
| SemVerError, | ||
| "You cannot use operators (<, <=, >=, >, ^) with version ranges (-)." | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's a good idea to check only for specific prefixes. For example currently this condition misses ~, which is also a prefix you can use here. Ranges can't have prefixes anyway so we should just error out here on any of them. I'd also make the message just that:
Components of version ranges cannot be prefixed with operators.
Might be also a good idea to show which token it was. You can use TokenTraits::friendlyName(token) for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How could I detect if it the range has a prefix?
At this point after calling the function parseMatchComponent() the range.components[0].prefix and range.components[1].prefix will at least have the default prefix (Assign) making impossible to know if it was assigned because it really has the prefix = or is the default one.
I think I could change it but it would modify the function parseMatchComponent(), a bigger change at the code.
On the other hand I could just add the prefix ~ at the condition.
As you decide, I await a reply! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, but it's a separate problem. Your current code has it too. It does not discern between = and having no prefix either. It's actually wrong, which I did not notice until you pointed it out, because it will treat =0.0 - =1.0 as a valid range (and by the way, a corner case like this should definitely have test case, whichever way it works).
As for how to distinguish them, I think we should change prefix to optional<Token> and stop setting a default in parseMatchComponent(). Then handle nullopt explicitly in matches().
| @@ -0,0 +1,3 @@ | |||
| pragma solidity <0.8.17 - 0.8.20; | |||
| // ---- | |||
| // ParserError 1684: (0-33): Invalid version pragma. You cannot use operators (<, <=, >=, >, ^) with version ranges (-). No newline at end of file | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please configure your editor to ensure that there is always a newline at the end of the file (github's diff is showing that one is missing here and in some other files). Makes for less random noise in diffs.
| @@ -0,0 +1,3 @@ | |||
| pragma solidity <0.8.17 - 0.8.20; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are not that many options to test (>, >=, <, <=, =, ^, ~, no operator) so it would be good to test them all, at both positions.
You can check multiple cases at the same time by using a multi-file test. E.g.:
==== Source: A ====
pragma solidity <0.8.17 - 0.8.20;
==== Source: B ====
pragma solidity <=0.8.17 - 0.8.20;
==== Source: C ====
pragma solidity 0.8.17 - >0.8.20;
| @@ -0,0 +1,3 @@ | |||
| pragma solidity 0.8.17 - >0.8.20; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also add a case without whitespace or with shorter versions. This sometimes makes a difference. For example 0.0 - =1.0 was valid, while 0.0-=1.0 was not. We should make sure that both cases are handled.
| @@ -0,0 +1,2 @@ | |||
| pragma solidity <0.8.15 || 0.8.17 - 0.8.20; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test uses a pragma with only one range, but the name says it has two. It should be called something like valid_range_operator_range_second_prefixed_version.sol.
| @@ -0,0 +1,3 @@ | |||
| pragma solidity 0.8.17 - >0.8.20; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a case where the range is prefixed with something that's not one of the currently allowed operators. The PR does not change that case but I don't think we have that case already covered.
| @@ -0,0 +1,2 @@ | |||
| pragma solidity <0.8.15 || 0.8.17 - 0.8.20; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also have a case where the prefix is next to || to make sure it is not handled differently:
pragma solidity 0.8.15 || <0.8.17 - 0.8.20;| @@ -0,0 +1,2 @@ | |||
| pragma solidity 0.8.17 - 0.8.20; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For positive cases please use a wider range of versions. This one will break in 0.8.21.
Other tests we already have use at least 0.9.0, but I think it would not hurt to use something much higher so that updating these tests is less annoying when we reach a breaking release.
Yeah, please keep it updated, but we use rebase for that. No need for merge commits that obscure history :)
Please use the |
|
Hi @eduardfina! 👋 are you planning to come back soon to this PR, or should we close it for now? |
|
Closing as stale. |
|
I can take over this PR and issue and start working on it next week |
|
I can take over this PR and issue and start working on it next week |
@veniger are you still up for working on this on the side :-)? |
Fixes #13920
When pragma is using version ranges (-) it doesn't allow to use prefixing operators (<, <=, >=, >, ^).