Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ protected override void ExtractInitializers(TextWriter trapFile)
{
case SyntaxKind.BaseConstructorInitializer:
initializerType = Symbol.ContainingType.BaseType!;
ExtractObjectInitCall(trapFile);
break;
case SyntaxKind.ThisConstructorInitializer:
initializerType = Symbol.ContainingType;
Expand All @@ -90,10 +91,12 @@ protected override void ExtractInitializers(TextWriter trapFile)
var primaryInfo = Context.GetSymbolInfo(primaryInitializer);
var primarySymbol = primaryInfo.Symbol;

ExtractObjectInitCall(trapFile);
ExtractSourceInitializer(trapFile, primarySymbol?.ContainingType, (IMethodSymbol?)primarySymbol, primaryInitializer.ArgumentList, primaryInitializer.GetLocation());
}
else if (Symbol.MethodKind is MethodKind.Constructor)
{
ExtractObjectInitCall(trapFile);
var baseType = Symbol.ContainingType.BaseType;
if (baseType is null)
{
Expand Down Expand Up @@ -127,6 +130,27 @@ protected override void ExtractInitializers(TextWriter trapFile)
}
}

private void ExtractObjectInitCall(TextWriter trapFile)
{
var target = ObjectInitMethod.Create(Context, ContainingType!);

var type = Context.Compilation.GetSpecialType(SpecialType.System_Void);

var info = new ExpressionInfo(Context,
AnnotatedTypeSymbol.CreateNotAnnotated(type),
Location,
Kinds.ExprKind.METHOD_INVOCATION,
this,
-2,
isCompilerGenerated: true,
null);
var obinitCall = new Expression(info);

trapFile.expr_call(obinitCall, target);

Expressions.This.CreateImplicit(Context, Symbol.ContainingType, Location, obinitCall, -1);
}

