private ExpressionSyntax GenIncDec(FuzzType type, bool isIncrement) { SyntaxKind[] acceptedTypes = { SyntaxKind.ULongKeyword, SyntaxKind.LongKeyword, SyntaxKind.UIntKeyword, SyntaxKind.IntKeyword, SyntaxKind.UShortKeyword, SyntaxKind.ShortKeyword, SyntaxKind.ByteKeyword, SyntaxKind.SByteKeyword, }; if (!(type is PrimitiveType pt) || !acceptedTypes.Contains(pt.Keyword)) { return(null); } LValueInfo subject = GenExistingLValue(type, int.MinValue); if (subject == null) { return(null); } ExpressionSyntax gen = PostfixUnaryExpression( isIncrement ? SyntaxKind.PostIncrementExpression : SyntaxKind.PostDecrementExpression, subject.Expression); return(gen); }
public ScopeValue(FuzzType type, ExpressionSyntax expr, int refEscapeScope, bool readOnly) { Type = type; Expression = expr; RefEscapeScope = refEscapeScope; ReadOnly = readOnly; }
public void Generate(FuzzType returnType, bool randomizeParams) { ReturnType = returnType; if (randomizeParams) { int numArgs = Options.MethodParameterCountDist.Sample(Random.Rng); Parameters = new VariableIdentifier[numArgs]; for (int i = 0; i < Parameters.Length; i++) { FuzzType type = Types.PickType(Options.ParameterIsByRefProb); string name = $"arg{i}"; // A ref to a by-ref parameter can escape to at least its parent method int refEscapeScope = type is RefType ? 1 : 0; Parameters[i] = new VariableIdentifier(type, name, refEscapeScope); } } else { Parameters = Array.Empty <VariableIdentifier>(); } _level = -1; Body = GenBlock(true); }
private ExpressionSyntax GenBoolProducingBinary() { FuzzType boolType = Types.GetPrimitiveType(SyntaxKind.BoolKeyword); ExpressionSyntax left, right; SyntaxKind op = (SyntaxKind)Options.BinaryBoolDist.Sample(Random.Rng); if (op == SyntaxKind.LogicalAndExpression || op == SyntaxKind.LogicalOrExpression || op == SyntaxKind.ExclusiveOrExpression || op == SyntaxKind.BitwiseAndExpression || op == SyntaxKind.BitwiseOrExpression) { // &&, ||, ^, &, | must be between bools to produce bool left = GenExpression(boolType); right = GenExpression(boolType); } else if (op == SyntaxKind.EqualsExpression || op == SyntaxKind.NotEqualsExpression) { PrimitiveType leftType = Types.PickPrimitiveType(f => true); left = GenExpression(leftType); PrimitiveType rightType = Types.PickPrimitiveType(f => BinOpTable.Equality.GetResultType(leftType.Keyword, f.Keyword).HasValue); right = GenExpression(rightType); } else { Debug.Assert(op == SyntaxKind.LessThanOrEqualExpression || op == SyntaxKind.LessThanExpression || op == SyntaxKind.GreaterThanOrEqualExpression || op == SyntaxKind.GreaterThanExpression); PrimitiveType leftType = Types.PickPrimitiveType(f => f.Keyword != SyntaxKind.BoolKeyword); PrimitiveType rightType = Types.PickPrimitiveType(f => BinOpTable.Relop.GetResultType(leftType.Keyword, f.Keyword).HasValue); left = GenExpression(leftType); right = GenExpression(rightType); } return(BinaryExpression(op, ParenthesizeIfNecessary(left), ParenthesizeIfNecessary(right))); }
public ArrayTypeSyntax GenReferenceToArrayType() { if (_type != null) { return(_type); } List <int> ranks = new() { Rank }; // int[][] => ArrayType(ArrayType(Int, 1), 1) // int[][,] => ArrayType(ArrayType(Int, 2), 1) FuzzType innerElem = ElementType; while (innerElem is ArrayType at) { ranks.Add(at.Rank); innerElem = at.ElementType; } _type = ArrayType( innerElem.GenReferenceTo(), ranks.Select( r => ArrayRankSpecifier( SeparatedList( Enumerable.Repeat((ExpressionSyntax)OmittedArraySizeExpression(), r)))).ToSyntaxList()); return(_type); }
public static ExpressionSyntax GenLiteral(TypeManager types, Randomizer random, FuzzType type) { switch (type) { case PrimitiveType prim: return(GenPrimitiveLiteral(random, prim)); case AggregateType agg: return (ObjectCreationExpression(type.GenReferenceTo()) .WithArgumentList( ArgumentList( SeparatedList(agg.Fields.Select(af => Argument(GenLiteral(types, random, af.Type))))))); case ArrayType arr: List <int> dims = GenArrayDimensions(random, arr); return(GenArrayCreation(types, random, arr, dims)); case InterfaceType it: return(GenLiteral(types, random, random.NextElement(types.GetImplementingTypes(it)))); default: throw new Exception("Unreachable"); } }
public LValueInfo(ExpressionSyntax expression, FuzzType type, int refEscapeScope, bool readOnly) { Expression = expression; Type = type; RefEscapeScope = refEscapeScope; ReadOnly = readOnly; }
/// <summary> /// Generates an access to a random member: /// * A static variable /// * A local variable /// * An argument /// * A field in one of these, if aggregate type /// * An element in one of these, if array type /// </summary> private LValueInfo GenMemberAccess(FuzzType ft) { List <LValueInfo> paths = Random.FlipCoin(Options.MemberAccessSelectLocalProb) ? CollectVariablePaths(ft, int.MinValue, true, false) : CollectVariablePaths(ft, int.MinValue, false, true); return(paths.Count > 0 ? Random.NextElement(paths) : null); }
public StaticField GenerateNewField(FuzzType type) { type = type ?? Types.PickType(); string name = "s_" + (++_counter); StaticField field = new StaticField(new VariableIdentifier(type, name), LiteralGenerator.GenLiteral(Random, type)); _fields.Add(field); return(field); }
public ArrayType(FuzzType elementType, int rank = 1) { if (rank < 1) { throw new ArgumentException("Invalid rank " + rank, nameof(rank)); } ElementType = elementType; Rank = rank; }
private ExpressionSyntax GenExpression(FuzzType type) { Debug.Assert(!(type is RefType)); ExpressionSyntax gen; do { ExpressionKind kind = (ExpressionKind)Options.ExpressionTypeDist.Sample(Random.Rng); switch (kind) { case ExpressionKind.MemberAccess: gen = GenMemberAccess(type)?.Expression; break; case ExpressionKind.Literal: gen = GenLiteral(type); break; case ExpressionKind.Binary: gen = GenBinary(type); break; case ExpressionKind.Call: gen = GenCall(type, true); break; case ExpressionKind.Increment: gen = GenIncDec(type, true); break; case ExpressionKind.Decrement: gen = GenIncDec(type, false); break; case ExpressionKind.NewObject: gen = GenNewObject(type); break; case ExpressionKind.Cast: gen = null; continue; gen = GenCast(type); break; default: throw new Exception("Unreachable"); } }while (gen == null); return(gen); }
/// <summary>Returns an lvalue.</summary> private LValueInfo GenLValue(FuzzType type, int minRefEscapeScope) { Debug.Assert(type != null); LValueInfo lv = GenExistingLValue(type, minRefEscapeScope); if (lv == null) { StaticField newStatic = Statics.GenerateNewField(type); lv = new LValueInfo(IdentifierName(newStatic.Var.Name), type, int.MaxValue); } return(lv); }
private ExpressionSyntax GenBinary(FuzzType type) { if (!(type is PrimitiveType pt)) { return(null); } if (pt.Keyword == SyntaxKind.BoolKeyword) { return(GenBoolProducingBinary()); } return(GenIntegralProducingBinary(pt)); }
private (ExpressionSyntax, ExpressionSyntax) GenLeftRightForBinary(FuzzType leftType, FuzzType rightType) { ExpressionSyntax left = GenExpression(leftType); ExpressionSyntax right; // There are two reasons we don't allow both left and right to be literals: // 1. If the computation overflows, this gives a C# compiler error // 2. The compiler is required to constant fold these expressions which is not interesting. do { right = GenExpression(rightType); } while (left is LiteralExpressionSyntax && right is LiteralExpressionSyntax); return(left, right); }
private static List <int> GenArrayDimensions(Randomizer random, ArrayType at) { int dimsRequired = at.Rank; FuzzType elemType = at.ElementType; while (elemType is ArrayType innerArr) { dimsRequired += innerArr.Rank; elemType = innerArr.ElementType; } List <int> dimensions = new(dimsRequired); for (int tries = 0; tries < 100; tries++) { // If we are constructing an aggregate type start out by the number of fields, to limit // the number of constants required here. int totalSize = elemType is AggregateType elemAgg?elemAgg.GetTotalNumPrimitiveFields() : 1; int maxArrayTotalSize = Math.Max(totalSize, random.Options.MaxArrayTotalSize); for (int i = 0; i < dimensions.Capacity; i++) { int dim = random.Next(1, random.Options.MaxArrayLengthPerDimension + 1); if (totalSize * dim > maxArrayTotalSize) { break; } dimensions.Add(dim); totalSize *= dim; } if (dimensions.Count == dimensions.Capacity) { return(dimensions); } dimensions.Clear(); } for (int i = 0; i < dimsRequired; i++) { dimensions.Add(1); } return(dimensions); }
private ExpressionSyntax GenNewObject(FuzzType type) { if (!(type is AggregateType at)) { return(null); } ObjectCreationExpressionSyntax creation = ObjectCreationExpression(type.GenReferenceTo()) .WithArgumentList( ArgumentList( SeparatedList( at.Fields.Select(f => Argument(GenExpression(f.Type)))))); return(creation); }
public StaticField PickStatic(FuzzType type = null) { List <StaticField> fields = _fields; if (type != null) { fields = _fields.Where(f => f.Var.Type == type).ToList(); } if (!fields.Any()) { return(GenerateNewField(type)); } return(Random.NextElement(fields)); }
public void Generate(FuzzType returnType, bool randomizeParams) { ReturnType = returnType; if (randomizeParams) { int numArgs = Options.MethodParameterCountDist.Sample(Random.Rng); ParameterTypes = Enumerable.Range(0, numArgs).Select(i => Types.PickType()).ToArray(); } else { ParameterTypes = Array.Empty <FuzzType>(); } _level = -1; Body = GenBlock(ReturnType != null); }
private ExpressionSyntax GenCall(FuzzType type, bool allowNew) { Debug.Assert(!(type is RefType), "Cannot GenCall to ref type -- use GenExistingLValue for that"); FuncGenerator func; if (allowNew && Random.FlipCoin(Options.GenNewMethodProb)) { type = type ?? Types.PickType(Options.ReturnTypeIsByRefProb); func = new FuncGenerator(_funcs, Random, Types, Statics, _genChecksumSiteId); func.Generate(type, true); } else { IEnumerable <FuncGenerator> funcs = _funcs.Skip(_funcIndex + 1); if (type != null) { funcs = funcs.Where(f => f.ReturnType.IsCastableTo(type) || (f.ReturnType is RefType rt && rt.InnerType.IsCastableTo(type))); } List <FuncGenerator> list = funcs.ToList(); if (list.Count == 0) { return(null); } func = Random.NextElement(list); type = type ?? func.ReturnType; } ArgumentSyntax[] args = GenArgs(func, 0, out _); InvocationExpressionSyntax invoc = InvocationExpression( IdentifierName(func.Name), ArgumentList( SeparatedList(args))); if (func.ReturnType == type || func.ReturnType is RefType retRt && retRt.InnerType == type) { return(invoc); } return(CastExpression(type.GenReferenceTo(), invoc)); }
private List <LValueInfo> CollectVariablePaths(FuzzType type, int minRefEscapeScope, bool collectLocals, bool collectStatics) { List <LValueInfo> paths = new List <LValueInfo>(); if (collectLocals) { foreach (ScopeFrame sf in _scope) { foreach (VariableIdentifier variable in sf.Variables) { AppendVariablePaths(paths, variable); } } } if (collectStatics) { foreach (StaticField stat in Statics.Fields) { AppendVariablePaths(paths, stat.Var); } } Debug.Assert(type == null || !(type is RefType), "Cannot collect variables of ref type"); // Verify that a type is allowed. Often we want to implicitly promote // a by-ref to its inner type, because we are taking a ref anyway or using // its value, and it is automatically promoted. // This checks for both of these cases: // ref int lhs = ...; // int rhs1 = ...; // ref int rhs2 = ...; // lhs = ref rhs1; // must be supported // lhs = ref rhs2; // must be supported bool IsAllowedType(FuzzType other) => type == null || (other == type || (other is RefType rt && rt.InnerType == type)); paths.RemoveAll(lv => lv.RefEscapeScope < minRefEscapeScope || !IsAllowedType(lv.Type)); return(paths); }
private ArgumentSyntax[] GenArgs(FuncGenerator funcToCall, int minRefEscapeScope, out int argsMinRefEscapeScope) { ArgumentSyntax[] args = new ArgumentSyntax[funcToCall.Parameters.Length]; argsMinRefEscapeScope = int.MaxValue; for (int i = 0; i < args.Length; i++) { FuzzType paramType = funcToCall.Parameters[i].Type; if (paramType is RefType rt) { LValueInfo lv = GenLValue(rt.InnerType, minRefEscapeScope); argsMinRefEscapeScope = Math.Min(argsMinRefEscapeScope, lv.RefEscapeScope); args[i] = Argument(lv.Expression).WithRefKindKeyword(Token(SyntaxKind.RefKeyword)); } else { args[i] = Argument(GenExpression(paramType)); } } return(args); }
public VariableIdentifier(FuzzType type, string name) { Type = type; Name = name; }
public RefType(FuzzType innerType) { Debug.Assert(!(innerType is RefType), "Cannot create ref to ref type"); InnerType = innerType; }
private ExpressionSyntax GenCast(FuzzType type) { return(CastExpression(type.GenReferenceTo(), GenExpression(type))); }
public AggregateField(FuzzType type, string name) { Type = type; Name = name; }
private ExpressionSyntax GenCall(FuzzType type, bool allowNew) { Debug.Assert(!(type is RefType), "Cannot GenCall to ref type -- use GenExistingLValue for that"); FuncGenerator func; if (allowNew && Random.FlipCoin(Options.GenNewFunctionProb) && !Options.FuncGenRejection.Reject(_funcs.Count, Random.Rng)) { type = type ?? Types.PickType(Options.ReturnTypeIsByRefProb); func = new FuncGenerator(_funcs, Random, Types, Statics, _genChecksumSiteId); func.Generate(type, true); } else { IEnumerable <FuncGenerator> funcs = _funcs .Skip(_funcIndex + 1) .Where(candidate => { // Make sure we do not get too many leaf calls. Here we compute what the new transitive // number of calls would be to each function, and if it's too much, reject this candidate. // Note that we will never reject calling a leaf function directly, even if the +1 puts // us over the cap. That is intentional. We only want to limit the exponential growth // which happens when functions call functions multiple times, and those functions also // call functions multiple times. foreach (var(transFunc, transNumCall) in candidate._callCountMap) { _callCountMap.TryGetValue(transFunc, out long curNumCalls); if (curNumCalls + transNumCall > Options.SingleFunctionMaxTotalCalls) { return(false); } } return(true); }); if (type != null) { funcs = funcs.Where(f => f.ReturnType.IsCastableTo(type) || (f.ReturnType is RefType rt && rt.InnerType.IsCastableTo(type))); } List <FuncGenerator> list = funcs.ToList(); if (list.Count == 0) { return(null); } func = Random.NextElement(list); type = type ?? func.ReturnType; } // Update transitive call counts before generating args, so we decrease chance of // calling the same methods in the arg expressions. _callCountMap.TryGetValue(func._funcIndex, out long numCalls); _callCountMap[func._funcIndex] = numCalls + 1; foreach (var(transFunc, transNumCalls) in func._callCountMap) { _callCountMap.TryGetValue(transFunc, out long curNumCalls); _callCountMap[transFunc] = curNumCalls + transNumCalls; } ArgumentSyntax[] args = GenArgs(func, 0, out _); InvocationExpressionSyntax invoc = InvocationExpression( IdentifierName(func.Name), ArgumentList( SeparatedList(args))); if (func.ReturnType == type || func.ReturnType is RefType retRt && retRt.InnerType == type) { return(invoc); } return(CastExpression(type.GenReferenceTo(), invoc)); }
public StaticField(FuzzType type, string name, ExpressionSyntax initializer) { Type = type; Name = name; Initializer = initializer; }
private StatementSyntax GenAssignmentStatement() { LValueInfo lvalue = null; if (!Random.FlipCoin(Options.AssignToNewVarProb)) { lvalue = GenExistingLValue(null, int.MinValue); } if (lvalue == null) { FuzzType newType = Types.PickType(Options.LocalIsByRefProb); // Determine if we should create a new local. We do this with a certain probabilty, // or always if the new type is a by-ref type (we cannot have static by-refs). if (newType is RefType || Random.FlipCoin(Options.NewVarIsLocalProb)) { VariableIdentifier variable; string varName = $"var{_varCounter++}"; ExpressionSyntax rhs; if (newType is RefType newRt) { LValueInfo rhsLV = GenLValue(newRt.InnerType, int.MinValue); variable = new VariableIdentifier(newType, varName, rhsLV.RefEscapeScope); rhs = RefExpression(rhsLV.Expression); } else { rhs = GenExpression(newType); variable = new VariableIdentifier(newType, varName, -(_scope.Count - 1)); } LocalDeclarationStatementSyntax decl = LocalDeclarationStatement( VariableDeclaration( variable.Type.GenReferenceTo(), SingletonSeparatedList( VariableDeclarator(variable.Name) .WithInitializer( EqualsValueClause(rhs))))); _scope.Last().Variables.Add(variable); return(decl); } StaticField newStatic = Statics.GenerateNewField(newType); lvalue = new LValueInfo(IdentifierName(newStatic.Var.Name), newType, int.MaxValue); } // Determine if we should generate a ref-reassignment. In that case we cannot do anything // clever, like generate compound assignments. FuzzType rhsType = lvalue.Type; if (lvalue.Type is RefType rt) { if (Random.FlipCoin(Options.AssignGenRefReassignProb)) { RefExpressionSyntax refRhs = RefExpression(GenLValue(rt.InnerType, lvalue.RefEscapeScope).Expression); return (ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, lvalue.Expression, refRhs))); } // We have a ref-type, but are not generating a ref-reassign, so lift the type and make a normal assignment. rhsType = rt.InnerType; } SyntaxKind assignmentKind = SyntaxKind.SimpleAssignmentExpression; // Determine if we should generate compound assignment. if (rhsType.AllowedAdditionalAssignmentKinds.Length > 0 && Random.FlipCoin(Options.CompoundAssignmentProb)) { assignmentKind = Random.NextElement(rhsType.AllowedAdditionalAssignmentKinds); } // Early our for simple cases. if (assignmentKind == SyntaxKind.PreIncrementExpression || assignmentKind == SyntaxKind.PreDecrementExpression) { return(ExpressionStatement(PrefixUnaryExpression(assignmentKind, lvalue.Expression))); } if (assignmentKind == SyntaxKind.PostIncrementExpression || assignmentKind == SyntaxKind.PostDecrementExpression) { return(ExpressionStatement(PostfixUnaryExpression(assignmentKind, lvalue.Expression))); } // Right operand of shifts are always ints. if (assignmentKind == SyntaxKind.LeftShiftAssignmentExpression || assignmentKind == SyntaxKind.RightShiftAssignmentExpression) { rhsType = Types.GetPrimitiveType(SyntaxKind.IntKeyword); } ExpressionSyntax right = GenExpression(rhsType); // For modulo and division we don't want to throw divide-by-zero exceptions, // so always or right-hand-side with 1. if (assignmentKind == SyntaxKind.ModuloAssignmentExpression || assignmentKind == SyntaxKind.DivideAssignmentExpression) { right = CastExpression( rhsType.GenReferenceTo(), ParenthesizedExpression( BinaryExpression( SyntaxKind.BitwiseOrExpression, ParenthesizeIfNecessary(right), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1))))); } return(ExpressionStatement(AssignmentExpression(assignmentKind, lvalue.Expression, right))); }
private ExpressionSyntax GenLiteral(FuzzType type) { return(LiteralGenerator.GenLiteral(Random, type)); }
private LValueInfo GenExistingLValue(FuzzType type, int minRefEscapeScope) { Debug.Assert(type == null || !(type is RefType)); LValueKind kind = (LValueKind)Options.ExistingLValueDist.Sample(Random.Rng); if (kind == LValueKind.RefReturningCall) { List <FuncGenerator> refReturningFuncs = new List <FuncGenerator>(); foreach (FuncGenerator func in _funcs.Skip(_funcIndex + 1)) { if (!(func.ReturnType is RefType rt)) { continue; } if (type == null || type == rt.InnerType) { refReturningFuncs.Add(func); } } // If we don't have a ref-returning function, fall back to local/static by retrying. // The reason we don't always fall back to trying other options is that otherwise // we may stack overflow in cases like ref int M(ref int a), if we have no locals // or statics of type int. We would keep generating method calls in this case. // This is kind of a hack, although it is pretty natural to pick statics/locals for // lvalues, so it is not that big of a deal. if (refReturningFuncs.Count == 0) { return(GenExistingLValue(type, minRefEscapeScope)); } FuncGenerator funcToCall = Random.NextElement(refReturningFuncs); // When calling a func that returns a by-ref, we need to take into account that the C# compiler // performs 'escape-analysis' on by-refs. If we want to produce a non-local by-ref we are only // allowed to pass non-local by-refs. The following example illustrates the analysis performed // by the compiler: // ref int M(ref int b, ref int c) { // int a = 2; // return ref Max(ref a, ref b); // compiler error, returned by-ref could point to 'a' and escape // return ref Max(ref b, ref c); // ok, returned ref cannot point to local // } // ref int Max(ref int a, ref int b) { ... } // This is the reason for the second arg passed to GenArgs. For example, if we want a call to return // a ref that may escape, we need a minimum ref escape scope of 1, since 0 could pass refs to locals, // and the result of that call would not be valid to return. ArgumentSyntax[] args = GenArgs(funcToCall, minRefEscapeScope, out int argsMinRefEscapeScope); InvocationExpressionSyntax invoc = InvocationExpression( IdentifierName(funcToCall.Name), ArgumentList( SeparatedList( args))); return(new LValueInfo(invoc, ((RefType)funcToCall.ReturnType).InnerType, argsMinRefEscapeScope)); } List <LValueInfo> lvalues = CollectVariablePaths(type, minRefEscapeScope, kind == LValueKind.Local, kind == LValueKind.Static); if (lvalues.Count == 0) { // We typically fall back to generating a static, so just try to find a static if we found no local. if (kind != LValueKind.Local) { return(null); } lvalues = CollectVariablePaths(type, minRefEscapeScope, false, true); if (lvalues.Count == 0) { return(null); } } return(Random.NextElement(lvalues)); }