Skip to content

Commit dde1405

Browse files
committed
feat(runtime): #1980 group G-1 -- OSPlatform default, RuntimeIdentifier, ExternalException ErrorCode
#1980's own acceptance criterion calls G-1 "purely ADDITIVE, closes three medium findings and CANNOT BREAK A CONSUMER -- it is the recommended minimum", which makes it ordinary SA-5 work rather than an approval. G-2..G-5 are untouched and #1980 stays blocked for them. SR-AUD-152 -- OSPlatform has a reachable default. .NET's is a readonly struct, so default(OSPlatform) has always been reachable and its Name is null; this port's only constructor was PRIVATE, so the value had no spelling at all. The asymmetry with Create(""), which still throws, is .NET's: the factory validates its argument, the default is the absence of one. SR-AUD-153 -- the header's note was right about ONE of its two members. * FrameworkDescription: the note holds. It is GENERATED AT BUILD TIME as ".NET {version}" (ProductVersionInfoGenerator.cs:55) -- the .NET PRODUCT version. There is none here, so any string this port produced would be an invention. It stays absent, now with the citation. * RuntimeIdentifier: the note does not hold. It is not derived from the platform at all: RuntimeInformation.cs:20-21 is AppContext.GetData("RUNTIME_IDENTIFIER") as string ?? "unknown" -- a lookup in a store this port has, with a literal fallback. Reproduced exactly. The `as string` is a TYPE TEST, not a coercion, the same rule AppContext's own APP_CONTEXT_BASE_DIRECTORY lookup follows (#2255); a blind cast is caught. SR-AUD-159 -- ExternalException gains (message, errorCode) and ErrorCode. ERRORCODE NEEDS NO DATA MEMBER, which corrects the finding's implication that it is separate state: .NET's is `public virtual int ErrorCode => HResult;`, an ALIAS for the HResult this type already sets. So sizeof is unchanged and no consumer rebuilds. It is non-virtual here because a virtual would add a vtable slot, which SA-3 excludes. The new constructor sets HResult = errorCode rather than E_FAIL, which is the whole point of the overload. ToString() WAS IMPLEMENTED AND THEN REMOVED, ON THE DOWNSTREAM MEASUREMENT SA-2 CONDITION 5 REQUIRES. .NET's is "{GetType()} (0x{HResult:X8})" and GetType() is the MOST DERIVED type. It was written with the name resolved statically at the site, exactly as #2323 did for Exception's fallback message -- and cna derives from ExternalException in THREE types (NoAudioHardwareException, InstancePlayLimitException, StorageDeviceNotConnectedException), every one of which that name would have misnamed. #2323's own rule applies in its own words: a message naming the wrong type is a lie, where an empty one is merely an absence. Both remaining routes cost more than this ticket may spend -- a virtual is a vtable change (SA-3 excludes) and a stored name is a data member on a class three downstream types derive from -- so it is #2387 (needs_user), and the absence is pinned by a static_assert naming it. The pin's `requires` takes a DEPENDENT parameter, the #2299 gcc trap. Mutations: 5, all caught. Two were invalid as first written and reformulated rather than counted: removing the (message,errorCode) body leaves the parameter unused and -Werror rejects it, and the OSPlatform mutation is caught BY COMPILATION -- the only way C++ reports a missing constructor. ToString's own two mutations were caught before it was withdrawn (X8 padding, and appending an empty message -- the latter only after a row was added, because every other case had a message); that work is recorded in the note because #2387 starts from it. Gate: 17,408 run, 17,408 passed, 0 failed, 0 skipped across 38 executables (+4, in SharpRuntimeTests_Runtime, 170 -> 174). Module graph unchanged at 41/93; no sizeof, vtable or signature change. Built in build/ with --parallel 2. Downstream: cna uses ExternalException's EXISTING (message) and (message, inner) constructors at its three derived types; both untouched, so nothing needs migrating. OSPlatform and RuntimeInformation have zero consumer sites. Neither repository was modified. docs/Migration-RuntimeG1AdditiveSurface.md
1 parent d992df3 commit dde1405

9 files changed

