Skip to content

Commit dcaa98c

Browse files
antmorChrisGuzak
andauthored
Remove throwing/originating errors in expected scenarios (Lookup/TryLookup and Cancel) (#1512)
* non-originating error compiles * fix with test cases * and remove ignore * add runsettings to run tests from visual studio tester, modified readme, make shouldOrigiante a template param. * change map shouldOriginate to template parameter. Note, could be a breakig change for usages of map that are not default (i.e. custom std::less) if so, might need to split up templates into separate template, or spin out into separate pr. changed bool to template parameter in hresult_error, as the char*-to-bool decaying conversion failed the old tests. also fixed runsettings, added note in readme. * settle on avoid_originate as naming scheme * add a printf only to Lookup so we can add SFINAE or something like that. * weird templating magic required... still not working, has error 1>G:\source\repos\cppwinrt\_build\x64\Debug\winrt\Windows.Foundation.Collections.h(868,51): error C3878: syntax error: unexpected token '>' following 'simple-type-specifier' 1>(compiling source file '/Class.cpp') 1> G:\source\repos\cppwinrt\_build\x64\Debug\winrt\Windows.Foundation.Collections.h(868,51): 1> missing one of: '(' '{' ? * trylookup exists to avoid throwing an error, and let's avoid avoid_originate now. * remove test. * cleaned up and correctness. * undo changes to untouched files * spacing changes , change do declval. * added special tag to parameter list to make sure not to break existing TryLookup impls, added test to verify this. * address avoid_oritinate comment, no need for a different name, resuse existing hresult_cancelled * change out async to a new name, with it being true by default. add new unittest to test nullable lookup. * Address hresult comments by adding tests and removing source_locatoin, special-cased further by checking the typename. * add unittest without specialization, make sure that the behaviour is opt-in --------- Co-authored-by: Chris Guzak <[email protected]>
1 parent ebace4a commit dcaa98c

File tree

12 files changed

+437
-15
lines changed

12 files changed

+437
-15
lines changed

.runsettings

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RunSettings>
3+
<RunConfiguration>
4+
<ResultsDirectory>.\TestResults</ResultsDirectory><!-- Path relative to solution directory -->
5+
<TestSessionTimeout>60000</TestSessionTimeout><!-- Milliseconds -->
6+
<ForceListContent>true</ForceListContent>
7+
</RunConfiguration>
8+
9+
<!-- Adapter Specific sections -->
10+
11+
<!-- Catch2 adapter -->
12+
<Catch2Adapter>
13+
<ExecutionMode>Single</ExecutionMode>
14+
<FilenameFilter>(?i:Test)</FilenameFilter><!-- Regex filter -->
15+
<ForceListContent>true</ForceListContent>
16+
<DebugBreak>on</DebugBreak>
17+
<IncludeHidden>true</IncludeHidden>
18+
<Logging>Verbose</Logging>
19+
<MessageFormat>AdditionalInfo</MessageFormat>
20+
<StackTraceFormat>ShortInfo</StackTraceFormat>
21+
<StackTracePointReplacement>,</StackTracePointReplacement>
22+
<TestCaseTimeout>20000</TestCaseTimeout><!-- 20s in Milliseconds -->
23+
</Catch2Adapter>
24+
</RunSettings>

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,8 @@ a dev command prompt at the root of the repo _after_ following the above build i
3434
* Run `build_prior_projection.cmd` in the dev command prompt as well
3535
* Run `prepare_versionless_diffs.cmd` which removes version stamps on both current and prior projection
3636
* Use a directory-level differencing tool to compare `_build\$(arch)\$(flavor)\winrt` and `_reference\$(arch)\$(flavor)\winrt`
37+
38+
## Testing
39+
This repository uses the [Catch2](https://github.com/catchorg/Catch2) testing framework.
40+
- From a Visual Studio command line, you should run `build_tests_all.cmd` to build and run the tests. To Debug the tests, you can debug the associated `_build\$(arch)\$(flavor)\<test>.exe` under the debugger of your choice.
41+
- Optionally, you can install the [Catch2Adapter](https://marketplace.visualstudio.com/items?itemName=JohnnyHendriks.ext01) to run the tests from Visual Studio.

cppwinrt/code_writers.h

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,7 +1873,37 @@ namespace cppwinrt
18731873
}
18741874
}
18751875

1876-
static void write_produce_method(writer& w, MethodDef const& method)
1876+
static void write_produce_upcall_TryLookup(writer& w, std::string_view const& upcall, method_signature const& method_signature)
1877+
{
1878+
auto name = method_signature.return_param_name();
1879+
1880+
w.write("auto out_param_val = %(%, trylookup_from_abi);",
1881+
upcall,
1882+
bind<write_produce_args>(method_signature));
1883+
w.write(R"(
1884+
if (out_param_val.has_value())
1885+
{
1886+
*% = detach_from<%>(std::move(*out_param_val));
1887+
}
1888+
else
1889+
{
1890+
return impl::error_out_of_bounds;
1891+
}
1892+
)",
1893+
name, method_signature.return_signature());
1894+
1895+
for (auto&& [param, param_signature] : method_signature.params())
1896+
{
1897+
if (param.Flags().Out() && !param_signature->Type().is_szarray() && is_object(param_signature->Type()))
1898+
{
1899+
auto param_name = param.Name();
1900+
1901+
w.write("\n if (%) *% = detach_abi(winrt_impl_%);", param_name, param_name, param_name);
1902+
}
1903+
}
1904+
}
1905+
1906+
static void write_produce_method(writer& w, MethodDef const& method, TypeDef const& type)
18771907
{
18781908
std::string_view format;
18791909

@@ -1902,13 +1932,45 @@ namespace cppwinrt
19021932
method_signature signature{ method };
19031933
auto async_types_guard = w.push_async_types(signature.is_async());
19041934
std::string upcall = "this->shim().";
1905-
upcall += get_name(method);
1935+
auto name = get_name(method);
1936+
upcall += name;
19061937

1907-
w.write(format,
1908-
get_abi_name(method),
1909-
bind<write_produce_params>(signature),
1910-
bind<write_produce_cleanup>(signature),
1911-
bind<write_produce_upcall>(upcall, signature));
1938+
auto typeName = type.TypeName();
1939+
if (((typeName == "IMapView`2") || (typeName == "IMap`2"))
1940+
&& (name == "Lookup"))
1941+
{
1942+
// Special-case IMap*::Lookup to look for a TryLookup here, to avoid extranous throw/originates
1943+
std::string tryLookupUpCall = "this->shim().TryLookup";
1944+
format = R"( int32_t __stdcall %(%) noexcept final try
1945+
{
1946+
% typename D::abi_guard guard(this->shim());
1947+
if constexpr (has_TryLookup_v<D, K>)
1948+
{
1949+
%
1950+
}
1951+
else
1952+
{
1953+
%
1954+
}
1955+
return 0;
1956+
}
1957+
catch (...) { return to_hresult(); }
1958+
)";
1959+
w.write(format,
1960+
get_abi_name(method),
1961+
bind<write_produce_params>(signature),
1962+
bind<write_produce_cleanup>(signature), // clear_abi
1963+
bind<write_produce_upcall_TryLookup>(tryLookupUpCall, signature),
1964+
bind<write_produce_upcall>(upcall, signature));
1965+
}
1966+
else
1967+
{
1968+
w.write(format,
1969+
get_abi_name(method),
1970+
bind<write_produce_params>(signature),
1971+
bind<write_produce_cleanup>(signature),
1972+
bind<write_produce_upcall>(upcall, signature));
1973+
}
19121974
}
19131975

