Skip to content
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

MINIFICPP-710 - Fix errors in Property StringToInt conversion #472

Closed
wants to merge 1 commit into from

Conversation

arpadboda
Copy link
Contributor

Thank you for submitting a contribution to Apache NiFi - MiNiFi C++.

In order to streamline the review of the contribution we ask you
to ensure the following steps have been taken:

For all changes:

  • Is there a JIRA ticket associated with this PR? Is it referenced
    in the commit message?

  • Does your PR title start with MINIFICPP-XXXX where XXXX is the JIRA number you are trying to resolve? Pay particular attention to the hyphen "-" character.

  • Has your PR been rebased against the latest commit within the target branch (typically master)?

  • Is your initial contribution a single, squashed commit?

For code changes:

  • If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under ASF 2.0?
  • If applicable, have you updated the LICENSE file?
  • If applicable, have you updated the NOTICE file?

For documentation related changes:

  • Have you ensured that format looks appropriate for the output in which it is rendered?

Note:

Please ensure that once the PR is submitted, you check travis-ci for build issues and submit an update to your PR as soon as possible.

@@ -107,3 +107,28 @@ TEST_CASE("Test Trimmer Left", "[testTrims]") {
REQUIRE(test.c_str()[1] == ' ');
}

TEST_CASE("Test int conversion", "[testStringToInt]") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Most of these would fail without this change.

Copy link
Contributor

@phrocker phrocker Jan 10, 2019

Choose a reason for hiding this comment

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

Almost all of these are invalid and shouldn't be accepted, though (not the changes -- the actual entries like - 2 GB ). This was the point of https://issues.apache.org/jira/browse/MINIFICPP-707. I don't want to trounce on this PR, but the reason I created the ticket was that some of these SHOULD be prevented from being used at all in configs, fail early. Therefore it's not as simple as doing what is done here. There needs to be more thought put into this, this was the primary reason I created the ticket versus implementing.

Copy link
Contributor

@phrocker phrocker Jan 11, 2019

Choose a reason for hiding this comment

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

I didn't think my last comment was entirely helpful so I went through and tried to parse out the parts that are appropriate and SHOULD be accepted. In retrospect I think I was being a bit terse jumping between tasks. Sorry if that comment didn't provide much insight into my thought process.

Hopefully I was more clear on what we can immediately accept and what I think needs more discussion. I'm totally okay if that discussion yields pushing some of the concerns down the road if it makes sense.

@@ -129,7 +129,7 @@ const C2Payload RESTProtocol::parseJsonResponse(const C2Payload &payload, const
} catch (...) {
}
#endif
return std::move(C2Payload(payload.getOperation(), state::UpdateState::READ_COMPLETE, true));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These generated copy-elision warnings in clang, so removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch. I rarely build with clang these days...

Copy link
Contributor

@phrocker phrocker left a comment

Choose a reason for hiding this comment

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

-1

REQUIRE(uint32_var == expected_value);

int32_t int32_var = 0;
REQUIRE(Property::StringToInt("3GB", int32_var) == false); // Doesn't fit
Copy link
Contributor

Choose a reason for hiding this comment

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

I'll have to look at the changes more closely but we shouldn't be worrying about int32, we're almost exclusively uint64 and when we're not ( such as someone saying a type is int ), we should be okay up-converting. It's possible that someone sets a type, but I don't think we can say at this prospect if the type fits or doesn't fit

Copy link
Contributor

Choose a reason for hiding this comment

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

One of the things that the big PR left as a todo was to write tests with conversions, so I will readily admit that there may be some cleanup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we only aim for uint64, I think we should consider having this function only for uint_64t instead of the current template solution and provide possibility to safely cast for smaller types (like I did in AssignIfFits).

As this would introduce a breaking change, not sure when it can be done properly.

int32_t int32_var = 0;
REQUIRE(Property::StringToInt("3GB", int32_var) == false); // Doesn't fit

REQUIRE(Property::StringToInt("-1 G", int32_var) == true);
Copy link
Contributor

Choose a reason for hiding this comment

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

We should almost exclusively not allow signed Byte values in string to int. It should always be positive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, thanks, will change.

REQUIRE(Property::StringToInt("-1 G", int32_var) == true);
REQUIRE(int32_var == -1 * 1000 * 1000 * 1000);

