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

Fix serialization of attribute data to account for named params argument #22231

Merged
merged 6 commits into from
Sep 29, 2017

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Sep 20, 2017

Customer scenario

Define attribute with MarkAttribute(bool a, params object[] b) and use it with [Mark(b: new object[] { "Hello", "World" }, a: true)].
It will be incorrectly emitted as [Mark(true, new object[] { true })].

Bugs this fixes:
Fixes #20741

Workarounds, if any
Use attribute without naming arguments.

Risk
Performance impact
Low. This only affects the serialization of attribute data for attributes that have a params parameter.

Is this a regression from a previous update?
No. This code was largely unchanged during the whole git history.

Root cause analysis:
The matching of arguments to parameters during overload resolution is fine. But when it comes time to prepare attribute data, we have logic to put arguments in the proper order (GetRewrittenAttributeConstructorArguments). It's implementation didn't handle named params arguments properly.

How was the bug found?
Reported by customer

Test documentation updated?
Not applicable

@jcouv jcouv added this to the 15.5 milestone Sep 20, 2017
@jcouv jcouv self-assigned this Sep 20, 2017
@jcouv
Copy link
Member Author

jcouv commented Sep 20, 2017

@dotnet/roslyn-compiler for review. Thanks

public object[] B { get; }
}

[Mark(b: new object[] { ""Hello"", ""World"" }, a: true)]
Copy link
Member

@cston cston Sep 21, 2017

Choose a reason for hiding this comment

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

Consider testing [Mark(b: "Hello", a: true)] as well. #Resolved

Copy link
Member Author

@jcouv jcouv Sep 21, 2017

Choose a reason for hiding this comment

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

Good catch. That is broken. I'm investigating. #Resolved

