Skip to content

Commit b0c9350

Browse files
authored
Avoid ECMA spec violation in PropertyStore (#12956)
The manual unboxing we were doing was not an ECMA approved use of the instruction. As such it could potentially fail in the future should the runtime decide to break the way we were using it. One way to fix this would be to set each of the fields individually on the unboxed ref as the current two types we're optimizing are mutable. I've instead used `StrongBox<T>`. While significantly more complicated, it will not run afoul of the ECMA spec should we get an immutable type we want to handle. If performance here ever ends up being a problem we could go the other way. Fixes #12933
1 parent 5a8d172 commit b0c9350

File tree

1 file changed

+18
-11
lines changed

1 file changed

+18
-11
lines changed

src/System.Windows.Forms/src/System/Windows/Forms/PropertyStore.cs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ internal sealed class PropertyStore
3737

3838
/// <summary>
3939
/// Gets the current value for the given key, or the <paramref name="defaultValue"/> if the key is not found.
40-
/// Does not allow stored values of <see langword="null"/>.
4140
/// </summary>
4241
public T? GetValueOrDefault<T>(int key, T? defaultValue = default)
4342
{
4443
if (_values.TryGetValue(key, out Value foundValue))
4544
{
46-
return foundValue.GetValue<T>();
45+
return foundValue.Type == typeof(StrongBox<T>)
46+
? foundValue.GetValue<StrongBox<T>>().Value
47+
: foundValue.GetValue<T>();
4748
}
4849

4950
return defaultValue;
@@ -85,7 +86,10 @@ public bool TryGetValue<T>(int key, [NotNullWhen(true)] out T? value)
8586
{
8687
if (_values.TryGetValue(key, out Value foundValue))
8788
{
88-
value = foundValue.GetValue<T>();
89+
value = foundValue.Type == typeof(StrongBox<T>)
90+
? foundValue.GetValue<StrongBox<T>>().Value
91+
: foundValue.GetValue<T>();
92+
8993
return value is not null;
9094
}
9195

@@ -159,9 +163,13 @@ public bool AddOrRemoveString(int key, string? value)
159163
bool isDefault = (value is null && defaultValue is null)
160164
|| (value is not null && value.Equals(defaultValue));
161165

166+
bool isStrongBox = foundValue.Type == typeof(StrongBox<T>);
167+
162168
// The previous should be whatever we found or what was specified as the default.
163169
T? previous = found
164-
? foundValue.Type is null ? default : foundValue.GetValue<T>()
170+
? isStrongBox
171+
? foundValue.GetValue<StrongBox<T>>().Value
172+
: foundValue.Type is null ? default : foundValue.GetValue<T>()
165173
: defaultValue;
166174

167175
if (isDefault)
@@ -174,8 +182,8 @@ public bool AddOrRemoveString(int key, string? value)
174182
}
175183
else if (!found || !ReferenceEquals(value, previous))
176184
{
177-
// If it wasn't found or it is the same instance we don't need set it.
178-
_values[key] = new(value);
185+
// If it wasn't found or it isn't the same instance we need set it.
186+
AddValue(key, value);
179187
}
180188

181189
return previous;
@@ -211,15 +219,14 @@ private unsafe void AddOrUpdate<T>(int key, T value) where T : unmanaged
211219
// Should only call this from SetValue<T> for value types that are larger than 8 bytes.
212220
Debug.Assert(sizeof(T) > 8);
213221

214-
if (_values.TryGetValue(key, out Value foundValue) && foundValue.Type == typeof(T))
222+
if (_values.TryGetValue(key, out Value foundValue) && foundValue.Type == typeof(StrongBox<T>))
215223
{
216-
object storedValue = foundValue.GetValue<object>();
217-
ref T unboxed = ref Unsafe.Unbox<T>(storedValue);
218-
unboxed = value;
224+
StrongBox<T> storedValue = foundValue.GetValue<StrongBox<T>>();
225+
storedValue.Value = value;
219226
}
220227
else
221228
{
222-
_values[key] = Value.Create(value);
229+
_values[key] = Value.Create(new StrongBox<T>(value));
223230
}
224231
}
225232
}

0 commit comments

Comments
 (0)