using System; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Semmle.Extraction.CSharp.Populators; namespace Semmle.Extraction.CSharp.Entities { internal class Parameter : CachedSymbol, IExpressionParentEntity { protected IEntity? Parent { get; set; } protected Parameter Original { get; } protected Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original) : base(cx, init) { Parent = parent; Original = original ?? this; } public override Microsoft.CodeAnalysis.Location ReportingLocation => Symbol.GetSymbolLocation(); public enum Kind { None = 0, Ref = 1, Out = 2, Params = 3, This = 4, In = 5, RefReadOnly = 6 } protected virtual int Ordinal => Symbol.Ordinal; 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) { var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param); return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, parent, original)); } public static Parameter Create(Context cx, IParameterSymbol param) { var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param); return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, null, null)); } public override void WriteId(EscapingTextWriter trapFile) { if (Parent is null) Parent = Method.Create(Context, Symbol.ContainingSymbol as IMethodSymbol); if (Parent is null) throw new InternalError(Symbol, "Couldn't get parent of symbol."); trapFile.WriteSubId(Parent); trapFile.Write('_'); trapFile.Write(Ordinal); trapFile.Write(";parameter"); } public override bool NeedsPopulation => true; private string Name { get { // Very rarely, two parameters have the same name according to the data model. // This breaks our database constraints. // Generate an impossible name to ensure that it doesn't conflict. var conflictingCount = Symbol.ContainingSymbol.GetParameters().Count(p => p.Ordinal < Symbol.Ordinal && p.Name == Symbol.Name); return conflictingCount > 0 ? $"{Symbol.Name}`{conflictingCount}" : Symbol.Name; } } public override void Populate(TextWriter trapFile) { PopulateAttributes(); PopulateNullability(trapFile, Symbol.GetAnnotatedType()); PopulateRefKind(trapFile, Symbol.RefKind); PopulateScopedKind(trapFile, Symbol.ScopedKind); if (Symbol.Name != Original.Symbol.Name) Context.ModelError(Symbol, "Inconsistent parameter declaration"); var type = Type.Create(Context, Symbol.Type); trapFile.@params(this, Name, type.TypeRef, Ordinal, ParamKind, Parent!, Original); if (Context.ExtractLocation(Symbol)) { var locations = Context.GetLocations(Symbol); WriteLocationsToTrap(trapFile.param_location, this, locations); } if (!Symbol.Locations.Any() && Symbol.ContainingSymbol is IMethodSymbol ms && ms.Name == WellKnownMemberNames.TopLevelStatementsEntryPointMethodName && ms.ContainingType.Name == WellKnownMemberNames.TopLevelStatementsEntryPointTypeName) { WriteLocationToTrap(trapFile.param_location, this, Context.CreateLocation()); } if (Symbol.HasExplicitDefaultValue && Context.Defines(Symbol)) { var defaultValueSyntax = GetDefaultValueFromSyntax(Symbol); Action defaultValueExpressionCreation = defaultValueSyntax is not null ? () => Expression.Create(Context, defaultValueSyntax.Value, this, 0) : () => Expression.CreateGenerated(Context, Symbol, this, 0, Location); Context.PopulateLater(defaultValueExpressionCreation); } if (!IsSourceDeclaration || !Symbol.FromSource()) return; BindComments(); if (IsSourceDeclaration) { foreach (var syntax in Symbol.DeclaringSyntaxReferences .Select(d => d.GetSyntax()) .OfType() .Where(s => s.Type is not null)) { TypeMention.Create(Context, syntax.Type!, this, type); } } } private static EqualsValueClauseSyntax? GetDefaultValueFromSyntax(IParameterSymbol symbol) { // This is a slight bug in the dbscheme // We should really define param_default(param, string) // And use parameter child #0 to encode the default expression. var defaultValue = GetParameterDefaultValue(symbol); if (defaultValue is null) { // In case this parameter belongs to an accessor of an indexer, we need // to get the default value from the corresponding parameter belonging // to the indexer itself if (symbol.ContainingSymbol is IMethodSymbol method) { var i = method.Parameters.IndexOf(symbol); if (method.AssociatedSymbol is IPropertySymbol indexer) defaultValue = GetParameterDefaultValue(indexer.Parameters[i]); } } return defaultValue; } public override bool IsSourceDeclaration => Symbol.IsSourceDeclaration(); bool IExpressionParentEntity.IsTopLevelParent => true; private static EqualsValueClauseSyntax? GetParameterDefaultValue(IParameterSymbol parameter) { var syntax = parameter.DeclaringSyntaxReferences.Select(@ref => @ref.GetSyntax()).OfType().FirstOrDefault(); return syntax?.Default; } private class ParameterFactory : CachedEntityFactory<(IParameterSymbol, IEntity?, Parameter?), 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 TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel; } internal class VarargsType : Type { private VarargsType(Context cx) : base(cx, null) { } public override void Populate(TextWriter trapFile) { trapFile.types(this, Kinds.TypeKind.ARGLIST, "__arglist"); trapFile.parent_namespace(this, Namespace.Create(Context, Context.Compilation.GlobalNamespace)); Modifier.HasModifier(Context, trapFile, this, "public"); } public override bool NeedsPopulation => true; public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write("__arglist;type"); } public override int GetHashCode() { return 98735267; } public override bool Equals(object? obj) { return obj is not null && obj.GetType() == typeof(VarargsType); } public static VarargsType Create(Context cx) => VarargsTypeFactory.Instance.CreateEntity(cx, typeof(VarargsType), null); private class VarargsTypeFactory : CachedEntityFactory { public static VarargsTypeFactory Instance { get; } = new VarargsTypeFactory(); public override VarargsType Create(Context cx, string? init) => new VarargsType(cx); } } internal class VarargsParam : Parameter { #nullable disable warnings private VarargsParam(Context cx, Method methodKey) : base(cx, null, methodKey, null) { } #nullable restore warnings public override void Populate(TextWriter trapFile) { var typeKey = VarargsType.Create(Context); // !! Maybe originaldefinition is wrong trapFile.@params(this, "", typeKey, Ordinal, Kind.None, Parent!, this); } protected override int Ordinal => ((Method)Parent!).OriginalDefinition.Symbol.Parameters.Length; public override int GetHashCode() { return 9873567; } public override bool Equals(object? obj) { return obj is not null && obj.GetType() == typeof(VarargsParam); } public static VarargsParam Create(Context cx, Method method) => VarargsParamFactory.Instance.CreateEntity(cx, typeof(VarargsParam), method); private class VarargsParamFactory : CachedEntityFactory { public static VarargsParamFactory Instance { get; } = new VarargsParamFactory(); public override VarargsParam Create(Context cx, Method init) => new VarargsParam(cx, init); } } }