using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Semmle.Extraction.CSharp.Populators; namespace Semmle.Extraction.CSharp.Entities { internal abstract class Method : CachedSymbol, IExpressionParentEntity, IStatementParentEntity, IMethodEntity { protected Method(Context cx, IMethodSymbol init) : base(cx, init) { } protected void PopulateParameters() { var originalMethod = OriginalDefinition; IEnumerable parameters = Symbol.Parameters; IEnumerable 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); } if (Symbol.IsVararg) { // Mono decided that "__arglist" should be an explicit parameter, // so now we need to populate it. VarargsParam.Create(Context, this); } } /// /// Extracts constructor initializers. /// protected virtual void ExtractInitializers(TextWriter trapFile) { // Normal methods don't have initializers, // so there's nothing to extract. } protected virtual void PopulateMethodBody(TextWriter trapFile) { if (!IsSourceDeclaration || Context.OnlyScaffold) return; var block = Block; var expr = ExpressionBody; Context.PopulateLater(() => ExtractInitializers(trapFile)); if (block is not null || expr is not null) { Context.PopulateLater( () => { if (block is not null) Statements.Block.Create(Context, block, this, 0); else Expression.Create(Context, expr!, this, 0); NumberOfLines(trapFile, BodyDeclaringSymbol, this); }); } } public static void NumberOfLines(TextWriter trapFile, ISymbol symbol, IEntity callable) { foreach (var decl in symbol.DeclaringSyntaxReferences) { var node = (CSharpSyntaxNode)decl.GetSyntax(); var lineCounts = node.Accept(new AstLineCounter()); if (lineCounts is not null) { trapFile.numlines(callable, lineCounts); } } } protected virtual MethodKind ExplicitlyImplementsKind => MethodKind.Ordinary; public void Overrides(TextWriter trapFile) { foreach (var explicitInterface in Symbol.ExplicitInterfaceImplementations .Where(sym => sym.MethodKind == ExplicitlyImplementsKind) .Select(impl => Type.Create(Context, impl.ContainingType))) { trapFile.explicitly_implements(this, explicitInterface.TypeRef); if (IsSourceDeclaration) { foreach (var syntax in Symbol.DeclaringSyntaxReferences.Select(d => d.GetSyntax()).OfType()) TypeMention.Create(Context, syntax.ExplicitInterfaceSpecifier!.Name, this, explicitInterface); } } if (Symbol.OverriddenMethod is not null && Symbol.OverriddenMethod.ShouldExtractSymbol()) { trapFile.overrides(this, Method.Create(Context, Symbol.OverriddenMethod)); } } /// /// Factored out to share logic between `Method` and `UserOperator`. /// private static void BuildMethodId(Method m, EscapingTextWriter trapFile) { if (!SymbolEqualityComparer.Default.Equals(m.Symbol, m.Symbol.OriginalDefinition)) { if (!SymbolEqualityComparer.Default.Equals(m.Symbol, m.ConstructedFromSymbol)) { trapFile.WriteSubId(Create(m.Context, m.ConstructedFromSymbol)); trapFile.Write('<'); // Encode the nullability of the type arguments in the label. // Type arguments with different nullability can result in // a constructed method with different nullability of its parameters and return type, // so we need to create a distinct database entity for it. trapFile.BuildList(",", m.Symbol.GetAnnotatedTypeArguments(), ta => { ta.Symbol.BuildOrWriteId(m.Context, trapFile, m.Symbol); trapFile.Write((int)ta.Nullability); }); trapFile.Write('>'); } else { trapFile.WriteSubId(m.ContainingType!); trapFile.Write("."); trapFile.WriteSubId(m.OriginalDefinition); } WritePostfix(m, trapFile); return; } m.Symbol.ReturnType.BuildOrWriteId(m.Context, trapFile, m.Symbol); trapFile.Write(" "); trapFile.WriteSubId(m.ContainingType!); AddExplicitInterfaceQualifierToId(m.Context, trapFile, m.Symbol.ExplicitInterfaceImplementations); trapFile.Write("."); trapFile.Write(m.Symbol.Name); if (m.Symbol.IsGenericMethod) { trapFile.Write('`'); trapFile.Write(m.Symbol.TypeParameters.Length); } AddParametersToId(m.Context, trapFile, m.Symbol); WritePostfix(m, trapFile); } private static void WritePostfix(Method m, EscapingTextWriter trapFile) { switch (m.Symbol.MethodKind) { case MethodKind.PropertyGet: trapFile.Write(";getter"); break; case MethodKind.PropertySet: trapFile.Write(";setter"); break; case MethodKind.EventAdd: trapFile.Write(";adder"); break; case MethodKind.EventRaise: trapFile.Write(";raiser"); break; case MethodKind.EventRemove: trapFile.Write(";remover"); break; default: trapFile.Write(";method"); break; } } public override void WriteId(EscapingTextWriter trapFile) { BuildMethodId(this, trapFile); } protected static void AddParametersToId(Context cx, EscapingTextWriter trapFile, IMethodSymbol method) { trapFile.Write('('); var index = 0; var @params = method.Parameters; foreach (var param in @params) { trapFile.WriteSeparator(",", ref index); param.Type.BuildOrWriteId(cx, trapFile, method); trapFile.Write(" "); trapFile.Write(param.Name); switch (param.RefKind) { case RefKind.Out: trapFile.Write(" out"); break; case RefKind.Ref: trapFile.Write(" ref"); break; } } if (method.IsVararg) { trapFile.WriteSeparator(",", ref index); trapFile.Write("__arglist"); } trapFile.Write(')'); } public static void AddExplicitInterfaceQualifierToId(Context cx, EscapingTextWriter trapFile, IEnumerable explicitInterfaceImplementations) { if (explicitInterfaceImplementations.Any()) trapFile.AppendList(",", explicitInterfaceImplementations.Select(impl => cx.CreateEntity(impl.ContainingType))); } public virtual string Name => Symbol.Name; /// /// Creates a method of the appropriate subtype. /// /// /// /// [return: NotNullIfNotNull(nameof(methodDecl))] public static Method? Create(Context cx, IMethodSymbol? methodDecl) { if (methodDecl is null) return null; var methodKind = methodDecl.MethodKind; if (methodKind == MethodKind.ExplicitInterfaceImplementation) { // Retrieve the original method kind if (methodDecl.ExplicitInterfaceImplementations.IsEmpty) { throw new InternalError(methodDecl, "Couldn't get the original method kind for an explicit interface implementation"); } methodKind = methodDecl.ExplicitInterfaceImplementations.Select(m => m.MethodKind).First(); } switch (methodKind) { case MethodKind.StaticConstructor: case MethodKind.Constructor: return Constructor.Create(cx, methodDecl); case MethodKind.ReducedExtension: if (SymbolEqualityComparer.Default.Equals(methodDecl, methodDecl.ConstructedFrom)) { return OrdinaryMethod.Create(cx, methodDecl.ReducedFrom!); } return OrdinaryMethod.Create(cx, methodDecl.ReducedFrom!.Construct(methodDecl.TypeArguments, methodDecl.TypeArgumentNullableAnnotations)); case MethodKind.Ordinary: case MethodKind.DelegateInvoke: return OrdinaryMethod.Create(cx, methodDecl); case MethodKind.Destructor: return Destructor.Create(cx, methodDecl); case MethodKind.PropertyGet: case MethodKind.PropertySet: return Accessor.GetPropertySymbol(methodDecl) is IPropertySymbol prop ? Accessor.Create(cx, methodDecl, prop) : OrdinaryMethod.Create(cx, methodDecl); case MethodKind.EventAdd: case MethodKind.EventRemove: return EventAccessor.GetEventSymbol(methodDecl) is IEventSymbol @event ? EventAccessor.Create(cx, methodDecl, @event) : OrdinaryMethod.Create(cx, methodDecl); case MethodKind.UserDefinedOperator: case MethodKind.BuiltinOperator: return UserOperator.Create(cx, methodDecl); case MethodKind.Conversion: return Conversion.Create(cx, methodDecl); case MethodKind.AnonymousFunction: throw new InternalError(methodDecl, "Attempt to create a lambda"); case MethodKind.LocalFunction: return LocalFunction.Create(cx, methodDecl); default: throw new InternalError(methodDecl, $"Unhandled method '{methodDecl}' of kind '{methodDecl.MethodKind}'"); } } public Method OriginalDefinition => Create(Context, Symbol.OriginalDefinition); public override Microsoft.CodeAnalysis.Location? FullLocation => ReportingLocation; public override bool IsSourceDeclaration => Symbol.IsSourceDeclaration(); /// /// Whether this method has type parameters. /// public bool IsGeneric => Symbol.IsGenericMethod; /// /// Whether this method has unbound type parameters. /// public bool IsUnboundGeneric => IsGeneric && SymbolEqualityComparer.Default.Equals(Symbol.ConstructedFrom, Symbol); public bool IsBoundGeneric => IsGeneric && !IsUnboundGeneric; protected IMethodSymbol ConstructedFromSymbol => Symbol.ConstructedFrom; bool IExpressionParentEntity.IsTopLevelParent => true; bool IStatementParentEntity.IsTopLevelParent => true; protected void PopulateGenerics(TextWriter trapFile) { var isFullyConstructed = IsBoundGeneric; if (IsGeneric) { var child = 0; if (isFullyConstructed) { trapFile.constructed_generic(this, Method.Create(Context, ConstructedFromSymbol)); foreach (var tp in Symbol.GetAnnotatedTypeArguments()) { trapFile.type_arguments(Type.Create(Context, tp.Symbol), child, this); child++; } var nullability = new Nullability(Symbol); if (!nullability.IsOblivious) trapFile.type_nullability(this, NullabilityEntity.Create(Context, nullability)); } else { foreach (var typeParam in Symbol.TypeParameters.Select(tp => TypeParameter.Create(Context, tp))) { trapFile.type_parameters(typeParam, child, this); child++; } } } } public static void ExtractRefReturn(TextWriter trapFile, IMethodSymbol method, IEntity element) { if (method.ReturnsByRef) trapFile.type_annotation(element, Kinds.TypeAnnotation.Ref); if (method.ReturnsByRefReadonly) trapFile.type_annotation(element, Kinds.TypeAnnotation.ReadonlyRef); } protected void PopulateMethod(TextWriter trapFile) { // Common population code for all callables BindComments(); PopulateAttributes(); PopulateParameters(); PopulateMethodBody(trapFile); PopulateGenerics(trapFile); PopulateNullability(trapFile, Symbol.GetAnnotatedReturnType()); } public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.PushesLabel; } }