19141976
static void write_fast_produce_methods(writer& w, TypeDef const& default_interface)
@@ -1951,7 +2013,7 @@ namespace cppwinrt
19512013
break;
19522014
}
19532015

1954-
w.write_each<write_produce_method>(info.type.MethodList());
2016+
w.write_each<write_produce_method>(info.type.MethodList(), info.type);
19552017
}
19562018
}
19572019

@@ -1973,7 +2035,7 @@ namespace cppwinrt
19732035
bind<write_comma_generic_typenames>(generics),
19742036
type,
19752037
type,
1976-
bind_each<write_produce_method>(type.MethodList()),
2038+
bind_each<write_produce_method>(type.MethodList(), type),
19772039
bind<write_fast_produce_methods>(type));
19782040
}
19792041

strings/base_collections_base.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,20 @@ WINRT_EXPORT namespace winrt
506506
template <typename D, typename K, typename V, typename Version = impl::no_collection_version>
507507
struct map_view_base : iterable_base<D, Windows::Foundation::Collections::IKeyValuePair<K, V>, Version>
508508
{
509+
// specialization of Lookup that avoids throwing the hresult
510+
std::optional<V> TryLookup(K const& key, trylookup_from_abi_t) const
511+
{
512+
[[maybe_unused]] auto guard = static_cast<D const&>(*this).acquire_shared();
513+
auto pair = static_cast<D const&>(*this).get_container().find(static_cast<D const&>(*this).wrap_value(key));
514+
515+
if (pair == static_cast<D const&>(*this).get_container().end())
516+
{
517+
return std::nullopt;
518+
}
519+
520+
return static_cast<D const&>(*this).unwrap_value(pair->second);
521+
}
522+
509523
V Lookup(K const& key) const
510524
{
511525
[[maybe_unused]] auto guard = static_cast<D const&>(*this).acquire_shared();
@@ -536,6 +550,7 @@ WINRT_EXPORT namespace winrt
536550
first = nullptr;
537551
second = nullptr;
538552
}
553+
539554
};
540555

