Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dca10f8
C#: Add extended_type to the DB scheme.
michaelnebel Feb 4, 2026
c68cd58
C#: Add parameter marker interface, allow a type to a parent for para…
michaelnebel Feb 4, 2026
60bb9a9
C#: Move some populate methods and location writing methods.
michaelnebel Feb 4, 2026
ab505e3
C#: Add class for making synthetic parameter entities.
michaelnebel Feb 4, 2026
edfdc98
C#: Extract extension types and members. Replacing invocations to sta…
michaelnebel Feb 4, 2026
9a4a6cf
C#: Add ExtensionType to the QL library.
michaelnebel Feb 4, 2026
b9f36f3
C#: Add extension callable and accessor classes.
michaelnebel Feb 4, 2026
5e02a86
C#: Add extension call classes.
michaelnebel Feb 4, 2026
e831c80
C#: Replace extension parameter access with the corresponding synthet…
michaelnebel Feb 4, 2026
849823e
C#: Add dispatch logic for calling extensions accessors as methods.
michaelnebel Feb 4, 2026
c040daa
C#: Add extensions test.
michaelnebel Feb 4, 2026
6cbe000
C#: Add PrintAst test for extensions.
michaelnebel Feb 4, 2026
4b6a53b
C#: Add extension data flow test.
michaelnebel Feb 4, 2026
bd3e4d3
C#: Add MaD tests for extensions.
michaelnebel Feb 4, 2026
02e4a8b
C#: Add change-note.
michaelnebel Feb 5, 2026
fe94b3b
C#: Address review comments.
michaelnebel Feb 9, 2026
bcdbd6e
C#: Use the fully qualified name for the extension type when printing…
michaelnebel Feb 9, 2026
d9fea15
C#: Update MaD models for extension members.
michaelnebel Feb 9, 2026
eff9f99
C#: Update test expected output.
michaelnebel Feb 9, 2026
42d2de8
C#: Add DB upgrade script.
michaelnebel Feb 9, 2026
3e914f7
C#: Add DB downgrade script.
michaelnebel Feb 9, 2026
bee1718
QL4QL: Allow Impl classes to implement getAPrimaryQLClass with non Im…
michaelnebel Feb 9, 2026
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
1,489 changes: 1,489 additions & 0 deletions csharp/downgrades/178a7e6cf335486d33d4e49543148e3f57f04a9a/old.dbscheme

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: Remove the relation extension_receiver_type and remove the extension_type type kind.
compatibility: backwards
extension_receiver_type.rel: delete
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Semmle.Util;
using Semmle.Extraction.CSharp.Entities;

namespace Semmle.Extraction.CSharp
Expand Down Expand Up @@ -164,6 +165,7 @@
case TypeKind.Enum:
case TypeKind.Delegate:
case TypeKind.Error:
case TypeKind.Extension:
var named = (INamedTypeSymbol)type;
named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType);
return;
Expand Down Expand Up @@ -275,6 +277,20 @@
public static IEnumerable<IFieldSymbol?> GetTupleElementsMaybeNull(this INamedTypeSymbol type) =>
type.TupleElements;

private static void BuildExtensionTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile)
{
trapFile.Write("extension(");
if (named.ExtensionMarkerName is not null)
{
trapFile.Write(named.ExtensionMarkerName);
}
else
{
trapFile.Write("unknown");
}
trapFile.Write(")");
}

private static void BuildQualifierAndName(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
{
if (named.ContainingType is not null)
Expand All @@ -289,8 +305,18 @@
named.ContainingNamespace.BuildNamespace(cx, trapFile);
}

var name = named.IsFileLocal ? named.MetadataName : named.Name;
trapFile.Write(name);
if (named.IsFileLocal)
{
trapFile.Write(named.MetadataName);
}
else if (named.IsExtension)
{
named.BuildExtensionTypeId(cx, trapFile);
}
else
{
trapFile.Write(named.Name);
}
}

