Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ internal static Expression Create(ExpressionNodeInfo info)
case SyntaxKind.ThisExpression:
return This.CreateExplicit(info);

case SyntaxKind.FieldExpression:
return PropertyFieldAccess.Create(info);

case SyntaxKind.AddressOfExpression:
return Unary.Create(info.SetKind(ExprKind.ADDRESS_OF));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Kinds;

namespace Semmle.Extraction.CSharp.Entities.Expressions
{
internal class PropertyFieldAccess : Expression<FieldExpressionSyntax>
{
private PropertyFieldAccess(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.FIELD_ACCESS)) { }

public static Expression Create(ExpressionNodeInfo info) => new PropertyFieldAccess(info).TryPopulate();

protected override void PopulateExpression(TextWriter trapFile)
{
var symbolInfo = Context.GetSymbolInfo(Syntax);
if (symbolInfo.Symbol is IFieldSymbol field)
{
var target = PropertyField.Create(Context, field);
trapFile.expr_access(this, target);
if (!field.IsStatic)
{
This.CreateImplicit(Context, field.ContainingType, Location, this, -1);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
internal class Field : CachedSymbol<IFieldSymbol>, IExpressionParentEntity
{
private Field(Context cx, IFieldSymbol init)
protected Field(Context cx, IFieldSymbol init)
: base(cx, init)
{
type = new Lazy<Type>(() => Entities.Type.Create(cx, Symbol.Type));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.IO;
using Microsoft.CodeAnalysis;
using Semmle.Extraction.CSharp.Util;
using Semmle.Extraction.Kinds;

namespace Semmle.Extraction.CSharp.Entities
{
/// <summary>
/// Represents the autogenerated backing field `field` for a property.
/// It is only created for properties that use the `field` keyword in their getter or setter, and
/// is not created for auto-properties.
/// </summary>
internal class PropertyField : Field
{
protected PropertyField(Context cx, IFieldSymbol init)
: base(cx, init)
{
}

public static new PropertyField Create(Context cx, IFieldSymbol field) => PropertyFieldFactory.Instance.CreateEntity(cx, (field, field.AssociatedSymbol), field);

public override bool NeedsPopulation => true;

public override void Populate(TextWriter trapFile)
{
PopulateNullability(trapFile, Symbol.GetAnnotatedType());

var unboundFieldKey = PropertyField.Create(Context, Symbol.OriginalDefinition);
var name = Symbol.AssociatedSymbol is not null ? $"{Symbol.AssociatedSymbol.GetName()}.field" : Symbol.Name;
trapFile.fields(this, VariableKind.None, name, ContainingType!, Type.TypeRef, unboundFieldKey);

if (Context.OnlyScaffold)
{
return;
}

if (Context.ExtractLocation(Symbol))
{
WriteLocationsToTrap(trapFile.field_location, this, Locations);
}
}

private class PropertyFieldFactory : CachedEntityFactory<IFieldSymbol, PropertyField>
{
public static PropertyFieldFactory Instance { get; } = new PropertyFieldFactory();

public override PropertyField Create(Context cx, IFieldSymbol init) => new PropertyField(cx, init);
}
}
}
4 changes: 4 additions & 0 deletions csharp/ql/lib/change-notes/2026-02-12-field-keyword.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* C# 14: Added support for the `field` keyword in properties.
65 changes: 65 additions & 0 deletions csharp/ql/test/library-tests/dataflow/fields/D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,68 @@ public static void Sink(object o) { }

static T Source<T>(object source) => throw null;
}

public class DFieldProps
{
object FieldProp0
{
get { return field; }
set { field = value; }
} = Source<object>(0);

object FieldProp1
{
get { return field; }
set { field = value; }
}

object FieldProp2
{
get { return field; }
set
{
var x = value;
field = x;
}
}

static object StaticFieldProp
{
get { return field; }
set { field = value; }
}

private void M()
{
var d0 = new DFieldProps();
Sink(d0.FieldProp0); // $ hasValueFlow=0
Sink(d0.FieldProp1); // no flow
Sink(d0.FieldProp2); // no flow
Sink(DFieldProps.StaticFieldProp); // no flow

var d1 = new DFieldProps();
var o1 = Source<object>(1);
d1.FieldProp1 = o1;
Sink(d1.FieldProp0); // $ hasValueFlow=0
Sink(d1.FieldProp1); // $ hasValueFlow=1
Sink(d1.FieldProp2); // no flow
Sink(DFieldProps.StaticFieldProp); // no flow

var d2 = new DFieldProps();
var o2 = Source<object>(2);
d2.FieldProp2 = o2;
Sink(d2.FieldProp0); // $ hasValueFlow=0
Sink(d2.FieldProp1); // no flow
Sink(d2.FieldProp2); // $ hasValueFlow=2
Sink(DFieldProps.StaticFieldProp); // no flow

var o3 = Source<object>(3);
DFieldProps.StaticFieldProp = o3;
Sink(DFieldProps.StaticFieldProp); // $ hasValueFlow=3
}

public static void Sink(object o) { }

static T Source<T>(object source) => throw null;

}
Loading