541556
template <typename D, typename K, typename V>

strings/base_coroutine_foundation.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ namespace winrt::impl
351351
return m_promise->enable_cancellation_propagation(value);
352352
}
353353

354+
bool originate_on_cancel(bool value = true) const noexcept
355+
{
356+
return m_promise->originate_on_cancel(value);
357+
}
354358
private:
355359

356360
Promise* m_promise;
@@ -484,7 +488,14 @@ namespace winrt::impl
484488
if (m_status.load(std::memory_order_relaxed) == AsyncStatus::Started)
485489
{
486490
m_status.store(AsyncStatus::Canceled, std::memory_order_relaxed);
487-
m_exception = std::make_exception_ptr(hresult_canceled());
491+
if (cancellable_promise::originate_on_cancel())
492+
{
493+
m_exception = std::make_exception_ptr(hresult_canceled());
494+
}
495+
else
496+
{
497+
m_exception = std::make_exception_ptr(hresult_canceled(hresult_error::no_originate));
498+
}
488499
cancel = std::move(m_cancel);
489500
}
490501
}
@@ -628,7 +639,14 @@ namespace winrt::impl
628639
{
629640
if (Status() == AsyncStatus::Canceled)
630641
{
631-
throw winrt::hresult_canceled();
642+
if (cancellable_promise::originate_on_cancel())
643+
{
644+
throw winrt::hresult_canceled();
645+
}
646+
else
647+
{
648+
throw winrt::hresult_canceled(hresult_error::no_originate);
649+
}
632650
}
633651

634652
return std::forward<Expression>(expression);

strings/base_coroutine_threadpool.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,23 @@ WINRT_EXPORT namespace winrt
180180
return m_propagate_cancellation;
181181
}
182182

183+
bool originate_on_cancel(bool value = true) noexcept
184+
{
185+
return std::exchange(m_originate_on_cancel, value);
186+
}
187+
188+
bool should_originate_on_cancel() const noexcept
189+
{
190+
return m_originate_on_cancel;
191+
}
192+
183193
private:
184194
static inline auto const cancelling_ptr = reinterpret_cast<canceller_t>(1);
185195