private static void BuildTupleId(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
Expand Down Expand Up @@ -391,6 +417,7 @@
case TypeKind.Enum:
case TypeKind.Delegate:
case TypeKind.Error:
case TypeKind.Extension:
var named = (INamedTypeSymbol)type;
named.BuildNamedTypeDisplayName(cx, trapFile, constructUnderlyingTupleType);
return;
Expand Down Expand Up @@ -465,6 +492,20 @@
private static void BuildFunctionPointerTypeDisplayName(this IFunctionPointerTypeSymbol funptr, Context cx, TextWriter trapFile) =>
BuildFunctionPointerSignature(funptr, trapFile, s => s.BuildDisplayName(cx, trapFile));

private static void BuildExtensionTypeDisplayName(this INamedTypeSymbol named, Context cx, TextWriter trapFile)
{
trapFile.Write("extension(");
if (named.ExtensionParameter?.Type is ITypeSymbol type)
{
type.BuildDisplayName(cx, trapFile);
}
else
{
trapFile.Write("unknown");
}
trapFile.Write(")");
}

private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Context cx, TextWriter trapFile, bool constructUnderlyingTupleType)
{
if (!constructUnderlyingTupleType && namedType.IsTupleType)
Expand All @@ -484,6 +525,12 @@
return;
}

if (namedType.IsExtension)
{
namedType.BuildExtensionTypeDisplayName(cx, trapFile);
return;
}

if (namedType.IsAnonymousType)
{
namedType.BuildAnonymousName(cx, trapFile);
Expand Down Expand Up @@ -596,6 +643,84 @@
return true;
}

/// <summary>
/// Return true if this method is a compiler-generated extension method.
/// </summary>
public static bool IsCompilerGeneratedExtensionMethod(this IMethodSymbol method) =>
method.TryGetExtensionMethod() is not null;

/// <summary>
/// Returns the extension method corresponding to this compiler-generated extension method, if it exists.
/// </summary>
public static IMethodSymbol? TryGetExtensionMethod(this IMethodSymbol method)
{
if (method.IsImplicitlyDeclared && method.ContainingSymbol is INamedTypeSymbol containingType)
{
// Extension types are declared within the same type as the generated
// extension method implementation.
var extensions = containingType.GetMembers()
.OfType<INamedTypeSymbol>()
.Where(t => t.IsExtension);
// Find the (possibly unbound) original extension method that maps to this implementation (if any).
var unboundDeclaration = extensions.SelectMany(e => e.GetMembers())
.OfType<IMethodSymbol>()
.FirstOrDefault(m => SymbolEqualityComparer.Default.Equals(m.AssociatedExtensionImplementation, method.ConstructedFrom));

var isFullyConstructed = method.IsBoundGenericMethod();
if (isFullyConstructed && unboundDeclaration?.ContainingType is INamedTypeSymbol extensionType)
{
try
{
// Use the type arguments from the constructed extension method to construct the extension type.
var arguments = method.TypeArguments.ToArray();
var (extensionTypeArguments, extensionMethodArguments) = arguments.SplitAt(extensionType.TypeParameters.Length);

// Construct the extension type.
var boundExtensionType = extensionType.IsUnboundGenericType()
? extensionType.Construct(extensionTypeArguments.ToArray())
: extensionType;

// Find the extension method declaration within the constructed extension type.
var extensionDeclaration = boundExtensionType.GetMembers()
.OfType<IMethodSymbol>()
.First(c => SymbolEqualityComparer.Default.Equals(c.OriginalDefinition, unboundDeclaration));

// If the extension declaration is unbound apply the remaning type arguments and construct it.
return extensionDeclaration.IsUnboundGenericMethod()
? extensionDeclaration.Construct(extensionMethodArguments.ToArray())
: extensionDeclaration;
}
catch
{
// If anything goes wrong, fall back to the unbound declaration.
return unboundDeclaration;
}
Comment on lines +693 to +697

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}
else
{
return unboundDeclaration;
}
}
return null;
}

/// <summary>
/// Returns true if this method is an unbound generic method.
/// </summary>
public static bool IsUnboundGenericMethod(this IMethodSymbol method) =>
method.IsGenericMethod && SymbolEqualityComparer.Default.Equals(method.ConstructedFrom, method);

/// <summary>
/// Returns true if this method is a bound generic method.
/// </summary>
public static bool IsBoundGenericMethod(this IMethodSymbol method) => method.IsGenericMethod && !method.IsUnboundGenericMethod();

/// <summary>
/// Returns true if this type is an unbound generic type.
/// </summary>
public static bool IsUnboundGenericType(this INamedTypeSymbol type) =>
type.IsGenericType && SymbolEqualityComparer.Default.Equals(type.ConstructedFrom, type);

/// <summary>
/// Gets the base type of `symbol`. Unlike `symbol.BaseType`, this excludes effective base
/// types of type parameters as well as `object` base types.
Expand Down Expand Up @@ -692,5 +817,35 @@
/// </summary>
public static IEnumerable<T> ExtractionCandidates<T>(this IEnumerable<T> symbols) where T : ISymbol =>
symbols.Where(symbol => symbol.ShouldExtractSymbol());

