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

Forbid unsafe types in records #76588

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

333fred
Copy link
Member

@333fred 333fred commented Dec 30, 2024

The specification for records forbids the use of unsafe types as an instance field of the type. However, we didn't check that in all cases; we only checked when generating the Equals method for the record type. This meant that if the user provided their own definition of Equals, they could bypass this restriction. We also did not check for nested unsafe types, so int*[] was permitted in any scenario. We have 2 options for fixing this:

  1. Treat it as a specification bug, and update the spec to follow the current behavior. We'd therefore fix Errors about prohibiting pointer fields on records not shown in the code, they're only visible on the build output #66312 by moving the diagnostic to SourceNamedTypeSymbol.AfterMembersChecks, and only error when the user didn't provide their own implementation of Equals.
  2. Treat it as a compiler bug, and forbid use of unsafe types in all scenarios.

I've opted for path 2 here, but we can discuss whether this is too aggressive and if we should opt for path 1 instead. Fixes #66312.

[The specification](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md#members-of-a-record-type) for records forbids the use of unsafe types as an instance field of the type. However, we didn't check that in all cases; we only checked when generating the `Equals` method for the record type. This meant that if the user provided their own definition of `Equals`, they could bypass this restriction. We also did not check for nested unsafe types, so `int*[]` was permitted in any scenario. We have 2 options for fixing this:

1. Treat it as a specification bug, and update the spec to follow the current behavior. We'd therefore fix dotnet#66312 by moving the diagnostic to SourceNamedTypeSymbol.AfterMembersChecks, and only error when the user didn't provide their own implementation of `Equals`.
2. Treat it as a compiler bug, and forbid use of unsafe types in all scenarios.

I've opted for path 2 here, but we can discuss whether this is too aggressive and if we should opt for path 1 instead. Fixes dotnet#66312.
@333fred 333fred requested a review from a team as a code owner December 30, 2024 22:04
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Dec 30, 2024
@333fred
Copy link
Member Author

333fred commented Dec 30, 2024

We also need to decide what to do with manually-implemented properties that have unsafe types. As pointed out by @hamarb123, even with this change we'll still generate invalid IL for PrintMembers in such a case, where we cast the pointer to object. By my reading of the spec, it says we should make it work; nothing about the definition of PrintMembers specifies that this cast needs to occur, and we could instead convert the pointer to IntPtr before doing the print. The main question is whether we should do this, or just forbid all pointer types in records, period.


***Introduced in Visual Studio 2022 version 17.14***

The specification for `record class` and `record struct` types indicated that any unsafe types are disallowed. However, this was not
Copy link
Contributor

Choose a reason for hiding this comment

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

indicated that any unsafe types are disallowed

This sounds too broad. The spec says:

It is an error for an instance field of a record to have an unsafe type.

enforced correctly in 2 scenarios:

1. When the `record class` or `record struct` type defined its own `Equals` implementation.
2. When the field type only used the unsafe type in a nested context, such as `int*[]`.
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 3, 2025

Choose a reason for hiding this comment

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

When the field type only used the unsafe type in a nested context, such as int*[].

I couldn't find a clear definition of "unsafe type" in the language spec. Therefore, it is not clear whether such array type should be considered as unsafe.

diagnostics.Add(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, TypeSyntax?.Location ?? this.GetFirstLocation(), type);
diagnostics.Add(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, getTypeErrorLocation(), type);
}
else if (!this.IsStatic && type.ContainsPointerOrFunctionPointer() && (ContainingType.IsRecord || ContainingType.IsRecordStruct))
Copy link
Contributor

Choose a reason for hiding this comment

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

(ContainingType.IsRecord || ContainingType.IsRecordStruct)

I assume this check is cheaper than the type check for pointers. Consider performing reordering the checks.

{
diagnostics.Add(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, TypeLocation, type);
if (!this.IsStatic && type.ContainsPointerOrFunctionPointer() && (ContainingType.IsRecord || ContainingType.IsRecordStruct))
Copy link
Contributor

Choose a reason for hiding this comment

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

(ContainingType.IsRecord || ContainingType.IsRecordStruct)

Similar suggestion

comp.VerifyEmitDiagnostics(
// (12,22): error CS0723: Cannot declare a variable of static type 'C2'
var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.Mscorlib461);
DiagnosticDescription[] expected = [// (12,22): error CS0723: Cannot declare a variable of static type 'C2'
Copy link
Contributor

Choose a reason for hiding this comment

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

// (12,22): error CS0723: Cannot declare a variable of static type 'C2'

Consider placing the comment on the next line

@AlekseyTs
Copy link
Contributor

It looks like more test base-lines need an adjustment

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
2 participants