186196
std::atomic<canceller_t> m_canceller{ nullptr };
187197
void* m_context{ nullptr };
188198
bool m_propagate_cancellation{ false };
199+
bool m_originate_on_cancel{ true }; // By default, will call RoOriginateError before throwing a cancel error code.
189200
};
190201

191202
template <typename Derived>

strings/base_error.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ WINRT_EXPORT namespace winrt
8484
{
8585
struct hresult_error
8686
{
87+
struct no_originate_t {};
88+
static constexpr no_originate_t no_originate{};
89+
8790
using from_abi_t = take_ownership_from_abi_t;
8891
static constexpr auto from_abi{ take_ownership_from_abi };
8992

@@ -109,6 +112,10 @@ WINRT_EXPORT namespace winrt
109112
originate(code, nullptr, sourceInformation);
110113
}
111114

115+
explicit hresult_error(hresult const code, no_originate_t) noexcept : m_code(verify_error(code))
116+
{
117+
}
118+
112119
hresult_error(hresult const code, param::hstring const& message, winrt::impl::slim_source_location const& sourceInformation = winrt::impl::slim_source_location::current()) noexcept : m_code(verify_error(code))
113120
{
114121
originate(code, get_abi(message), sourceInformation);
@@ -325,6 +332,7 @@ WINRT_EXPORT namespace winrt
325332
struct hresult_canceled : hresult_error
326333
{
327334
hresult_canceled(winrt::impl::slim_source_location const& sourceInformation = winrt::impl::slim_source_location::current()) noexcept : hresult_error(impl::error_canceled, sourceInformation) {}
335+
hresult_canceled(hresult_error::no_originate_t) noexcept : hresult_error(impl::error_canceled, hresult_error::no_originate) {}
328336
hresult_canceled(param::hstring const& message, winrt::impl::slim_source_location const& sourceInformation = winrt::impl::slim_source_location::current()) noexcept : hresult_error(impl::error_canceled, message, sourceInformation) {}
329337
hresult_canceled(take_ownership_from_abi_t, winrt::impl::slim_source_location const& sourceInformation = winrt::impl::slim_source_location::current()) noexcept : hresult_error(impl::error_canceled, take_ownership_from_abi, sourceInformation) {}
330338
};

strings/base_meta.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ WINRT_EXPORT namespace winrt
1010
struct take_ownership_from_abi_t {};
1111
inline constexpr take_ownership_from_abi_t take_ownership_from_abi{};
1212

13+
// Map implementations can implement TryLookup with trylookup_from_abi_t as an optimization
14+
struct trylookup_from_abi_t {};
15+
inline constexpr trylookup_from_abi_t trylookup_from_abi{};
16+
1317
template <typename T>
1418
struct com_ptr;
1519

@@ -298,4 +302,16 @@ namespace winrt::impl
298302
return (func(Types{}) || ...);
299303
}
300304
};
305+
306+
template <typename D, typename K>
307+
struct has_TryLookup
308+
{
309+
template <typename U, typename = decltype(std::declval<U>().TryLookup(std::declval<K>(), trylookup_from_abi))> static constexpr bool get_value(int) { return true; }
310+
template <typename> static constexpr bool get_value(...) { return false; }
311+
public:
312+
static constexpr bool value = get_value<D>(0);
313+
};
314+
315+
template <typename D, typename K>
316+
inline constexpr bool has_TryLookup_v = has_TryLookup<D, K>::value;
301317
}

test/old_tests/UnitTests/Errors.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ TEST_CASE("Errors")
233233

234234
// Make sure trimming works.
235235
hresult_error e(E_FAIL, L":) is \u263A \n \t ");
236+
auto x = e.message();
236237
REQUIRE(e.message() == L":) is \u263A");
237238

238239
// Make sure delegates propagate correctly.

0 commit comments

Comments
 (0)