mirror of
https://github.com/github/codeql.git
synced 2026-02-12 05:01:06 +01:00
Merge pull request #21220 from michaelnebel/csharp14/extension
C# 14: Support `extension` types.
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Semmle.Util;
|
||||
using Semmle.Extraction.CSharp.Entities;
|
||||
|
||||
namespace Semmle.Extraction.CSharp
|
||||
@@ -164,6 +165,7 @@ namespace Semmle.Extraction.CSharp
|
||||
case TypeKind.Enum:
|
||||
case TypeKind.Delegate:
|
||||
case TypeKind.Error:
|
||||
case TypeKind.Extension:
|
||||
var named = (INamedTypeSymbol)type;
|
||||
named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType);
|
||||
return;
|
||||
@@ -275,6 +277,20 @@ namespace Semmle.Extraction.CSharp
|
||||
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)
|
||||
@@ -289,8 +305,18 @@ namespace Semmle.Extraction.CSharp
|
||||
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)
|
||||
@@ -391,6 +417,7 @@ namespace Semmle.Extraction.CSharp
|
||||
case TypeKind.Enum:
|
||||
case TypeKind.Delegate:
|
||||
case TypeKind.Error:
|
||||
case TypeKind.Extension:
|
||||
var named = (INamedTypeSymbol)type;
|
||||
named.BuildNamedTypeDisplayName(cx, trapFile, constructUnderlyingTupleType);
|
||||
return;
|
||||
@@ -465,6 +492,20 @@ namespace Semmle.Extraction.CSharp
|
||||
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)
|
||||
@@ -484,6 +525,12 @@ namespace Semmle.Extraction.CSharp
|
||||
return;
|
||||
}
|
||||
|
||||
if (namedType.IsExtension)
|
||||
{
|
||||
namedType.BuildExtensionTypeDisplayName(cx, trapFile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (namedType.IsAnonymousType)
|
||||
{
|
||||
namedType.BuildAnonymousName(cx, trapFile);
|
||||
@@ -596,6 +643,84 @@ namespace Semmle.Extraction.CSharp
|
||||
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;
|
||||
}
|
||||
}
|
||||
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.
|
||||
@@ -692,5 +817,35 @@ namespace Semmle.Extraction.CSharp
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
return Parameter.Kind.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,22 +54,6 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -32,32 +32,6 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -24,7 +26,7 @@ namespace Semmle.Extraction.CSharp
|
||||
trapFile.WriteUnescaped('\"');
|
||||
}
|
||||
|
||||
public abstract Location? ReportingLocation { get; }
|
||||
public abstract Microsoft.CodeAnalysis.Location? ReportingLocation { get; }
|
||||
|
||||
public abstract TrapStackBehaviour TrapStackBehaviour { get; }
|
||||
|
||||
@@ -65,6 +67,48 @@ namespace Semmle.Extraction.CSharp
|
||||
}
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
|
||||
private bool IsExplicitDelegateInvokeCall() => Kind == ExprKind.DELEGATE_INVOCATION && Context.GetModel(Syntax.Expression).GetSymbolInfo(Syntax.Expression).Symbol is IMethodSymbol m && m.MethodKind == MethodKind.DelegateInvoke;
|
||||
|
||||
private bool IsOperatorCall() => Kind == ExprKind.OPERATOR_INVOCATION;
|
||||
|
||||
private bool IsValidMemberAccessKind()
|
||||
{
|
||||
return Kind == ExprKind.METHOD_INVOCATION ||
|
||||
IsEventDelegateCall() ||
|
||||
IsExplicitDelegateInvokeCall() ||
|
||||
IsOperatorCall();
|
||||
}
|
||||
|
||||
protected override void PopulateExpression(TextWriter trapFile)
|
||||
{
|
||||
if (IsNameof(Syntax))
|
||||
@@ -37,7 +47,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
var target = TargetSymbol;
|
||||
switch (Syntax.Expression)
|
||||
{
|
||||
case MemberAccessExpressionSyntax memberAccess when Kind == ExprKind.METHOD_INVOCATION || IsEventDelegateCall() || IsExplicitDelegateInvokeCall():
|
||||
case MemberAccessExpressionSyntax memberAccess when IsValidMemberAccessKind():
|
||||
memberName = memberAccess.Name.Identifier.Text;
|
||||
if (Syntax.Expression.Kind() == SyntaxKind.SimpleMemberAccessExpression)
|
||||
// Qualified method call; `x.M()`
|
||||
@@ -113,14 +123,24 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
|
||||
public SymbolInfo SymbolInfo => info.SymbolInfo;
|
||||
|
||||
private static bool IsOperatorLikeCall(ExpressionNodeInfo info)
|
||||
{
|
||||
return info.SymbolInfo.Symbol is IMethodSymbol method &&
|
||||
method.TryGetExtensionMethod()?.MethodKind == MethodKind.UserDefinedOperator;
|
||||
}
|
||||
|
||||
public IMethodSymbol? TargetSymbol
|
||||
{
|
||||
get
|
||||
{
|
||||
var si = SymbolInfo;
|
||||
|
||||
if (si.Symbol is not null)
|
||||
return si.Symbol as IMethodSymbol;
|
||||
if (si.Symbol is ISymbol symbol)
|
||||
{
|
||||
var method = symbol as IMethodSymbol;
|
||||
// Case for compiler-generated extension methods.
|
||||
return method?.TryGetExtensionMethod() ?? method;
|
||||
}
|
||||
|
||||
if (si.CandidateReason == CandidateReason.OverloadResolutionFailure)
|
||||
{
|
||||
@@ -196,15 +216,25 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
|
||||
|
||||
private static ExprKind GetKind(ExpressionNodeInfo info)
|
||||
{
|
||||
return IsNameof((InvocationExpressionSyntax)info.Node)
|
||||
? ExprKind.NAMEOF
|
||||
: IsDelegateLikeCall(info)
|
||||
? IsDelegateInvokeCall(info)
|
||||
? ExprKind.DELEGATE_INVOCATION
|
||||
: ExprKind.FUNCTION_POINTER_INVOCATION
|
||||
: IsLocalFunctionInvocation(info)
|
||||
? ExprKind.LOCAL_FUNCTION_INVOCATION
|
||||
: ExprKind.METHOD_INVOCATION;
|
||||
if (IsNameof((InvocationExpressionSyntax)info.Node))
|
||||
{
|
||||
return ExprKind.NAMEOF;
|
||||
}
|
||||
if (IsDelegateLikeCall(info))
|
||||
{
|
||||
return IsDelegateInvokeCall(info)
|
||||
? ExprKind.DELEGATE_INVOCATION
|
||||
: ExprKind.FUNCTION_POINTER_INVOCATION;
|
||||
}
|
||||
if (IsLocalFunctionInvocation(info))
|
||||
{
|
||||
return ExprKind.LOCAL_FUNCTION_INVOCATION;
|
||||
}
|
||||
if (IsOperatorLikeCall(info))
|
||||
{
|
||||
return ExprKind.OPERATOR_INVOCATION;
|
||||
}
|
||||
return ExprKind.METHOD_INVOCATION;
|
||||
}
|
||||
|
||||
private static bool IsNameof(InvocationExpressionSyntax syntax)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for parameter entities.
|
||||
/// </summary>
|
||||
internal interface IParameter : IEntity
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,28 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
protected Method(Context cx, IMethodSymbol init)
|
||||
: base(cx, init) { }
|
||||
|
||||
private SyntheticExtensionParameter? SyntheticParameter { get; set; }
|
||||
|
||||
private int SynthesizeExtensionParameter()
|
||||
{
|
||||
// Synthesize implicit parameter for extension methods declared using extension(...) syntax.
|
||||
if (Symbol.ContainingSymbol is INamedTypeSymbol type &&
|
||||
type.IsExtension && type.ExtensionParameter is IParameterSymbol parameter &&
|
||||
!string.IsNullOrEmpty(parameter.Name) && !Symbol.IsStatic)
|
||||
{
|
||||
var originalSyntheticParam = OriginalDefinition.SyntheticParameter;
|
||||
SyntheticParameter = SyntheticExtensionParameter.Create(Context, this, parameter, originalSyntheticParam);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void PopulateParameters()
|
||||
{
|
||||
var originalMethod = OriginalDefinition;
|
||||
var positionOffset = SynthesizeExtensionParameter();
|
||||
|
||||
IEnumerable<IParameterSymbol> parameters = Symbol.Parameters;
|
||||
IEnumerable<IParameterSymbol> originalParameters = originalMethod.Symbol.Parameters;
|
||||
|
||||
@@ -24,8 +43,8 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
var original = SymbolEqualityComparer.Default.Equals(p.paramSymbol, p.originalParam)
|
||||
? null
|
||||
: Parameter.Create(Context, p.originalParam, originalMethod);
|
||||
Parameter.Create(Context, p.paramSymbol, this, original);
|
||||
: Parameter.Create(Context, p.originalParam, originalMethod, null, positionOffset);
|
||||
Parameter.Create(Context, p.paramSymbol, this, original, positionOffset);
|
||||
}
|
||||
|
||||
if (Symbol.IsVararg)
|
||||
@@ -302,9 +321,9 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
/// <summary>
|
||||
/// Whether this method has unbound type parameters.
|
||||
/// </summary>
|
||||
public bool IsUnboundGeneric => IsGeneric && SymbolEqualityComparer.Default.Equals(Symbol.ConstructedFrom, Symbol);
|
||||
public bool IsUnboundGeneric => Symbol.IsUnboundGenericMethod();
|
||||
|
||||
public bool IsBoundGeneric => IsGeneric && !IsUnboundGeneric;
|
||||
public bool IsBoundGeneric => Symbol.IsBoundGenericMethod();
|
||||
|
||||
protected IMethodSymbol ConstructedFromSymbol => Symbol.ConstructedFrom;
|
||||
|
||||
|
||||
@@ -23,7 +23,11 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
? Symbol.ContainingType.GetSymbolLocation()
|
||||
: BodyDeclaringSymbol.GetSymbolLocation();
|
||||
|
||||
public override bool NeedsPopulation => base.NeedsPopulation || IsCompilerGeneratedDelegate();
|
||||
public override bool NeedsPopulation =>
|
||||
(base.NeedsPopulation || IsCompilerGeneratedDelegate()) &&
|
||||
// Exclude compiler-generated extension methods. A call to such a method
|
||||
// is replaced by a call to the defining extension method.
|
||||
!Symbol.IsCompilerGeneratedExtensionMethod();
|
||||
|
||||
public override void Populate(TextWriter trapFile)
|
||||
{
|
||||
|
||||
@@ -7,16 +7,23 @@ using Semmle.Extraction.CSharp.Populators;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
internal class Parameter : CachedSymbol<IParameterSymbol>, IExpressionParentEntity
|
||||
internal class Parameter : CachedSymbol<IParameterSymbol>, IExpressionParentEntity, IParameter
|
||||
{
|
||||
protected IEntity? Parent { get; set; }
|
||||
protected Parameter Original { get; }
|
||||
private int PositionOffset { get; set; }
|
||||
|
||||
protected Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original)
|
||||
private Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original, int positionOffset)
|
||||
: base(cx, init)
|
||||
{
|
||||
Parent = parent;
|
||||
Original = original ?? this;
|
||||
PositionOffset = positionOffset;
|
||||
}
|
||||
|
||||
protected Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original)
|
||||
: this(cx, init, parent, original, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public override Microsoft.CodeAnalysis.Location ReportingLocation => Symbol.GetSymbolLocation();
|
||||
@@ -32,46 +39,18 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
RefReadOnly = 6
|
||||
}
|
||||
|
||||
protected virtual int Ordinal => Symbol.Ordinal;
|
||||
protected virtual int Ordinal => Symbol.Ordinal + PositionOffset;
|
||||
|
||||
private Kind ParamKind
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Symbol.RefKind)
|
||||
{
|
||||
case RefKind.Out:
|
||||
return Kind.Out;
|
||||
case RefKind.Ref:
|
||||
return Kind.Ref;
|
||||
case RefKind.In:
|
||||
return Kind.In;
|
||||
case RefKind.RefReadOnlyParameter:
|
||||
return Kind.RefReadOnly;
|
||||
default:
|
||||
if (Symbol.IsParams)
|
||||
return Kind.Params;
|
||||
|
||||
if (Ordinal == 0)
|
||||
{
|
||||
if (Symbol.ContainingSymbol is IMethodSymbol method && method.IsExtensionMethod)
|
||||
return Kind.This;
|
||||
}
|
||||
return Kind.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Parameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null)
|
||||
public static Parameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null, int positionOffset = 0)
|
||||
{
|
||||
var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param);
|
||||
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, parent, original));
|
||||
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, parent, original, positionOffset));
|
||||
}
|
||||
|
||||
public static Parameter Create(Context cx, IParameterSymbol param)
|
||||
{
|
||||
var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param);
|
||||
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, null, null));
|
||||
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, null, null, 0));
|
||||
}
|
||||
|
||||
public override void WriteId(EscapingTextWriter trapFile)
|
||||
@@ -79,6 +58,9 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
if (Parent is null)
|
||||
Parent = Method.Create(Context, Symbol.ContainingSymbol as IMethodSymbol);
|
||||
|
||||
if (Parent is null && Symbol.ContainingSymbol is INamedTypeSymbol type && type.IsExtension)
|
||||
Parent = Type.Create(Context, type);
|
||||
|
||||
if (Parent is null)
|
||||
throw new InternalError(Symbol, "Couldn't get parent of symbol.");
|
||||
|
||||
@@ -113,7 +95,8 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
Context.ModelError(Symbol, "Inconsistent parameter declaration");
|
||||
|
||||
var type = Type.Create(Context, Symbol.Type);
|
||||
trapFile.@params(this, Name, type.TypeRef, Ordinal, ParamKind, Parent!, Original);
|
||||
var kind = Symbol.GetParameterKind();
|
||||
trapFile.@params(this, Name, type.TypeRef, Ordinal, kind, Parent!, Original);
|
||||
|
||||
if (Context.OnlyScaffold)
|
||||
{
|
||||
@@ -194,11 +177,11 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
return syntax?.Default;
|
||||
}
|
||||
|
||||
private class ParameterFactory : CachedEntityFactory<(IParameterSymbol, IEntity?, Parameter?), Parameter>
|
||||
private class ParameterFactory : CachedEntityFactory<(IParameterSymbol, IEntity?, Parameter?, int), Parameter>
|
||||
{
|
||||
public static ParameterFactory Instance { get; } = new ParameterFactory();
|
||||
|
||||
public override Parameter Create(Context cx, (IParameterSymbol, IEntity?, Parameter?) init) => new Parameter(cx, init.Item1, init.Item2, init.Item3);
|
||||
public override Parameter Create(Context cx, (IParameterSymbol, IEntity?, Parameter?, int) init) => new Parameter(cx, init.Item1, init.Item2, init.Item3, init.Item4);
|
||||
}
|
||||
|
||||
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Synthetic parameter for extension methods declared using the extension syntax.
|
||||
/// That is, we add a synthetic parameter `s` to `IsValid` in the following example:
|
||||
/// extension(string s) {
|
||||
/// public bool IsValid() { ... }
|
||||
/// }
|
||||
///
|
||||
/// Note, that we use the characteristics of the parameter of the extension type
|
||||
/// to populate the database.
|
||||
/// </summary>
|
||||
internal class SyntheticExtensionParameter : FreshEntity, IParameter
|
||||
{
|
||||
private Method ExtensionMethod { get; }
|
||||
private IParameterSymbol ExtensionParameter { get; }
|
||||
private SyntheticExtensionParameter Original { get; }
|
||||
|
||||
private SyntheticExtensionParameter(Context cx, Method method, IParameterSymbol parameter, SyntheticExtensionParameter? original) : base(cx)
|
||||
{
|
||||
ExtensionMethod = method;
|
||||
ExtensionParameter = parameter;
|
||||
Original = original ?? this;
|
||||
}
|
||||
|
||||
private static int Ordinal => 0;
|
||||
|
||||
private string Name => ExtensionParameter.Name;
|
||||
|
||||
private bool IsSourceDeclaration => ExtensionMethod.Symbol.IsSourceDeclaration();
|
||||
|
||||
protected override void Populate(TextWriter trapFile)
|
||||
{
|
||||
PopulateNullability(trapFile, ExtensionParameter.GetAnnotatedType());
|
||||
PopulateRefKind(trapFile, ExtensionParameter.RefKind);
|
||||
|
||||
var type = Type.Create(Context, ExtensionParameter.Type);
|
||||
var kind = ExtensionParameter.GetParameterKind();
|
||||
trapFile.@params(this, Name, type.TypeRef, Ordinal, kind, ExtensionMethod, Original);
|
||||
|
||||
if (Context.OnlyScaffold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Context.ExtractLocation(ExtensionParameter))
|
||||
{
|
||||
var locations = Context.GetLocations(ExtensionParameter);
|
||||
WriteLocationsToTrap(trapFile.param_location, this, locations);
|
||||
}
|
||||
|
||||
if (IsSourceDeclaration)
|
||||
{
|
||||
foreach (var syntax in ExtensionParameter.DeclaringSyntaxReferences
|
||||
.Select(d => d.GetSyntax())
|
||||
.OfType<ParameterSyntax>()
|
||||
.Where(s => s.Type is not null))
|
||||
{
|
||||
TypeMention.Create(Context, syntax.Type!, this, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SyntheticExtensionParameter Create(Context cx, Method method, IParameterSymbol parameter, SyntheticExtensionParameter? original)
|
||||
{
|
||||
var p = new SyntheticExtensionParameter(cx, method, parameter, original);
|
||||
p.TryPopulate();
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,8 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
public static NamedType Create(Context cx, INamedTypeSymbol type) =>
|
||||
NamedTypeFactory.Instance.CreateEntityFromSymbol(cx, type);
|
||||
|
||||
public NamedType OriginalDefinition => Create(Context, Symbol.OriginalDefinition);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a named type entity from a tuple type. Unlike <see cref="Create"/>, this
|
||||
/// will create an entity for the underlying `System.ValueTuple` struct.
|
||||
@@ -90,6 +92,25 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
trapFile.anonymous_types(this);
|
||||
}
|
||||
|
||||
if (Symbol.IsExtension && Symbol.ExtensionParameter is IParameterSymbol parameter)
|
||||
{
|
||||
// For some reason an extension type has a receiver parameter with an empty name
|
||||
// even when there is no parameter.
|
||||
if (!string.IsNullOrEmpty(parameter.Name))
|
||||
{
|
||||
var originalType = OriginalDefinition;
|
||||
// In case this is a constructed generic, we also need to create the unbound parameter.
|
||||
var originalParameter = SymbolEqualityComparer.Default.Equals(Symbol, originalType.Symbol.ExtensionParameter) || originalType.Symbol.ExtensionParameter is null
|
||||
? null
|
||||
: Parameter.Create(Context, originalType.Symbol.ExtensionParameter, originalType);
|
||||
Parameter.Create(Context, parameter, this, originalParameter);
|
||||
}
|
||||
|
||||
// Use the parameter type as the receiver type.
|
||||
var receiverType = Type.Create(Context, parameter.Type).TypeRef;
|
||||
trapFile.extension_receiver_type(this, receiverType);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Lazy<Type[]> typeArgumentsLazy;
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
case TypeKind.Pointer: return Kinds.TypeKind.POINTER;
|
||||
case TypeKind.FunctionPointer: return Kinds.TypeKind.FUNCTION_POINTER;
|
||||
case TypeKind.Error: return Kinds.TypeKind.UNKNOWN;
|
||||
case TypeKind.Extension: return Kinds.TypeKind.EXTENSION;
|
||||
default:
|
||||
cx.ModelError(Symbol, $"Unhandled type kind '{Symbol.TypeKind}'");
|
||||
return Kinds.TypeKind.UNKNOWN;
|
||||
@@ -366,7 +367,7 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
private DelegateTypeParameter(Context cx, IParameterSymbol init, IEntity parent, Parameter? original)
|
||||
: base(cx, init, parent, original) { }
|
||||
|
||||
public static new DelegateTypeParameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null) =>
|
||||
public static DelegateTypeParameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null) =>
|
||||
// We need to use a different cache key than `param` to avoid mixing up
|
||||
// `DelegateTypeParameter`s and `Parameter`s
|
||||
DelegateTypeParameterFactory.Instance.CreateEntity(cx, (typeof(DelegateTypeParameter), new SymbolEqualityWrapper(param)), (param, parent, original));
|
||||
|
||||
@@ -38,5 +38,6 @@ namespace Semmle.Extraction.Kinds
|
||||
TUPLE = 32,
|
||||
FUNCTION_POINTER = 33,
|
||||
INLINE_ARRAY = 34,
|
||||
EXTENSION = 35
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,9 @@ namespace Semmle.Extraction.CSharp
|
||||
internal static void extend(this TextWriter trapFile, Type type, Type super) =>
|
||||
trapFile.WriteTuple("extend", type, super);
|
||||
|
||||
internal static void extension_receiver_type(this TextWriter trapFile, Type @extension, Type receiverType) =>
|
||||
trapFile.WriteTuple("extension_receiver_type", extension, receiverType);
|
||||
|
||||
internal static void anonymous_types(this TextWriter trapFile, Type type) =>
|
||||
trapFile.WriteTuple("anonymous_types", type);
|
||||
|
||||
@@ -292,10 +295,10 @@ namespace Semmle.Extraction.CSharp
|
||||
internal static void overrides(this TextWriter trapFile, Method overriding, Method overridden) =>
|
||||
trapFile.WriteTuple("overrides", overriding, overridden);
|
||||
|
||||
internal static void param_location(this TextWriter trapFile, Parameter param, Location location) =>
|
||||
internal static void param_location(this TextWriter trapFile, IParameter param, Location location) =>
|
||||
trapFile.WriteTuple("param_location", param, location);
|
||||
|
||||
internal static void @params(this TextWriter trapFile, Parameter param, string name, Type type, int child, Parameter.Kind mode, IEntity method, Parameter originalDefinition) =>
|
||||
internal static void @params(this TextWriter trapFile, IParameter param, string name, Type type, int child, Parameter.Kind mode, IEntity method, IParameter originalDefinition) =>
|
||||
trapFile.WriteTuple("params", param, name, type, child, (int)mode, method, originalDefinition);
|
||||
|
||||
internal static void parent_namespace(this TextWriter trapFile, IEntity type, Namespace parent) =>
|
||||
|
||||
@@ -119,5 +119,28 @@ namespace Semmle.Util
|
||||
/// </summary>
|
||||
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> items) where T : class =>
|
||||
items.Where(i => i is not null)!;
|
||||
|
||||
/// <summary>
|
||||
/// Splits the sequence at the given index.
|
||||
/// </summary>
|
||||
public static (IEnumerable<T>, IEnumerable<T>) SplitAt<T>(this IEnumerable<T> items, int index)
|
||||
{
|
||||
var left = new List<T>();
|
||||
var right = new List<T>();
|
||||
var i = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (i < index)
|
||||
{
|
||||
left.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
right.Add(item);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return (left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user