Skip to content

Conversation

@eduardfina
Copy link

@eduardfina eduardfina commented Feb 16, 2023

Fixes #13920

When pragma is using version ranges (-) it doesn't allow to use prefixing operators (<, <=, >=, >, ^).

@github-actions
Copy link

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.


bool SemVerMatchExpressionParser::containPrefixingToken() const
{
if (std::find_if(m_tokens.begin(), m_tokens.end(), TokenTraits::isPragmaOp) != m_tokens.end())
Copy link
Contributor

@matheusaaguiar matheusaaguiar Feb 16, 2023

Choose a reason for hiding this comment

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

Suggested change
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; }
Copy link
Contributor

@matheusaaguiar matheusaaguiar Feb 16, 2023

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.

Copy link
Contributor

@matheusaaguiar matheusaaguiar Feb 16, 2023

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.

Copy link
Contributor

@matheusaaguiar matheusaaguiar 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 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).

@matheusaaguiar
Copy link
Contributor

matheusaaguiar commented Feb 16, 2023

You should also add some tests.
If you haven't already, I would suggest reading our PR Review Checklist as well as the Contributing Guide

@r0qs
Copy link
Member

r0qs commented Feb 17, 2023

I agree with @matheusaaguiar's comments about using ranges::find_if and doing the comparison directly (i.e. removing containPrefixingToken function) . To give you some directions about the tests, you will need to add some syntax tests at test/libsolidity/syntaxTests/pragma, see this one as example: https://github.com/ethereum/solidity/blob/develop/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_two_ranges.sol

@eduardfina
Copy link
Author

Thank you so much for the feedback, I agree with all you said.
I was thinking about including the = too, but I didn't know if it would be correct
I'm new at Solidity development and I'm still understanding how it all works.
I'll make the changes as soon as possible!

@NunoFilipeSantos
Copy link
Contributor

It looks like this PR is still in progress, so we're converting it back to a draft.

@NunoFilipeSantos NunoFilipeSantos marked this pull request as draft February 21, 2023 11:09
@eduardfina
Copy link
Author

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.
2- I evaluate the components prefix to validate before changing it to the range prefix at the function parseMatchExpression.
3- I couldn’t find an easy way to include the = to the invalid operators because at the function parseMatchComponent() the default value is Assign (=), making like if all the components without operator had an =. On the other hand I think it could not damage having the possibility of using = in a range (ex. pragma solidity =0.8.17 - 0.8.20;)
4- I left the function isPragmaOp(), we can’t use isCompareOp because it’ll wrongly compare != and == too.
5- I didn’t refactor the function isPragmaOp() to isAllowedInPragma() because the operator = is also allowed and is not part of the condition.
6- I added some tests 3 invalid and 2 valid.

@eduardfina
Copy link
Author

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!

@eduardfina eduardfina marked this pull request as ready for review February 21, 2023 19:46
Copy link
Collaborator

@cameel cameel left a 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.

Comment on lines +214 to +220
if (TokenTraits::isPragmaOp(range.components[0].prefix) || TokenTraits::isPragmaOp(range.components[1].prefix))
{
solThrow(
SemVerError,
"You cannot use operators (<, <=, >=, >, ^) with version ranges (-)."
);
}
Copy link
Collaborator

@cameel cameel Feb 23, 2023

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.

Copy link
Author

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! :)

Copy link
Collaborator

@cameel cameel Feb 23, 2023

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
Copy link
Collaborator

@cameel cameel Feb 23, 2023

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;
Copy link
Collaborator

@cameel cameel Feb 23, 2023

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;
Copy link
Collaborator

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;
Copy link
Collaborator

@cameel cameel Feb 23, 2023

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;
Copy link
Collaborator

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;
Copy link
Collaborator

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;
Copy link
Collaborator

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.

@cameel
Copy link
Collaborator

cameel commented Feb 23, 2023

Also I don't know if I should do it but I updated the code to the last develop version before the commit.

Yeah, please keep it updated, but we use rebase for that. No need for merge commits that obscure history :)

Fixed issue: #13920

Please use the Fixes #13920 form. This way github will close the issue automatically once we merge the PR.

@NunoFilipeSantos
Copy link
Contributor

Hi @eduardfina! 👋 are you planning to come back soon to this PR, or should we close it for now?

@cameel cameel added the takeover Can be taken over by someone other than label giver label Apr 17, 2023
@NunoFilipeSantos
Copy link
Contributor

Closing as stale.

@nikola-matic nikola-matic reopened this Jul 24, 2023
@nikola-matic nikola-matic self-assigned this Jul 24, 2023
@nikola-matic nikola-matic removed their assignment Jul 24, 2023
@veniger
Copy link
Contributor

veniger commented Jul 24, 2023

I can take over this PR and issue and start working on it next week

@nikola-matic nikola-matic removed the takeover Can be taken over by someone other than label giver label Jul 24, 2023
@veniger
Copy link
Contributor

veniger commented Jul 26, 2023

I can take over this PR and issue and start working on it next week

@ekpyron
Copy link
Collaborator

ekpyron commented Dec 13, 2023

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 :-)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Version pragma does not disallow prefixing version ranges with comparison operators

8 participants