Foreach stmt

Addded support for the foreach stmt (for now only the "canonical" desugaring).
Added a test and updated the expected output.
This commit is contained in:
AndreiDiaconu1
2019-08-29 17:05:47 +01:00
parent a5ec763035
commit 4dd548bfa2
6 changed files with 651 additions and 10 deletions

View File

@@ -269,7 +269,11 @@ newtype TTranslatedElement =
)
} or
// A local declaration
TTranslatedDeclaration(LocalVariableDeclExpr entry) or
TTranslatedDeclaration(LocalVariableDeclExpr entry) {
// foreach var decl and init is treated separately,
// because foreach needs desugaring
not entry.getParent() instanceof ForeachStmt
} or
// A compiler generated element, generated by `generatedBy` during the
// desugaring process
TTranslatedCompilerGeneratedElement(Element generatedBy, int index) {

View File

@@ -11,6 +11,7 @@ private import TranslatedInitialization
private import common.TranslatedConditionBlueprint
private import IRInternal
private import semmle.code.csharp.ir.internal.IRUtilities
private import desugar.Foreach
TranslatedStmt getTranslatedStmt(Stmt stmt) { result.getAST() = stmt }
@@ -853,3 +854,35 @@ class TranslatedSwitchStmt extends TranslatedStmt {
)
}
}
class TranslatedEnumeratorForeach extends TranslatedLoop {
override ForeachStmt stmt;
override TranslatedElement getChild(int id) {
id = 0 and result = getTempEnumDecl() or
id = 1 and result = getTry()
}
override Instruction getFirstInstruction() {
result = getTempEnumDecl().getFirstInstruction()
}
override Instruction getChildSuccessor(TranslatedElement child) {
(
child = getTempEnumDecl() and
result = getTry().getFirstInstruction()
) or
(
child = getTry() and
result = getParent().getChildSuccessor(this)
)
}
private TranslatedElement getTry() {
result = ForeachElements::getTry(stmt)
}
private TranslatedElement getTempEnumDecl() {
result = ForeachElements::getEnumDecl(stmt)
}
}

View File