public static void Main()
{
Console.Write(""Method call: "");
Test(b: new object[] { ""Hello"", ""World"" }, a: true);
Copy link
Member

@cston cston Sep 21, 2017

Choose a reason for hiding this comment

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

Is this Test necessary? #Resolved

[WorkItem(20741, "https://github.com/dotnet/roslyn/issues/20741")]
public void TestNamedArgumentOnStringParamsArgument()
{
var source = CreateCompilationWithMscorlib46(@"
Copy link
Member

@cston cston Sep 21, 2017

Choose a reason for hiding this comment

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

Minor point: Can use CreateStandardCompilation in these tests. #Resolved

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 needed 46 for those reflection APIs.

}

TypeSymbol argumentType = (TypeSymbol)argument.Type;
// Easy out (i.e. don't both classifying conversion).
Copy link
Member

@cston cston Sep 21, 2017

Choose a reason for hiding this comment

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

don't bother? #Resolved


// A named argument for a params parameter must be trailing, so expanded params must have a single value
var singleValue = new TypedConstant[1] { constructorArgsArray[argIndex] };
return new TypedConstant(parameter.Type, singleValue.AsImmutableOrNull());
Copy link
Member

@cston cston Sep 21, 2017

Choose a reason for hiding this comment

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

Avoid the extra array with var singleValue = ImmutableArray.Create(constructorArgsArray[argIndex])); #Resolved

if (!constructorArgumentNamesOpt.IsDefault)
{
int argIndex = constructorArgumentNamesOpt.IndexOf(parameter.Name);
if (TryGetNormalParamValue(parameter, constructorArgsArray, argIndex, conversions, out var namedValue))
Copy link
Member

@cston cston Sep 21, 2017

Choose a reason for hiding this comment

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

Does TryGetNormalParamValue handle argIndex = -1? #Resolved

Copy link
Member Author

@jcouv jcouv Sep 21, 2017

Choose a reason for hiding this comment

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

There's indeed a problem there. Thanks
Fixed in new commit #Resolved

{
TypedConstant argument = constructorArgsArray[argIndex];
if (argument.Kind != TypedConstantKind.Array)
{
Copy link
Member

Choose a reason for hiding this comment

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

Do we hit this case with Mark(a: true, b: null)? (We should generate a null array.)

int paramArrayArgCount = argumentsCount - argsConsumedCount;

// If there are zero arguments left
if (paramArrayArgCount == 0)
{
return new TypedConstant(parameter.Type, ImmutableArray<TypedConstant>.Empty);
}
Copy link
Member

@cston cston Sep 22, 2017

Choose a reason for hiding this comment

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

Minor point: consider handling the zero arguments left case first. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I think it must be after the named arguments case.


In reply to: 140549685 [](ancestors = 140549685)

return namedValue;
}

// A named argument for a params parameter must be trailing, so expanded params must have a single value
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2017

Choose a reason for hiding this comment

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

// A named argument for a params parameter must be trailing, so expanded params must have a single value [](start = 20, length = 104)

Named argument doesn't have to be trailing, so this comment is somewhat confusing. Consider rephrasing to something like: "Using an expanded form, the argument is the only element of the array." #Closed

@@ -578,7 +578,7 @@ private TypeSymbol BindNamedAttributeArgumentType(AttributeArgumentSyntax namedA

if (parameter.IsParams && parameter.Type.IsSZArray() && i + 1 == parameterCount)
{
reorderedArgument = GetParamArrayArgument(parameter, constructorArgsArray, argumentsCount, argsConsumedCount, this.Conversions);
reorderedArgument = GetParamArrayArgument(parameter, constructorArgsArray, constructorArgumentNamesOpt, argumentsCount, argsConsumedCount, this.Conversions);
sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount);
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2017

Choose a reason for hiding this comment

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

sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount); [](start = 20, length = 77)

Should sourceIndices be adjusted differently for a named case? #Closed

Copy link
Member Author

@jcouv jcouv Sep 23, 2017

Choose a reason for hiding this comment

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

I'm looking into this. Thanks #Closed

public static void Main()
{
var attr = typeof(Program).GetCustomAttribute<MarkAttribute>();
Console.Write(attr.B[0]);
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2017

Choose a reason for hiding this comment

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

Console.Write(attr.B[0]); [](start = 8, length = 25)

Consider verifying the length of the array and the value of a as well. #Closed

public static void Main()
{
var attr = typeof(Program).GetCustomAttribute<MarkAttribute>();
Console.Write(attr.B[0]);
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2017

Choose a reason for hiding this comment

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

Console.Write(attr.B[0]); [](start = 8, length = 25)

Consider verifying the length of the array and the value of a as well. #Closed

public static void Main()
{
var attr = typeof(Program).GetCustomAttribute<MarkAttribute>();
Console.Write(attr.B);
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2017

Choose a reason for hiding this comment

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

Console.Write(attr.B); [](start = 8, length = 22)

Consider verifying the value of a as well. #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Sep 22, 2017

Done with review pass (iteration 4). #Closed

@jcouv jcouv added this to Requires BCL work in Compiler: Julien's umbrellas Sep 25, 2017
@jcouv jcouv moved this from Requires BCL work to Bad PRs in Compiler: Julien's umbrellas Sep 25, 2017
sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount);
reorderedArgument = GetParamArrayArgument(parameter, constructorArgsArray, constructorArgumentNamesOpt, argumentsCount,
argsConsumedCount, this.Conversions, out bool foundNamed);
if (!foundNamed)
Copy link
Contributor

Choose a reason for hiding this comment

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

if (!foundNamed) [](start = 20, length = 16)

It doesn't feel right that we don't allocate the sourceIndices for the case when the argument is named, but the form is not expanded.

@AlekseyTs
Copy link
Contributor

Done with review pass (iteration 6). It looks like the issue about sourceIndices is not critical and can be addressed separately.

@jcouv
Copy link
Member Author

jcouv commented Sep 27, 2017

Filed #22372 for the pre-existing problem of params values getting a -1 source index.

@AlekseyTs I think this should be good to go.

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM

@jcouv
Copy link
Member Author

jcouv commented Sep 28, 2017

test windows_debug_unit32_prtest please

@jcouv
Copy link
Member Author

jcouv commented Sep 28, 2017

windows_debug_unit32_prtest failed with Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.SQLitePersistentStorageTests.PersistentService_Document_WriteReadSameInstance [FAIL] (details)

@dotnet/roslyn-infrastructure Is this a known issue?

@jcouv
Copy link
Member Author

jcouv commented Sep 28, 2017

test windows_debug_unit32_prtest please

@jcouv
Copy link
Member Author

jcouv commented Sep 29, 2017

@dotnet-bot test windows_debug_unit32_prtest please

@jcouv jcouv merged commit 999e20e into dotnet:master Sep 29, 2017
@jcouv jcouv deleted the named-params branch September 29, 2017 02:09
333fred added a commit to 333fred/roslyn that referenced this pull request Sep 29, 2017
* dotnet/master:
  Report binding error for typed LINQ query on a type expression (dotnet#22412)
  clean up
  more refactoring
  Fixed tests
  fix test after rebase
  one more test
  CR feedback
  Allow taking unmanaged/pinned addresses of readonly variables.
  Fix typo in INotifyCompletion API in doc (dotnet#22417)
  Fix serialization of attribute data to account for named params argument (dotnet#22231)
  Create tests to cover test plans dotnet#19216 and dotnet#20127 (dotnet#22284)
  Checked uses of RefKind.RefReadOnly
  String rename
  Check for null before registering incremental analyzer in work coordinator.
  clean up
  Create tests to cover test plans dotnet#19216 and dotnet#20127
@jcouv jcouv removed this from Bad PRs and bugs in Compiler: Julien's umbrellas Jan 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

C# Named params argument in attribute constructor gets assigned wrong.
4 participants