mirror of
https://github.com/github/codeql.git
synced 2026-06-29 08:37:04 +02:00
226 lines
9.6 KiB
C#
226 lines
9.6 KiB
C#
using System.IO;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Semmle.Extraction.Kinds;
|
|
|
|
namespace Semmle.Extraction.CSharp.Entities.Expressions
|
|
{
|
|
internal abstract class ElementAccess : Expression<ExpressionSyntax>
|
|
{
|
|
protected ElementAccess(ExpressionNodeInfo info, ExpressionSyntax qualifier, BracketedArgumentListSyntax argumentList)
|
|
: base(info.SetKind(GetKind(info.Context, info.Node, qualifier)))
|
|
{
|
|
this.qualifier = qualifier;
|
|
this.argumentList = argumentList;
|
|
}
|
|
|
|
private readonly ExpressionSyntax qualifier;
|
|
private readonly BracketedArgumentListSyntax argumentList;
|
|
|
|
|
|
private ISymbol? GetTargetSymbol()
|
|
{
|
|
return Context.GetSymbolInfo(base.Syntax).Symbol;
|
|
}
|
|
|
|
private static void SetExprArgument(TextWriter trapFile, Expression left, Expression right)
|
|
{
|
|
trapFile.expr_argument(left, 0);
|
|
trapFile.expr_argument(right, 0);
|
|
}
|
|
|
|
private Expression MakeZeroFromEndExpression(IExpressionParentEntity parent, int child)
|
|
{
|
|
var info = new ExpressionInfo(
|
|
Context,
|
|
AnnotatedTypeSymbol.CreateNotAnnotated(Context.Compilation.GetSpecialType(SpecialType.System_Int32)),
|
|
Location,
|
|
ExprKind.INDEX,
|
|
parent,
|
|
child,
|
|
isCompilerGenerated: true,
|
|
null);
|
|
|
|
var index = new Expression(info);
|
|
|
|
MakeZeroLiteral(index, 0);
|
|
return index;
|
|
}
|
|
|
|
private Expression MakeZeroLiteral(IExpressionParentEntity parent, int child)
|
|
{
|
|
return Literal.CreateGenerated(Context, parent, child, Context.Compilation.GetSpecialType(SpecialType.System_Int32), 0, Location);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// It is assumed that either the input is
|
|
/// 1. A normal expression that can be used as endpoint (e.g a constant like "3").
|
|
/// 2. An index expression indicating that we should read from the end (e.g "^1").
|
|
/// </summary>
|
|
/// <param name="syntax">The syntax node representing the range endpoint.</param>
|
|
/// <param name="parent">The parent expression entity.</param>
|
|
/// <param name="child">The child index within the parent.</param>
|
|
/// <returns>An expression representing the endpoint of a range to be used in conjunction with a slice operation.</returns>
|
|
private Expression MakeFromRangeEndpoint(ExpressionSyntax syntax, IExpressionParentEntity parent, int child)
|
|
{
|
|
var info = new ExpressionNodeInfo(Context, syntax, parent, child);
|
|
|
|
return syntax.Kind() == SyntaxKind.IndexExpression
|
|
? PrefixUnary.Create(info.SetKind(ExprKind.INDEX))
|
|
: Factory.Create(info);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the given method is a slice method, which is defined as a method with
|
|
/// the name "Slice" or "Substring" and two parameters.
|
|
/// </summary>
|
|
/// <param name="method">The method symbol to check.</param>
|
|
/// <returns>True if the method is a slice method; false otherwise.</returns>
|
|
private bool IsSlice(IMethodSymbol method, out RangeExpressionSyntax? range)
|
|
{
|
|
range = null;
|
|
|
|
if (argumentList.Arguments.Count == 1)
|
|
{
|
|
range = argumentList.Arguments[0].Expression as RangeExpressionSyntax;
|
|
}
|
|
|
|
return (method.Name == "Slice" || method.Name == "Substring")
|
|
&& method.Parameters.Length == 2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates a slice method call based on the given range.
|
|
/// Roslyn translates indexer accesses with range expressions in the following way.
|
|
/// 1. s[a..b] -> s.Slice(a, b - a)
|
|
/// 2. s[..b] -> s.Slice(0, b)
|
|
/// 3. s[a..] -> s.Slice(a, s.Length - a)
|
|
/// 4. s[..] -> s.Slice(0, s.Length)
|
|
/// However, it is possible that both the qualifier or the index endpoints may contain method calls.
|
|
/// If we want to translate this accurately, we would need to introduce synthetic statements for qualifier and
|
|
/// the endpoints, which should then be used in the slice method call.
|
|
/// To avoid this, we translate as follows.
|
|
/// 1. s[a..b] -> s.Slice(a, b)
|
|
/// 2. s[..b] -> s.Slice(0, b)
|
|
/// 3. s[a..] -> s.Slice(a, ^0)
|
|
/// 4. s[..] -> s.Slice(0, ^0)
|
|
///
|
|
/// Even though index expressions can't technically be used in this way, they signal that we
|
|
/// could perceive ^b as "length - b".
|
|
///
|
|
/// Call arguments are only populated when a range expression is directly available in
|
|
/// the list of arguments.
|
|
/// This means that cases like below are not handled.
|
|
/// System.Range x = 1..3;
|
|
/// s[x]
|
|
/// </summary>
|
|
/// <param name="trapFile">The trap file to write to.</param>
|
|
/// <param name="slice">The slice method symbol.</param>
|
|
/// <param name="range">The range expression syntax.</param>
|
|
private void PopulateSlice(TextWriter trapFile, IMethodSymbol slice, RangeExpressionSyntax? range)
|
|
{
|
|
if (range is not null)
|
|
{
|
|
// Populate the call arguments
|
|
var left = range.LeftOperand is ExpressionSyntax lsyntax
|
|
? MakeFromRangeEndpoint(lsyntax, this, 0)
|
|
: MakeZeroLiteral(this, 0);
|
|
|
|
var right = range.RightOperand is ExpressionSyntax rsyntax
|
|
? MakeFromRangeEndpoint(rsyntax, this, 1)
|
|
: MakeZeroFromEndExpression(this, 1);
|
|
|
|
SetExprArgument(trapFile, left, right);
|
|
}
|
|
trapFile.expr_call(this, Method.Create(Context, slice));
|
|
}
|
|
|
|
protected override void PopulateExpression(TextWriter trapFile)
|
|
{
|
|
if (Kind == ExprKind.POINTER_INDIRECTION)
|
|
{
|
|
var qualifierInfo = new ExpressionNodeInfo(Context, qualifier, this, 0);
|
|
var add = new Expression(new ExpressionInfo(Context, qualifierInfo.Type, Location, ExprKind.ADD, this, 0, isCompilerGenerated: false, null));
|
|
qualifierInfo.SetParent(add, 0);
|
|
CreateFromNode(qualifierInfo);
|
|
PopulateArguments(trapFile, argumentList, 1);
|
|
}
|
|
else
|
|
{
|
|
Create(Context, qualifier, this, -1);
|
|
|
|
var target = GetTargetSymbol();
|
|
if (target is IMethodSymbol method && IsSlice(method, out var range))
|
|
{
|
|
// When an indexer on a span or string is used in conjunction with a range expression, the compiler translates
|
|
// this into a call to the "Slice" or "Substring" method.
|
|
// In this case, we want to populate a slice/substring method call instead of an indexer access.
|
|
PopulateSlice(trapFile, method, range);
|
|
return;
|
|
}
|
|
|
|
PopulateArguments(trapFile, argumentList, 0);
|
|
if (target is IPropertySymbol { IsIndexer: true } indexer)
|
|
{
|
|
trapFile.expr_access(this, Indexer.Create(Context, indexer));
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed override Microsoft.CodeAnalysis.Location? ReportingLocation => base.ReportingLocation;
|
|
|
|
private static bool IsArray(ITypeSymbol symbol) =>
|
|
symbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Array || symbol.IsInlineArray();
|
|
|
|
private static ExprKind GetKind(Context cx, ExpressionSyntax syntax, ExpressionSyntax qualifier)
|
|
{
|
|
if (cx.GetSymbolInfo(syntax).Symbol is IMethodSymbol)
|
|
return ExprKind.METHOD_INVOCATION;
|
|
|
|
var qualifierType = cx.GetType(qualifier);
|
|
|
|
// This is a compilation error, so make a guess and continue.
|
|
if (qualifierType.Symbol is null)
|
|
return ExprKind.ARRAY_ACCESS;
|
|
|
|
if (qualifierType.Symbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Pointer)
|
|
{
|
|
// Convert expressions of the form a[b] into *(a+b)
|
|
return ExprKind.POINTER_INDIRECTION;
|
|
}
|
|
|
|
return IsDynamic(cx, qualifier)
|
|
? ExprKind.DYNAMIC_ELEMENT_ACCESS
|
|
: IsArray(qualifierType.Symbol)
|
|
? ExprKind.ARRAY_ACCESS
|
|
: ExprKind.INDEXER_ACCESS;
|
|
}
|
|
}
|
|
|
|
internal class NormalElementAccess : ElementAccess
|
|
{
|
|
private NormalElementAccess(ExpressionNodeInfo info)
|
|
: base(info, ((ElementAccessExpressionSyntax)info.Node).Expression, ((ElementAccessExpressionSyntax)info.Node).ArgumentList) { }
|
|
|
|
public static Expression Create(ExpressionNodeInfo info) => new NormalElementAccess(info).TryPopulate();
|
|
}
|
|
|
|
internal class BindingElementAccess : ElementAccess
|
|
{
|
|
private BindingElementAccess(ExpressionNodeInfo info)
|
|
: base(info, FindConditionalQualifier(info.Node), ((ElementBindingExpressionSyntax)info.Node).ArgumentList)
|
|
{
|
|
}
|
|
|
|
public static Expression Create(ExpressionNodeInfo info) => new BindingElementAccess(info).TryPopulate();
|
|
|
|
protected override void PopulateExpression(TextWriter trapFile)
|
|
{
|
|
base.PopulateExpression(trapFile);
|
|
MakeConditional(trapFile);
|
|
}
|
|
}
|
|
}
|