@@ -0,0 +1,514 @@
/**
* File that provides the desugaring of a `Foreach` stmt.
* Since Roslyn rewrites it in quite a few ways,
* for now I will only desugar it to a "canonical" form.
* Also we only deal with foreach stmts where there is only
* one declaration (see bellow).
* For example the code:
* ```
* foreach(var item in some_enumerable) {
* // body
* }
* ```
* gets desugared to:
* ```
* Enumerator e = some_enumerable.GetEnumerator();
* try
* {
* while(e.MoveNext())
* {
* int current = e.Current;
* //body
* }
* }
* finally
* {
* e.Dispose();
* }
* ```
* More info about the desugaring process for `foreach` stmts:
* https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs
* A TODO is to not call `Dispose` no matter what, but desugar the `finally` as an `AsExpr` (cast to IDisposable),
* the call to `Dispose` being made only if the result of the `AsExpr` is not null.
* This is a rough approximation which will need further refining.
*/
import csharp
private import semmle.code.csharp.ir.implementation.Opcode
private import semmle.code.csharp.ir.implementation.internal.OperandTag
private import semmle.code.csharp.ir.internal.TempVariableTag
private import semmle.code.csharp.ir.implementation.raw.internal.InstructionTag
private import semmle.code.csharp.ir.implementation.raw.internal.TranslatedExpr
private import semmle.code.csharp.ir.implementation.raw.internal.TranslatedElement
private import semmle.code.csharp.ir.implementation.raw.internal.TranslatedStmt
private import semmle.code.csharp.ir.implementation.raw.internal.common.TranslatedConditionBlueprint
private import semmle.code.csharp.ir.implementation.raw.internal.common.TranslatedExprBlueprint
private import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language
private import Common
private import internal.TranslatedCompilerGeneratedStmt
private import internal.TranslatedCompilerGeneratedCall
private import internal.TranslatedCompilerGeneratedDeclaration
private import internal.TranslatedCompilerGeneratedCondition
private import internal.TranslatedCompilerGeneratedElement
/**
* Module that exposes the functions needed for the translation of the `foreach` stmt.
*/
module ForeachElements {
TranslatedForeachTry getTry(ForeachStmt generatedBy) {
exists(TranslatedForeachTry try |
try.getAST() = generatedBy and
result = try
)
}
TranslatedForeachEnum getEnumDecl(ForeachStmt generatedBy) {
exists(TranslatedForeachEnum enum |
enum.getAST() = generatedBy and
result = enum
)
}
}
private class TranslatedForeachTry extends TranslatedCompilerGeneratedTry, TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachTry() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 0)
}
override TranslatedElement getFinally() {
exists(TranslatedForeachFinally ff |
ff.getAST() = generatedBy and
result = ff
)
}
override TranslatedElement getBody() {
exists(TranslatedForeachWhile fw |
fw.getAST() = generatedBy and
result = fw
)
}
}
/**
* The translation of the finally block.
*/
private class TranslatedForeachFinally extends TranslatedCompilerGeneratedBlock,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachFinally() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 1)
}
override TranslatedElement getStmt(int index) {
index = 0 and
exists(TranslatedForeachDispose fd |
fd.getAST() = generatedBy and
result = fd
)
}
}
/**
* The compiler generated while loop.
*/
private class TranslatedForeachWhile extends TranslatedCompilerGeneratedStmt, ConditionContext,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachWhile() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 2)
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag,
Type resultType, boolean isLValue) {
none()
}
override Instruction getInstructionSuccessor(InstructionTag tag,
EdgeKind kind) {
none()
}
override Instruction getFirstInstruction() {
result = getCondition().getFirstInstruction()
}
override Instruction getChildSuccessor(TranslatedElement child) {
child = getInit() and result = getBody().getFirstInstruction() or
child = getBody() and result = getCondition().getFirstInstruction()
}
override TranslatedElement getChild(int id) {
id = 0 and result = getCondition() or
id = 1 and result = getInit() or
id = 2 and result = getBody()
}
override final Instruction getChildTrueSuccessor(ConditionBlueprint child) {
child = getCondition() and result = getInit().getFirstInstruction()
}
override final Instruction getChildFalseSuccessor(ConditionBlueprint child) {
child = getCondition() and result = getParent().getChildSuccessor(this)
}
private TranslatedStmt getBody() {
result = getTranslatedStmt(generatedBy.getBody())
}
private TranslatedElement getInit() {
exists(TranslatedForeachIterVar iv |
iv.getAST() = generatedBy and
result = iv
)
}
private ValueConditionBlueprint getCondition() {
exists(TranslatedForeachWhileCondition cond |
cond.getAST() = generatedBy and
result = cond
)
}
}
/**
* The translation of the call to the `MoveNext` method, used as a condition for the while.
*/
private class TranslatedForeachMoveNext extends TranslatedCompilerGeneratedCall,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachMoveNext() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 3)
}
override Callable getInstructionFunction(InstructionTag tag) {
tag = CallTargetTag() and
exists(Callable internal |
internal.getName() = "MoveNext" and
internal.getReturnType() instanceof BoolType and result = internal
)
}
override Type getCallResultType() {
result instanceof BoolType
}
override TranslatedExpr getArgument(int id) { none() }
override predicate hasArguments() { none() }
override TranslatedExprBlueprint getQualifier() {
exists(TranslatedMoveNextEnumAcc acc |
acc.getAST() = generatedBy and
result = acc
)
}
override Instruction getQualifierResult() {
result = getQualifier().getResult()
}
}
/**
* The translation of the call to retrieve the enumerator.
*/
private class TranslatedForeachGetEnumerator extends TranslatedCompilerGeneratedCall,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachGetEnumerator() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 4)
}
final override Type getCallResultType() {
result = getInstructionFunction(CallTargetTag()).getReturnType()
}
override Callable getInstructionFunction(InstructionTag tag) {
tag = CallTargetTag() and
exists(Callable internal |
internal.getName() = "GetEnumerator" and
// TODO: For now ignore the possibility that the
// foreach variable can have a generic type.
// The type of the callable will need to be fabricated,
// since we might not find the correct callable in the DB.
// Probably will have change the way the immediate
// operand of `FunctionAddress` is calculated.
internal.getReturnType().getName() = "IEnumerator" and
result = internal
)
}
override TranslatedExpr getArgument(int id) { none() }
override predicate hasArguments() { none() }
override TranslatedExprBlueprint getQualifier() {
result = getTranslatedExpr(generatedBy.getIterableExpr())
}
override Instruction getQualifierResult() {
result = getQualifier().getResult()
}
}
/**
* The translation of the call to the getter method of the `Current` property of the enumerator.
*/
private class TranslatedForeachCurrent extends TranslatedCompilerGeneratedCall,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachCurrent() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 5)
}
override Type getCallResultType() {
result = generatedBy.getAVariable().getType()
}
override TranslatedExpr getArgument(int id) { none() }
override predicate hasArguments() { none() }
override TranslatedExprBlueprint getQualifier() {
exists(TranslatedForeachCurrentEnumAcc acc |
acc.getAST() = generatedBy and
result = acc
)
}
override Instruction getQualifierResult() {
result = getQualifier().getResult()
}
override Callable getInstructionFunction(InstructionTag tag) {
tag = CallTargetTag() and
exists(Property prop |
prop.getName() = "Current" and
result = prop.getGetter()
)
}
}
/**
* The translation of the call to dispose (inside the finally block)
*/
private class TranslatedForeachDispose extends TranslatedCompilerGeneratedCall,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachDispose() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 6)
}
override Callable getInstructionFunction(InstructionTag tag) {
tag = CallTargetTag() and
exists(Callable dispose |
dispose.getName() = "Dispose" and
result = dispose
)
}
final override Type getCallResultType() {
result instanceof VoidType
}
override TranslatedExpr getArgument(int id) { none() }
override predicate hasArguments() { none() }
override TranslatedExprBlueprint getQualifier() {
exists(TranslatedForeachDisposeEnumAcc acc |
acc.getAST() = generatedBy and
result = acc
)
}
override Instruction getQualifierResult() {
result = getQualifier().getResult()
}
}
/**
* The condition for the while, ie. a call to MoveNext.
*/
private class TranslatedForeachWhileCondition extends TranslatedCompilerGeneratedValueCondition,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachWhileCondition() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 7)
}
override TranslatedCompilerGeneratedCall getValueExpr() {
exists(TranslatedForeachMoveNext mn |
mn.getAST() = generatedBy and
result = mn
)
}
override Instruction valueExprResult() {
result = getValueExpr().getResult()
}
}
/**
* Class that represents that translation of the declaration that happens before the `try ... finally` block (the
* declaration of the `temporary` enumerator variable)
*/
private class TranslatedForeachEnum extends TranslatedCompilerGeneratedDeclaration,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachEnum() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 8)
}
override predicate hasTempVariable(TempVariableTag tag, Type type) {
tag = ForeachEnumTempVar() and
type = getInitialization().getCallResultType()
}
override IRTempVariable getIRVariable() {
result = getIRTempVariable(generatedBy, ForeachEnumTempVar())
}
override TranslatedCompilerGeneratedCall getInitialization() {
exists(TranslatedForeachGetEnumerator ge |
ge.getAST() = generatedBy and
result = ge
)
}
override Instruction getInitializationResult() {
result = getInitialization().getResult()
}
}
/**
* Class that represents that translation of the declaration that's happening inside the body of the while.
*/
private class TranslatedForeachIterVar extends TranslatedCompilerGeneratedDeclaration,
TTranslatedCompilerGeneratedElement {
override ForeachStmt generatedBy;
TranslatedForeachIterVar() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 9)
}
override IRVariable getInstructionVariable(InstructionTag tag) {
tag = InitializerVariableAddressTag() and
result = getIRVariable()
}
override IRVariable getIRVariable() {
result = getIRUserVariable(getFunction(), generatedBy.getAVariable())
}
override TranslatedCompilerGeneratedCall getInitialization() {
exists(TranslatedForeachCurrent crtProp |
crtProp.getAST() = generatedBy and
result = crtProp
)
}
override Instruction getInitializationResult() {
result = getInitialization().getResult()
}
}
/**
* Class that represents that translation of access to the temporary enumerator variable. Used as the qualifier
* for the call to `MoveNext`.
*/
private class TranslatedMoveNextEnumAcc extends TTranslatedCompilerGeneratedElement,
TranslatedCompilerGeneratedVariableAccess {
override ForeachStmt generatedBy;
TranslatedMoveNextEnumAcc() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 10)
}
override Type getResultType() {
result instanceof BoolType
}
override predicate hasTempVariable(TempVariableTag tag, Type type) {
tag = ForeachEnumTempVar() and
type = getResultType()
}
override IRVariable getInstructionVariable(InstructionTag tag) {
tag = AddressTag() and
result = getTempVariable(ForeachEnumTempVar())
}
override predicate needsLoad() { any() }
}
/**
* Class that represents that translation of access to the temporary enumerator variable. Used as the qualifier
* for the call to the getter of the property `Current`.
*/
private class TranslatedForeachCurrentEnumAcc extends TTranslatedCompilerGeneratedElement,
TranslatedCompilerGeneratedVariableAccess {
override ForeachStmt generatedBy;
TranslatedForeachCurrentEnumAcc() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 11)
}
override Type getResultType() {
result instanceof BoolType
}
override predicate hasTempVariable(TempVariableTag tag, Type type) {
tag = ForeachEnumTempVar() and
type = getResultType()
}
override IRVariable getInstructionVariable(InstructionTag tag) {
tag = AddressTag() and
result = getTempVariable(ForeachEnumTempVar())
}
override predicate needsLoad() { any() }
}
/**
* Class that represents that translation of access to the temporary enumerator variable. Used as the qualifier
* for the call to `Dispose`.
*/
private class TranslatedForeachDisposeEnumAcc extends TTranslatedCompilerGeneratedElement,
TranslatedCompilerGeneratedVariableAccess {
override ForeachStmt generatedBy;
TranslatedForeachDisposeEnumAcc() {
this = TTranslatedCompilerGeneratedElement(generatedBy, 12)
}
override Type getResultType() {
result instanceof BoolType
}
override predicate hasTempVariable(TempVariableTag tag, Type type) {
tag = ForeachEnumTempVar() and
type = getResultType()
}
override IRVariable getInstructionVariable(InstructionTag tag) {
tag = AddressTag() and
result = getTempVariable(ForeachEnumTempVar())
}
override predicate needsLoad() { any() }
}