REQUIRE(Property::StringToInt("-1G", uint32_var) == false); // Negative to uint
Copy link
Contributor

@phrocker phrocker Jan 11, 2019

Choose a reason for hiding this comment

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

This shouldn't be allowed. Whether this barrier exists here or elsewhere is what I think we should discuss. It's about the end to end correctness, so not sure if the appropriate action is to fix the signedness issues or correct the overall issue that 707 was hoping to look into. I fear that resolving any issues like signed values without looking into solving the whole picture takes us to an unknown state. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, however as you see, it's not allowed, this conversion fails.

Although I think the current implementation is still more forgiving than it should be according to the whole picture, so let's try to explore that before going ahead with this.


//Signed
template<typename T, typename std::enable_if<std::is_signed<T>::value, T>::type* = nullptr>
static bool AssignIfFits(int64_t base, uint64_t mult, T& output) {
Copy link
Contributor

Choose a reason for hiding this comment

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

These I like and think we should use this.

auto ival = std::strtoll(cvalue, &pEnd, 0);

T ival = 0;
if(!StringToNum(cvalue, &pEnd, ival)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is only applicable if we reach the null terminator and it is not a byte string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I get the point, this does the same strto(u)ll call inside that was here before.
The only change is the return value, which comes from AssignIfFits.

//Unsigned
template<typename T, typename std::enable_if<!std::is_signed<T>::value, T>::type* = nullptr>
static bool StringToNum(const char* str_begin, char **str_end, T& output) {
if(str_begin[0] == '-') {
Copy link
Contributor

Choose a reason for hiding this comment

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

can't assume that it's zero, and that it's non-empty

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good shout, thanks!
This also means adding a tectcase to check with empty and invalid strings to make sure it handles errors gracefully.

}
return false;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Does this code compile in windows? I'm not sure that it will.

Copy link
Contributor

Choose a reason for hiding this comment

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

Appveyor showed that it didn't. Do you have access to windows? I can probably help with that since I do.

Copy link
Contributor Author

@arpadboda arpadboda Jan 12, 2019

Choose a reason for hiding this comment

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

Yes, please! I can check Appveyor err messages (those made me find the fix), but that's not so efficient in longer term.

REQUIRE(Property::StringToInt("-2", uint64_var) == false); // Negative shouldn't be converted to uint

uint32_t uint32_var = 0;
REQUIRE(Property::StringToInt("3 GB", uint32_var) == true); // Test skipping of spaces, too
Copy link
Contributor

Choose a reason for hiding this comment

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

Some of these tests exist in yaml if I recall correctly ( but I didn't write them ), if you're interested.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Surely yes

using org::apache::nifi::minifi::core::Property;

uint64_t uint64_var = 0;
REQUIRE(Property::StringToInt("-2", uint64_var) == false); // Negative shouldn't be converted to uint
Copy link
Contributor

@phrocker phrocker Jan 11, 2019

Choose a reason for hiding this comment

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

I realize 710 kind of had a different focus, but your comment on the prior PR in which you mentioned this was to open 707 in hopes of opening a discussion for better dealing with this. For example, it may be the case where sometimes a negative value makes sense AND the conversion make sense. I will do some research, as I know these cases exist. In others, that is NOT true.

While the above test is totally valid from the perspective of 710, the hope was that we could discuss and more intelligently handle these cases, perhaps with the validators themselves. What is your perspective?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, let's focus on the long term solution.

Just scratched this as I spotted errors in the code, however as you correctly pointed out, my change doesn't include the integration approach.

template<typename T, typename std::enable_if<std::is_signed<T>::value, T>::type* = nullptr>
static bool StringToNum(const char* str_begin, char **str_end, T& output) {
int64_t val = std::strtoll(str_begin, str_end, 0);
return AssignIfFits<T>(val, 1, output);
Copy link
Contributor

@phrocker phrocker Jan 11, 2019

Choose a reason for hiding this comment

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

why have any multiplication at all if it is 1? same with the one, below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to reuse that code. The goal here was to make sure that the value just read fits target in case that's smaller than 64 bits.

@szaszm
Copy link
Member

szaszm commented Dec 16, 2020

Closing this because of inactivity

@szaszm szaszm closed this Dec 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants