-
Notifications
You must be signed in to change notification settings - Fork 32
/
Generator.fs
331 lines (285 loc) · 12.7 KB
/
Generator.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
namespace VSharp.Fuzzer
open System
open System.Collections.Generic
open System.Reflection
open System.Runtime.InteropServices
open VSharp
open VSharp.Core
type internal GenerationData = {
seed: int
method: Method
this: obj
thisType: Type
args: obj array
argsTypes: Type array
mocks: Dictionary<ITypeMock, Mocking.Type>
typeStorage: typeStorage
referencedObjects: HashSet<obj>
allocatedObjects: HashSet<obj>
instantiatedMocks: Dictionary<obj, ITypeMock>
}
type internal Generator(options: Startup.FuzzerOptions, typeSolver: TypeSolver) =
let instancesCache = Dictionary<Type, Type[] option>()
let mutable allocatedObjects = HashSet<obj>()
let mutable instantiatedMocks = Dictionary<obj, ITypeMock>()
let mutable referencedObjects = HashSet<obj>()
let mutable mockedGenerics = Dictionary<Type, ITypeMock>()
let traceGeneration (t: Type) msg = Logger.traceGeneration $"[{t.Name}] {msg}"
let setAllFields (t: Type) (setter: Type -> obj) =
traceGeneration t "Set all fields"
let isStatic = t.IsAbstract && t.IsSealed
assert (not isStatic)
let fields = Reflection.fieldsOf isStatic t
let instance = Reflection.createObject t
for _, fieldInfo in fields do
fieldInfo.SetValue(instance, setter fieldInfo.FieldType)
instance
let generateViaConstructor commonGenerator (t: Type) (rnd: Random) =
traceGeneration t "Try generate via constructor"
let constructors = t.GetConstructors()
if (constructors.Length = 0)
then
traceGeneration t "Constructor not found"
None
else
try
traceGeneration t "Constructor found"
let constructor = constructors[rnd.Next(0, constructors.Length)]
let constructorArgsTypes = constructor.GetParameters() |> Array.map (fun p -> p.ParameterType)
let constructorArgs = constructorArgsTypes |> Array.map (commonGenerator rnd)
constructor.Invoke(constructorArgs) |> Some
with
| :? TargetInvocationException as e ->
traceGeneration t $"Constructor thrown an exception: {e}"
None
let findInstances (t: Type) =
traceGeneration t "Try find installable type"
match instancesCache.TryGetValue t with
| true, instances ->
traceGeneration t "Installable types got from cache"
instances
| false, _ ->
let instances =
t.Assembly.GetTypes()
|> Array.filter (fun x ->
x.IsClass
&& not x.ContainsGenericParameters
&& not x.IsAbstract
&& x.IsPublic
&& t.IsAssignableFrom(x)
)
if instances.Length = 0 then
traceGeneration t "Installable type not found"
instancesCache.Add(t, None)
else
traceGeneration t "Installable types found"
instancesCache.Add(t, Some instances)
instancesCache[t]
let generateUnboxedChar (rnd: Random) =
// Supports only ASCII for compatibility with test XML serializer
rnd.Next(33, 126) |> char
let builtinNumericTypes = [
typeof<int8>; typeof<int16>; typeof<int32>; typeof<int64>
typeof<uint8>; typeof<uint16>; typeof<uint32>; typeof<uint64>
typeof<float32>; typeof<double>
typeof<byte>
]
let generateUnboxedBool (rnd: Random) =
rnd.Next(0, 2) = 1
// Generators
let generateBuiltinNumeric _ (rnd: Random) (t: Type) =
traceGeneration t "Generate builtin numeric"
let size = Marshal.SizeOf t
let (convert: byte array -> obj) =
match t with
| _ when t = typeof<int8> -> (fun x -> sbyte x[0]) >> box
| _ when t = typeof<int16> -> BitConverter.ToInt16 >> box
| _ when t = typeof<int32> -> BitConverter.ToInt32 >> box
| _ when t = typeof<int64> -> BitConverter.ToInt64 >> box
| _ when t = typeof<uint8> -> (fun x -> x[0]) >> box
| _ when t = typeof<uint16> -> BitConverter.ToUInt16 >> box
| _ when t = typeof<uint32> -> BitConverter.ToUInt32 >> box
| _ when t = typeof<uint64> -> BitConverter.ToUInt64 >> box
| _ when t = typeof<float32> -> BitConverter.ToSingle >> box
| _ when t = typeof<double> -> BitConverter.ToDouble >> box
| _ -> failwith $"Unexpected type in generateBuiltinNumeric {t}"
let buffer = Array.zeroCreate<byte> size
rnd.NextBytes(buffer)
convert buffer
let generateBool _ (rnd: Random) t =
traceGeneration t "Generate bool"
(generateUnboxedBool rnd) :> obj
let generateDecimal _ (rnd: Random) t =
traceGeneration t "Generate decimal"
let scale = rnd.Next(29) |> byte
let sign = generateUnboxedBool rnd
Decimal (rnd.Next(), rnd.Next(), rnd.Next(), sign, scale) :> obj
let generateChar _ (rnd: Random) t =
traceGeneration t "Generate char"
(generateUnboxedChar rnd) :> obj
let generateString _ (rnd: Random) t =
traceGeneration t "Generate string"
let size = rnd.Next (0, options.stringMaxSize + 1)
String(Array.init size (fun _ -> generateUnboxedChar rnd)) :> obj
let generateEnum _ (rnd: Random) (t: Type) =
traceGeneration t "Generate enum"
let values = Enum.GetValues(t)
let index = rnd.Next(0, values.Length)
values.GetValue(index)
let generateArray commonGenerator (rnd: Random) (t: Type) =
traceGeneration t "Generate array"
if t.IsSZArray then
let arraySize = rnd.Next(0, options.arrayMaxSize + 1) |> int
let elementType = t.GetElementType()
let array = Array.CreateInstance(elementType, arraySize)
for i in 0 .. arraySize - 1 do
array.SetValue(commonGenerator rnd elementType, i)
array :> obj
else
// TODO: multidimensional arrays
// TODO: LowerBound
__notImplemented__ ()
let generateAbstractClassWithInstances commonGenerator (rnd: Random) (t: Type) =
traceGeneration t "Generate abstract class with instances"
let instances = findInstances t |> Option.defaultWith (fun () ->
internalfailf "Incorrect usage of generateAbstractClassWithInstances"
)
let instance = instances[rnd.Next(0, instances.Length)]
commonGenerator rnd instance
let generateAbstractClassWithoutInstances commonGenerator (rnd: Random) (t: Type) =
traceGeneration t "Generate abstract class without instances"
assert (findInstances t |> Option.isNone)
let mock, typ = typeSolver.MockType t (commonGenerator rnd)
let result = commonGenerator rnd typ
instantiatedMocks.Add(result, mock)
result
let generateByRef commonGenerator (rnd: Random) (t: Type) =
traceGeneration t "Generate ByRef"
let referencedType = t.GetElementType()
let object = commonGenerator rnd referencedType
referencedObjects.Add(object) |> ignore
object
let generatePointer (commonGenerator: Random -> Type -> obj) (rnd: Random) (t: Type) =
traceGeneration t "Generate pointer"
let elementType = t.GetElementType()
let object = commonGenerator rnd elementType
let reference = ref object
let pointer = System.Runtime.CompilerServices.Unsafe.AsPointer(reference)
let result = Pointer.Box(pointer, t)
allocatedObjects.Add result |> ignore
result
let generateDelegate commonGenerator (rnd: Random) (t: Type) =
Prelude.__notImplemented__ ()
let generateClass commonGenerator (rnd: Random) (t: Type) =
traceGeneration t "Generate class"
match generateViaConstructor commonGenerator t rnd with
| Some obj -> obj
| None -> setAllFields t (commonGenerator rnd)
// Classification
let (|ValueType|PointerType|ReferenceType|ByRefType|) (t: Type) =
if t.IsValueType then ValueType
elif t.IsPointer then PointerType
elif t.IsByRef then ByRefType
else ReferenceType
// Value types
let (|Enum|BuiltinNumeric|Decimal|Boolean|Char|Void|OtherStruct|) (t: Type) =
if t.IsEnum then Enum
elif List.contains t builtinNumericTypes then BuiltinNumeric
elif t = typeof<Decimal> then Decimal
elif t = typeof<bool> then Boolean
elif t = typeof<char> then Char
elif t = typeof<System.Void> then Void
else OtherStruct
// Reference types
let (|Array|Delegate|String|AbstractClassWithInstances|AbstractClassWithoutInstances|OtherClass|) (t: Type) =
if t.IsArray then Array
elif t = typeof<string> then String
elif t.IsSubclassOf typeof<System.Delegate> then Delegate
elif t.IsAbstract || t.IsInterface then
if findInstances t |> Option.isSome then
AbstractClassWithInstances
else
AbstractClassWithoutInstances
else OtherClass
let rec commonGenerate (rnd: Random) (t: Type) =
Logger.traceGeneration $"Generate: {t.Name}"
let concreteGenerate =
match t with
| ValueType ->
match t with
| Enum -> generateEnum
| BuiltinNumeric -> generateBuiltinNumeric
| Decimal -> generateDecimal
| Boolean -> generateBool
| Char -> generateChar
| Void -> fun _ _ _ -> ()
// A structure does not differ from a class in terms of generation
| OtherStruct -> generateClass
| ReferenceType ->
match t with
| Array -> generateArray
| Delegate -> generateDelegate
| String -> generateString
| AbstractClassWithoutInstances -> __notImplemented__ ()
| AbstractClassWithInstances -> generateAbstractClassWithInstances
| OtherClass -> generateClass
| ByRefType -> __notImplemented__ ()
| PointerType -> __notImplemented__ ()
concreteGenerate commonGenerate rnd t
member private this.GenerateObject rnd (t: Type) =
Logger.traceGeneration $"Target type: {t.Name}"
let result =
match t with
| ByRefType -> generateByRef commonGenerate rnd t
| PointerType -> generatePointer commonGenerate rnd t
| AbstractClassWithoutInstances -> generateAbstractClassWithoutInstances commonGenerate rnd t
| _ -> commonGenerate rnd t
if mockedGenerics.ContainsKey t then
traceGeneration t "Added generated object to instantiated mocks"
instantiatedMocks.Add(result, mockedGenerics[t])
result
member private this.RefreshInstantiatedMocks () =
let result = instantiatedMocks
instantiatedMocks <- Dictionary<obj, ITypeMock>()
result
member private this.RefreshReferencedObjects () =
let result = referencedObjects
referencedObjects <- HashSet<obj>()
result
member private this.RefreshAllocatedObjects () =
let result = allocatedObjects
allocatedObjects <- HashSet<obj>()
result
member private this.GenerateCase (method: MethodBase) rndSeed =
let rnd = Random(rndSeed)
let methodThisType = method.DeclaringType
let methodThis =
if Reflection.hasThis method
then this.GenerateObject rnd methodThisType
else null
let argsTypes =
method.GetParameters()
|> Array.map (fun info -> info.ParameterType)
let args =
argsTypes
|> Array.map (this.GenerateObject rnd)
{| this = methodThis; thisType = methodThisType; args = args; argsTypes = argsTypes |}
member this.Generate methodMockedGenerics (method: MethodBase) typeStorage rndSeed =
mockedGenerics <- methodMockedGenerics
let case = this.GenerateCase method rndSeed
{
seed = rndSeed
method = Application.getMethod method
this = case.this
thisType = case.thisType
args = case.args
argsTypes = case.argsTypes
typeStorage = typeStorage
allocatedObjects = this.RefreshAllocatedObjects ()
referencedObjects = this.RefreshReferencedObjects ()
instantiatedMocks = this.RefreshInstantiatedMocks ()
mocks = typeSolver.GetMocks ()
}
member this.GenerateClauseObject rnd (t: Type) =
Logger.traceGeneration $"Target clause type: {t.Name}"
commonGenerate rnd t