View File

@@ -1,18 +1,17 @@
import csharp
// TODO: ARE THOSE TAGS ENOUGH?
newtype TTempVariableTag =
ConditionValueTempVar() or
ReturnValueTempVar() or
ThrowTempVar() or
LambdaTempVar()
LambdaTempVar() or
ForeachEnumTempVar()
string getTempVariableTagId(TTempVariableTag tag) {
tag = ConditionValueTempVar() and result = "CondVal"
or
tag = ReturnValueTempVar() and result = "Ret"
or
tag = ThrowTempVar() and result = "Throw"
or
tag = LambdaTempVar() and result = "Lambda"
tag = ConditionValueTempVar() and result = "CondVal" or
tag = ReturnValueTempVar() and result = "Ret" or
tag = ThrowTempVar() and result = "Throw" or
tag = LambdaTempVar() and result = "Lambda" or
tag = ForeachEnumTempVar() and result = "ForeachEnum"
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
class ForEach {
public static void Main() {
int[] a_array = new int[] { 1, 2, 3, 4, 5, 6, 7 };
foreach(int items in a_array)
{
int x = items;
}
}
}

View File

@@ -354,6 +354,85 @@ delegates.cs:
# 11| v0_17(Void) = UnmodeledUse : mu*
# 11| v0_18(Void) = ExitFunction :
foreach.cs:
# 4| System.Void ForEach.Main()
# 4| Block 0
# 4| v0_0(Void) = EnterFunction :
# 4| mu0_1(null) = AliasedDefinition :
# 4| mu0_2(null) = UnmodeledDefinition :
# 5| r0_3(glval<Int32[]>) = VariableAddress[a_array] :
# 5| mu0_4(Int32[]) = Uninitialized[a_array] : &:r0_3
# 5| r0_5(Int32) = Constant[0] :
# 5| r0_6(glval<Int32>) = PointerAdd : r0_3, r0_5
# 5| r0_7(Int32) = Constant[1] :
# 5| mu0_8(Int32) = Store : &:r0_6, r0_7
# 5| r0_9(Int32) = Constant[1] :
# 5| r0_10(glval<Int32>) = PointerAdd : r0_3, r0_9
# 5| r0_11(Int32) = Constant[2] :
# 5| mu0_12(Int32) = Store : &:r0_10, r0_11
# 5| r0_13(Int32) = Constant[2] :
# 5| r0_14(glval<Int32>) = PointerAdd : r0_3, r0_13
# 5| r0_15(Int32) = Constant[3] :
# 5| mu0_16(Int32) = Store : &:r0_14, r0_15
# 5| r0_17(Int32) = Constant[3] :
# 5| r0_18(glval<Int32>) = PointerAdd : r0_3, r0_17
# 5| r0_19(Int32) = Constant[4] :
# 5| mu0_20(Int32) = Store : &:r0_18, r0_19
# 5| r0_21(Int32) = Constant[4] :
# 5| r0_22(glval<Int32>) = PointerAdd : r0_3, r0_21
# 5| r0_23(Int32) = Constant[5] :
# 5| mu0_24(Int32) = Store : &:r0_22, r0_23
# 5| r0_25(Int32) = Constant[5] :
# 5| r0_26(glval<Int32>) = PointerAdd : r0_3, r0_25
# 5| r0_27(Int32) = Constant[6] :
# 5| mu0_28(Int32) = Store : &:r0_26, r0_27
# 5| r0_29(Int32) = Constant[6] :
# 5| r0_30(glval<Int32>) = PointerAdd : r0_3, r0_29
# 5| r0_31(Int32) = Constant[7] :
# 5| mu0_32(Int32) = Store : &:r0_30, r0_31
# 7| r0_33(glval<IEnumerator>) = VariableAddress[#temp7:9] :
# 7| r0_34(glval<Int32[]>) = VariableAddress[a_array] :
# 7| r0_35(Int32[]) = Load : &:r0_34, ~mu0_2
# 7| r0_36(glval<null>) = FunctionAddress[GetEnumerator] :
# 7| r0_37(IEnumerator) = Call : func:r0_36, this:r0_35
# 7| mu0_38(null) = ^CallSideEffect : ~mu0_2
# 7| mu0_39(IEnumerator) = Store : &:r0_33, r0_37
#-----| Goto -> Block 1
# 7| Block 1
# 7| r1_0(glval<Boolean>) = VariableAddress[#temp7:9] :
# 7| r1_1(Boolean) = Load : &:r1_0, ~mu0_2
# 7| r1_2(glval<null>) = FunctionAddress[MoveNext] :
# 7| r1_3(Boolean) = Call : func:r1_2, this:r1_1
# 7| mu1_4(null) = ^CallSideEffect : ~mu0_2
# 7| v1_5(Void) = ConditionalBranch : r1_3
#-----| False (back edge) -> Block 3
#-----| True (back edge) -> Block 2
# 7| Block 2
# 7| r2_0(glval<Int32>) = VariableAddress[items] :
# 7| r2_1(glval<Boolean>) = VariableAddress[#temp7:9] :
# 7| r2_2(Boolean) = Load : &:r2_1, ~mu0_2
# 7| r2_3(glval<null>) = FunctionAddress[get_Current] :
# 7| r2_4(Int32) = Call : func:r2_3, this:r2_2
# 7| mu2_5(null) = ^CallSideEffect : ~mu0_2
# 7| mu2_6(Int32) = Store : &:r2_0, r2_4
# 9| r2_7(glval<Int32>) = VariableAddress[x] :
# 9| r2_8(glval<Int32>) = VariableAddress[items] :
# 9| r2_9(Int32) = Load : &:r2_8, ~mu0_2
# 9| mu2_10(Int32) = Store : &:r2_7, r2_9
#-----| Goto (back edge) -> Block 1
# 7| Block 3
# 7| r3_0(glval<Boolean>) = VariableAddress[#temp7:9] :
# 7| r3_1(Boolean) = Load : &:r3_0, ~mu0_2
# 7| r3_2(glval<null>) = FunctionAddress[Dispose] :
# 7| v3_3(Void) = Call : func:r3_2, this:r3_1
# 7| mu3_4(null) = ^CallSideEffect : ~mu0_2
# 4| v3_5(Void) = ReturnVoid :
# 4| v3_6(Void) = UnmodeledUse : mu*
# 4| v3_7(Void) = ExitFunction :
func_with_param_call.cs:
# 5| System.Int32 test_call_with_param.f(System.Int32,System.Int32)
# 5| Block 0