-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathTraverser.kt
More file actions
4331 lines (3734 loc) · 194 KB
/
Traverser.kt
File metadata and controls
4331 lines (3734 loc) · 194 KB
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package org.utbot.engine
import kotlinx.collections.immutable.persistentHashMapOf
import kotlinx.collections.immutable.persistentHashSetOf
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.toPersistentMap
import kotlinx.collections.immutable.toPersistentSet
import mu.KotlinLogging
import org.utbot.framework.plugin.api.ArtificialError
import org.utbot.framework.plugin.api.OverflowDetectionError
import org.utbot.framework.plugin.api.TaintAnalysisError
import org.utbot.common.WorkaroundReason.HACK
import org.utbot.framework.UtSettings.ignoreStaticsFromTrustedLibraries
import org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES
import org.utbot.common.unreachableBranch
import org.utbot.common.withAccessibility
import org.utbot.common.workaround
import org.utbot.engine.overrides.UtArrayMock
import org.utbot.engine.overrides.UtLogicMock
import org.utbot.engine.overrides.UtOverrideMock
import org.utbot.engine.pc.NotBoolExpression
import org.utbot.engine.pc.Simplificator
import org.utbot.engine.pc.UtAddNoOverflowExpression
import org.utbot.engine.pc.UtAddrExpression
import org.utbot.engine.pc.UtAndBoolExpression
import org.utbot.engine.pc.UtArrayApplyForAll
import org.utbot.engine.pc.UtArrayExpressionBase
import org.utbot.engine.pc.UtArraySelectExpression
import org.utbot.engine.pc.UtArraySetRange
import org.utbot.engine.pc.UtArraySort
import org.utbot.engine.pc.UtBoolExpression
import org.utbot.engine.pc.UtBoolOpExpression
import org.utbot.engine.pc.UtBvConst
import org.utbot.engine.pc.UtBvLiteral
import org.utbot.engine.pc.UtByteSort
import org.utbot.engine.pc.UtCastExpression
import org.utbot.engine.pc.UtCharSort
import org.utbot.engine.pc.UtContextInitializer
import org.utbot.engine.pc.UtExpression
import org.utbot.engine.pc.UtFalse
import org.utbot.engine.pc.UtInstanceOfExpression
import org.utbot.engine.pc.UtIntSort
import org.utbot.engine.pc.UtIsExpression
import org.utbot.engine.pc.UtIteExpression
import org.utbot.engine.pc.UtLongSort
import org.utbot.engine.pc.UtMkTermArrayExpression
import org.utbot.engine.pc.UtNegExpression
import org.utbot.engine.pc.UtOrBoolExpression
import org.utbot.engine.pc.UtPrimitiveSort
import org.utbot.engine.pc.UtShortSort
import org.utbot.engine.pc.UtSolver
import org.utbot.engine.pc.UtSolverStatusSAT
import org.utbot.engine.pc.UtSolverStatusUNSAT
import org.utbot.engine.pc.UtSubNoOverflowExpression
import org.utbot.engine.pc.UtTrue
import org.utbot.engine.pc.addrEq
import org.utbot.engine.pc.align
import org.utbot.engine.pc.cast
import org.utbot.engine.pc.findTheMostNestedAddr
import org.utbot.engine.pc.isInteger
import org.utbot.engine.pc.mkAnd
import org.utbot.engine.pc.mkBVConst
import org.utbot.engine.pc.mkBoolConst
import org.utbot.engine.pc.mkChar
import org.utbot.engine.pc.mkEq
import org.utbot.engine.pc.mkFalse
import org.utbot.engine.pc.mkFpConst
import org.utbot.engine.pc.mkInt
import org.utbot.engine.pc.mkNot
import org.utbot.engine.pc.mkOr
import org.utbot.engine.pc.select
import org.utbot.engine.pc.store
import org.utbot.engine.state.Edge
import org.utbot.engine.state.ExecutionState
import org.utbot.engine.state.LocalVariableMemory
import org.utbot.engine.state.StateLabel
import org.utbot.engine.state.createExceptionState
import org.utbot.engine.state.pop
import org.utbot.engine.state.push
import org.utbot.engine.state.update
import org.utbot.engine.state.withLabel
import org.utbot.engine.symbolic.HardConstraint
import org.utbot.engine.symbolic.emptyAssumption
import org.utbot.engine.symbolic.emptyHardConstraint
import org.utbot.engine.symbolic.emptySoftConstraint
import org.utbot.engine.symbolic.SymbolicStateUpdate
import org.utbot.engine.symbolic.asHardConstraint
import org.utbot.engine.symbolic.asSoftConstraint
import org.utbot.engine.symbolic.asAssumption
import org.utbot.engine.symbolic.asUpdate
import org.utbot.engine.simplificators.MemoryUpdateSimplificator
import org.utbot.engine.simplificators.simplifySymbolicStateUpdate
import org.utbot.engine.simplificators.simplifySymbolicValue
import org.utbot.engine.types.*
import org.utbot.engine.types.ARRAYS_SOOT_CLASS
import org.utbot.engine.types.CLASS_REF_SOOT_CLASS
import org.utbot.engine.types.CLASS_REF_TYPE
import org.utbot.engine.types.ENUM_ORDINAL
import org.utbot.engine.types.EQUALS_SIGNATURE
import org.utbot.engine.types.HASHCODE_SIGNATURE
import org.utbot.engine.types.METHOD_FILTER_MAP_FIELD_SIGNATURE
import org.utbot.engine.types.NEW_INSTANCE_SIGNATURE
import org.utbot.engine.types.NUMBER_OF_PREFERRED_TYPES
import org.utbot.engine.types.OBJECT_TYPE
import org.utbot.engine.types.SECURITY_FIELD_SIGNATURE
import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues
import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics
import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName
import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates
import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates
import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues
import org.utbot.framework.ExploreThrowableDepth
import org.utbot.framework.UtSettings
import org.utbot.framework.UtSettings.preferredCexOption
import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable
import org.utbot.framework.isFromTrustedLibrary
import org.utbot.framework.context.NonNullSpeculator
import org.utbot.framework.context.TypeReplacer
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.TypeReplacementMode.AnyImplementor
import org.utbot.framework.plugin.api.TypeReplacementMode.KnownImplementor
import org.utbot.framework.plugin.api.TypeReplacementMode.NoImplementors
import org.utbot.framework.plugin.api.classId
import org.utbot.framework.plugin.api.id
import org.utbot.framework.plugin.api.isAbstractType
import org.utbot.framework.plugin.api.util.*
import org.utbot.framework.util.executableId
import org.utbot.framework.util.graph
import org.utbot.summary.ast.declaredClassName
import org.utbot.framework.util.sootMethodOrNull
import org.utbot.taint.TaintContext
import org.utbot.taint.model.*
import java.lang.reflect.ParameterizedType
import kotlin.collections.plus
import kotlin.collections.plusAssign
import kotlin.math.max
import kotlin.math.min
import soot.ArrayType
import soot.BooleanType
import soot.ByteType
import soot.CharType
import soot.DoubleType
import soot.FloatType
import soot.IntType
import soot.LongType
import soot.Modifier
import soot.PrimType
import soot.RefLikeType
import soot.RefType
import soot.Scene
import soot.ShortType
import soot.SootClass
import soot.SootField
import soot.SootMethod
import soot.SootMethodRef
import soot.Type
import soot.Value
import soot.VoidType
import soot.jimple.ArrayRef
import soot.jimple.BinopExpr
import soot.jimple.ClassConstant
import soot.jimple.Constant
import soot.jimple.DefinitionStmt
import soot.jimple.DoubleConstant
import soot.jimple.Expr
import soot.jimple.FieldRef
import soot.jimple.FloatConstant
import soot.jimple.IdentityRef
import soot.jimple.IntConstant
import soot.jimple.InvokeExpr
import soot.jimple.LongConstant
import soot.jimple.MonitorStmt
import soot.jimple.NeExpr
import soot.jimple.NullConstant
import soot.jimple.ParameterRef
import soot.jimple.ReturnStmt
import soot.jimple.StaticFieldRef
import soot.jimple.Stmt
import soot.jimple.StringConstant
import soot.jimple.SwitchStmt
import soot.jimple.ThisRef
import soot.jimple.internal.JAddExpr
import soot.jimple.internal.JArrayRef
import soot.jimple.internal.JAssignStmt
import soot.jimple.internal.JBreakpointStmt
import soot.jimple.internal.JCastExpr
import soot.jimple.internal.JCaughtExceptionRef
import soot.jimple.internal.JDivExpr
import soot.jimple.internal.JDynamicInvokeExpr
import soot.jimple.internal.JEqExpr
import soot.jimple.internal.JGeExpr
import soot.jimple.internal.JGotoStmt
import soot.jimple.internal.JGtExpr
import soot.jimple.internal.JIdentityStmt
import soot.jimple.internal.JIfStmt
import soot.jimple.internal.JInstanceFieldRef
import soot.jimple.internal.JInstanceOfExpr
import soot.jimple.internal.JInterfaceInvokeExpr
import soot.jimple.internal.JInvokeStmt
import soot.jimple.internal.JLeExpr
import soot.jimple.internal.JLengthExpr
import soot.jimple.internal.JLookupSwitchStmt
import soot.jimple.internal.JLtExpr
import soot.jimple.internal.JMulExpr
import soot.jimple.internal.JNeExpr
import soot.jimple.internal.JNegExpr
import soot.jimple.internal.JNewArrayExpr
import soot.jimple.internal.JNewExpr
import soot.jimple.internal.JNewMultiArrayExpr
import soot.jimple.internal.JNopStmt
import soot.jimple.internal.JRemExpr
import soot.jimple.internal.JRetStmt
import soot.jimple.internal.JReturnStmt
import soot.jimple.internal.JReturnVoidStmt
import soot.jimple.internal.JSpecialInvokeExpr
import soot.jimple.internal.JStaticInvokeExpr
import soot.jimple.internal.JSubExpr
import soot.jimple.internal.JTableSwitchStmt
import soot.jimple.internal.JThrowStmt
import soot.jimple.internal.JVirtualInvokeExpr
import soot.jimple.internal.JimpleLocal
import soot.toolkits.graph.ExceptionalUnitGraph
import java.lang.reflect.GenericArrayType
import java.lang.reflect.TypeVariable
import java.lang.reflect.WildcardType
private val CAUGHT_EXCEPTION = LocalVariable("@caughtexception")
private val logger = KotlinLogging.logger {}
class Traverser(
private val methodUnderTest: ExecutableId,
internal val typeRegistry: TypeRegistry,
internal val hierarchy: Hierarchy,
// TODO HACK violation of encapsulation
internal val typeResolver: TypeResolver,
private val globalGraph: InterProceduralUnitGraph,
private val mocker: Mocker,
private val typeReplacer: TypeReplacer,
private val nonNullSpeculator: NonNullSpeculator,
private val taintContext: TaintContext,
) : UtContextInitializer() {
private val visitedStmts: MutableSet<Stmt> = mutableSetOf()
private val classLoader: ClassLoader
get() = utContext.classLoader
// TODO: move this and other mutable fields to [TraversalContext]
lateinit var environment: Environment
private val solver: UtSolver
get() = environment.state.solver
// TODO HACK violation of encapsulation
val memory: Memory
get() = environment.state.memory
private val localVariableMemory: LocalVariableMemory
get() = environment.state.localVariableMemory
//HACK (long strings)
internal var softMaxArraySize = 40
/**
* Contains information about the generic types used in the parameters of the method under test.
*
* Mutable set here is required since this object might be passed into several methods
* and get several piece of information about their parameterized types
*/
private val instanceAddrToGenericType = mutableMapOf<UtAddrExpression, MutableSet<ParameterizedType>>()
private val preferredCexInstanceCache = mutableMapOf<ObjectValue, MutableSet<SootField>>()
private var queuedSymbolicStateUpdates = SymbolicStateUpdate()
internal val objectCounter = ObjectCounter(TypeRegistry.objectCounterInitialValue)
private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression {
val newAddr = objectCounter.createNewAddr()
// return negative address for objects created inside static initializer
// to make their address space be intersected with address space of
// parameters of method under symbolic execution
// @see ObjectWithFinalStaticTest::testParameterEqualsFinalStatic
val signedAddr = if (insideStaticInitializer) -newAddr else newAddr
return UtAddrExpression(signedAddr)
}
internal fun findNewAddr() = findNewAddr(environment.state.isInsideStaticInitializer).also { touchAddress(it) }
private val dynamicInvokeResolver: DynamicInvokeResolver = DelegatingDynamicInvokeResolver()
// Counter used for a creation symbolic results of "hashcode" and "equals" methods.
private var equalsCounter = 0
private var hashcodeCounter = 0
// A counter for objects created as native method call result.
private var unboundedConstCounter = 0
fun traverse(state: ExecutionState): Collection<ExecutionState> {
val context = TraversalContext()
val currentStmt = state.stmt
environment = Environment(globalGraph.method(state.stmt), state)
if (currentStmt !in visitedStmts) {
environment.state.updateIsVisitedNew()
visitedStmts += currentStmt
}
environment.state.lastEdge?.let {
globalGraph.visitEdge(it)
}
try {
val exception = environment.state.exception
if (exception != null) {
context.traverseException(currentStmt, exception)
} else {
context.traverseStmt(currentStmt)
}
} catch (ex: Throwable) {
environment.state.close()
logger.error(ex) { "Test generation failed on stmt $currentStmt, symbolic stack trace:\n$symbolicStackTrace" }
// TODO: enrich with nice description for known issues
throw ex
}
queuedSymbolicStateUpdates = SymbolicStateUpdate()
return context.nextStates
}
internal val simplificator = Simplificator()
private val memoryUpdateSimplificator = MemoryUpdateSimplificator(simplificator)
private fun TraversalContext.traverseStmt(current: Stmt) {
if (doPreparatoryWorkIfRequired(current)) return
when (current) {
is JAssignStmt -> traverseAssignStmt(current)
is JIdentityStmt -> traverseIdentityStmt(current)
is JIfStmt -> traverseIfStmt(current)
is JInvokeStmt -> traverseInvokeStmt(current)
is SwitchStmt -> traverseSwitchStmt(current)
is JReturnStmt -> processResult(symbolicSuccess(current))
is JReturnVoidStmt -> processResult(SymbolicSuccess(voidValue))
is JRetStmt -> error("This one should be already removed by Soot: $current")
is JThrowStmt -> traverseThrowStmt(current)
is JBreakpointStmt -> offerState(updateQueued(globalGraph.succ(current)))
is JGotoStmt -> offerState(updateQueued(globalGraph.succ(current)))
is JNopStmt -> offerState(updateQueued(globalGraph.succ(current)))
is MonitorStmt -> offerState(updateQueued(globalGraph.succ(current)))
is DefinitionStmt -> TODO("$current")
else -> error("Unsupported: ${current::class}")
}
}
/**
* Handles preparatory work for static initializers, multi-dimensional arrays creation
* and `newInstance` reflection call post-processing.
*
* For instance, it could push handmade graph with preparation statements to the path selector.
*
* Returns:
* - True if work is required and the constructed graph was pushed. In this case current
* traverse stops and continues after the graph processing;
* - False if preparatory work is not required or it is already done.
* environment.state.methodResult can contain the work result.
*/
private fun TraversalContext.doPreparatoryWorkIfRequired(current: Stmt): Boolean {
if (current !is JAssignStmt) return false
return when {
processStaticInitializerIfRequired(current) -> true
unfoldMultiArrayExprIfRequired(current) -> true
pushInitGraphAfterNewInstanceReflectionCall(current) -> true
else -> false
}
}
/**
* Handles preparatory work for static initializers. To do it, this method checks if any parts of the given
* statement is StaticRefField and the class this field belongs to hasn't been initialized yet.
* If so, it pushes a graph of the corresponding `<clinit>` to the path selector.
*
* Returns:
* - True if the work is required and the graph was pushed. In this case current
* traversal stops and continues after the graph processing;
* - False if preparatory work is not required or it is already done. In this case a result from the
* environment.state.methodResult already processed and applied.
*
* Note: similar but more granular approach used if Engine decides to process static field concretely.
*/
private fun TraversalContext.processStaticInitializerIfRequired(stmt: JAssignStmt): Boolean {
val right = stmt.rightOp
val left = stmt.leftOp
val method = environment.method
val declaringClass = method.declaringClass
val result = listOf(right, left)
.filterIsInstance<StaticFieldRef>()
.filterNot { insideStaticInitializer(it, method, declaringClass) }
.firstOrNull { processStaticInitializer(it, stmt) }
return result != null
}
/**
* Handles preparatory work for multi-dimensional arrays. Constructs unfolded representation for
* JNewMultiArrayExpr in the [unfoldMultiArrayExpr].
*
* Returns:
* - True if right part of the JAssignStmt contains JNewMultiArrayExpr and there is no calculated result in the
* environment.state.methodResult.
* - False otherwise
*/
private fun TraversalContext.unfoldMultiArrayExprIfRequired(stmt: JAssignStmt): Boolean {
// We have already unfolded the statement and processed constructed graph, have the calculated result
if (environment.state.methodResult != null) return false
val right = stmt.rightOp
if (right !is JNewMultiArrayExpr) return false
val graph = unfoldMultiArrayExpr(stmt)
val resolvedSizes = right.sizes.map { (resolve(it, IntType.v()) as PrimitiveValue).align() }
negativeArraySizeCheck(*resolvedSizes.toTypedArray())
pushToPathSelector(graph, caller = null, resolvedSizes)
return true
}
/**
* If the previous stms was `newInstance` method invocation,
* pushes a graph of the default constructor of the constructed type, if present,
* and pushes a state with a [InstantiationException] otherwise.
*/
private fun TraversalContext.pushInitGraphAfterNewInstanceReflectionCall(stmt: JAssignStmt): Boolean {
// Check whether the previous stmt was a `newInstance` invocation
val lastStmt = environment.state.path.lastOrNull() as? JAssignStmt ?: return false
if (!lastStmt.containsInvokeExpr()) {
return false
}
val lastMethodInvocation = lastStmt.invokeExpr.method
if (lastMethodInvocation.subSignature != NEW_INSTANCE_SIGNATURE) {
return false
}
// Process the current stmt as cast expression
val right = stmt.rightOp as? JCastExpr ?: return false
val castType = right.castType as? RefType ?: return false
val castedJimpleVariable = right.op as? JimpleLocal ?: return false
val castedLocalVariable = (localVariableMemory.local(castedJimpleVariable.variable) as? ReferenceValue) ?: return false
val castSootClass = castType.sootClass
// We need to consider a situation when this class does not have a default constructor
// Since it can be a cast of a class with constructor to the interface (or ot the ancestor without default constructor),
// we cannot always throw a `java.lang.InstantiationException`.
// So, instead we will just continue the analysis without analysis of <init>.
val initMethod = castSootClass.getMethodUnsafe("void <init>()") ?: return false
if (!initMethod.canRetrieveBody()) {
return false
}
val initGraph = ExceptionalUnitGraph(initMethod.activeBody)
pushToPathSelector(
initGraph,
castedLocalVariable,
callParameters = emptyList(),
)
return true
}
/**
* Processes static initialization for class.
*
* If class is not initialized yet, creates graph for that and pushes to the path selector;
* otherwise class is initialized and environment.state.methodResult can contain initialization result.
*
* If contains, adds state with the last edge to the path selector;
* if doesn't contain, it's already processed few steps before, nothing to do.
*
* Returns true if processing takes place and Engine should end traversal of current statement.
*/
private fun TraversalContext.processStaticInitializer(
fieldRef: StaticFieldRef,
stmt: Stmt
): Boolean {
// This order of processing options is important.
// First, we should process classes that
// cannot be analyzed without clinit sections, e.g., enums
if (shouldProcessStaticFieldConcretely(fieldRef)) {
return processStaticFieldConcretely(fieldRef, stmt)
}
// Then we should check if we should analyze clinit sections at all
if (!UtSettings.enableClinitSectionsAnalysis) {
return false
}
// Finally, we decide whether we should analyze clinit sections concretely or not
if (UtSettings.processAllClinitSectionsConcretely) {
return processStaticFieldConcretely(fieldRef, stmt)
}
val field = fieldRef.field
val declaringClass = field.declaringClass
val declaringClassId = declaringClass.id
val methodResult = environment.state.methodResult
if (!memory.isInitialized(declaringClassId) &&
!isStaticInstanceInMethodResult(declaringClassId, methodResult)
) {
val initializer = declaringClass.staticInitializerOrNull()
return if (initializer == null) {
false
} else {
val graph = classInitGraph(initializer)
pushToPathSelector(graph, null, emptyList())
true
}
}
val result = methodResult ?: return false
when (result.symbolicResult) {
// This branch could be useful if we have a static field, i.e. x = 5 / 0
is SymbolicFailure -> traverseException(stmt, result.symbolicResult)
is SymbolicSuccess -> offerState(
updateQueued(
environment.state.lastEdge!!,
result.symbolicStateUpdate
)
)
}
return true
}
/**
* Decides should we read this static field concretely or not.
*/
private fun shouldProcessStaticFieldConcretely(fieldRef: StaticFieldRef): Boolean {
workaround(HACK) {
val className = fieldRef.field.declaringClass.name
// We should process clinit sections for classes from these packages.
// Note that this list is not exhaustive, so it may be supplemented in the future.
val packagesToProcessConcretely = javaPackagesToProcessConcretely + sunPackagesToProcessConcretely
val declaringClass = fieldRef.field.declaringClass
val isFromPackageToProcessConcretely = packagesToProcessConcretely.any { className.startsWith(it) }
// it is required to remove classes we override, since
// we could accidentally initialize their final fields
// with values that will later affect our overridden classes
&& fieldRef.field.declaringClass.type !in classToWrapper.keys
// because of the same reason we should not use
// concrete information from clinit sections for enums
&& !fieldRef.field.declaringClass.isEnum
//hardcoded string for class name is used cause class is not public
//this is a hack to avoid crashing on code with Math.random()
&& !className.endsWith("RandomNumberGeneratorHolder")
// we can process concretely only enums that does not affect the external system
val isEnumNotAffectingExternalStatics = declaringClass.let {
it.isEnum && !it.isEnumAffectingExternalStatics(typeResolver)
}
return isEnumNotAffectingExternalStatics || isFromPackageToProcessConcretely
}
}
private val javaPackagesToProcessConcretely = listOf(
"applet", "awt", "beans", "io", "lang", "math", "net",
"nio", "rmi", "security", "sql", "text", "time", "util"
).map { "java.$it" }
private val sunPackagesToProcessConcretely = listOf(
"applet", "audio", "awt", "corba", "font", "instrument",
"invoke", "io", "java2d", "launcher", "management", "misc",
"net", "nio", "print", "reflect", "rmi", "security",
"swing", "text", "tools.jar", "tracing", "util"
).map { "sun.$it" }
/**
* Checks if field was processed (read) already.
* Otherwise offers to path selector the same statement, but with memory and constraints updates for this field.
*
* Returns true if processing takes place and Engine should end traversal of current statement.
*/
private fun TraversalContext.processStaticFieldConcretely(fieldRef: StaticFieldRef, stmt: Stmt): Boolean {
val field = fieldRef.field
val fieldId = field.fieldId
if (memory.isInitialized(fieldId)) {
return false
}
// Gets concrete value, converts to symbolic value
val declaringClass = field.declaringClass
val updates = if (declaringClass.isEnum) {
makeConcreteUpdatesForEnumsWithStmt(fieldId, declaringClass, stmt)
} else {
makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass, stmt)
}
// a static initializer can be the first statement in method so there will be no last edge
// for example, as it is during Enum::values method analysis:
// public static ClassWithEnum$StatusEnum[] values()
// {
// ClassWithEnum$StatusEnum[] $r0, $r2;
// java.lang.Object $r1;
// $r0 = <ClassWithEnum$StatusEnum: ClassWithEnum$StatusEnum[] $VALUES>;
val edge = environment.state.lastEdge ?: globalGraph.succ(stmt)
val newState = updateQueued(edge, updates)
offerState(newState)
return true
}
private fun makeConcreteUpdatesForEnum(
type: RefType,
fieldId: FieldId? = null
): Pair<SymbolicStateUpdate, SymbolicValue?> {
val jClass = type.id.jClass
// symbolic value for enum class itself, we don't want to have mocks for this value
val enumClassValue = findOrCreateStaticObject(type, mockInfoGenerator = null)
// values for enum constants
val enumConstantConcreteValues = jClass.enumConstants.filterIsInstance<Enum<*>>()
val (enumConstantSymbolicValues, enumConstantSymbolicResultsByName) =
makeSymbolicValuesFromEnumConcreteValues(type, enumConstantConcreteValues)
val enumFields = typeResolver.findFields(type)
val sootFieldsWithRuntimeValues =
associateEnumSootFieldsWithConcreteValues(enumFields, enumConstantConcreteValues)
val (staticFields, nonStaticFields) = sootFieldsWithRuntimeValues.partition { it.first.isStatic }
val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates(
staticFields,
type.sootClass,
enumConstantSymbolicResultsByName,
enumConstantSymbolicValues,
enumClassValue,
fieldId
)
val nonStaticFieldsUpdates = makeEnumNonStaticFieldsUpdates(enumConstantSymbolicValues, nonStaticFields)
// we do not mark static fields for enum constants and $VALUES as meaningful
// because we should not set them in generated code
val meaningfulStaticFields = staticFields.filterNot {
val name = it.first.name
name in enumConstantSymbolicResultsByName.keys || isEnumValuesFieldName(name)
}
val initializedStaticFieldsMemoryUpdate = MemoryUpdate(
initializedStaticFields = staticFields.map { it.first.fieldId }.toPersistentSet(),
meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet(),
symbolicEnumValues = enumConstantSymbolicValues.toPersistentList()
)
return Pair(
staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate,
curFieldSymbolicValueForLocalVariable
)
}
@Suppress("UnnecessaryVariable")
private fun makeConcreteUpdatesForEnumsWithStmt(
fieldId: FieldId,
declaringClass: SootClass,
stmt: Stmt
): SymbolicStateUpdate {
val (enumUpdates, curFieldSymbolicValueForLocalVariable) =
makeConcreteUpdatesForEnum(declaringClass.type, fieldId)
val allUpdates = enumUpdates + createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable)
return allUpdates
}
@Suppress("UnnecessaryVariable")
private fun makeConcreteUpdatesForNonEnumStaticField(
field: SootField,
fieldId: FieldId,
declaringClass: SootClass,
stmt: Stmt
): SymbolicStateUpdate {
val concreteValue = extractConcreteValue(field)
val (symbolicResult, symbolicStateUpdate) = toMethodResult(concreteValue, field.type)
val symbolicValue = (symbolicResult as SymbolicSuccess).value
// Collects memory updates
val initializedFieldUpdate =
MemoryUpdate(initializedStaticFields = persistentHashSetOf(fieldId))
val mockInfoGenerator = UtMockInfoGenerator { addr -> UtStaticObjectMockInfo(declaringClass.id, addr) }
val objectUpdate = objectUpdate(
instance = findOrCreateStaticObject(declaringClass.type, mockInfoGenerator),
field = field,
value = valueToExpression(symbolicValue, field.type)
)
val allUpdates = symbolicStateUpdate +
initializedFieldUpdate +
objectUpdate +
createConcreteLocalValueUpdate(stmt, symbolicValue)
return allUpdates
}
/**
* Creates a local update consisting [symbolicValue] for a local variable from [stmt] in case [stmt] is [JAssignStmt].
*/
private fun createConcreteLocalValueUpdate(
stmt: Stmt,
symbolicValue: SymbolicValue?,
): LocalMemoryUpdate {
// we need to make locals update if it is an assignment statement
// for enums we have only two types for assignment with enums — enum constant or $VALUES field
// for example, a jimple body for Enum::values method starts with the following lines:
// public static ClassWithEnum$StatusEnum[] values()
// {
// ClassWithEnum$StatusEnum[] $r0, $r2;
// java.lang.Object $r1;
// $r0 = <ClassWithEnum$StatusEnum: ClassWithEnum$StatusEnum[] $VALUES>;
// $r1 = virtualinvoke $r0.<java.lang.Object: java.lang.Object clone()>();
// so, we have to make an update for the local $r0
return if (stmt is JAssignStmt && stmt.leftOp is JimpleLocal) {
val local = stmt.leftOp as JimpleLocal
localMemoryUpdate(local.variable to symbolicValue)
} else {
LocalMemoryUpdate()
}
}
// Some fields are inaccessible with reflection, so we have to instantiate it by ourselves.
// Otherwise, extract it from the class.
// TODO JIRA:1593
private fun extractConcreteValue(field: SootField): Any? =
when (field.signature) {
SECURITY_FIELD_SIGNATURE -> SecurityManager()
// todo change to class loading
//FIELD_FILTER_MAP_FIELD_SIGNATURE -> mapOf(Reflection::class to arrayOf("fieldFilterMap", "methodFilterMap"))
METHOD_FILTER_MAP_FIELD_SIGNATURE -> emptyMap<Class<*>, Array<String>>()
else -> {
val fieldId = field.fieldId
val jField = fieldId.jField
jField.let {
it.withAccessibility {
it.get(null)
}
}
}
}
private fun isStaticInstanceInMethodResult(id: ClassId, methodResult: MethodResult?) =
methodResult != null && id in methodResult.symbolicStateUpdate.memoryUpdates.staticInstanceStorage
private fun TraversalContext.skipVerticesForThrowableCreation(current: JAssignStmt) {
val rightType = current.rightOp.type as RefType
val exceptionType = Scene.v().getRefType(rightType.className)
val mockInfoGenerator = UtMockInfoGenerator { mockAddr ->
UtNewInstanceMockInfo(exceptionType.id, mockAddr, environment.method.declaringClass.id)
}
val createdException = createObject(
findNewAddr(),
exceptionType,
useConcreteType = true,
mockInfoGenerator = mockInfoGenerator
)
val currentExceptionJimpleLocal = current.leftOp as JimpleLocal
queuedSymbolicStateUpdates += localMemoryUpdate(currentExceptionJimpleLocal.variable to createdException)
// mark the rest of the path leading to the '<init>' statement as covered
do {
environment.state = updateQueued(globalGraph.succ(environment.state.stmt))
globalGraph.visitEdge(environment.state.lastEdge!!)
globalGraph.visitNode(environment.state)
} while (!environment.state.stmt.isConstructorCall(currentExceptionJimpleLocal))
offerState(updateQueued(globalGraph.succ(environment.state.stmt)))
}
private fun TraversalContext.traverseAssignStmt(current: JAssignStmt) {
val rightValue = current.rightOp
if (UtSettings.exploreThrowableDepth == ExploreThrowableDepth.SKIP_ALL_STATEMENTS) {
val rightType = rightValue.type
if (rightValue is JNewExpr && rightType is RefType) {
val throwableInheritors = typeResolver.findOrConstructInheritorsIncludingTypes(THROWABLE_TYPE)
// skip all the vertices in the CFG between `new` and `<init>` statements
if (rightType in throwableInheritors) {
skipVerticesForThrowableCreation(current)
return
}
}
}
val rightPartWrappedAsMethodResults = if (rightValue is InvokeExpr) {
invokeResult(rightValue)
} else {
val value = resolve(rightValue, current.leftOp.type)
listOf(MethodResult(value))
}
rightPartWrappedAsMethodResults.forEach { methodResult ->
val taintAnalysisUpdate = if (UtSettings.useTaintAnalysis && rightValue is InvokeExpr) {
processTaintAnalysis(rightValue, methodResult)
} else {
SymbolicStateUpdate()
}
when (methodResult.symbolicResult) {
is SymbolicFailure -> { //exception thrown
if (environment.state.executionStack.last().doesntThrow) return@forEach
val nextState = createExceptionStateQueued(
methodResult.symbolicResult,
methodResult.symbolicStateUpdate + taintAnalysisUpdate
)
globalGraph.registerImplicitEdge(nextState.lastEdge!!)
offerState(nextState)
}
is SymbolicSuccess -> {
val update = traverseAssignLeftPart(
current.leftOp,
methodResult.symbolicResult.value
)
offerState(
updateQueued(
globalGraph.succ(current),
update + methodResult.symbolicStateUpdate + taintAnalysisUpdate
)
)
}
}
}
}
/**
* This hack solves the problem with static final fields, which are equal by reference with parameter.
*
* Let be the address of a parameter and correspondingly the address of final field be p0.
* The initial state of a chunk array for this static field is always (mkArray Class_field Int -> Int)
* And the current state of this chunk array is (store (mkArray Class_field Int -> Int) (p0: someValue))
* At initial chunk array under address p0 can be placed any value, because during symbolic execution we
* always refer only to current state.
*
* At the resolving stage, to resolve model of parameter before invoke, we get it from initial chunk array
* by address p0, where can be placed any value. However, resolved model for parameter after execution will
* be correct, as current state has correct value in chunk array under p0 address.
*/
private fun addConstraintsForFinalAssign(left: SymbolicValue, value: SymbolicValue) {
if (left is PrimitiveValue) {
if (left.type is DoubleType) {
queuedSymbolicStateUpdates += mkOr(
Eq(left, value as PrimitiveValue),
Ne(left, left),
Ne(value, value)
).asHardConstraint()
} else {
queuedSymbolicStateUpdates += Eq(left, value as PrimitiveValue).asHardConstraint()
}
} else if (left is ReferenceValue) {
queuedSymbolicStateUpdates += addrEq(left.addr, (value as ReferenceValue).addr).asHardConstraint()
}
}
/**
* Check for [ArrayStoreException] when an array element is assigned
*
* @param arrayInstance Symbolic value corresponding to the array being updated
* @param value Symbolic value corresponding to the right side of the assignment
*/
private fun TraversalContext.arrayStoreExceptionCheck(arrayInstance: SymbolicValue, value: SymbolicValue) {
require(arrayInstance is ArrayValue)
val valueType = value.type
val valueBaseType = valueType.baseType
val arrayElementType = arrayInstance.type.elementType
// We should check for [ArrayStoreException] only for reference types.
// * For arrays of primitive types, incorrect assignment is prevented as compile time.
// * When assigning primitive literals (e.g., `1`) to arrays of corresponding boxed classes (`Integer`),
// the conversion to the reference type is automatic.
// * [System.arraycopy] and similar functions that can throw [ArrayStoreException] accept [Object] arrays
// as arguments, so array elements are references.
if (valueBaseType is RefType) {
val arrayElementTypeStorage = typeResolver.constructTypeStorage(arrayElementType, useConcreteType = false)
// Generate ASE only if [value] is not a subtype of the type of array elements
val isExpression = typeRegistry.typeConstraint(value.addr, arrayElementTypeStorage).isConstraint()
val notIsExpression = mkNot(isExpression)
// `null` is compatible with any reference type, so we should not throw ASE when `null` is assigned
val nullEqualityConstraint = addrEq(value.addr, nullObjectAddr)
val notNull = mkNot(nullEqualityConstraint)
// Currently the negation of [UtIsExpression] seems to work incorrectly for [java.lang.Object]:
// https://github.com/UnitTestBot/UTBotJava/issues/1007
// It is related to [org.utbot.engine.pc.Z3TranslatorVisitor.filterInappropriateTypes] that removes
// internal engine classes for [java.lang.Object] type storage, and this logic is not fully
// consistent with the negation.
// Here we have a specific test for [java.lang.Object] as the type of array elements:
// any reference type may be assigned to elements of an Object array, so we should not generate
// [ArrayStoreException] in these cases.
// TODO: remove enclosing `if` when [UtIfExpression] negation is fixed
if (!arrayElementType.isJavaLangObject()) {
implicitlyThrowException(ArrayStoreException(), setOf(notIsExpression, notNull))
}
// If ASE is not thrown, we know that either the value is null, or it has a compatible type
queuedSymbolicStateUpdates += mkOr(isExpression, nullEqualityConstraint).asHardConstraint()
}
}
/**
* Traverses left part of assignment i.e. where to store resolved value.
*/
private fun TraversalContext.traverseAssignLeftPart(left: Value, value: SymbolicValue): SymbolicStateUpdate = when (left) {
is ArrayRef -> {
val arrayInstance = resolve(left.base) as ArrayValue
val addr = arrayInstance.addr
nullPointerExceptionCheck(addr)
val index = (resolve(left.index) as PrimitiveValue).align()
val length = memory.findArrayLength(addr)
indexOutOfBoundsChecks(index, length)
queuedSymbolicStateUpdates += Le(length, softMaxArraySize).asHardConstraint() // TODO: fix big array length
arrayStoreExceptionCheck(arrayInstance, value)
// add constraint for possible array type
val valueType = value.type
val valueBaseType = valueType.baseType
if (valueBaseType is RefType) {
val valueTypeAncestors = typeResolver.findOrConstructAncestorsIncludingTypes(valueBaseType)
val valuePossibleBaseTypes = value.typeStorage.possibleConcreteTypes.map { it.baseType }
// Either one of the possible types or one of their ancestor (to add interfaces and abstract classes)
val arrayPossibleBaseTypes = valueTypeAncestors + valuePossibleBaseTypes
val arrayPossibleTypes = arrayPossibleBaseTypes.map {
it.makeArrayType(arrayInstance.type.numDimensions)
}
val typeStorage = typeResolver.constructTypeStorage(OBJECT_TYPE, arrayPossibleTypes)
queuedSymbolicStateUpdates += typeRegistry
.typeConstraint(arrayInstance.addr, typeStorage)
.isConstraint()
.asHardConstraint()
}
val elementType = arrayInstance.type.elementType
val valueExpression = valueToExpression(value, elementType)
SymbolicStateUpdate(memoryUpdates = arrayUpdate(arrayInstance, index, valueExpression))
}
is FieldRef -> {
val instanceForField = resolveInstanceForField(left)
val objectUpdate = objectUpdate(
instance = instanceForField,
field = left.field,
value = valueToExpression(value, left.field.type)
)
// This hack solves the problem with static final fields, which are equal by reference with parameter
workaround(HACK) {
if (left.field.isFinal) {
addConstraintsForFinalAssign(resolve(left), value)
}
}
if (left is StaticFieldRef) {
val fieldId = left.field.fieldId
val staticFieldMemoryUpdate = StaticFieldMemoryUpdateInfo(fieldId, value)