Merge pull request #11996 from michaelnebel/csharp/refstructreffield

C# 11: Extractor support for `ref` fields in `ref struct`.
This commit is contained in:
Michael Nebel
2023-01-31 13:08:57 +01:00
committed by GitHub
23 changed files with 8454 additions and 47 deletions

View File

@@ -0,0 +1,11 @@
class AnnotatedElement extends @cil_has_type_annotation {
string toString() { none() }
}
class Field extends @cil_field {
string toString() { none() }
}
from AnnotatedElement element, int annotation
where cil_type_annotation(element, annotation) and not element instanceof Field
select element, annotation

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
class AnnotatedElement extends @has_type_annotation {
string toString() { none() }
}
class Field extends @field {
string toString() { none() }
}
from AnnotatedElement element, int annotation
where type_annotation(element, annotation) and not element instanceof Field
select element, annotation

View File

@@ -0,0 +1,4 @@
description: Remove CIL fields as entities that can have type annotations and remove field type annotations.
compatibility: backwards
cil_type_annotation.rel: run cil_type_annotation.qlo
type_annotation.rel: run type_annotation.qlo

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using System.IO;
using System.Collections.Generic;
namespace Semmle.Extraction.CIL.Entities
{
@@ -38,6 +35,11 @@ namespace Semmle.Extraction.CIL.Entities
t = mt.Unmodified;
yield return Tuples.cil_custom_modifiers(this, mt.Modifier, mt.IsRequired);
}
if (t is ByRefType brt)
{
t = brt.ElementType;
yield return Tuples.cil_type_annotation(this, TypeAnnotation.Ref);
}
yield return Tuples.cil_field(this, DeclaringType, Name, t);
}
}

View File

@@ -30,6 +30,7 @@ namespace Semmle.Extraction.CSharp.Entities
PopulateAttributes();
ContainingType!.PopulateGenerics();
PopulateNullability(trapFile, Symbol.GetAnnotatedType());
PopulateRefKind(trapFile, Symbol.RefKind);
var unboundFieldKey = Field.Create(Context, Symbol.OriginalDefinition);
trapFile.fields(this, (Symbol.IsConst ? 2 : 1), Symbol.Name, ContainingType, Type.TypeRef, unboundFieldKey);

View File

