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

Resolve several ExpectedT<T> problems #511

Merged
merged 28 commits into from
May 26, 2022

Conversation

BillyONeal
Copy link
Member

@BillyONeal BillyONeal commented Apr 16, 2022

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:

  • Delete the "ErrorHolder" metaprogramming from Expected<T>. That performed 2 functions: 1. avoided storing an extra bool for ExpectedT<..., std::error_code>, and 2. giving ExpectedT a way to print the error case on value_or_exit. 1. was completely unused: there are no uses of ExpectedT<..., 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.
  • Make it possible to form ExpectedT<..., not-default-constructible>. This allows callers to avoid creating ExpectedT<..., 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).
  • Callers of ExpectedT that fell into the default "value was error" were fixed to print the actual error.
  • A use after free bug in our unit tests where the code wanted C++ references to be assignable was fixed ( https://twitter.com/MalwareMinigun/status/1515171930705317889 )

Also cleans up a use-after-free in several platform expression tests where the original author wished references were rebindable.
@BillyONeal BillyONeal marked this pull request as draft April 21, 2022 00:53
@BillyONeal BillyONeal added the depends:different-pr This PR depends on a different PR which has been filed label Apr 22, 2022
@BillyONeal
Copy link
Member Author

Depends on #520

@BillyONeal BillyONeal removed the depends:different-pr This PR depends on a different PR which has been filed label Apr 22, 2022
@BillyONeal BillyONeal added the depends:different-pr This PR depends on a different PR which has been filed label Apr 24, 2022
@BillyONeal
Copy link
Member Author

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
BillyONeal added a commit to BillyONeal/vcpkg-tool that referenced this pull request May 25, 2022
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)
BillyONeal added a commit to BillyONeal/vcpkg-tool that referenced this pull request May 25, 2022
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.
@BillyONeal
Copy link
Member Author

Depends on #557

BillyONeal added a commit that referenced this pull request May 25, 2022
* 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.
@BillyONeal BillyONeal removed the depends:different-pr This PR depends on a different PR which has been filed label May 25, 2022
# 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
@BillyONeal BillyONeal marked this pull request as ready for review May 26, 2022 00:00
{
if (!value_is_error)
{
Checks::unreachable(VCPKG_LINE_INFO);
Copy link
Member Author

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.

Copy link
Contributor

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

include/vcpkg/base/expected.h Outdated Show resolved Hide resolved
include/vcpkg/base/expected.h Show resolved Hide resolved
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>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
template<class ConvToError, std::enable_if_t<std::is_convertible_v<ConvToError, Error>, int> = 0>
template<class ConvToError>

ditto above

include/vcpkg/base/expected.h Outdated Show resolved Hide resolved
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&
Copy link
Contributor

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?

Copy link
Member Author

@BillyONeal BillyONeal May 26, 2022

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

Copy link
Member Author

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

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); }

Copy link
Member Author

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?

src/vcpkg-test/platform-expression.cpp Outdated Show resolved Hide resolved
BillyONeal and others added 3 commits May 26, 2022 11:03
Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
@BillyONeal BillyONeal merged commit dcc7041 into microsoft:main May 26, 2022
@BillyONeal BillyONeal deleted the cleanup-expected branch May 26, 2022 21:33
BillyONeal added a commit to BillyONeal/vcpkg-tool that referenced this pull request May 27, 2022
ras0219-msft pushed a commit that referenced this pull request Jun 2, 2022
* 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.
BillyONeal added a commit that referenced this pull request Jun 8, 2022
* 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants