using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Semmle.Extraction.CSharp.Populators; using Semmle.Extraction.Entities; using Semmle.Extraction.Kinds; using System.IO; using System.Linq; namespace Semmle.Extraction.CSharp.Entities { public interface IExpressionParentEntity : IEntity { /// /// Whether this entity is the parent of a top-level expression. /// bool IsTopLevelParent { get; } } class Expression : FreshEntity, IExpressionParentEntity { private readonly IExpressionInfo Info; public readonly AnnotatedType Type; public readonly Extraction.Entities.Location Location; public readonly ExprKind Kind; internal Expression(IExpressionInfo info) : base(info.Context) { Info = info; Location = info.Location; Kind = info.Kind; Type = info.Type; if (Type.Type is null) Type = NullType.Create(cx); TryPopulate(); } protected sealed override void Populate(TextWriter trapFile) { trapFile.expressions(this, Kind, Type.Type.TypeRef); if (Info.Parent.IsTopLevelParent) trapFile.expr_parent_top_level(this, Info.Child, Info.Parent); else trapFile.expr_parent(this, Info.Child, Info.Parent); trapFile.expr_location(this, Location); if (Info.IsCompilerGenerated) trapFile.expr_compiler_generated(this); if (Info.ExprValue is string value) trapFile.expr_value(this, value); Type.Type.PopulateGenerics(); } public override Microsoft.CodeAnalysis.Location ReportingLocation => Location.symbol; bool IExpressionParentEntity.IsTopLevelParent => false; /// /// Gets a string represention of a constant value. /// /// The value. /// The string representation. public static string ValueAsString(object value) { return value == null ? "null" : value is bool ? ((bool)value ? "true" : "false") : value.ToString(); } /// /// Creates an expression from a syntax node. /// Inserts type conversion as required. /// /// The extraction context. /// The node to extract. /// The parent entity. /// The child index. /// A type hint. /// The new expression. public static Expression Create(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child) => CreateFromNode(new ExpressionNodeInfo(cx, node, parent, child)); public static Expression CreateFromNode(ExpressionNodeInfo info) => Expressions.ImplicitCast.Create(info); /// /// Creates an expression from a syntax node. /// Inserts type conversion as required. /// Population is deferred to avoid overflowing the stack. /// /// The extraction context. /// The node to extract. /// The parent entity. /// The child index. /// A type hint. public static void CreateDeferred(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child) { if (ContainsPattern(node)) // Expressions with patterns should be created right away, as they may introduce // local variables referenced in `LocalVariable::GetAlreadyCreated()` Create(cx, node, parent, child); else cx.PopulateLater(() => Create(cx, node, parent, child)); } static bool ContainsPattern(SyntaxNode node) => node is PatternSyntax || node is VariableDesignationSyntax || node.ChildNodes().Any(ContainsPattern); /// /// Adapt the operator kind depending on whether it's a dynamic call or a user-operator call. /// /// /// /// /// public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, ExpressionSyntax node) => GetCallType(cx, node).AdjustKind(originalKind); /// /// If the expression calls an operator, add an expr_call() /// to show the target of the call. Also note the dynamic method /// name if available. /// /// Context /// The expression. public void OperatorCall(TextWriter trapFile, ExpressionSyntax node) { var @operator = cx.GetSymbolInfo(node); if (@operator.Symbol is IMethodSymbol method) { var callType = GetCallType(cx, node); if (callType == CallType.Dynamic) { UserOperator.OperatorSymbol(method.Name, out string operatorName); trapFile.dynamic_member_name(this, operatorName); return; } trapFile.expr_call(this, Method.Create(cx, method)); } } public enum CallType { None, BuiltInOperator, Dynamic, UserOperator } /// /// Determine what type of method was called for this expression. /// /// The context. /// The expression /// The call type. public static CallType GetCallType(Context cx, ExpressionSyntax node) { var @operator = cx.GetSymbolInfo(node); if (@operator.Symbol is IMethodSymbol method) { if (method.ContainingSymbol is ITypeSymbol containingSymbol && containingSymbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic) { return CallType.Dynamic; } switch (method.MethodKind) { case MethodKind.BuiltinOperator: if (method.ContainingType != null && method.ContainingType.TypeKind == Microsoft.CodeAnalysis.TypeKind.Delegate) return CallType.UserOperator; return CallType.BuiltInOperator; case MethodKind.Constructor: // The index operator ^... generates a constructor call to System.Index. // Instead, treat this as a regular operator. return CallType.None; default: return CallType.UserOperator; } } return CallType.None; } public static bool IsDynamic(Context cx, ExpressionSyntax node) { var ti = cx.GetTypeInfo(node).ConvertedType; return ti != null && ti.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic; } /// /// Given b in a?.b.c, return a. /// /// A MemberBindingExpression. /// The qualifier of the conditional access. protected static ExpressionSyntax FindConditionalQualifier(ExpressionSyntax node) { for (SyntaxNode n = node; n != null; n = n.Parent) { var conditionalAccess = n.Parent as ConditionalAccessExpressionSyntax; if (conditionalAccess != null && conditionalAccess.WhenNotNull == n) return conditionalAccess.Expression; } throw new InternalError(node, "Unable to locate a ConditionalAccessExpression"); } public void MakeConditional(TextWriter trapFile) { trapFile.conditional_access(this); } public void PopulateArguments(TextWriter trapFile, BaseArgumentListSyntax args, int child) { foreach (var arg in args.Arguments) PopulateArgument(trapFile, arg, child++); } private void PopulateArgument(TextWriter trapFile, ArgumentSyntax arg, int child) { var expr = Create(cx, arg.Expression, this, child); int mode; switch (arg.RefOrOutKeyword.Kind()) { case SyntaxKind.RefKeyword: mode = 1; break; case SyntaxKind.OutKeyword: mode = 2; break; case SyntaxKind.None: mode = 0; break; case SyntaxKind.InKeyword: mode = 3; break; default: throw new InternalError(arg, "Unknown argument type"); } trapFile.expr_argument(expr, mode); if (arg.NameColon != null) { trapFile.expr_argument_name(expr, arg.NameColon.Name.Identifier.Text); } } public override string ToString() => Label.ToString(); public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel; } static class CallTypeExtensions { /// /// Adjust the expression kind to match this call type. /// public static ExprKind AdjustKind(this Expression.CallType ct, ExprKind k) { switch (ct) { case Expression.CallType.Dynamic: case Expression.CallType.UserOperator: return ExprKind.OPERATOR_INVOCATION; default: return k; } } } abstract class Expression : Expression where SyntaxNode : ExpressionSyntax { public readonly SyntaxNode Syntax; protected Expression(ExpressionNodeInfo info) : base(info) { Syntax = (SyntaxNode)info.Node; } /// /// Populates expression-type specific relations in the trap file. The general relations /// expressions and expr_location are populated by the constructor /// (should not fail), so even if expression-type specific population fails (e.g., in /// standalone extraction), the expression created via /// will /// still be valid. /// protected abstract void PopulateExpression(TextWriter trapFile); protected new Expression TryPopulate() { cx.Try(Syntax, null, ()=>PopulateExpression(cx.TrapWriter.Writer)); return this; } } /// /// Holds all information required to create an Expression entity. /// interface IExpressionInfo { Context Context { get; } /// /// The type of the expression. /// AnnotatedType Type { get; } /// /// The location of the expression. /// Extraction.Entities.Location Location { get; } /// /// The kind of the expression. /// ExprKind Kind { get; } /// /// The parent of the expression. /// IExpressionParentEntity Parent { get; } /// /// The child index of the expression. /// int Child { get; } /// /// Holds if this is an implicit expression. /// bool IsCompilerGenerated { get; } /// /// Gets a string representation of the value. /// null is encoded as the string "null". /// If the expression does not have a value, then this /// is null. /// string ExprValue { get; } } /// /// Explicitly constructed expression information. /// class ExpressionInfo : IExpressionInfo { public Context Context { get; } public AnnotatedType Type { get; } public Extraction.Entities.Location Location { get; } public ExprKind Kind { get; } public IExpressionParentEntity Parent { get; } public int Child { get; } public bool IsCompilerGenerated { get; } public string ExprValue { get; } public ExpressionInfo(Context cx, AnnotatedType type, Extraction.Entities.Location location, ExprKind kind, IExpressionParentEntity parent, int child, bool isCompilerGenerated, string value) { Context = cx; Type = type; Location = location; Kind = kind; Parent = parent; Child = child; ExprValue = value; IsCompilerGenerated = isCompilerGenerated; } } /// /// Expression information constructed from a syntax node. /// class ExpressionNodeInfo : IExpressionInfo { public ExpressionNodeInfo(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child) : this(cx, node, parent, child, cx.GetTypeInfo(node)) { } public ExpressionNodeInfo(Context cx, ExpressionSyntax node, IExpressionParentEntity parent, int child, TypeInfo typeInfo) { Context = cx; Node = node; Parent = parent; Child = child; TypeInfo = typeInfo; Conversion = cx.GetModel(node).GetConversion(node); } public Context Context { get; } public ExpressionSyntax Node { get; private set; } public IExpressionParentEntity Parent { get; set; } public int Child { get; set; } public TypeInfo TypeInfo { get; } public Microsoft.CodeAnalysis.CSharp.Conversion Conversion { get; } public AnnotatedTypeSymbol ResolvedType => new AnnotatedTypeSymbol(TypeInfo.Type.DisambiguateType(), TypeInfo.Nullability.Annotation); public AnnotatedTypeSymbol ConvertedType => new AnnotatedTypeSymbol(TypeInfo.ConvertedType.DisambiguateType(), TypeInfo.ConvertedNullability.Annotation); public AnnotatedTypeSymbol ExpressionType { get { var type = ResolvedType; if (type.Symbol == null) type.Symbol = (TypeInfo.Type ?? TypeInfo.ConvertedType).DisambiguateType(); // Roslyn workaround: It can't work out the type of "new object[0]" // Clearly a bug. if (type.Symbol?.TypeKind == Microsoft.CodeAnalysis.TypeKind.Error) { var arrayCreation = Node as ArrayCreationExpressionSyntax; if (arrayCreation != null) { var elementType = Context.GetType(arrayCreation.Type.ElementType); if (elementType.Symbol != null) // There seems to be no way to create an array with a nullable element at present. return new AnnotatedTypeSymbol(Context.Compilation.CreateArrayTypeSymbol(elementType.Symbol, arrayCreation.Type.RankSpecifiers.Count), NullableAnnotation.NotAnnotated); } Context.ModelError(Node, "Failed to determine type"); } return type; } } Microsoft.CodeAnalysis.Location location; public Microsoft.CodeAnalysis.Location CodeAnalysisLocation { get { if (location == null) location = Node.FixedLocation(); return location; } set { location = value; } } public SemanticModel Model => Context.GetModel(Node); public string ExprValue { get { var c = Model.GetConstantValue(Node); return c.HasValue ? Expression.ValueAsString(c.Value) : null; } } AnnotatedType cachedType; public AnnotatedType Type { get { if (cachedType.Type == null) cachedType = Entities.Type.Create(Context, ExpressionType); return cachedType; } set { cachedType = value; } } Extraction.Entities.Location cachedLocation; public Extraction.Entities.Location Location { get { if (cachedLocation == null) cachedLocation = Context.Create(CodeAnalysisLocation); return cachedLocation; } set { cachedLocation = value; } } public ExprKind Kind { get; set; } = ExprKind.UNKNOWN; public bool IsCompilerGenerated { get; set; } public ExpressionNodeInfo SetParent(IExpressionParentEntity parent, int child) { Parent = parent; Child = child; return this; } public ExpressionNodeInfo SetKind(ExprKind kind) { Kind = kind; return this; } public ExpressionNodeInfo SetType(AnnotatedType type) { Type = type; return this; } public ExpressionNodeInfo SetNode(ExpressionSyntax node) { Node = node; return this; } SymbolInfo cachedSymbolInfo; public SymbolInfo SymbolInfo { get { if (cachedSymbolInfo.Symbol == null && cachedSymbolInfo.CandidateReason == CandidateReason.None) cachedSymbolInfo = Model.GetSymbolInfo(Node); return cachedSymbolInfo; } } } }