-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Struct layout check – break an infinite generic expansion cycle that doesn’t include the target struct (a cycle at the end of the path) #75702
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -219,6 +219,316 @@ public void GenericStructWithPropertyUsingStruct() | |
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S<T>.P", "S<T[]>?").WithLocation(3, 13)); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
public void InstanceMemberExplosion_01() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider testing ref structs with ref fields. #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Wy do you think this will be interesting in context of this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ref fields cannot cause cycles in the struct layout, but I think an error would be reported today. However, since ref to ref struct is an error anyway, it's probably fine, and unrelated to the PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion this scenario is not interesting for the purpose of this PR regardless whether ref fields are considered as a possible source of cycle. Because this PR doesn't change what fields are looked at. It simply ensures we do not infinitely expand types in the process. |
||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct A<T> | ||
{ | ||
A<A<T>> x; | ||
} | ||
|
||
struct B<T> | ||
{ | ||
A<B<T>> x; | ||
} | ||
|
||
struct C<T> | ||
{ | ||
D<T> x; | ||
} | ||
struct D<T> | ||
{ | ||
C<D<T>> x; | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (4,13): error CS0523: Struct member 'A<T>.x' of type 'A<A<T>>' causes a cycle in the struct layout | ||
// A<A<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("A<T>.x", "A<A<T>>").WithLocation(4, 13), | ||
// (14,10): error CS0523: Struct member 'C<T>.x' of type 'D<T>' causes a cycle in the struct layout | ||
// D<T> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("C<T>.x", "D<T>").WithLocation(14, 10), | ||
// (18,13): error CS0523: Struct member 'D<T>.x' of type 'C<D<T>>' causes a cycle in the struct layout | ||
// C<D<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("D<T>.x", "C<D<T>>").WithLocation(18, 13) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
public void InstanceMemberExplosion_02() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct A<T> | ||
{ | ||
A<A<T>> x; | ||
} | ||
|
||
struct B<T> | ||
{ | ||
A<C<B<T>>> x; | ||
} | ||
|
||
struct C<T> | ||
{ | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (4,13): error CS0523: Struct member 'A<T>.x' of type 'A<A<T>>' causes a cycle in the struct layout | ||
// A<A<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("A<T>.x", "A<A<T>>").WithLocation(4, 13) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
public void InstanceMemberExplosion_03() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct E | ||
{} | ||
|
||
struct X<T> | ||
{ | ||
T _t; | ||
} | ||
|
||
struct Y | ||
{ | ||
X<Z> xz; | ||
} | ||
|
||
struct Z | ||
{ | ||
X<E> xe; | ||
X<Y> xy; | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (12,10): error CS0523: Struct member 'Y.xz' of type 'X<Z>' causes a cycle in the struct layout | ||
// X<Z> xz; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "xz").WithArguments("Y.xz", "X<Z>").WithLocation(12, 10), | ||
// (18,10): error CS0523: Struct member 'Z.xy' of type 'X<Y>' causes a cycle in the struct layout | ||
// X<Y> xy; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "xy").WithArguments("Z.xy", "X<Y>").WithLocation(18, 10) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these static cases affected by this issue? #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The scenario is not broken, if that is what you are asking. But I consider the test related to the issue. |
||
public void InstanceMemberExplosion_04() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct A<T> | ||
{ | ||
A<A<T>> x; | ||
} | ||
|
||
struct C<T> | ||
{ | ||
C<C<T>> x; | ||
} | ||
|
||
struct B<T> | ||
{ | ||
A<B<T>> x; | ||
C<B<T>> y; | ||
B<T> z; | ||
} | ||
|
||
struct D | ||
{ | ||
B<int> x; | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (4,13): error CS0523: Struct member 'A<T>.x' of type 'A<A<T>>' causes a cycle in the struct layout | ||
// A<A<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("A<T>.x", "A<A<T>>").WithLocation(4, 13), | ||
// (9,13): error CS0523: Struct member 'C<T>.x' of type 'C<C<T>>' causes a cycle in the struct layout | ||
// C<C<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("C<T>.x", "C<C<T>>").WithLocation(9, 13), | ||
// (16,10): error CS0523: Struct member 'B<T>.z' of type 'B<T>' causes a cycle in the struct layout | ||
// B<T> z; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "z").WithArguments("B<T>.z", "B<T>").WithLocation(16, 10) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
public void InstanceMemberExplosion_05() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct A<T> | ||
{ | ||
A<A<T>> x; | ||
} | ||
|
||
struct C<T> | ||
{ | ||
C<C<T>> x; | ||
} | ||
|
||
struct B<T> | ||
{ | ||
B<T> z; | ||
A<B<T>> x; | ||
C<B<T>> y; | ||
} | ||
|
||
struct D | ||
{ | ||
B<int> x; | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (4,13): error CS0523: Struct member 'A<T>.x' of type 'A<A<T>>' causes a cycle in the struct layout | ||
// A<A<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("A<T>.x", "A<A<T>>").WithLocation(4, 13), | ||
// (9,13): error CS0523: Struct member 'C<T>.x' of type 'C<C<T>>' causes a cycle in the struct layout | ||
// C<C<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("C<T>.x", "C<C<T>>").WithLocation(9, 13), | ||
// (14,10): error CS0523: Struct member 'B<T>.z' of type 'B<T>' causes a cycle in the struct layout | ||
// B<T> z; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "z").WithArguments("B<T>.z", "B<T>").WithLocation(14, 10) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
public void InstanceMemberExplosion_06() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct A<T> | ||
{ | ||
A<A<T>> x; | ||
} | ||
|
||
struct C<T> | ||
{ | ||
C<C<T>> x; | ||
} | ||
|
||
struct B<T> | ||
{ | ||
A<B<T>> x; | ||
B<T> z; | ||
C<B<T>> y; | ||
} | ||
|
||
struct D | ||
{ | ||
B<int> x; | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (4,13): error CS0523: Struct member 'A<T>.x' of type 'A<A<T>>' causes a cycle in the struct layout | ||
// A<A<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("A<T>.x", "A<A<T>>").WithLocation(4, 13), | ||
// (9,13): error CS0523: Struct member 'C<T>.x' of type 'C<C<T>>' causes a cycle in the struct layout | ||
// C<C<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("C<T>.x", "C<C<T>>").WithLocation(9, 13), | ||
// (15,10): error CS0523: Struct member 'B<T>.z' of type 'B<T>' causes a cycle in the struct layout | ||
// B<T> z; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "z").WithArguments("B<T>.z", "B<T>").WithLocation(15, 10) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
public void StaticMemberExplosion_01() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct A<T> | ||
{ | ||
static A<A<T>> x; | ||
} | ||
|
||
struct B<T> | ||
{ | ||
static A<B<T>> x; | ||
} | ||
|
||
struct C<T> | ||
{ | ||
static D<T> x; | ||
} | ||
struct D<T> | ||
{ | ||
static C<D<T>> x; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we testing a mix of static and instance members? For instance: struct C<T>
{
static D<T> x;
}
struct D<T>
{
C<D<T>> x;
}
``` #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These are not interesting in context of the problem we are facing because cycles across static and instance fields are detected separately and by different methods. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example above currently compiles, but peverify fails, and the native compiler reports a cycle. Do we need to log an issue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please do, we can investigate in more details later.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (4,20): error CS0523: Struct member 'A<T>.x' of type 'A<A<T>>' causes a cycle in the struct layout | ||
// static A<A<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("A<T>.x", "A<A<T>>").WithLocation(4, 20), | ||
// (14,17): error CS0523: Struct member 'C<T>.x' of type 'D<T>' causes a cycle in the struct layout | ||
// static D<T> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("C<T>.x", "D<T>").WithLocation(14, 17), | ||
// (18,20): error CS0523: Struct member 'D<T>.x' of type 'C<D<T>>' causes a cycle in the struct layout | ||
// static C<D<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("D<T>.x", "C<D<T>>").WithLocation(18, 20) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
public void StaticMemberExplosion_02() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct A<T> | ||
{ | ||
static A<A<T>> x; | ||
} | ||
|
||
struct B<T> | ||
{ | ||
static A<C<B<T>>> x; | ||
} | ||
|
||
struct C<T> | ||
{ | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// (4,20): error CS0523: Struct member 'A<T>.x' of type 'A<A<T>>' causes a cycle in the struct layout | ||
// static A<A<T>> x; | ||
Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("A<T>.x", "A<A<T>>").WithLocation(4, 20) | ||
); | ||
} | ||
|
||
[Fact] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/66844")] | ||
[WorkItem("https://github.com/dotnet/roslyn/issues/75701")] | ||
public void StaticMemberExplosion_03() | ||
{ | ||
string program = @"#pragma warning disable CS0169 // The field is never used | ||
struct E | ||
{} | ||
|
||
struct X<T> | ||
{ | ||
static T _t; | ||
} | ||
|
||
struct Y | ||
{ | ||
static X<Z> xz; | ||
} | ||
|
||
struct Z | ||
{ | ||
static X<E> xe; | ||
static X<Y> xy; | ||
} | ||
"; | ||
CreateCompilation(program).VerifyDiagnostics( | ||
// Errors are expected here, see https://github.com/dotnet/roslyn/issues/75701. | ||
); | ||
} | ||
|
||
[Fact, WorkItem(1017887, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1017887")] | ||
public void EmptyStructsFromMetadata() | ||
{ | ||
|
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.
Are there any examples where
typesWithCycle
willl have more than one item?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 see nothing preventing that. If that is what you are asking.
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.
If it's possible, it would be useful to have a test that has multiple entries in
typesWithCycle
, to prevent us from changing this to a single value in the future.