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.Util; namespace Semmle.Extraction.CSharp.Entities { internal class Constructor : Method { private readonly List declaringReferenceSyntax; private Constructor(Context cx, IMethodSymbol init) : base(cx, init) { declaringReferenceSyntax = Symbol.DeclaringSyntaxReferences .Select(r => r.GetSyntax()) .ToList(); } public override void Populate(TextWriter trapFile) { PopulateMethod(trapFile); PopulateModifiers(trapFile); ContainingType!.PopulateGenerics(); trapFile.constructors(this, Symbol.ContainingType.Name, ContainingType, (Constructor)OriginalDefinition); if (Symbol.IsImplicitlyDeclared) { var lineCounts = new LineCounts() { Total = 2, Code = 1, Comment = 0 }; trapFile.numlines(this, lineCounts); } ExtractCompilerGenerated(trapFile); if (Context.OnlyScaffold) { return; } if (MakeSynthetic) { // Create a synthetic empty body for primary and default constructors. Statements.SyntheticEmptyBlock.Create(Context, this, 0, Location); } if (Context.ExtractLocation(Symbol) && (!IsDefault || IsBestSourceLocation)) { WriteLocationToTrap(trapFile.constructor_location, this, Location); } } protected override void ExtractInitializers(TextWriter trapFile) { // Do not extract initializers for constructed types. // Extract initializers for constructors with a body, primary constructors // and default constructors for classes and structs declared in source code. if (Block is null && ExpressionBody is null && !MakeSynthetic || Context.OnlyScaffold) { return; } if (OrdinaryConstructorSyntax?.Initializer is ConstructorInitializerSyntax initializer) { ITypeSymbol initializerType; var initializerInfo = Context.GetSymbolInfo(initializer); switch (initializer.Kind()) { case SyntaxKind.BaseConstructorInitializer: initializerType = Symbol.ContainingType.BaseType!; ExtractObjectInitCall(trapFile); break; case SyntaxKind.ThisConstructorInitializer: initializerType = Symbol.ContainingType; break; default: Context.ModelError(initializer, "Unknown initializer"); return; } ExtractSourceInitializer(trapFile, initializerType, (IMethodSymbol?)initializerInfo.Symbol, initializer.ArgumentList, initializer.ThisOrBaseKeyword.GetLocation()); } else if (PrimaryBase is PrimaryConstructorBaseTypeSyntax primaryInitializer) { var primaryInfo = Context.GetSymbolInfo(primaryInitializer); var primarySymbol = primaryInfo.Symbol; ExtractObjectInitCall(trapFile); ExtractSourceInitializer(trapFile, primarySymbol?.ContainingType, (IMethodSymbol?)primarySymbol, primaryInitializer.ArgumentList, primaryInitializer.GetLocation()); } else if (Symbol.MethodKind is MethodKind.Constructor) { ExtractObjectInitCall(trapFile); var baseType = Symbol.ContainingType.BaseType; if (baseType is null) { if (Symbol.ContainingType.SpecialType != SpecialType.System_Object) { Context.ModelError(Symbol, "Unable to resolve base type in implicit constructor initializer"); } return; } var baseConstructor = baseType.InstanceConstructors.FirstOrDefault(c => c.Arity is 0); if (baseConstructor is null) { Context.ModelError(Symbol, "Unable to resolve implicit constructor initializer call"); return; } var baseConstructorTarget = Create(Context, baseConstructor); var info = new ExpressionInfo(Context, AnnotatedTypeSymbol.CreateNotAnnotated(baseType), Location, Kinds.ExprKind.CONSTRUCTOR_INIT, this, -1, isCompilerGenerated: true, null); trapFile.expr_call(new Expression(info), baseConstructorTarget); } } private void ExtractObjectInitCall(TextWriter trapFile) { var target = ObjectInitMethod.Create(Context, ContainingType!); var type = Context.Compilation.GetSpecialType(SpecialType.System_Void); var info = new ExpressionInfo(Context, AnnotatedTypeSymbol.CreateNotAnnotated(type), Location, Kinds.ExprKind.METHOD_INVOCATION, this, -2, isCompilerGenerated: true, null); var obinitCall = new Expression(info); trapFile.expr_call(obinitCall, target); Expressions.This.CreateImplicit(Context, Symbol.ContainingType, Location, obinitCall, -1); } private void ExtractSourceInitializer(TextWriter trapFile, ITypeSymbol? type, IMethodSymbol? symbol, ArgumentListSyntax arguments, Microsoft.CodeAnalysis.Location location) { var initInfo = new ExpressionInfo(Context, AnnotatedTypeSymbol.CreateNotAnnotated(type), Context.CreateLocation(location), Kinds.ExprKind.CONSTRUCTOR_INIT, this, -1, false, null); var init = new Expression(initInfo); var target = Constructor.Create(Context, symbol); if (target is null) { Context.ModelError(Symbol, "Unable to resolve call"); return; } trapFile.expr_call(init, target); init.PopulateArguments(trapFile, arguments, 0); } private ConstructorDeclarationSyntax? OrdinaryConstructorSyntax => declaringReferenceSyntax .OfType() .FirstOrDefault(); private TypeDeclarationSyntax? PrimaryConstructorSyntax => declaringReferenceSyntax .OfType() .FirstOrDefault(t => t is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax); private PrimaryConstructorBaseTypeSyntax? PrimaryBase => PrimaryConstructorSyntax? .BaseList? .Types .OfType() .FirstOrDefault(); private bool IsPrimary => PrimaryConstructorSyntax is not null; // This is a default constructor in a class or struct declared in source. private bool IsDefault => Symbol.IsImplicitlyDeclared && Symbol.ContainingType.FromSource() && Symbol.ContainingType.TypeKind is TypeKind.Class or TypeKind.Struct && Symbol.ContainingType.IsSourceDeclaration() && !Symbol.ContainingType.IsAnonymousType; /// /// Returns true if we consider the reporting location of this constructor entity the best /// location of the constructor. /// For partial classes with default constructors, Roslyn consider each partial class declaration /// as the possible location for the implicit default constructor. /// private bool IsBestSourceLocation => ReportingLocation is not null && Context.IsLocationInContext(ReportingLocation); private bool MakeSynthetic => (IsPrimary || (IsDefault && IsBestSourceLocation)) && !Context.OnlyScaffold; [return: NotNullIfNotNull(nameof(constructor))] public static new Constructor? Create(Context cx, IMethodSymbol? constructor) { if (constructor is null) return null; switch (constructor.MethodKind) { case MethodKind.StaticConstructor: case MethodKind.Constructor: return ConstructorFactory.Instance.CreateEntityFromSymbol(cx, constructor); default: throw new InternalError(constructor, "Attempt to create a Constructor from a symbol that isn't a constructor"); } } public override void WriteId(EscapingTextWriter trapFile) { if (!SymbolEqualityComparer.Default.Equals(Symbol, Symbol.OriginalDefinition)) { trapFile.WriteSubId(ContainingType!); trapFile.Write("."); trapFile.WriteSubId(OriginalDefinition); trapFile.Write(";constructor"); return; } if (Symbol.IsStatic) trapFile.Write("static"); trapFile.WriteSubId(ContainingType!); AddParametersToId(Context, trapFile, Symbol); trapFile.Write(";constructor"); } public override Microsoft.CodeAnalysis.Location? FullLocation => ReportingLocation; public override Microsoft.CodeAnalysis.Location? ReportingLocation { get { if (OrdinaryConstructorSyntax is not null) { return OrdinaryConstructorSyntax.Identifier.GetLocation(); } if (PrimaryConstructorSyntax is not null) { return PrimaryConstructorSyntax.Identifier.GetLocation(); } if (Symbol.IsImplicitlyDeclared) { var best = Symbol.Locations.Where(l => l.IsInSource).BestOrDefault(); return best ?? ContainingType!.ReportingLocation; } return Symbol.ContainingType.Locations.FirstOrDefault(); } } private class ConstructorFactory : CachedEntityFactory { public static ConstructorFactory Instance { get; } = new ConstructorFactory(); public override Constructor Create(Context cx, IMethodSymbol init) => new Constructor(cx, init); } } }