/// <summary> /// Creates a deterministic or nondeterministic fault effect body. /// </summary> private BlockSyntax CreateBody(IMethodSymbol method, BlockSyntax originalBody, SyntaxNode baseEffect) { var lineAdjustedOriginalBody = originalBody.AppendLineDirective(-1).PrependLineDirective(originalBody.GetLineNumber()); var componentType = _typeLookup[method.ContainingType.BaseType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)]; var faultEffectType = _typeLookup[method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)]; var faults = _faults[componentType]; var baseStatements = !method.ReturnsVoid ? new[] { Syntax.ReturnStatement(baseEffect) } : new[] { Syntax.ExpressionStatement(baseEffect), Syntax.ReturnStatement() }; IMethodSymbol methodSymbol; BlockSyntax body = null; if (_methodLookup.TryGetValue(method.OverriddenMethod.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), out methodSymbol)) { var priorityFaults = faults.Where(fault => fault.GetPriority(Compilation) == method.ContainingType.GetPriority(Compilation)).ToArray(); var overridingEffects = priorityFaults.Where(f => f.GetMembers().OfType<IMethodSymbol>().Any(m => m.Overrides(methodSymbol))).ToArray(); var overrideCount = overridingEffects.Length; if (overrideCount > 1) { var fieldName = _faultChoiceFields[Tuple.Create(methodSymbol, priorityFaults[0].GetPriority(Compilation))]; var effectIndex = Array.IndexOf(priorityFaults, faultEffectType); var choiceField = Syntax.MemberAccessExpression(Syntax.ThisExpression(), fieldName); var levelCondition = Syntax.ValueNotEqualsExpression(choiceField, Syntax.LiteralExpression(effectIndex)); var ifStatement = Syntax.IfStatement(levelCondition, baseStatements).NormalizeWhitespace().WithTrailingNewLines(1); if (overridingEffects.Last().Equals(faultEffectType)) { var levelChoiceVariable = "levelChoice".ToSynthesized(); var levelCountVariable = "levelCount".ToSynthesized(); var writer = new CodeWriter(); writer.AppendLine("unsafe"); writer.AppendBlockStatement(() => { writer.AppendLine($"var {levelChoiceVariable} = stackalloc int[{overrideCount}];"); writer.AppendLine($"var {levelCountVariable} = 0;"); for (var i = 0; i < overrideCount; ++i) { var effectType = overridingEffects[i].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var index = Array.IndexOf(priorityFaults, overridingEffects[i]); writer.AppendLine($"if ({_tryActivate}((({effectType})this).{"fault".ToSynthesized()}))"); writer.IncreaseIndent(); writer.AppendLine($"{levelChoiceVariable}[{levelCountVariable}++] = {index};"); writer.DecreaseIndent(); writer.NewLine(); } writer.AppendLine($"{fieldName} = {levelCountVariable} == 0 ? - 1 : {levelChoiceVariable}[ChooseIndex({levelCountVariable})];"); }); var selectionStatement = SyntaxFactory.ParseStatement(writer.ToString()); body = SyntaxFactory.Block(selectionStatement, (StatementSyntax)ifStatement, lineAdjustedOriginalBody); } else body = SyntaxFactory.Block((StatementSyntax)ifStatement, lineAdjustedOriginalBody); } } if (body == null) { var writer = new CodeWriter(); writer.AppendLine($"if (!{_tryActivate}(this.{"fault".ToSynthesized()}))"); writer.AppendBlockStatement(() => { // Optimization: If we're normalizing a non-void returning method without ref/out parameters and // the fault effect simply returns a constant value of primitive type, we generate code to check whether the non-fault // value for the case that the fault is not activated (which is always the first case) actually differs // from the constant value returned by the fault effect when the fault is activated. If both values are // the same, the activation of the fault will have no effect, so we can undo it, reducing the number // of transitions that have to be checked var signatureAllowsOptimization = !method.ReturnsVoid && CanBeCompared(method.ReturnType) && method.Parameters.All(parameter => parameter.RefKind == RefKind.None); var faultEffectReturn = originalBody.Statements.Count == 1 ? originalBody.Statements[0] as ReturnStatementSyntax : null; var isConstantValue = faultEffectReturn != null && SemanticModel.GetConstantValue(faultEffectReturn.Expression).HasValue; if (signatureAllowsOptimization && isConstantValue) { writer.AppendLine($"var {"tmp".ToSynthesized()} = {baseEffect.ToFullString()};"); writer.AppendLine($"if ({"tmp".ToSynthesized()} == {faultEffectReturn.Expression.ToFullString()})"); writer.AppendBlockStatement(() => { writer.AppendLine($"{_undoActivation}(this.{"fault".ToSynthesized()});"); }); writer.AppendLine($"return {"tmp".ToSynthesized()};"); } else { foreach (var statement in baseStatements) writer.AppendLine(statement.NormalizeWhitespace().ToFullString()); } }); writer.NewLine(); body = SyntaxFactory.Block(SyntaxFactory.ParseStatement(writer.ToString()), lineAdjustedOriginalBody); } return body.PrependLineDirective(-1); }
/// <summary> /// Creates a deterministic or nondeterministic fault effect body. /// </summary> private BlockSyntax CreateBody(IMethodSymbol method, BlockSyntax originalBody, SyntaxNode baseEffect) { var lineAdjustedOriginalBody = originalBody.AppendLineDirective(-1).PrependLineDirective(originalBody.GetLineNumber()); var componentType = _typeLookup[method.ContainingType.BaseType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)]; var faultEffectType = _typeLookup[method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)]; var faults = _faults[componentType]; var baseStatements = !method.ReturnsVoid ? new[] { Syntax.ReturnStatement(baseEffect) } : new[] { Syntax.ExpressionStatement(baseEffect), Syntax.ReturnStatement() }; IMethodSymbol methodSymbol; BlockSyntax body = null; if (_methodLookup.TryGetValue(method.OverriddenMethod.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), out methodSymbol)) { var priorityFaults = faults.Where(fault => fault.GetPriority(Compilation) == method.ContainingType.GetPriority(Compilation)).ToArray(); var overridingEffects = priorityFaults.Where(f => f.GetMembers().OfType <IMethodSymbol>().Any(m => m.Overrides(methodSymbol))).ToArray(); var overrideCount = overridingEffects.Length; if (overrideCount > 1) { var fieldName = _faultChoiceFields[Tuple.Create(methodSymbol, priorityFaults[0].GetPriority(Compilation))]; var effectIndex = Array.IndexOf(priorityFaults, faultEffectType); var choiceField = Syntax.MemberAccessExpression(Syntax.ThisExpression(), fieldName); var levelCondition = Syntax.ValueNotEqualsExpression(choiceField, Syntax.LiteralExpression(effectIndex)); var ifStatement = Syntax.IfStatement(levelCondition, baseStatements).NormalizeWhitespace().WithTrailingNewLines(1); if (overridingEffects.Last().Equals(faultEffectType)) { var levelChoiceVariable = "levelChoice".ToSynthesized(); var levelCountVariable = "levelCount".ToSynthesized(); var writer = new CodeWriter(); writer.AppendLine("unsafe"); writer.AppendBlockStatement(() => { writer.AppendLine($"var {levelChoiceVariable} = stackalloc int[{overrideCount}];"); writer.AppendLine($"var {levelCountVariable} = 0;"); for (var i = 0; i < overrideCount; ++i) { var effectType = overridingEffects[i].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var index = Array.IndexOf(priorityFaults, overridingEffects[i]); writer.AppendLine($"if ({_tryActivate}((({effectType})this).{"fault".ToSynthesized()}))"); writer.IncreaseIndent(); writer.AppendLine($"{levelChoiceVariable}[{levelCountVariable}++] = {index};"); writer.DecreaseIndent(); writer.NewLine(); } writer.AppendLine($"{fieldName} = {levelCountVariable} == 0 ? - 1 : {levelChoiceVariable}[ChooseIndex({levelCountVariable})];"); }); var selectionStatement = SyntaxFactory.ParseStatement(writer.ToString()); body = SyntaxFactory.Block(selectionStatement, (StatementSyntax)ifStatement, lineAdjustedOriginalBody); } else { body = SyntaxFactory.Block((StatementSyntax)ifStatement, lineAdjustedOriginalBody); } } } if (body == null) { var writer = new CodeWriter(); writer.AppendLine($"if (!{_tryActivate}(this.{"fault".ToSynthesized()}))"); writer.AppendBlockStatement(() => { // Optimization: If we're normalizing a non-void returning method without ref/out parameters and // the fault effect simply returns a constant value of primitive type, we generate code to check whether the non-fault // value for the case that the fault is not activated (which is always the first case) actually differs // from the constant value returned by the fault effect when the fault is activated. If both values are // the same, the activation of the fault will have no effect, so we can undo it, reducing the number // of transitions that have to be checked var signatureAllowsOptimization = !method.ReturnsVoid && CanBeCompared(method.ReturnType) && method.Parameters.All(parameter => parameter.RefKind == RefKind.None); var faultEffectReturn = originalBody.Statements.Count == 1 ? originalBody.Statements[0] as ReturnStatementSyntax : null; var isConstantValue = faultEffectReturn != null && SemanticModel.GetConstantValue(faultEffectReturn.Expression).HasValue; if (signatureAllowsOptimization && isConstantValue) { writer.AppendLine($"var {"tmp".ToSynthesized()} = {baseEffect.ToFullString()};"); writer.AppendLine($"if ({"tmp".ToSynthesized()} == {faultEffectReturn.Expression.ToFullString()})"); writer.AppendBlockStatement(() => { writer.AppendLine($"{_undoActivation}(this.{"fault".ToSynthesized()});"); }); writer.AppendLine($"return {"tmp".ToSynthesized()};"); } else { foreach (var statement in baseStatements) { writer.AppendLine(statement.NormalizeWhitespace().ToFullString()); } } }); writer.NewLine(); body = SyntaxFactory.Block(SyntaxFactory.ParseStatement(writer.ToString()), lineAdjustedOriginalBody); } return(body.PrependLineDirective(-1)); }