/// <summary>
/// Returns the parameter kind for this parameter symbol, e.g. `ref`, `out`, `params`, etc.
/// </summary>
public static Parameter.Kind GetParameterKind(this IParameterSymbol parameter)
{
switch (parameter.RefKind)
{
case RefKind.Out:
return Parameter.Kind.Out;
case RefKind.Ref:
return Parameter.Kind.Ref;
case RefKind.In:
return Parameter.Kind.In;
case RefKind.RefReadOnlyParameter:
return Parameter.Kind.RefReadOnly;
default:
if (parameter.IsParams)
return Parameter.Kind.Params;

if (parameter.Ordinal == 0)
{
if (parameter.ContainingSymbol is IMethodSymbol method && method.IsExtensionMethod)
{
return Parameter.Kind.This;
}
}
Comment on lines +840 to +846

Check notice

Code scanning / CodeQL

Nested 'if' statements can be combined Note

These 'if' statements can be combined.
return Parameter.Kind.None;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,6 @@ public string DebugContents
}
}

protected static void WriteLocationToTrap<T1>(Action<T1, Location> writeAction, T1 entity, Location l)
{
if (l is not EmptyLocation)
{
writeAction(entity, l);
}
}

protected static void WriteLocationsToTrap<T1>(Action<T1, Location> writeAction, T1 entity, IEnumerable<Location> locations)
{
foreach (var loc in locations)
{
WriteLocationToTrap(writeAction, entity, loc);
}
}

public override bool NeedsPopulation { get; }

public override int GetHashCode() => Symbol is null ? 0 : Symbol.GetHashCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,6 @@ protected void PopulateAttributes()
Attribute.ExtractAttributes(Context, Symbol, this);
}

protected void PopulateNullability(TextWriter trapFile, AnnotatedTypeSymbol type)
{
var n = NullabilityEntity.Create(Context, Nullability.Create(type));
if (!type.HasObliviousNullability())
{
trapFile.type_nullability(this, n);
}
}

protected void PopulateRefKind(TextWriter trapFile, RefKind kind)
{
switch (kind)
{
case RefKind.Out:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Out);
break;
case RefKind.Ref:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Ref);
break;
case RefKind.RefReadOnly:
case RefKind.RefReadOnlyParameter:
trapFile.type_annotation(this, Kinds.TypeAnnotation.ReadonlyRef);
break;
}
}

protected void PopulateScopedKind(TextWriter trapFile, ScopedKind kind)
{
switch (kind)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis;
using Semmle.Extraction.CSharp.Entities;

namespace Semmle.Extraction.CSharp
{
Expand All @@ -24,7 +26,7 @@ public virtual void WriteQuotedId(EscapingTextWriter trapFile)
trapFile.WriteUnescaped('\"');
}

public abstract Location? ReportingLocation { get; }
public abstract Microsoft.CodeAnalysis.Location? ReportingLocation { get; }

public abstract TrapStackBehaviour TrapStackBehaviour { get; }

Expand Down Expand Up @@ -65,6 +67,48 @@ public string GetDebugLabel()
}
#endif

protected void PopulateRefKind(TextWriter trapFile, RefKind kind)
{
switch (kind)
{
case RefKind.Out:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Out);
break;
case RefKind.Ref:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Ref);
break;
case RefKind.RefReadOnly:
case RefKind.RefReadOnlyParameter:
trapFile.type_annotation(this, Kinds.TypeAnnotation.ReadonlyRef);
break;
}
}

protected void PopulateNullability(TextWriter trapFile, AnnotatedTypeSymbol type)
{
var n = NullabilityEntity.Create(Context, Nullability.Create(type));
if (!type.HasObliviousNullability())
{
trapFile.type_nullability(this, n);
}
}

protected static void WriteLocationToTrap<T1>(Action<T1, Entities.Location> writeAction, T1 entity, Entities.Location l)
{
if (l is not EmptyLocation)
{
writeAction(entity, l);
}
}

protected static void WriteLocationsToTrap<T1>(Action<T1, Entities.Location> writeAction, T1 entity, IEnumerable<Entities.Location> locations)
{
foreach (var loc in locations)
{
WriteLocationToTrap(writeAction, entity, loc);
}
}

public override string ToString() => Label.ToString();
}
}
Loading
Loading