-
Notifications
You must be signed in to change notification settings - Fork 288
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
Resolve several ExpectedT<T> problems #511
Conversation
…d E = std::error_code.
Also cleans up a use-after-free in several platform expression tests where the original author wished references were rebindable.
Depends on #520 |
Depends on #521 |
# Conflicts: # include/vcpkg/paragraphparser.h # locales/messages.en.json # locales/messages.json # src/vcpkg/sourceparagraph.cpp
…expected # Conflicts: # include/vcpkg/base/format.h # include/vcpkg/base/parse.h # include/vcpkg/paragraphparser.h # locales/messages.en.json # locales/messages.json # src/vcpkg-test/json.cpp # src/vcpkg/paragraphs.cpp # src/vcpkg/sourceparagraph.cpp
…rrors from expected.
Currently, Expected<T> has a bug for references, in that it is default constructible, but references shouldn't be default constructible. (This is hidden because we store a pointer internally). In VersionedPackageGraph::require_port_version, an ExpectedS<T&> is default constructed, which is in the "impossible" state of neither having a stored T (since references can't be default constructed), but also not being assigned an error state. This change hoists the only place where the ExpectedS could contain an error into an early-return, allowing a plain pointer to be used, and avoiding constructing the impossible thing. (The bug in Expected that allowed this to happen is fixed in microsoft#511 , but this change was tricky enough I thought extracting it for review alone made sense)
Currently, Expected<T> has a bug for references, in that it is default constructible, but references shouldn't be default constructible. (This is hidden because we store a pointer internally). In VersionedPackageGraph::require_port_version, an ExpectedS<T&> is default constructed, which is in the "impossible" state of neither having a stored T (since references can't be default constructed), but also not being assigned an error state. This change hoists the only place where the ExpectedS could contain an error into an early-return, allowing a plain pointer to be used, and avoiding constructing the impossible thing. (The bug in Expected that allowed this to happen is fixed in microsoft#511 , but this change was tricky enough I thought extracting it for review alone made sense) Reasoning, using "after" line numbers: 1557-1567: This if block could never have stored an error state in the expected, since it is only entered if there is an overlay control file that will be the result. Therefore, we will never add an error to m_errors from here. 1578: get_control_file can return an expected in the error state, so we have to check if it's an error and, if so, add it to m_errors. 1587: At this point, we know p_scfl is not nullptr since both if branches set it on 1556 or 1578.
Depends on #557 |
* Don't attempt to default-construct a reference. Currently, Expected<T> has a bug for references, in that it is default constructible, but references shouldn't be default constructible. (This is hidden because we store a pointer internally). In VersionedPackageGraph::require_port_version, an ExpectedS<T&> is default constructed, which is in the "impossible" state of neither having a stored T (since references can't be default constructed), but also not being assigned an error state. This change hoists the only place where the ExpectedS could contain an error into an early-return, allowing a plain pointer to be used, and avoiding constructing the impossible thing. (The bug in Expected that allowed this to happen is fixed in #511 , but this change was tricky enough I thought extracting it for review alone made sense) Reasoning, using "after" line numbers: 1557-1567: This if block could never have stored an error state in the expected, since it is only entered if there is an overlay control file that will be the result. Therefore, we will never add an error to m_errors from here. 1578: get_control_file can return an expected in the error state, so we have to check if it's an error and, if so, add it to m_errors. 1587: At this point, we know p_scfl is not nullptr since both if branches set it on 1556 or 1578.
# Conflicts: # include/vcpkg/base/messages.h # include/vcpkg/base/parse.h # include/vcpkg/paragraphparser.h # locales/messages.en.json # locales/messages.json # src/vcpkg-test/json.cpp # src/vcpkg/base/git.cpp # src/vcpkg/dependencies.cpp # src/vcpkg/paragraphs.cpp
1b21422
to
79b5b65
Compare
{ | ||
if (!value_is_error) | ||
{ | ||
Checks::unreachable(VCPKG_LINE_INFO); |
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.
In a future change I would like to rename value_or_exit to just value, and add a line_info parameter to error(), but that change is so mechanical and cross-cutting that I want it to be its own review.
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.
Broadly LGTM, but going to re-review in the morning.
…the metaprogramming.
b74e8de
to
b8fafa5
Compare
ExpectedT(const T& t, ExpectedLeftTag = {}) : m_t(t) { } | ||
template<class U = T, std::enable_if_t<!std::is_reference<U>::value, int> = 0> | ||
ExpectedT(T&& t, ExpectedLeftTag = {}) : m_t(std::move(t)) | ||
template<class ConvToError, std::enable_if_t<std::is_convertible_v<ConvToError, Error>, int> = 0> |
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.
template<class ConvToError, std::enable_if_t<std::is_convertible_v<ConvToError, Error>, int> = 0> | |
template<class ConvToError> |
ditto above
template<class F, class... Args> | ||
std::invoke_result_t<F, const_ref_type, Args&&...> then(F f, Args&&... args) const& | ||
std::invoke_result_t<F, const T&, Args...> then(F f, Args&&... args) const& |
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.
Why is Args&&
not required?
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.
Because both Args
and Args&&
name rvalues.
That is, given:
int returns_prvalue();
int&& returns_xvalue();
int& returns_lvalue();
template<class Arg> void consume(Arg&&);
consume
can't tell the difference between consume(returns_prvalue())
and consume(returns_xvalue())
, since forwarding references only sense lvalue-or-rvalueness. (In both cases, Arg
would deduce to int
, giving void consume<int>(int&&)
. (and consume(returns_lvalue())
would deduce int&
giving void consume<int&>(int&)
))
Convention when implementing perfect forwarders like this is to not add the meaningless &&s when passing types through, for example http://eel.is/c++draft/func.invoke
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.
@@ -105,191 +26,332 @@ namespace vcpkg | |||
template<class T> | |||
struct ExpectedHolder |
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.
Since you're removing ErrorHolder
, would it be "better" to change this to a traits struct as well? e.g.
union {
Error m_err;
typename ExpectedHolder<T>::type m_t;
};
auto get() const noexcept { return ExpectedHolder<T>::get(m_t); }
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 did strongly consider doing that, but it didn't seem to reduce much code because it also affects every special member function to need to conditionally insert the T&
-> T*
transformation.
I think it would be worth doing if we can use if constexpr
but I have been paranoid about adding use of standard features that we don't already use for fear of damaging compiler support. But between when I wrote this and now we have affirmed that we only care about our test compilers so maybe I should go ahead and do that?
Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
* Introduce Expected<void>. * Delete unwrap, which was identical to value_or_exit but lost the VCPKG_LINE_INFO. * Also apply #511 (comment) more completely. * Do expected<Unit> rather than expected<void> * Also remove forward decl.
* Introduce Expected<void>. * Delete unwrap, which was identical to value_or_exit but lost the VCPKG_LINE_INFO. * Also apply #511 (comment) more completely. * Introduce a better type to store system API call failures. * Apply ExpectedApi to windows_create_process. * Apply ExpectedApi to windows_create_windowless_process. * Apply ExpectedApi to windows_create_process_redirect. * Apply ExpectedApi to cmd_execute_and_stream_data. * Apply ExpectedApi to cmd_execute_and_stream_lines. * Apply ExpectedApi to cmd_execute_and_capture_output. * Apply ExpectedApi to cmd_execute. * Apply ExpectedApi to cmd_execute_clean. * Fix MacOS builds and the FIXME in archives.cpp of compress_directory_to_zip * Regenerate messages. * Do expected<Unit> rather than expected<void> * Also remove forward decl. * Fix linux build. * Eliminate SystemApiError. * Delete unnecessary .has_value() calls, and fix unused variable warning. * Also fix POSIX build errors. * Make flatten_out move the input.
While forming my response to #483 I ran into several problems trying to use
ExpectedT
, so I separated out these specific changes which are intended to affect only that type:ErrorHolder
" metaprogramming fromExpected<T>
. That performed 2 functions: 1. avoided storing an extra bool forExpectedT<..., std::error_code>
, and 2. givingExpectedT
a way to print the error case onvalue_or_exit
. 1. was completely unused: there are no uses ofExpectedT<..., std::error_code>
in the codebase. 2. should use the same stuff we use to print everything else (the to_string protocol), which this change does.ExpectedT<..., not-default-constructible>
. This allows callers to avoid creatingExpectedT<..., unique_ptr<...>>
as a workaround, as they are doing in some places. Fixing callers to avoid doing that is not in this change though (it is big enough).ExpectedT
that fell into the default "value was error" were fixed to print the actual error.