-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Expression.Default and Expression.New with structs with parameterless constructors #57034
Conversation
Tagging subscribers to this area: @cston Issue DetailsFixes #45397. First commit based on GrabYourPitchforks@1a22b6a.
|
e870d78
to
c661b31
Compare
@@ -424,7 +424,7 @@ public override int Run(InterpretedFrame frame) | |||
|
|||
try | |||
{ | |||
value = Activator.CreateInstance(_type); | |||
value = RuntimeHelpers.GetUninitializedObject(_type); |
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.
@GrabYourPitchforks, does RuntimeHelpers.GetUnitializedObject(_type)
support Nullable<T>
or should this method use Activator.CreateInstance(_type)
or perhaps just null
if _type.IsNullableType()
?
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.
GetUninitializedObject
allows typeof(Nullable<T>)
to be passed in. It extracts the T and returns a boxed default(T)
.
runtime/src/coreclr/vm/reflectioninvocation.cpp
Lines 2240 to 2242 in 1011edc
// If it is a nullable, return the underlying type instead. | |
if (pMT->IsNullable()) | |
pMT = pMT->GetInstantiation()[0].GetMethodTable(); |
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.
GetUninitializedObject
allowstypeof(Nullable<T>)
to be passed in. It extracts the T and returns a boxeddefault(T)
.
Thanks. For MutableBox
and MutableValue
, it seems the value should be (T?)null
rather than (T?)default(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.
LGTM. Thanks for fixing this.
Just a request to update the suppress message justifications.
@@ -26,7 +27,7 @@ internal DefaultValueInstruction(Type type) | |||
Justification = "_type is a ValueType. You can always create an instance of a ValueType.")] |
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.
Can we update these justifications to match the new code? (here and below)
Justification = "_type is a ValueType. You can always create an instance of a ValueType.")] | |
Justification = "_type is a ValueType. You can always get an uninitialized ValueType.")] |
@dotnet/roslyn-compiler, please review. |
public override int Run(InterpretedFrame frame) | ||
{ | ||
if (frame.Peek() == null) | ||
{ | ||
frame.Pop(); | ||
frame.Push(Activator.CreateInstance(_defaultValueType)); | ||
frame.Push(RuntimeHelpers.GetUninitializedObject(_defaultValueType)); |
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.
There is no assertion in the ctor here that _defaultValueType
is not nullable as there is in the ctor for DefaultValueInstruction
. Would expect an assert in both places. #Resolved
value = _type.IsNullableType() ? | ||
Activator.CreateInstance(_type) : | ||
RuntimeHelpers.GetUninitializedObject(_type); |
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.
This pattern is repeated, did you consider adding a helper?
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.
Will leave as is to avoid moving suppression messages, etc.
@@ -439,6 +439,13 @@ public override int Run(InterpretedFrame frame) | |||
|
|||
public override string InstructionName => "InitMutableBox"; | |||
} | |||
|
|||
private static object? GetUninitializedObjectOrNull(Type type) |
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.
You are going to have to move the suppress messages now... #Resolved
@@ -23,10 +24,10 @@ internal DefaultValueInstruction(Type type) | |||
public override string InstructionName => "DefaultValue"; | |||
|
|||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern", |
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.
Do we still get a warning after this change if the attribute is removed?
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.
You should, yes, because RuntimeHelpers.GetUninitializedObject is annotated as requiring Constructors on the Type:
Lines 145 to 152 in c0662e8
public static object GetUninitializedObject( | |
// This API doesn't call any constructors, but the type needs to be seen as constructed. | |
// A type is seen as constructed if a constructor is kept. | |
// This obviously won't cover a type with no constructor. Reference types with no | |
// constructor are an academic problem. Valuetypes with no constructors are a problem, | |
// but IL Linker currently treats them as always implicitly boxed. | |
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] | |
Type type) |
public override int Run(InterpretedFrame frame) | ||
{ | ||
object? value; | ||
|
||
try | ||
{ | ||
value = Activator.CreateInstance(_type); | ||
value = _type.IsNullableType() ? |
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.
It wasn't clear to me why we want to use Activator.CreateInstance when the type is a System.Nullable<T>
. Does something different/unexpected happen if we use RuntimeHelpers.GetUninitializedObject(_type)
here unconditionally? #Resolved
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.
Does something different/unexpected happen if we use
RuntimeHelpers.GetUninitializedObject(_type)
here unconditionally?
Yes, GetUninitializedObject(_type)
will return (ValueType?)default(ValueType)
- that is, a boxed instance of ValueType
- rather than null
when _type == typeof(ValueType?)
.
{ | ||
// f = () => | ||
// { | ||
// ValueTypeWithParameterlessConstructorThatThrows p; |
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 feel like I might not understand the semantics of this interpreter. Do we just implicitly initialize locals to default
? Do you think it would improve clarity to throw in an = default
here? #ByDesign
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.
Yes, we implicitly initialize the value to default
in MutableValue
. I was not including = default
to ensure we caught regressions where the behavior of MutableValue
changes. Since we're using a value type that throws an exception in the parameterless constructor, that might also catch the same regressions though.
|
||
[Theory] | ||
[ClassData(typeof(CompilationTypes))] | ||
public static void GetValueOrDefault(bool useInterpreter) |
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 didn't understand whether there was significance to this being static
. #Resolved
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.
It looks like we have a mix of static
and instance tests. The only reason this one is static
is it was originally added in a location where others were static
. Removed the static
here for consistency.
Fixes #45397.
First commit based on GrabYourPitchforks@1a22b6a.
Relates to parameterless struct constructors: dotnet/roslyn#51698 (test plan)