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 @@ -5,6 +5,7 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Semmle.Extraction.CSharp.Entities;
using Semmle.Extraction.CSharp.Entities.Statements;

namespace Semmle.Extraction.CSharp
{
Expand Down Expand Up @@ -164,6 +165,7 @@ public static void BuildTypeId(this ITypeSymbol type, Context cx, EscapingTextWr
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,16 @@ private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol f
public static IEnumerable<IFieldSymbol?> GetTupleElementsMaybeNull(this INamedTypeSymbol type) =>
type.TupleElements;

private static string GetExtensionTypeName(this INamedTypeSymbol named, Context cx)
{
var type = named.ExtensionParameter?.Type.Name;
if (type is null)
{
cx.ModelError(named, "Failed to get extension method type.");
}
return $"extension({type ?? "unknown"})";
}

private static void BuildQualifierAndName(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
{
if (named.ContainingType is not null)
Expand All @@ -289,8 +301,19 @@ private static void BuildQualifierAndName(INamedTypeSymbol named, Context cx, Es
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)
{
var name = GetExtensionTypeName(named, cx);
trapFile.Write(name);
}
else
{
trapFile.Write(named.Name);
}
}

private static void BuildTupleId(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
Expand Down Expand Up @@ -391,6 +414,7 @@ public static void BuildDisplayName(this ITypeSymbol type, Context cx, TextWrite
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 @@ -484,6 +508,13 @@ private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, C
return;
}

if (namedType.IsExtension)
{
var name = GetExtensionTypeName(namedType, cx);
trapFile.Write(name);
return;
}

if (namedType.IsAnonymousType)
{
namedType.BuildAnonymousName(cx, trapFile);
Expand Down Expand Up @@ -596,6 +627,34 @@ public static bool IsSourceDeclaration(this IParameterSymbol parameter)
return true;
}

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

/// <summary>
/// Returns true if this method is a compiler-generated extension method,
/// and outputs the original extension method declaration.
/// </summary>
public static bool TryGetExtensionMethod(this IMethodSymbol method, out IMethodSymbol? declaration)
{
declaration = null;
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 original extension method that maps to this implementation (if any).
declaration = extensions.SelectMany(e => e.GetMembers())
.OfType<IMethodSymbol>()
.FirstOrDefault(m => SymbolEqualityComparer.Default.Equals(m.AssociatedExtensionImplementation, method));
return declaration is not null;
}
return false;
}
/// <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
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ private Invocation(ExpressionNodeInfo info)

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 IsAccessorInvocation() => Kind == ExprKind.ACCESSOR_INVOCATION;

private bool IsValidMemberAccessKind()
{
return Kind == ExprKind.METHOD_INVOCATION ||
IsEventDelegateCall() ||
IsExplicitDelegateInvokeCall() ||
IsOperatorCall() ||
IsAccessorInvocation();
}

protected override void PopulateExpression(TextWriter trapFile)
{
if (IsNameof(Syntax))
Expand All @@ -37,7 +50,7 @@ protected override void PopulateExpression(TextWriter trapFile)
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()`
Expand Down Expand Up @@ -113,14 +126,39 @@ private static bool IsDynamicCall(ExpressionNodeInfo info)

public SymbolInfo SymbolInfo => info.SymbolInfo;

private static bool IsOperatorLikeCall(ExpressionNodeInfo info)
{
return info.SymbolInfo.Symbol is IMethodSymbol method &&
method.TryGetExtensionMethod(out var original) &&
original!.MethodKind == MethodKind.UserDefinedOperator;
}

private static bool IsAccessorLikeInvocation(ExpressionNodeInfo info)
{
return info.SymbolInfo.Symbol is IMethodSymbol method &&
method.TryGetExtensionMethod(out var original) &&
(original!.MethodKind == MethodKind.PropertyGet ||
original!.MethodKind == MethodKind.PropertySet);
}

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.
if (method is not null &&
method.TryGetExtensionMethod(out var original))
{
return original;
}

return method;
}

if (si.CandidateReason == CandidateReason.OverloadResolutionFailure)
{
Expand Down Expand Up @@ -196,15 +234,29 @@ private static bool IsLocalFunctionInvocation(ExpressionNodeInfo info)

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;
}
if (IsAccessorLikeInvocation(info))
{
return ExprKind.ACCESSOR_INVOCATION;
}
return ExprKind.METHOD_INVOCATION;
}

private static bool IsNameof(InvocationExpressionSyntax syntax)
Expand Down
23 changes: 21 additions & 2 deletions csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,37 @@ internal abstract class Method : CachedSymbol<IMethodSymbol>, IExpressionParentE
protected Method(Context cx, IMethodSymbol init)
: base(cx, init) { }

private IParameter? 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 = ImplicitExtensionParameter.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;

foreach (var p in parameters.Zip(originalParameters, (paramSymbol, originalParam) => new { paramSymbol, originalParam }))
{
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ protected OrdinaryMethod(Context cx, IMethodSymbol init)
? 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)
{
Expand Down
Loading
Loading