@@ -25,19 +25,14 @@ namespace Semmle.Extraction.CSharp.Entities
public static string AccessibilityModifier(Accessibility access)
{
switch (access)
return access switch
{
case Accessibility.Private:
return "private";
case Accessibility.Protected:
return "protected";
case Accessibility.Public:
return "public";
case Accessibility.Internal:
return "internal";
default:
throw new InternalError("Unavailable modifier combination");
}
Accessibility.Private => Modifiers.Private,
Accessibility.Protected => Modifiers.Protected,
Accessibility.Public => Modifiers.Public,
Accessibility.Internal => Modifiers.Internal,
_ => throw new InternalError("Unavailable modifier combination"),
};
}
public static void HasAccessibility(Context cx, TextWriter trapFile, IEntity type, Accessibility access)
@@ -48,17 +43,17 @@ namespace Semmle.Extraction.CSharp.Entities
case Accessibility.Public:
case Accessibility.Protected:
case Accessibility.Internal:
HasModifier(cx, trapFile, type, Modifier.AccessibilityModifier(access));
HasModifier(cx, trapFile, type, AccessibilityModifier(access));
break;
case Accessibility.NotApplicable:
break;
case Accessibility.ProtectedOrInternal:
HasModifier(cx, trapFile, type, "protected");
HasModifier(cx, trapFile, type, "internal");
HasModifier(cx, trapFile, type, Modifiers.Protected);
HasModifier(cx, trapFile, type, Modifiers.Internal);
break;
case Accessibility.ProtectedAndInternal:
HasModifier(cx, trapFile, type, "protected");
HasModifier(cx, trapFile, type, "private");
HasModifier(cx, trapFile, type, Modifiers.Protected);
HasModifier(cx, trapFile, type, Modifiers.Private);
break;
default:
throw new InternalError($"Unhandled Microsoft.CodeAnalysis.Accessibility value: {access}");
@@ -70,6 +65,27 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.has_modifiers(target, Modifier.Create(cx, modifier));
}
private static void ExtractNamedTypeModifiers(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol)
{
if (symbol.Kind != SymbolKind.NamedType)
return;
if (symbol is not INamedTypeSymbol nt)
throw new InternalError(symbol, "Symbol kind is inconsistent with its type");
if (nt.IsRecord)
HasModifier(cx, trapFile, key, Modifiers.Record);
if (nt.TypeKind == TypeKind.Struct)
{
if (nt.IsReadOnly)
HasModifier(cx, trapFile, key, Modifiers.Readonly);
if (nt.IsRefLikeType)
HasModifier(cx, trapFile, key, Modifiers.Ref);
}
}
public static void ExtractModifiers(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol)
{
HasAccessibility(cx, trapFile, key, symbol.DeclaredAccessibility);
@@ -77,51 +93,35 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.has_modifiers(key, Modifier.Create(cx, Accessibility.Public));
if (symbol.IsAbstract && (symbol.Kind != SymbolKind.NamedType || ((INamedTypeSymbol)symbol).TypeKind != TypeKind.Interface))
HasModifier(cx, trapFile, key, "abstract");
HasModifier(cx, trapFile, key, Modifiers.Abstract);
if (symbol.IsSealed)
HasModifier(cx, trapFile, key, "sealed");
HasModifier(cx, trapFile, key, Modifiers.Sealed);
var fromSource = symbol.DeclaringSyntaxReferences.Length > 0;
if (symbol.IsStatic && !(symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsConst && !fromSource))
HasModifier(cx, trapFile, key, "static");
HasModifier(cx, trapFile, key, Modifiers.Static);
if (symbol.IsVirtual)
HasModifier(cx, trapFile, key, "virtual");
HasModifier(cx, trapFile, key, Modifiers.Virtual);
if (symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsReadOnly)
HasModifier(cx, trapFile, key, "readonly");
HasModifier(cx, trapFile, key, Modifiers.Readonly);
if (symbol.IsOverride)
HasModifier(cx, trapFile, key, "override");
HasModifier(cx, trapFile, key, Modifiers.Override);
if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).IsAsync)
HasModifier(cx, trapFile, key, "async");
HasModifier(cx, trapFile, key, Modifiers.Async);
if (symbol.IsExtern)
HasModifier(cx, trapFile, key, "extern");
HasModifier(cx, trapFile, key, Modifiers.Extern);
foreach (var modifier in symbol.GetSourceLevelModifiers())
HasModifier(cx, trapFile, key, modifier);
if (symbol.Kind == SymbolKind.NamedType)
{
var nt = symbol as INamedTypeSymbol;
if (nt is null)
throw new InternalError(symbol, "Symbol kind is inconsistent with its type");
if (nt.IsRecord)
HasModifier(cx, trapFile, key, "record");
if (nt.TypeKind == TypeKind.Struct)
{
if (nt.IsReadOnly)
HasModifier(cx, trapFile, key, "readonly");
if (nt.IsRefLikeType)
HasModifier(cx, trapFile, key, "ref");
}
}
ExtractNamedTypeModifiers(cx, trapFile, key, symbol);
}
public static Modifier Create(Context cx, string modifier)

View File

@@ -0,0 +1,20 @@
internal static class Modifiers
{
public const string Abstract = "abstract";
public const string Async = "async";
public const string Const = "const";
public const string Extern = "extern";
public const string Internal = "internal";
public const string New = "new";
public const string Override = "override";
public const string Partial = "partial";
public const string Private = "private";
public const string Protected = "protected";
public const string Public = "public";
public const string Readonly = "readonly";
public const string Record = "record";
public const string Ref = "ref";
public const string Sealed = "sealed";
public const string Static = "static";
public const string Virtual = "virtual";
}

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* C# 11: Added extractor support for `ref` fields in `ref struct` declarations.