Lines changed: 285 additions & 4 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<!-- SPDX-License-Identifier: MIT -->
2+
<!-- Copyright (c) Robert Vokac and contributors -->
3+
4+
# Migration — `System::Runtime` group G-1: three additive members (ticket #1980)
5+
6+
*2026-08-19.* #1980's own acceptance criterion calls G-1 *"purely **ADDITIVE**, closes three
7+
medium findings and **cannot break a consumer** — it is the recommended minimum"*, which makes it
8+
ordinary **SA-5** work rather than an approval. G-2 to G-5 are untouched and stay blocked.
9+
10+
Nothing was removed, no signature changed, no `sizeof` moved, no vtable moved.
11+
12+
---
13+
14+
## 1. SR-AUD-152 — `OSPlatform` has a reachable default
15+
16+
.NET's `OSPlatform` is a `readonly struct`, so `default(OSPlatform)` has always been reachable and
17+
its `Name` is null. This port's only constructor was **private**, so the value had no spelling at
18+
all. `OSPlatform() = default;` is now public.
19+
20+
The asymmetry with `Create("")`, which still throws `ArgumentException`, is .NET's: the factory
21+
validates its argument, the default is the absence of one. Both are pinned so neither is tidied
22+
into the other.
23+
24+
## 2. SR-AUD-153 — the note was right about one member and wrong about the other
25+
26+
The header said `RuntimeIdentifier` and `FrameworkDescription` *"are not reproduced — there is no
27+
.NET runtime/assembly identity in a native C++ build for these to describe"*. Measured:
28+
29+
* **`FrameworkDescription` — the note holds.** It is *generated at build time* as the literal
30+
`".NET {version}"` (`ProductVersionInfoGenerator.cs:55`) — the .NET **product** version. There
31+
is no such product here, so any string this port produced would be an invention. It stays
32+
absent, and the note now says why with the citation.
33+
* **`RuntimeIdentifier` — the note does not hold.** It is not derived from the platform at all:
34+
`RuntimeInformation.cs:20-21` is
35+
`AppContext.GetData("RUNTIME_IDENTIFIER") as string ?? "unknown"` — a lookup in a store this
36+
port has, with a literal fallback. It is reproduced exactly.
37+
38+
The `as string` is a **type test, not a coercion**: an entry of another type falls through to
39+
`"unknown"` rather than being rendered, which is the same rule `AppContext`'s own
40+
`APP_CONTEXT_BASE_DIRECTORY` lookup follows (#2255). A mutation that casts blindly is caught.
41+
42+
## 3. SR-AUD-159 — `ExternalException`, and one member deliberately not landed
43+
44+
`ExternalException(message, errorCode)` and `getErrorCodeProperty()` landed.
45+
46+
**`ErrorCode` needed no data member**, which corrects the finding's implication that it is separate
47+
state: .NET's is `public virtual int ErrorCode => HResult;` — an **alias** for the HResult this
48+
type already sets. So `sizeof` is unchanged and no consumer rebuilds. It is non-virtual here
49+
because a virtual would add a vtable slot, which SA-3 excludes.
50+
51+
The new constructor sets `HResult = errorCode` rather than `E_FAIL`, which is the whole point of
52+
the overload existing; the other three keep `E_FAIL`.
53+
54+
### `ToString()` was implemented and then removed, on the downstream measurement
55+
56+
.NET's is `$"{GetType()} (0x{HResult:X8})"` plus the message and any inner exception, and
57+
`GetType()` is the **most derived** type — reflection this port permanently lacks. It was
58+
implemented with the name resolved statically at the site, exactly as **#2323** did for
59+
`Exception`'s fallback message, and then removed when SA-2's condition 5 was discharged:
60+
61+
> **`cna` derives from `ExternalException` in three types**`NoAudioHardwareException`,
62+
> `InstancePlayLimitException` and `StorageDeviceNotConnectedException` — every one of which a
63+
> static name would have misnamed.
64+
65+
That is #2323's own rule in its own words: *a message naming the wrong type is a lie, where an
66+
empty one is merely an absence.* The two remaining routes both cost more than this ticket may
67+
spend — a virtual `ToString()` is a vtable change (SA-3 excludes), and a stored type name is a
68+
data member on a class three downstream types derive from. It is ticket **#2387**, and the absence
69+
is pinned by a `static_assert` naming it.
70+
71+
The pin's `requires` takes a **dependent** parameter, because gcc evaluates a non-dependent one
72+
eagerly and hard-errors instead of yielding false (#2299).
73+
74+
## 4. Evidence
75+
76+
Five mutations, **all caught**:
77+
78+
| Mutation | Caught by |
79+
|---|---|
80+
| the `(message, errorCode)` constructor ignores its argument | 2 cases |
81+
| `ToString`'s `X8` padding dropped |*(removed with `ToString`)* |
82+
| `RuntimeIdentifier` casts blindly instead of type-testing | its own case |
83+
| the `OSPlatform` default constructor removed | **compilation** — the only way C++ reports a missing constructor |
84+
85+
Two were invalid as first written and reformulated rather than counted: removing the
86+
`(message, errorCode)` body leaves the parameter unused and `-Werror` rejects it, and the
87+
`OSPlatform` mutation is caught as a build failure by construction.
88+
89+
Before `ToString()` was withdrawn its two mutations were caught (the `%x`-instead-of-`%08X`
90+
padding, and appending an empty message) — and the empty-message one needed a row added, because
91+
every other case had a message. That work is recorded here because it is what #2387 will start
92+
from.
93+
94+
## 5. Downstream, measured
95+
96+
`cna` uses `ExternalException`'s **existing** `(message)` and `(message, inner)` constructors at
97+
its three derived types; both are untouched, so nothing needs migrating. `OSPlatform` and
98+
`RuntimeInformation` have zero consumer sites. Neither repository was modified.

modules/runtime/include/System/Runtime/InteropServices/ExternalException.hpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,21 @@
44
#pragma once
55

66
#include "System/SystemException.hpp"
7+
#include <cstdio>
78
#include <string>
89

910
namespace System::Runtime::InteropServices
1011
{
11-
/** Base class for exceptions thrown by, or related to, external (unmanaged) code. */
12+
/**
13+
* @brief Base class for exceptions thrown by, or related to, external (unmanaged) code.
14+
*
15+
* @note <b>#1980 group G-1 (SR-AUD-159).</b> The `(message, errorCode)` constructor,
16+
* `getErrorCodeProperty()` and `ToString()` were absent. `ErrorCode` needed <b>no data
17+
* member</b>: .NET's is `public virtual int ErrorCode => HResult;` (`ExternalException.cs`),
18+
* an alias for the HResult this type already sets, so `sizeof` is unchanged and no consumer
19+
* rebuilds. The finding's implication that it is separate state does not survive the
20+
* reference.
21+
*/
1222
class ExternalException : public System::SystemException
1323
{
1424
public:
@@ -27,6 +37,49 @@ namespace System::Runtime::InteropServices
2737
setHResultProperty(static_cast<SharpRuntime::intcs>(0x80004005u)); // E_FAIL
2838
}
2939

40+
/**
41+
* @brief Creates an exception carrying a specific HRESULT.
42+
*
43+
* `ExternalException.cs`: `public ExternalException(string? message, int errorCode)`
44+
* sets `HResult = errorCode` -- it does NOT default to E_FAIL, which is the whole point
45+
* of the overload.
46+
*/
47+
ExternalException(const std::string& message, SharpRuntime::intcs errorCode)
48+
: System::SystemException(message) {
49+
setHResultProperty(errorCode);
50+
}
51+
52+
/**
53+
* @brief The HRESULT of the error.
54+
*
55+
* .NET's is `public virtual int ErrorCode => HResult;` -- an alias, not a second field.
56+
* It is non-virtual here because making it virtual would add a slot to this class's
57+
* vtable, which `docs/StandingApprovals.md` SA-3 excludes; nothing in this repository
58+
* derives from `ExternalException` (measured -- `Win32Exception` derives from
59+
* `System::Exception` directly and says so at its own site), so no override is lost.
60+
*/
61+
[[nodiscard]] SharpRuntime::intcs getErrorCodeProperty() const {
62+
return getHResultProperty();
63+
}
64+
65+
/**
66+
* @note <b>`ToString()` is deliberately ABSENT, and #1980 measured why rather than
67+
* assuming it.</b> .NET's is `$"{GetType()} (0x{HResult:X8})"` plus the message and any
68+
* inner exception -- and `GetType()` is the MOST DERIVED type, which is reflection this
69+
* port permanently lacks.
70+
*
71+
* It was implemented, with the type name resolved statically at this site as #2323 did
72+
* for `Exception`'s fallback message, and then <b>removed on the downstream
73+
* measurement</b> SA-2 condition 5 requires: `cna` derives from this class in
74+
* <b>three</b> types -- `NoAudioHardwareException`, `InstancePlayLimitException` and
75+
* `StorageDeviceNotConnectedException` -- every one of which the static name would have
76+
* misnamed. That is exactly #2323's own rule, in its own words: <i>a message naming the
77+
* wrong type is a lie, where an empty one is merely an absence.</i>
78+
*
79+
* The remaining routes both cost more than this ticket may spend: a virtual `ToString()`
80+
* adds a slot to this class's vtable, which SA-3 excludes, and a stored type name is a
81+
* data member on a class three downstream types derive from. Ticket <b>#2387</b>.
82+
*/
3083
~ExternalException() override = default;
3184
};
3285

modules/runtime/include/System/Runtime/InteropServices/OSPlatform.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ namespace System::Runtime::InteropServices {
2727
}
2828

2929
public:
30+
/**
31+
* @brief Creates the default OSPlatform, whose name is empty.
32+
*
33+
* #1980 group G-1 (SR-AUD-152). .NET's `OSPlatform` is a `readonly struct`, so
34+
* `default(OSPlatform)` has always been reachable and its `Name` is null; this port's
35+
* only constructor was private, so the value had no spelling at all.
36+
*
37+
* @note The asymmetry with `Create("")` is .NET's, not an oversight here: `Create` rejects
38+
* an empty name with `ArgumentException` while the default value carries one. `Create` is
39+
* a factory that validates its argument; the default is the absence of an argument.
40+
*/
41+
OSPlatform() = default;
42+
3043
/** @brief Creates a new OSPlatform instance. Consider caching the result if called frequently. */
3144
[[nodiscard]] static OSPlatform Create(const std::string& osPlatform) {
3245
if (osPlatform.empty()) {

modules/runtime/include/System/Runtime/InteropServices/RuntimeInformation.hpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,19 @@ namespace System::Runtime::InteropServices {
1313
*
1414
* C++ counterpart of .NET System.Runtime.InteropServices.RuntimeInformation.
1515
*
16-
* @note `RuntimeIdentifier`/`FrameworkDescription` are not reproduced — there is no .NET
17-
* runtime/assembly identity in a native C++ build for these to describe.
16+
* @note <b>#1980 group G-1 (SR-AUD-153) corrected this note, which was right about one of
17+
* the two members and wrong about the other.</b> It said both "are not reproduced — there is
18+
* no .NET runtime/assembly identity in a native C++ build for these to describe".
19+
*
20+
* That holds for <b>`FrameworkDescription`</b>, and the reference shows why: it is
21+
* <i>generated at build time</i> as the literal `".NET {version}"`
22+
* (`ProductVersionInfoGenerator.cs:55`), i.e. the .NET <i>product</i> version. There is no
23+
* such product here, so any string this port produced would be an invention.
24+
*
25+
* It does <b>not</b> hold for `RuntimeIdentifier`, which is not derived from the platform at
26+
* all: `RuntimeInformation.cs:20-21` is
27+
* `AppContext.GetData("RUNTIME_IDENTIFIER") as string ?? "unknown"` — a lookup in a store
28+
* this port has, with a literal fallback. It is reproduced exactly.
1829
*/
1930
class RuntimeInformation {
2031
public:
@@ -23,6 +34,18 @@ namespace System::Runtime::InteropServices {
2334
/** @return A human-readable description of the operating system. */
2435
[[nodiscard]] static std::string getOSDescriptionProperty();
2536

37+
/**
38+
* @return The platform on which the application is running, or `"unknown"`.
39+
*
40+
* `RuntimeInformation.cs:20-21`, transcribed:
41+
* `AppContext.GetData("RUNTIME_IDENTIFIER") as string ?? "unknown"`.
42+
*
43+
* @note The `as string` matters and is reproduced: an entry of some other type falls
44+
* through to `"unknown"` rather than being coerced, which is the same rule
45+
* `AppContext`'s own `APP_CONTEXT_BASE_DIRECTORY` lookup follows (#2255).
46+
*/
47+
[[nodiscard]] static std::string getRuntimeIdentifierProperty();
48+
2649
/** @return true if the current platform matches @p osPlatform. */
2750
[[nodiscard]] static bool IsOSPlatform(const OSPlatform& osPlatform);
2851

modules/runtime/src/System/Runtime/InteropServices/RuntimeInformation.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Copyright (c) Robert Vokac and contributors
33
// Portions based on .NET runtime API (MIT License, Copyright .NET Foundation and Contributors)
44
#include "System/Runtime/InteropServices/RuntimeInformation.hpp"
5+
#include "System/AppContext.hpp"
6+
#include <any>
7+
#include <string>
58

69
#if defined(_WIN32)
710
#include <windows.h>
@@ -13,6 +16,16 @@
1316

1417
namespace System::Runtime::InteropServices {
1518

19+
std::string RuntimeInformation::getRuntimeIdentifierProperty() {
20+
// RuntimeInformation.cs:20-21 -- AppContext.GetData("RUNTIME_IDENTIFIER") as string ?? "unknown".
21+
// The `as string` is a TYPE TEST, not a coercion: an entry of another type falls through to
22+
// the literal rather than being rendered, exactly as AppContext's own base-directory lookup
23+
// does (#2255).
24+
const std::any data = System::AppContext::GetData("RUNTIME_IDENTIFIER");
25+
if (const auto* text = std::any_cast<std::string>(&data)) return *text;
26+
return "unknown";
27+
}
28+
1629
Architecture RuntimeInformation::getProcessArchitectureProperty() {
1730
#if defined(__x86_64__) || defined(_M_X64)
1831
return Architecture::X64;

modules/runtime/tests/System/Runtime/InteropServices/RuntimeInformationTests.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright (c) Robert Vokac and contributors
33
// Portions based on .NET runtime API (MIT License, Copyright .NET Foundation and Contributors)
44
#include <gtest/gtest.h>
5+
#include "System/AppContext.hpp"
56
#include <vector>
67
#include <algorithm>
78
#include "System/ArgumentException.hpp"
@@ -119,3 +120,43 @@ TEST(RuntimeInformationTests, Fix1983_TheProcessArchitectureIsTheCompilationTarg
119120
SUCCEED() << "no assertion for this target; the #error guarantees the list covers it";
120121
#endif
121122
}
123+
124+
// ===========================================================================
125+
// #1980 group G-1 -- SR-AUD-152 (OSPlatform default) and SR-AUD-153
126+
// (RuntimeIdentifier). The ticket calls G-1 "purely ADDITIVE ... cannot break a
127+
// consumer", which is what makes it ordinary SA-5 work rather than an approval.
128+
// ===========================================================================
129+
130+
TEST(RuntimeInformationTests, Fix1980G1_OSPlatformHasAReachableDefault) {
131+
// .NET's OSPlatform is a readonly struct, so default(OSPlatform) has always been reachable
132+
// and its Name is null. This port's only constructor was private, so the value had no
133+
// spelling at all.
134+
OSPlatform def;
135+
EXPECT_TRUE(def.ToString().empty());
136+
EXPECT_TRUE(def == OSPlatform());
137+
138+
// The asymmetry with Create("") is .NET's: the factory validates its argument, the default
139+
// is the absence of one. Both rows are asserted so neither can be "tidied" into the other.
140+
EXPECT_THROW((void)OSPlatform::Create(""), System::ArgumentException);
141+
EXPECT_FALSE(def == OSPlatform::Create("LINUX"));
142+
}
143+
144+
TEST(RuntimeInformationTests, Fix1980G1_RuntimeIdentifierIsAnAppContextLookupWithALiteralFallback) {
145+
// RuntimeInformation.cs:20-21 -- AppContext.GetData("RUNTIME_IDENTIFIER") as string ??
146+
// "unknown". Nothing is derived from the platform, which is why this is reproducible at all.
147+
EXPECT_EQ(RuntimeInformation::getRuntimeIdentifierProperty(), "unknown");
148+
149+
System::AppContext::SetData("RUNTIME_IDENTIFIER", std::string("linux-x64"));
150+
EXPECT_EQ(RuntimeInformation::getRuntimeIdentifierProperty(), "linux-x64");
151+
152+
// `as string` is a TYPE TEST, not a coercion: a non-string entry falls through to the
153+
// literal rather than being rendered. The row that fails if the lookup casts blindly.
154+
System::AppContext::SetData("RUNTIME_IDENTIFIER", 42);
155+
EXPECT_EQ(RuntimeInformation::getRuntimeIdentifierProperty(), "unknown");
156+
157+
System::AppContext::SetData("RUNTIME_IDENTIFIER", std::string());
158+
EXPECT_EQ(RuntimeInformation::getRuntimeIdentifierProperty(), "")
159+
<< "an entry that IS a string is returned even when empty -- `as string` succeeds";
160+
161+
System::AppContext::SetData("RUNTIME_IDENTIFIER", std::string("unknown"));
162+
}

modules/runtime/tests/System/Runtime/RuntimeTests.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,3 +830,43 @@ TEST(ConditionalWeakTableTests, ScalarKeys_StillExpireWhenTheLastOwnerDrops) {
830830
while (e->MoveNext()) ++enumerated;
831831
EXPECT_EQ(enumerated, 0) << "an expired scalar key was still enumerated";
832832
}
833+
834+
// ===========================================================================
835+
// #1980 group G-1 -- SR-AUD-159. ExternalException's (message, errorCode)
836+
// constructor, ErrorCode and ToString.
837+
// ===========================================================================
838+
839+
TEST(ExternalExceptionTests, Fix1980G1_ErrorCodeIsTheHResultAndNeedsNoField) {
840+
// .NET's is `public virtual int ErrorCode => HResult;` -- an ALIAS, not separate state. The
841+
// finding's implication that it is a second field does not survive the reference, and this
842+
// row is what says so: setting one moves the other.
843+
ExternalException withCode("boom", static_cast<SharpRuntime::intcs>(0x80070005));
844+
EXPECT_EQ(withCode.getErrorCodeProperty(), static_cast<SharpRuntime::intcs>(0x80070005));
845+
EXPECT_EQ(withCode.getHResultProperty(), withCode.getErrorCodeProperty());
846+
847+
// The other constructors keep E_FAIL, which is the whole point of the new overload existing.
848+
EXPECT_EQ(ExternalException().getErrorCodeProperty(),
849+
static_cast<SharpRuntime::intcs>(0x80004005));
850+
EXPECT_EQ(ExternalException("m").getErrorCodeProperty(),
851+
static_cast<SharpRuntime::intcs>(0x80004005));
852+
}
853+
854+
// #1980 G-1 deliberately did NOT land ToString(), and this pins the absence so it cannot be
855+
// added without the decision. It was implemented and then removed on the downstream measurement:
856+
// cna derives from ExternalException in THREE types, and a statically resolved type name would
857+
// have misnamed every one -- #2323's own rule, that a message naming the wrong type is a lie
858+
// where an empty one is merely an absence. Ticket #2387.
859+
//
860+
// The parameter is DEPENDENT because gcc evaluates a non-dependent `requires` eagerly and
861+
// hard-errors instead of yielding false (#2299, recorded in CLAUDE.md).
862+
template <typename T> concept HasToString = requires(const T& e) { e.ToString(); };
863+
864+
TEST(ExternalExceptionTests, Decl1980G1_ToStringIsAbsentUntilItsTypeNameQuestionIsAnswered) {
865+
static_assert(!HasToString<ExternalException>,
866+
"#2387: ToString() landed without resolving the derived-type name -- three cna "
867+
"types derive from this class and a static name misnames all of them.");
868+
// The two members that DID land are unaffected and stay reachable.
869+
ExternalException ex("boom", static_cast<SharpRuntime::intcs>(0x80070005));
870+
EXPECT_EQ(ex.getErrorCodeProperty(), static_cast<SharpRuntime::intcs>(0x80070005));
871+
SUCCEED();
872+
}

plan.sqlite3

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)