private void ExtractSourceInitializer(TextWriter trapFile, ITypeSymbol? type, IMethodSymbol? symbol, ArgumentListSyntax arguments, Microsoft.CodeAnalysis.Location location)
{
var initInfo = new ExpressionInfo(Context,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Semmle.Extraction.CSharp.Entities
{
/// <summary>
/// Marker interface for method entities.
/// </summary>
public interface IMethodEntity : IEntity
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Semmle.Extraction.CSharp.Entities
{
internal abstract class Method : CachedSymbol<IMethodSymbol>, IExpressionParentEntity, IStatementParentEntity
internal abstract class Method : CachedSymbol<IMethodSymbol>, IExpressionParentEntity, IStatementParentEntity, IMethodEntity
{
protected Method(Context cx, IMethodSymbol init)
: base(cx, init) { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.IO;
using Microsoft.CodeAnalysis;

namespace Semmle.Extraction.CSharp.Entities
{
internal sealed class ObjectInitMethod : CachedEntity, IMethodEntity
{
private Type ContainingType { get; }

private ObjectInitMethod(Context cx, Type containingType)
: base(cx)
{
this.ContainingType = containingType;
}

public static readonly string Name = "<object initializer>";

public static ObjectInitMethod Create(Context cx, Type containingType)
{
return ObjectInitMethodFactory.Instance.CreateEntity(cx, (typeof(ObjectInitMethod), containingType), containingType);
}

public override void Populate(TextWriter trapFile)
{
var returnType = Type.Create(Context, Context.Compilation.GetSpecialType(SpecialType.System_Void));

trapFile.methods(this, Name, ContainingType, returnType.TypeRef, this);

trapFile.compiler_generated(this);

trapFile.method_location(this, Context.CreateLocation(ReportingLocation));
}

public override void WriteId(EscapingTextWriter trapFile)
{
trapFile.WriteSubId(ContainingType);
trapFile.Write(".");
trapFile.Write(Name);
trapFile.Write(";method");
}

public override Microsoft.CodeAnalysis.Location? ReportingLocation => ContainingType.ReportingLocation;

public override bool NeedsPopulation => true;

public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;

private class ObjectInitMethodFactory : CachedEntityFactory<Type, ObjectInitMethod>
{
public static ObjectInitMethodFactory Instance { get; } = new ObjectInitMethodFactory();

public override ObjectInitMethod Create(Context cx, Type containingType) =>
new ObjectInitMethod(cx, containingType);
}
}
}
6 changes: 3 additions & 3 deletions csharp/extractor/Semmle.Extraction.CSharp/Trap/Tuples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ internal static void expr_argument(this TextWriter trapFile, Expression expr, in
internal static void expr_argument_name(this TextWriter trapFile, Expression expr, string name) =>
trapFile.WriteTuple("expr_argument_name", expr, name);

internal static void expr_call(this TextWriter trapFile, Expression expr, Method target) =>
internal static void expr_call(this TextWriter trapFile, Expression expr, IMethodEntity target) =>
trapFile.WriteTuple("expr_call", expr, target);

internal static void expr_flowstate(this TextWriter trapFile, Expression expr, int flowState) =>
Expand Down Expand Up @@ -247,10 +247,10 @@ internal static void localvar_location(this TextWriter trapFile, LocalVariable v
internal static void localvars(this TextWriter trapFile, LocalVariable key, VariableKind kind, string name, int @var, Type type, Expression expr) =>
trapFile.WriteTuple("localvars", key, (int)kind, name, @var, type, expr);

internal static void method_location(this TextWriter trapFile, Method method, Location location) =>
internal static void method_location(this TextWriter trapFile, IMethodEntity method, Location location) =>
trapFile.WriteTuple("method_location", method, location);

internal static void methods(this TextWriter trapFile, Method method, string name, Type declType, Type retType, Method originalDefinition) =>
internal static void methods(this TextWriter trapFile, IMethodEntity method, string name, Type declType, Type retType, IMethodEntity originalDefinition) =>
trapFile.WriteTuple("methods", method, name, declType, retType, originalDefinition);

internal static void modifiers(this TextWriter trapFile, Label entity, string modifier) =>
Expand Down
17 changes: 16 additions & 1 deletion csharp/ql/lib/semmle/code/csharp/Callable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ class Method extends Callable, Virtualizable, Attributable, @method {
/** Holds if this method has a `params` parameter. */
predicate hasParams() { exists(this.getParamsType()) }

// Remove when `Callable.isOverridden()` is removed
override predicate fromSource() {
Callable.super.fromSource() and
not this.isCompilerGenerated()
Expand Down Expand Up @@ -317,6 +316,19 @@ class ExtensionMethod extends Method {
override string getAPrimaryQlClass() { result = "ExtensionMethod" }
}

/**
* An object initializer method.
*
* This is an extractor-synthesized method that executes the field
* initializers. Note that the AST nodes for the field initializers are nested
* directly under the class, and therefore this method has no body in the AST.
* On the other hand, this provides the unique enclosing callable for the field
* initializers and their control flow graph.
*/
class ObjectInitMethod extends Method {
ObjectInitMethod() { this.getName() = "<object initializer>" }
}

/**
* A constructor, for example `public C() { }` on line 2 in
*
Expand Down Expand Up @@ -350,6 +362,9 @@ class Constructor extends Callable, Member, Attributable, @constructor {
*/
ConstructorInitializer getInitializer() { result = this.getChildExpr(-1) }

/** Gets the object initializer call of this constructor, if any. */
MethodCall getObjectInitializerCall() { result = this.getChildExpr(-2) }

/** Holds if this constructor has an initializer. */
predicate hasInitializer() { exists(this.getInitializer()) }

Expand Down
5 changes: 4 additions & 1 deletion csharp/ql/lib/semmle/code/csharp/ExprOrStmtParent.qll
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class TopLevelExprParent extends Element, @top_level_expr_parent {
/** INTERNAL: Do not use. */
Expr getExpressionBody(Callable c) {
result = c.getAChildExpr() and
not result = c.(Constructor).getInitializer()
not result = c.(Constructor).getInitializer() and
not result = c.(Constructor).getObjectInitializerCall()
}

/** INTERNAL: Do not use. */
Expand Down Expand Up @@ -211,6 +212,8 @@ private module Cached {
enclosingBody(cfe, getBody(c))
or
parent*(enclosingStart(cfe), c.(Constructor).getInitializer())
or
parent*(cfe, c.(Constructor).getObjectInitializerCall())
}

/** Holds if the enclosing statement of expression `e` is `s`. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,65 @@
import csharp
private import codeql.controlflow.Cfg as CfgShared
private import Completion
private import Splitting
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.commons.Compilation

private module Initializers {
/**
* A non-static member with an initializer, for example a field `int Field = 0`.
*/
class InitializedInstanceMember extends Member {
InitializedInstanceMember() {
exists(AssignExpr ae |
not this.isStatic() and
expr_parent_top_level(ae, _, this) and
not ae = any(Callable c).getExpressionBody()
)
}

/** Gets the initializer expression. */
AssignExpr getInitializer() { expr_parent_top_level(result, _, this) }
}

/**
* Holds if `obinit` is an object initializer method that performs the initialization
* of a member via assignment `init`.
*/
predicate obinitInitializes(ObjectInitMethod obinit, AssignExpr init) {
exists(InitializedInstanceMember m |
obinit.getDeclaringType().getAMember() = m and
init = m.getInitializer()
)
}

/**
* Gets the `i`th member initializer expression for object initializer method `obinit`
* in compilation `comp`.
*/
AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, CompilationExt comp, int i) {
obinitInitializes(obinit, result) and
result =
rank[i + 1](AssignExpr ae0, Location l |
obinitInitializes(obinit, ae0) and
l = ae0.getLocation() and
getCompilation(l.getFile()) = comp
|
ae0 order by l.getStartLine(), l.getStartColumn(), l.getFile().getAbsolutePath()
)
}

/**
* Gets the last member initializer expression for non-static constructor `c`
* in compilation `comp`.
*/
AssignExpr lastInitializer(ObjectInitMethod obinit, CompilationExt comp) {
exists(int i |
result = initializedInstanceMemberOrder(obinit, comp, i) and
not exists(initializedInstanceMemberOrder(obinit, comp, i + 1))
)
}
}

/** An element that defines a new CFG scope. */
class CfgScope extends Element, @top_level_exprorstmt_parent {
CfgScope() {
Expand All @@ -19,7 +74,7 @@ class CfgScope extends Element, @top_level_exprorstmt_parent {
any(Callable c |
c.(Constructor).hasInitializer()
or
InitializerSplitting::constructorInitializes(c, _)
Initializers::obinitInitializes(c, _)
or
c.hasBody()
)
Expand Down Expand Up @@ -146,14 +201,16 @@ private predicate expr_parent_top_level_adjusted2(
predicate scopeFirst(CfgScope scope, AstNode first) {
scope =
any(Callable c |
if exists(c.(Constructor).getInitializer())
then first(c.(Constructor).getInitializer(), first)
if exists(c.(Constructor).getObjectInitializerCall())
then first(c.(Constructor).getObjectInitializerCall(), first)
else
if InitializerSplitting::constructorInitializes(c, _)
then first(InitializerSplitting::constructorInitializeOrder(c, _, 0), first)
if exists(c.(Constructor).getInitializer())
then first(c.(Constructor).getInitializer(), first)
else first(c.getBody(), first)
)
or
first(Initializers::initializedInstanceMemberOrder(scope, _, 0), first)
or
expr_parent_top_level_adjusted2(any(Expr e | first(e, first)), _, scope) and
not scope instanceof Callable
}
Expand All @@ -165,14 +222,33 @@ predicate scopeLast(CfgScope scope, AstNode last, Completion c) {
last(callable.getBody(), last, c) and
not c instanceof GotoCompletion
or
last(InitializerSplitting::lastConstructorInitializer(scope, _), last, c) and
last(callable.(Constructor).getInitializer(), last, c) and
not callable.hasBody()
)
or
last(Initializers::lastInitializer(scope, _), last, c)
or
expr_parent_top_level_adjusted2(any(Expr e | last(e, last, c)), _, scope) and
not scope instanceof Callable
}

private class ObjectInitTree extends ControlFlowTree instanceof ObjectInitMethod {
final override predicate propagatesAbnormal(AstNode child) { none() }

final override predicate first(AstNode first) { none() }

final override predicate last(AstNode last, Completion c) { none() }

final override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(CompilationExt comp, int i |
// Flow from one member initializer to the next
last(Initializers::initializedInstanceMemberOrder(this, comp, i), pred, c) and
c instanceof NormalCompletion and
first(Initializers::initializedInstanceMemberOrder(this, comp, i + 1), succ)
)
}
}

private class ConstructorTree extends ControlFlowTree instanceof Constructor {
final override predicate propagatesAbnormal(AstNode child) { none() }

Expand All @@ -187,18 +263,23 @@ private class ConstructorTree extends ControlFlowTree instanceof Constructor {
comp = getCompilation(result.getFile())
}

pragma[noinline]
private MethodCall getObjectInitializerCall(CompilationExt comp) {
result = super.getObjectInitializerCall() and
comp = getCompilation(result.getFile())
}

pragma[noinline]
private ConstructorInitializer getInitializer(CompilationExt comp) {
result = super.getInitializer() and
comp = getCompilation(result.getFile())
}

final override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(CompilationExt comp, int i, AssignExpr ae |
ae = InitializerSplitting::constructorInitializeOrder(this, comp, i) and
last(ae, pred, c) and
c instanceof NormalCompletion
|
// Flow from one member initializer to the next
first(InitializerSplitting::constructorInitializeOrder(this, comp, i + 1), succ)
or
// Flow from last member initializer to constructor body
ae = InitializerSplitting::lastConstructorInitializer(this, comp) and
first(this.getBody(comp), succ)
exists(CompilationExt comp |
last(this.getObjectInitializerCall(comp), pred, c) and
c instanceof NormalCompletion and
first(this.getInitializer(comp), succ)
)
}
}
Expand Down Expand Up @@ -837,13 +918,7 @@ module Expressions {
last(this, pred, c) and
con = super.getConstructor() and
comp = getCompilation(this.getFile()) and
c instanceof NormalCompletion
|
// Flow from constructor initializer to first member initializer
first(InitializerSplitting::constructorInitializeOrder(con, comp, 0), succ)
or
// Flow from constructor initializer to first element of constructor body
not exists(InitializerSplitting::constructorInitializeOrder(con, comp, _)) and
c instanceof NormalCompletion and
first(con.getBody(comp), succ)
)
}
Expand Down
Loading