View File

@@ -152,4 +152,7 @@ class Field extends DotNet::Field, Variable, Member, CustomModifierReceiver, @ci
override ValueOrRefType getDeclaringType() { cil_field(this, result, _, _) }
override Location getLocation() { result = this.getDeclaringType().getLocation() }
/** Holds if this declaration is `ref`. */
predicate isRef() { cil_type_annotation(this, 32) }
}

View File

@@ -399,6 +399,12 @@ class Field extends Variable, AssignableMember, Attributable, TopLevelExprParent
/** Holds if this field is `volatile`. */
predicate isVolatile() { this.hasModifier("volatile") }
/** Holds if this is a `ref` field. */
predicate isRef() { this.getAnnotatedType().isRef() }
/** Holds if this is a `ref readonly` field. */
predicate isReadonlyRef() { this.getAnnotatedType().isReadonlyRef() }
/** Holds if this field is `readonly`. */
predicate isReadOnly() { this.hasModifier("readonly") }

View File

@@ -1868,7 +1868,7 @@ cil_field(
@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event;
@cil_custom_modifier_receiver = @cil_method | @cil_property | @cil_parameter | @cil_field | @cil_function_pointer_type;
@cil_parameterizable = @cil_method | @cil_function_pointer_type;
@cil_has_type_annotation = @cil_stack_variable | @cil_property | @cil_method | @cil_function_pointer_type;
@cil_has_type_annotation = @cil_stack_variable | @cil_property | @cil_field | @cil_method | @cil_function_pointer_type;
#keyset[parameterizable, index]
cil_parameter(

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Add CIL fields as entities that can have type annotations.
compatibility: backwards

View File

@@ -0,0 +1,15 @@
// TODO: Test needs to be enabled when .NET 7 is used as the runtime.
namespace structassembly;
public class MyEmptyClass { }
public ref struct RefStruct
{
public int MyInt;
public ref byte MyByte;
public ref object MyObject;
internal ref MyEmptyClass MyEmptyClass;
public ref readonly byte MyReadonlyByte;
public readonly ref object MyReadonlyObject;
public readonly ref readonly string MyReadonlyString;
}

View File

@@ -0,0 +1,14 @@
namespace structassembly;
public class MyEmptyClass { }
public ref struct RefStruct
{
public int MyInt;
public ref byte MyByte;
public ref object MyObject;
internal ref MyEmptyClass MyEmptyClass;
public ref readonly byte MyReadonlyByte;
public readonly ref object MyReadonlyObject;
public readonly ref readonly string MyReadonlyString;
}

View File

@@ -0,0 +1,6 @@
| file://:0:0:0:0 | MyByte | Byte |
| file://:0:0:0:0 | MyEmptyClass | MyEmptyClass |
| file://:0:0:0:0 | MyObject | Object |
| file://:0:0:0:0 | MyReadonlyByte | Byte |
| file://:0:0:0:0 | MyReadonlyObject | Object |
| file://:0:0:0:0 | MyReadonlyString | String |

View File

@@ -0,0 +1,5 @@
import cil
query predicate cilfields(CIL::Field f, string type) {
f.isRef() and type = f.getType().toString() and f.getDeclaringType().getName() = "RefStruct"
}

View File

@@ -0,0 +1,3 @@
reffields
readonlyreffields
readonlyfield

View File

@@ -0,0 +1,16 @@
import csharp
query predicate reffields(Field f) {
f.getFile().getStem() = "Struct" and
f.isRef()
}
query predicate readonlyreffields(Field f) {
f.getFile().getStem() = "Struct" and
f.isReadonlyRef()
}
query predicate readonlyfield(Field f) {
f.getFile().getStem() = "Struct" and
f.isReadOnly()
}