Skip to content

Commit 0f022d5

Browse files
committed
C#: Extractor support for extension blocks.
1 parent 2bd4cce commit 0f022d5

File tree

10 files changed

+188
-13
lines changed

10 files changed

+188
-13
lines changed

csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ public static void BuildTypeId(this ITypeSymbol type, Context cx, EscapingTextWr
164164
case TypeKind.Enum:
165165
case TypeKind.Delegate:
166166
case TypeKind.Error:
167+
case TypeKind.Extension:
167168
var named = (INamedTypeSymbol)type;
168169
named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType);
169170
return;
@@ -391,6 +392,7 @@ public static void BuildDisplayName(this ITypeSymbol type, Context cx, TextWrite
391392
case TypeKind.Enum:
392393
case TypeKind.Delegate:
393394
case TypeKind.Error:
395+
case TypeKind.Extension:
394396
var named = (INamedTypeSymbol)type;
395397
named.BuildNamedTypeDisplayName(cx, trapFile, constructUnderlyingTupleType);
396398
return;
@@ -484,6 +486,14 @@ private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, C
484486
return;
485487
}
486488

489+
if (namedType.IsExtension)
490+
{
491+
var type = namedType.ExtensionParameter?.Type.Name;
492+
var name = $"extension({type})";
493+
trapFile.Write(TrapExtensions.EncodeString(name));
494+
return;
495+
}
496+
487497
if (namedType.IsAnonymousType)
488498
{
489499
namedType.BuildAnonymousName(cx, trapFile);
@@ -596,6 +606,12 @@ public static bool IsSourceDeclaration(this IParameterSymbol parameter)
596606
return true;
597607
}
598608

609+
/// <summary>
610+
/// Return true if this method is a compiler-generated extension method.
611+
/// </summary>
612+
public static bool IsCompilerGeneratedExtensionMethod(this IMethodSymbol method) =>
613+
method.IsImplicitlyDeclared && method.IsExtensionMethod;
614+
599615
/// <summary>
600616
/// Gets the base type of `symbol`. Unlike `symbol.BaseType`, this excludes effective base
601617
/// types of type parameters as well as `object` base types.

csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedSymbol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public virtual IEnumerable<Location> Locations
107107
/// Bind comments to this symbol.
108108
/// Comments are only bound to source declarations.
109109
/// </summary>
110-
protected void BindComments()
110+
protected virtual void BindComments()
111111
{
112112
if (!Symbol.IsImplicitlyDeclared && IsSourceDeclaration && Symbol.FromSource())
113113
Context.BindComments(this, FullLocation);

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,28 @@ public IMethodSymbol? TargetSymbol
119119
{
120120
var si = SymbolInfo;
121121

122-
if (si.Symbol is not null)
123-
return si.Symbol as IMethodSymbol;
122+
if (si.Symbol is ISymbol symbol)
123+
{
124+
var method = symbol as IMethodSymbol;
125+
// Case for compiler-generated extension methods.
126+
if (method is not null &&
127+
method.IsCompilerGeneratedExtensionMethod() &&
128+
method.ContainingSymbol is INamedTypeSymbol containingType)
129+
{
130+
// Extension types are declared within the same type as the generated
131+
// extension method implementation.
132+
var extensions = containingType.GetMembers()
133+
.OfType<INamedTypeSymbol>()
134+
.Where(t => t.IsExtension);
135+
// Find the original extension method that maps to this implementation (if any).
136+
var original = extensions.SelectMany(e => e.GetMembers())
137+
.OfType<IMethodSymbol>()
138+
.FirstOrDefault(m => SymbolEqualityComparer.Default.Equals(m.AssociatedExtensionImplementation, method));
139+
return original ?? method;
140+
}
141+
142+
return method;
143+
}
124144

125145
if (si.CandidateReason == CandidateReason.OverloadResolutionFailure)
126146
{

csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,19 @@ protected Method(Context cx, IMethodSymbol init)
1616

1717
protected void PopulateParameters()
1818
{
19+
var positionOffset = 0;
1920
var originalMethod = OriginalDefinition;
21+
22+
// Synthesize implicit parameter for extension methods declared using extension(...) syntax.
23+
if (Symbol.ContainingSymbol is INamedTypeSymbol type &&
24+
type.IsExtension && !string.IsNullOrEmpty(type.ExtensionParameter?.Name) &&
25+
Symbol.AssociatedExtensionImplementation is IMethodSymbol associated)
26+
{
27+
// TODO: Check that this works for generics as well. We might need to also take
28+
ImplicitExtensionParameter.Create(Context, this);
29+
positionOffset++;
30+
}
31+
2032
IEnumerable<IParameterSymbol> parameters = Symbol.Parameters;
2133
IEnumerable<IParameterSymbol> originalParameters = originalMethod.Symbol.Parameters;
2234

csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ protected OrdinaryMethod(Context cx, IMethodSymbol init)
2323
? Symbol.ContainingType.GetSymbolLocation()
2424
: BodyDeclaringSymbol.GetSymbolLocation();
2525

26-
public override bool NeedsPopulation => base.NeedsPopulation || IsCompilerGeneratedDelegate();
26+
public override bool NeedsPopulation =>
27+
(base.NeedsPopulation || IsCompilerGeneratedDelegate()) &&
28+
// Exclude compiler-generated extension methods. A call to such a method
29+
// is replaced by a call to the defining extension method.
30+
!Symbol.IsCompilerGeneratedExtensionMethod();
2731

2832
public override void Populate(TextWriter trapFile)
2933
{

csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs

Lines changed: 127 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,25 @@
77

88
namespace Semmle.Extraction.CSharp.Entities
99
{
10-
internal class Parameter : CachedSymbol<IParameterSymbol>, IExpressionParentEntity
10+
// Marker interface for parameter entities.
11+
public interface IParameterEntity : IEntity { }
12+
internal class Parameter : CachedSymbol<IParameterSymbol>, IExpressionParentEntity, IParameterEntity
1113
{
1214
protected IEntity? Parent { get; set; }
1315
protected Parameter Original { get; }
16+
private int PositionOffset { get; set; }
1417

15-
protected Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original)
18+
private Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original, int positionOffset)
1619
: base(cx, init)
1720
{
1821
Parent = parent;
1922
Original = original ?? this;
23+
PositionOffset = positionOffset;
24+
}
25+
26+
protected Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original)
27+
: this(cx, init, parent, original, 0)
28+
{
2029
}
2130

2231
public override Microsoft.CodeAnalysis.Location ReportingLocation => Symbol.GetSymbolLocation();
@@ -32,7 +41,7 @@ public enum Kind
3241
RefReadOnly = 6
3342
}
3443

35-
protected virtual int Ordinal => Symbol.Ordinal;
44+
protected virtual int Ordinal => Symbol.Ordinal + PositionOffset;
3645

3746
private Kind ParamKind
3847
{
@@ -55,30 +64,35 @@ private Kind ParamKind
5564
if (Ordinal == 0)
5665
{
5766
if (Symbol.ContainingSymbol is IMethodSymbol method && method.IsExtensionMethod)
67+
{
5868
return Kind.This;
69+
}
5970
}
6071
return Kind.None;
6172
}
6273
}
6374
}
6475

65-
public static Parameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null)
76+
public static Parameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null, int positionOffset = 0)
6677
{
6778
var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param);
68-
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, parent, original));
79+
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, parent, original, positionOffset));
6980
}
7081

