using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Semmle.Extraction.CSharp.Entities.Expressions; using Semmle.Extraction.CSharp.Util; using Semmle.Extraction.Kinds; namespace Semmle.Extraction.CSharp.Entities { internal class Expression : FreshEntity, IExpressionParentEntity { private readonly IExpressionInfo info; public AnnotatedTypeSymbol? Type { get; private set; } public Location Location { get; } public ExprKind Kind { get; } internal Expression(IExpressionInfo info, bool shouldPopulate = true) : base(info.Context) { this.info = info; Location = info.Location; Kind = info.Kind; Type = info.Type; if (shouldPopulate) { TryPopulate(); } } protected sealed override void Populate(TextWriter trapFile) { var type = Type.HasValue ? Entities.Type.Create(Context, Type.Value) : NullType.Create(Context); trapFile.expressions(this, Kind, 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 (Type.HasValue && !Type.Value.HasObliviousNullability()) { var n = NullabilityEntity.Create(Context, Nullability.Create(Type.Value)); trapFile.type_nullability(this, n); } if (info.FlowState != NullableFlowState.None) { trapFile.expr_flowstate(this, (int)info.FlowState); } if (info.IsCompilerGenerated) trapFile.compiler_generated(this); if (info.ExprValue is string value) trapFile.expr_value(this, value); type.PopulateGenerics(); } public override Microsoft.CodeAnalysis.Location? ReportingLocation => Location.Symbol; internal void SetType(ITypeSymbol? type) { if (type is not null) { Type = new AnnotatedTypeSymbol(type, type.NullableAnnotation); } } bool IExpressionParentEntity.IsTopLevelParent => false; /// /// Gets a string representation of a constant value. /// /// The value. /// The string representation. public static string ValueAsString(object? value) { return value is null ? "null" : value is bool b ? b ? "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, Boolean isCompilerGenerated = false) { var info = new ExpressionNodeInfo(cx, node, parent, child) { IsCompilerGenerated = isCompilerGenerated }; return CreateFromNode(info); } 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)); } private static bool ContainsPattern(SyntaxNode node) => node is PatternSyntax || node is VariableDesignationSyntax || node.ChildNodes().Any(ContainsPattern); /// /// Creates a generated expression from a typed constant. /// public static Expression? CreateGenerated(Context cx, TypedConstant constant, IExpressionParentEntity parent, int childIndex, Location location) { if (constant.IsNull || constant.Type is null) { return Literal.CreateGeneratedNullLiteral(cx, parent, childIndex, location); } switch (constant.Kind) { case TypedConstantKind.Primitive: return Literal.CreateGenerated(cx, parent, childIndex, constant.Type, constant.Value, location); case TypedConstantKind.Enum: // Enum value is generated in the following format: (Enum)value Action createChild = (parent, index) => Literal.CreateGenerated(cx, parent, index, ((INamedTypeSymbol)constant.Type).EnumUnderlyingType!, constant.Value, location); var cast = Cast.CreateGenerated(cx, parent, childIndex, constant.Type!, constant.Value, createChild, location); return cast; case TypedConstantKind.Type: var type = ((ITypeSymbol)constant.Value!).OriginalDefinition; return TypeOf.CreateGenerated(cx, parent, childIndex, type, location); case TypedConstantKind.Array: // Single dimensional arrays are in the following format: // * new Type[N] { item1, item2, ..., itemN } // * new Type[0] // // itemI is generated recursively. return NormalArrayCreation.CreateGenerated(cx, parent, childIndex, constant.Type, constant.Values, location); case TypedConstantKind.Error: default: cx.ExtractionError("Couldn't extract constant in attribute", constant.ToString(), location); return null; } } /// /// Creates a generated expression for a default argument value. /// public static Expression? CreateGenerated(Context cx, IParameterSymbol parameter, IExpressionParentEntity parent, int childIndex, Location location) { if (!parameter.HasExplicitDefaultValue || parameter.Type is IErrorTypeSymbol) { return null; } var defaultValue = parameter.ExplicitDefaultValue; var type = parameter.Type; if (type.IsBoundNullable() && type is INamedTypeSymbol named) { type = named.TypeArguments[0]; } if (type is INamedTypeSymbol nt && nt.EnumUnderlyingType is not null) { // = (MyEnum)1, = MyEnum.Value1, = default(MyEnum), = new MyEnum() // we're generating a (MyEnum)value cast expression: defaultValue ??= 0; Action createChild = (parent, index) => Literal.CreateGenerated(cx, parent, index, nt.EnumUnderlyingType, defaultValue, location); return Cast.CreateGenerated(cx, parent, childIndex, parameter.Type, defaultValue, createChild, location); } if (defaultValue is null) { // = null, = default, = default(T), = new MyStruct() // we're generating a default expression: return Default.CreateGenerated(cx, parent, childIndex, location, parameter.Type.IsReferenceType ? ValueAsString(null) : null); } if (type.SpecialType is SpecialType.None) { return ImplicitCast.CreateGeneratedConversion(cx, parent, childIndex, type, defaultValue, location); } if (type.SpecialType is SpecialType.System_DateTime) { return DateTimeObjectCreation.CreateGenerated(cx, parent, childIndex, type, defaultValue, location); } if (type.SpecialType is SpecialType.System_Object || type.SpecialType is SpecialType.System_IntPtr || type.SpecialType is SpecialType.System_UIntPtr) { return ImplicitCast.CreateGenerated(cx, parent, childIndex, type, defaultValue, location); } // const literal: return Literal.CreateGenerated(cx, parent, childIndex, type, defaultValue, location); } /// /// 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 = Context.GetSymbolInfo(node); if (@operator.Symbol is IMethodSymbol method) { var callType = GetCallType(Context, node); if (callType == CallType.Dynamic) { method.TryGetOperatorSymbol(out var operatorName); trapFile.dynamic_member_name(this, operatorName); return; } trapFile.expr_call(this, Method.Create(Context, 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 is not 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 is not null && ti.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic; } /// /// Given `b` in `a?.b.c`, return `(a?.b, a?.b)`. /// /// Given `c` in `a?.b?.c.d`, return `(b?.c, a?.b?.c)`. /// /// A MemberBindingExpression. /// The conditional access. public static (ConditionalAccessExpressionSyntax Parent, ConditionalAccessExpressionSyntax Root) FindConditionalAccessParent(ExpressionSyntax node) { (ConditionalAccessExpressionSyntax, ConditionalAccessExpressionSyntax)? res = null; SyntaxNode? prev = null; for (SyntaxNode? n = node; n is not null; prev = n, n = n.Parent) { if (n is ConditionalAccessExpressionSyntax conditionalAccess && (prev is null || conditionalAccess.WhenNotNull == prev)) { res = res is null ? (conditionalAccess, conditionalAccess) : (res.Value.Item1, conditionalAccess); } else if (res.HasValue) { break; } } if (res.HasValue) { return res.Value; } throw new InternalError(node, "Unable to locate a ConditionalAccessExpression"); } /// /// Given b in a?.b.c, return a. /// /// A MemberBindingExpression. /// The qualifier of the conditional access. protected static ExpressionSyntax FindConditionalQualifier(ExpressionSyntax node) => FindConditionalAccessParent(node).Parent.Expression; public void MakeConditional(TextWriter trapFile) { trapFile.conditional_access(this); } public void PopulateArguments(TextWriter trapFile, BaseArgumentListSyntax args, int child) { PopulateArguments(trapFile, args.Arguments, child); } public void PopulateArguments(TextWriter trapFile, IEnumerable args, int child) { foreach (var arg in args) PopulateArgument(trapFile, arg, child++); } private void PopulateArgument(TextWriter trapFile, ArgumentSyntax arg, int child) { var expr = Create(Context, 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 is not null) { trapFile.expr_argument_name(expr, arg.NameColon.Name.Identifier.Text); } } public override string ToString() => Label.ToString(); public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel; } }