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.
/// 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.
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));
}
protected 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.
///
/// 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;
}
}