7182
public static Parameter Create(Context cx, IParameterSymbol param)
7283
{
7384
var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param);
74-
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, null, null));
85+
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, null, null, 0));
7586
}
7687

7788
public override void WriteId(EscapingTextWriter trapFile)
7889
{
7990
if (Parent is null)
8091
Parent = Method.Create(Context, Symbol.ContainingSymbol as IMethodSymbol);
8192

93+
if (Parent is null && Symbol.ContainingSymbol is INamedTypeSymbol type && type.IsExtension)
94+
Parent = Type.Create(Context, type);
95+
8296
if (Parent is null)
8397
throw new InternalError(Symbol, "Couldn't get parent of symbol.");
8498

@@ -194,11 +208,11 @@ Symbol.ContainingSymbol is IMethodSymbol ms &&
194208
return syntax?.Default;
195209
}
196210

197-
private class ParameterFactory : CachedEntityFactory<(IParameterSymbol, IEntity?, Parameter?), Parameter>
211+
private class ParameterFactory : CachedEntityFactory<(IParameterSymbol, IEntity?, Parameter?, int), Parameter>
198212
{
199213
public static ParameterFactory Instance { get; } = new ParameterFactory();
200214

201-
public override Parameter Create(Context cx, (IParameterSymbol, IEntity?, Parameter?) init) => new Parameter(cx, init.Item1, init.Item2, init.Item3);
215+
public override Parameter Create(Context cx, (IParameterSymbol, IEntity?, Parameter?, int) init) => new Parameter(cx, init.Item1, init.Item2, init.Item3, init.Item4);
202216
}
203217

204218
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
@@ -243,6 +257,111 @@ private class VarargsTypeFactory : CachedEntityFactory<string?, VarargsType>
243257
}
244258
}
245259

260+
/// <summary>
261+
/// Synthetic parameter for extension methods declared using the extensions syntax.
262+
/// That is, we add a synthetic parameter s to IsValid in the following example:
263+
/// extension(string s) {
264+
/// public bool IsValid() { ... }
265+
/// }
266+
///
267+
/// Note, that we use the characteristics of the parameter of the associated (compiler generated) extension method
268+
/// to populate the database.
269+
/// </summary>
270+
internal class ImplicitExtensionParameter : Parameter
271+
{
272+
private Method ExtensionMethod { get; init; }
273+
274+
private ImplicitExtensionParameter(Context cx, Method method)
275+
#nullable disable warnings
276+
: base(cx, method.Symbol.AssociatedExtensionImplementation.Parameters[0], method, null)
277+
{
278+
ExtensionMethod = method;
279+
}
280+
#nullable restore warnings
281+
282+
protected override int Ordinal => 0;
283+
284+
private Kind ParamKind
285+
{
286+
get
287+
{
288+
switch (Symbol.RefKind)
289+
{
290+
case RefKind.Ref:
291+
return Kind.Ref;
292+
case RefKind.In:
293+
return Kind.In;
294+
case RefKind.RefReadOnlyParameter:
295+
return Kind.RefReadOnly;
296+
default:
297+
return Kind.None;
298+
}
299+
}
300+
}
301+
302+
private string Name => Symbol.Name;
303+
304+
public override bool IsSourceDeclaration => ExtensionMethod.Symbol.IsSourceDeclaration();
305+
306+
/// <summary>
307+
/// Bind comments to this symbol.
308+
/// Comments are only bound to source declarations.
309+
/// </summary>
310+
protected override void BindComments()
311+
{
312+
if (IsSourceDeclaration && Symbol.FromSource())
313+
Context.BindComments(this, FullLocation);
314+
}
315+
316+
public override void Populate(TextWriter trapFile)
317+
{
318+
PopulateAttributes();
319+
PopulateNullability(trapFile, Symbol.GetAnnotatedType());
320+
PopulateRefKind(trapFile, Symbol.RefKind);
321+
322+
var type = Type.Create(Context, Symbol.Type);
323+
trapFile.@params(this, Name, type.TypeRef, Ordinal, ParamKind, Parent!, this);
324+
325+
if (Context.OnlyScaffold)
326+
{
327+
return;
328+
}
329+
330+
if (Context.ExtractLocation(Symbol))
331+
{
332+
var locations = Context.GetLocations(Symbol);
333+
WriteLocationsToTrap(trapFile.param_location, this, locations);
334+
}
335+
336+
if (!IsSourceDeclaration || !Symbol.FromSource())
337+
return;
338+
339+
BindComments();
340+
341+
if (IsSourceDeclaration)
342+
{
343+
foreach (var syntax in Symbol.DeclaringSyntaxReferences
344+
.Select(d => d.GetSyntax())
345+
.OfType<ParameterSyntax>()
346+
.Where(s => s.Type is not null))
347+
{
348+
TypeMention.Create(Context, syntax.Type!, this, type);
349+
}
350+
}
351+
}
352+
353+
public static ImplicitExtensionParameter Create(Context cx, Method method) => ImplicitExtensionParameterFactory.Instance.CreateEntity(cx, typeof(ImplicitExtensionParameter), method);
354+
355+
private class ImplicitExtensionParameterFactory : CachedEntityFactory<Method, ImplicitExtensionParameter>
356+
{
357+
public static ImplicitExtensionParameterFactory Instance { get; } = new ImplicitExtensionParameterFactory();
358+
359+
public override ImplicitExtensionParameter Create(Context cx, Method init) => new ImplicitExtensionParameter(cx, init);
360+
}
361+
362+
363+
}
364+
246365
internal class VarargsParam : Parameter
247366
{
248367
#nullable disable warnings

csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public override void Populate(TextWriter trapFile)
103103
trapFile.expr_access(access, this);
104104
if (!Symbol.IsStatic)
105105
{
106+
// TODO: What about the containing type for extensions?
106107
This.CreateImplicit(Context, Symbol.ContainingType, Location, access, -1);
107108
}
108109
});

csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public Kinds.TypeKind GetTypeKind(Context cx, bool constructUnderlyingTupleType)
105105
case TypeKind.Pointer: return Kinds.TypeKind.POINTER;
106106
case TypeKind.FunctionPointer: return Kinds.TypeKind.FUNCTION_POINTER;
107107
case TypeKind.Error: return Kinds.TypeKind.UNKNOWN;
108+
case TypeKind.Extension: return Kinds.TypeKind.EXTENSION;
108109
default:
109110
cx.ModelError(Symbol, $"Unhandled type kind '{Symbol.TypeKind}'");
110111
return Kinds.TypeKind.UNKNOWN;
@@ -366,7 +367,7 @@ private class DelegateTypeParameter : Parameter
366367
private DelegateTypeParameter(Context cx, IParameterSymbol init, IEntity parent, Parameter? original)
367368
: base(cx, init, parent, original) { }
368369

369-
public static new DelegateTypeParameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null) =>
370+
public static DelegateTypeParameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null) =>
370371
// We need to use a different cache key than `param` to avoid mixing up
371372
// `DelegateTypeParameter`s and `Parameter`s
372373
DelegateTypeParameterFactory.Instance.CreateEntity(cx, (typeof(DelegateTypeParameter), new SymbolEqualityWrapper(param)), (param, parent, original));

csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public override Type ContainingType
6868
/// <returns></returns>
6969
private bool IsImplicitOperator(out ITypeSymbol containingType)
7070
{
71+
// TODO: Do we need to handle extension operators here?
7172
containingType = Symbol.ContainingType;
7273
if (containingType is not null)
7374
{

csharp/extractor/Semmle.Extraction.CSharp/Kinds/TypeKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@ public enum TypeKind
3838
TUPLE = 32,
3939
FUNCTION_POINTER = 33,
4040
INLINE_ARRAY = 34,
41+
EXTENSION = 35
4142
}
4243
}

0 commit comments

Comments
 (0)