-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathabstract.py
More file actions
1260 lines (1051 loc) · 42.9 KB
/
abstract.py
File metadata and controls
1260 lines (1051 loc) · 42.9 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
## Abstract semantics (State)
import sys
import traceback
import config
import copy
from debug import debug
from enum import Enum
from typing import Set, Union, List, Dict, Optional, Callable
import re
import esprima
class MissingMode(Enum):
"""
This enum is associated to each JSObject class. MISSING_IS_UNDEF means that a field not represented
in the JSObject's properties is assumed to be undefined. Otherwise, it is assumed to be JSTop (any value)
"""
MISSING_IS_UNDEF = 0
MISSING_IS_TOP = 1
class GCConfig:
"""
This class contains various fields useful to control the behavior of the garbage collector
"""
pass
class State(object):
"""
This class represents an abstract state (i.e. an over-approximation of possible concrete states at some point in the program)
"""
def __init__(self, glob : bool = False, bottom : bool = False) -> None:
"""
Class constructor
:param bool glob: True to create State in global scope, False otherwise (local scope)
:param bool bottom: True to create a bottom state (i.e. corresponding to no concrete state), False otherwise
"""
if bottom:
self.objs : Dict[int, 'JSObject']= {}
"""Dict from ref-ids to JSObjects, represents the "heap" """
self.gref : int = None
"""ref-id to the JSObject representing global context"""
self.lref : int = None
"""ref-id to the JSObject representing local context"""
self.pending : Set[int]= set()
"""set of ref-ids representing newly-created objects that are not yet referenced"""
self.is_bottom : bool = True
"""True if this is a bottom state"""
self.stack_frames : List[int] = []
"""Stack of ref-ids, each element is a ref-id to the JSObject representing the stack frame local context"""
self.value : 'JSValue' = JSBot
"""Represents the value computed by the current statement"""
else:
self.is_bottom = False
self.objs = {}
self.pending = set()
self.stack_frames = []
self.gref = State.new_id()
self.objs[self.gref] = JSObject({})
if glob:
self.lref = self.gref
else:
self.lref = State.new_id()
self.objs[self.lref] = JSObject({})
self.objs[self.gref].set_missing_mode(MissingMode.MISSING_IS_TOP)
self.objs[self.lref].set_missing_mode(MissingMode.MISSING_IS_TOP)
self.value = JSTop
# Class attributes
next_id = 0
# Class methods
@staticmethod
def bottom() -> 'State':
"""'
Returns a new bottom state
:find State:
:return: a bottom state
"""
st = State(glob=False, bottom=True)
return st
@staticmethod
def new_id() -> int:
"""
Returns a analysis-wide unique ID
:rtype int:
:return: the ID
"""
State.next_id += 1
return State.next_id - 1
@staticmethod
def set_next_id(next_id : int) -> None:
"""
Set the next id counter
:param int next_id: The next ID
"""
State.next_id = next_id
@staticmethod
def object_join(obj1 : 'JSObject', obj2 : 'JSObject') -> None:
"""
Joins two JSObjects, storing the result in the first argument
:param JSObject obj1: The first object to join (will be modified to store the join result)
:param JSObject obj2: The second object (will not be modified)
"""
if obj1.simfct != obj2.simfct:
obj1.simfct = None
if obj1.body != obj2.body:
obj1.body = None
if obj1.env != obj2.env:
obj1.env = None
if obj1.missing_mode == obj2.missing_mode:
if obj1.tablength != obj2.tablength:
obj1.tablength = None
else:
obj1.set_missing_mode(MissingMode.MISSING_IS_TOP)
if obj1.missing_mode == MissingMode.MISSING_IS_TOP:
bye = []
for k in obj1.properties:
if not k in obj2.properties:
bye.append(k)
else:
if not State.value_equal(obj1.properties[k], obj2.properties[k]):
obj1.properties[k] = State.value_join(obj1.properties[k], obj2.properties[k])
for k in bye:
del obj1.properties[k]
else:
for k in obj1.properties:
if not k in obj2.properties:
obj1.properties[k] = State.value_join(JSUndef, obj1.properties[k])
else:
if not State.value_equal(obj1.properties[k], obj2.properties[k]):
obj1.properties[k] = State.value_join(obj1.properties[k], obj2.properties[k])
for k in obj2.properties:
if not k in obj1.properties:
obj1.properties[k] = State.value_join(JSUndef, obj2.properties[k])
@staticmethod
def value_equal(v1 : 'JSValue', v2 : 'JSValue') -> bool:
"""
Compare two abstract values
:param JSVal v1: First abstract value
:param JSVal v2: Second abstract value
:rtype: bool
:return: Comparison result
"""
return type(v1) == type(v2) and v1 == v2
@staticmethod
def keep_or(s : Set['JSValue']) -> bool:
"""
Heuristic used to determine, when a variable / field / ... can have multiple values, if we
keep all of them using a JSOr, or not.
:param Set[JSValue] s: Set of values
:rtype bool:
:return: True if we keep the multiple values, False if we discard them
"""
if not config.use_or:
return False
if len(s) > 2:
return False
return JSUndef in s
@staticmethod
def value_join(v1 : 'JSValue', v2 : 'JSValue') -> 'JSValue':
"""
Join two jsvalues, returning the result
:param JSValue v1: First value
:param JSValue v2: Second value
:rtype: JSValue
:return: The join result
"""
if v1 is None or v1 is JSBot:
return v2
if v2 is None or v2 is JSBot:
return v1
if State.value_equal(v1, v2):
return v1
else:
if isinstance(v1, JSOr):
s1 = v1.choices
else:
s1 = set([v1])
if isinstance(v2, JSOr):
s2 = v2.choices
else:
s2 = set([v2])
total = s1.union(s2)
if State.keep_or(total):
ret = JSOr(total)
return ret
else:
return JSTop
# Instance methods
def set_to_bottom(self) -> None:
"""
Convert this state to the bottom state
"""
self.objs.clear()
self.gref = None
self.lref = None
self.is_bottom = True
self.stack_frames = []
self.pending = set()
self.value = JSBot
def clone(self) -> 'State':
"""
Clone this state
:rtype: State
:return: State copy
"""
c = State()
c.assign(self)
return c
# TODO rewrite/revert
def __eq__(self, other : 'State') -> bool:
"""
Test state equality. The two states must have been garbage-collected before test.
:param State other: the other state
:rtype: bool
:return: True if equal, False otherwise
"""
if self.is_bottom != other.is_bottom:
return False
if self.gref != other.gref:
return False
if self.lref != other.lref:
return False
if self.pending != other.pending:
return False
if self.value != other.value:
return False
if self.stack_frames != other.stack_frames:
return False
seen = set()
def extract_ref(val):
if isinstance(val, JSRef):
return val
if isinstance(val, JSOr):
s = {v for v in val.choices if isinstance(v, JSRef)}
if len(s) == 1:
return list(s)[0]
return None
def eq_aux(obj1, obj2):
nonlocal self
if obj1.is_closure() and obj2.is_closure():
if obj1.closure_env() not in seen:
seen.add(obj1.closure_env())
if not eq_aux(self.objs[obj1.closure_env()], other.objs[obj2.closure_env()]):
return False
for p in obj1.properties:
if p not in obj2.properties or obj1.properties[p] != obj2.properties[p]:
return False
ref = extract_ref(obj1.properties.get(p))
if ref is not None:
if ref.target() not in seen:
seen.add(ref.target())
if not eq_aux(self.objs[ref.target()], other.objs[ref.target()]):
return False
if ref.is_bound() and type(ref.this()) is int:
if ref.this() not in seen:
seen.add(ref.this())
if not eq_aux(self.objs[ref.this()], other.objs[ref.this()]):
return False
return obj1 == obj2
if not eq_aux(self.objs[self.lref], other.objs[other.lref]):
return False
if not eq_aux(self.objs[self.gref], other.objs[other.gref]):
return False
for p in self.pending:
if not eq_aux(self.objs[p], other.objs[p]):
return False
for s in self.stack_frames:
if not eq_aux(self.objs[s], other.objs[s]):
return False
return True
#TODO revert/rewrite
def assign(self, other : 'State') -> None:
"""
Do state assignment (self <- other)
:param State other: the other state
"""
if other.is_bottom:
self.set_to_bottom()
return
self.is_bottom = False
self.gref = other.gref
self.lref = other.lref
self.pending = other.pending.copy()
self.stack_frames = other.stack_frames.copy()
self.value = other.value.clone()
seen = set()
def extract_ref(val):
if isinstance(val, JSRef):
return val
if isinstance(val, JSOr):
s = {v for v in val.choices if isinstance(v, JSRef)}
if len(s) == 1:
return list(s)[0]
return None
def assign_aux(obj1, obj2):
nonlocal self
obj1.assign(obj2)
if obj2.is_closure():
if obj2.closure_env() not in seen:
seen.add(obj2.closure_env())
self.objs[obj2.closure_env()] = JSObject({})
assign_aux(self.objs[obj2.closure_env()], other.objs[obj2.closure_env()])
for p in obj2.properties:
ref = extract_ref(obj2.properties.get(p))
if ref is not None:
if ref.target() not in seen:
seen.add(ref.target())
self.objs[ref.target()] = JSObject({})
assign_aux(self.objs[ref.target()], other.objs[ref.target()])
if ref.is_bound() and type(ref.this()) is int:
if ref.this() not in seen:
seen.add(ref.this())
self.objs[ref.this()] = JSObject({})
assign_aux(self.objs[ref.this()], other.objs[ref.this()])
if other.lref not in seen:
seen.add(other.lref)
self.objs[other.lref] = JSObject({})
assign_aux(self.objs[other.lref], other.objs[other.lref])
if other.gref not in seen:
seen.add(other.gref)
self.objs[other.gref] = JSObject({})
assign_aux(self.objs[other.gref], other.objs[other.gref])
for p in other.pending:
if p not in seen:
seen.add(p)
self.objs[p] = JSObject({})
assign_aux(self.objs[p], other.objs[p])
for s in other.stack_frames:
if s not in seen:
seen.add(s)
self.objs[s] = JSObject({})
assign_aux(self.objs[s], other.objs[s])
for r in range(len(GCConfig.preexisting_objects)):
if r not in seen:
seen.add(r)
self.objs[r] = JSObject({})
assign_aux(self.objs[r], other.objs[r])
self.value = other.value.clone()
ref = extract_ref(self.value)
if ref is not None and ref.target() not in self.objs:
if ref.target() not in seen:
seen.add(ref.target())
self.objs[ref.target()] = JSObject({})
assign_aux(self.objs[ref.target()], other.objs[ref.target()])
if ref.is_bound() and type(ref.this()) is int and ref.this() not in seen:
seen.add(ref.this())
self.objs[ref.this()] = JSObject({})
assign_aux(self.objs[ref.this()], other.objs[ref.this()])
def visit(self, f):
modify = []
seen = set()
def aux(val):
nonlocal modify
if isinstance(val, JSOr):
s = set()
for c in val.choices:
s.add(aux(c))
if s != val.choices:
return JSOr(s)
else:
return val
elif isinstance(val, JSRef):
target = f(val.target())
#nr = JSRef(target)
if val.target() not in seen:
seen.add(val.target())
aux(self.objs[val.target()])
this = None
if val.is_bound() and type(val.this()) is int:
this = f(val.this())
if val.this() not in seen:
seen.add(val.this())
aux(self.objs[val.this()])
nr = JSRef(target, this)
if nr == val:
return val
else:
return nr
elif isinstance(val, JSObject):
for p, v in val.properties.items():
nv = aux(v)
if v != nv:
modify.append((val, p, nv))
if val.is_closure():
if val.closure_env() not in seen:
seen.add(val.closure_env())
aux(self.objs[val.closure_env()])
val.env = f(val.env)
else:
return val
aux(self.objs[self.lref])
aux(self.objs[self.gref])
for p in self.pending:
aux(self.objs[p])
for p in self.stack_frames:
aux(self.objs[p])
self.value = aux(self.value)
for o, p, v in modify:
o.properties[p] = v
#TODO cleanup
def unify(self, other : 'State') -> None:
"""
Remap self state to use IDs from other state
:param State other: the other state
"""
if not config.use_unify:
return
if self.lref != other.lref or self.stack_frames != other.stack_frames:
return
seen = set()
def extract_ref(val):
if isinstance(val, JSRef):
return val
if isinstance(val, JSOr):
s = {v for v in val.choices if isinstance(v, JSRef)}
if len(s) == 1:
return list(s)[0]
return None
def unify_aux(obj1, obj2):
nonlocal remap
nonlocal self
if obj1.is_closure() and obj2.is_closure():
if obj1.closure_env() not in seen:
seen.add(obj1.closure_env())
unify_aux(self.objs[obj1.closure_env()], other.objs[obj2.closure_env()])
if obj1.closure_env() != obj2.closure_env():
remap[obj1.closure_env()] = obj2.closure_env()
for p in obj1.properties:
#print("pre: ", p, obj1.properties.get(p), obj2.properties.get(p))
ref1 = extract_ref(obj1.properties.get(p))
ref2 = extract_ref(obj2.properties.get(p))
if ref1 is not None and ref2 is not None:
#print("processing 1", p)
if ref1.target() not in seen:
#print("add", ref1.target(), ref2.target())
seen.add(ref1.target())
unify_aux(self.objs[ref1.target()], other.objs[ref2.target()])
if ref1.target() != ref2.target():
if ref2.target() in self.objs:
remap[ref2.target()] = State.new_id()
remap[ref1.target()] = ref2.target()
if ref1.is_bound() and ref2.is_bound() and type(ref1.this()) is int and type(ref2.this()) is int:
if ref1.this() not in seen:
seen.add(ref1.this())
unify_aux(self.objs[ref1.this()], other.objs[ref2.this()])
if ref1.this() != ref2.this():
if ref2.this() in self.objs:
remap[ref2.this()] = State.new_id()
remap[ref1.this()] = ref2.this()
remap = {}
unify_aux(self.objs[self.lref], other.objs[other.lref])
unify_aux(self.objs[self.gref], other.objs[other.gref])
#for p in self.pending:
# unify_aux(self.objs[p], other.objs[p])
for s in self.stack_frames:
unify_aux(self.objs[s], other.objs[s])
def do_remap(_id):
if _id in remap:
return remap[_id]
return _id
self.visit(do_remap)
for old, new in remap.items():
self.objs[new] = self.objs.pop(old)
#TODO rewrite/revert
#In case of join on recursion state, other is the state of the greater recursion depth, self is the state of lesser recursion depth
def join(self, other : 'State') -> None:
"""
Perform join between two states, modify self to store result
:param State other: the other state
"""
if other.is_bottom:
return
if self.is_bottom:
self.assign(other)
return
assert self.gref == other.gref
#handle recursion
if self.lref != other.lref or self.stack_frames != other.stack_frames:
if len(self.stack_frames) >= len(other.stack_frames):
print(self)
print(other)
raise ValueError
assert self.lref < other.lref
assert(self.lref in other.stack_frames)
lref_idx = other.stack_frames.index(self.lref)
assert(self.stack_frames == other.stack_frames[0:lref_idx])
State.object_join(self.objs[self.lref], other.objs[other.lref])
self.pending.intersection_update(other.pending)
self.unify(other)
seen = set()
def extract_ref(val):
if isinstance(val, JSRef):
return val
if isinstance(val, JSOr):
s = {v for v in val.choices if isinstance(v, JSRef)}
if len(s) == 1:
return list(s)[0]
return None
def join_aux(obj1, obj2):
nonlocal self
State.object_join(obj1, obj2)
if obj1.is_closure() and obj2.is_closure():
#and obj1.closure_env() == obj2.closure_env():
assert(obj1.closure_env() == obj2.closure_env())
if obj1.closure_env() not in self.objs:
self.objs[obj1.closure_env()] = other.objs[obj2.closure_env()].clone()
if obj1.closure_env() not in seen:
seen.add(obj1.closure_env())
join_aux(self.objs[obj1.closure_env()], other.objs[obj2.closure_env()])
for p in obj1.properties:
ref1 = extract_ref(obj1.properties.get(p))
ref2 = extract_ref(obj2.properties.get(p))
if ref1 is not None and ref2 is not None:
assert ref1 == ref2
if ref1.target() not in self.objs:
self.objs[ref1.target()] = other.objs[ref1.target()].clone()
if ref1.target() not in seen:
seen.add(ref1.target())
join_aux(self.objs[ref1.target()], other.objs[ref2.target()])
if ref1.is_bound() and type(ref1.this()) is int:
if ref1.this() not in self.objs:
self.objs[ref1.this()] = other.objs[ref1.this()].clone()
if ref1.this() not in seen:
seen.add(ref1.this())
join_aux(self.objs[ref1.this()], other.objs[ref2.this()])
join_aux(self.objs[self.lref], other.objs[other.lref])
join_aux(self.objs[self.gref], other.objs[other.gref])
for p in self.pending:
join_aux(self.objs[p], other.objs[p])
for s in self.stack_frames:
join_aux(self.objs[s], other.objs[s])
if self.value is JSBot:
self.value = other.value.clone()
elif not State.value_equal(self.value, other.value):
self.value = JSTop
def scope_lookup(self, name : str) -> 'JSObject':
"""
Search for a variable with the given name in local scope, then closure scopes, and global scope.
Returns the JSObject representing the scope where the variable was found, otherwise returns the JSObject representing the global scope.
:param str name: The variable name
:rtype: JSObject
:return: The JSObject representing the scope containing the variable
"""
if name in self.objs[self.lref].properties:
return self.objs[self.lref]
current_scope = self.objs[self.lref]
found = False
while '__closure' in current_scope.properties and not found:
current_scope = self.objs[current_scope.properties['__closure'].ref_id]
found = name in current_scope.properties
if found:
return current_scope
return self.objs[self.gref]
def __str__(self) -> str:
"""
Gives string representation of state
:rtype: str
:return: String representation
"""
if self.is_bottom:
return "Bottom";
return("frames=" + str(self.stack_frames) + " gref=" + str(self.gref) + ", lref=" + str(self.lref) +", objs=" + str(self.objs) + ", pending="+ str(self.pending) + ", endval=" + str(self.value))
def __repr__(self) -> str:
"""
Gives string representation of state
:rtype: str
:return: String representation
"""
return self.__str__()
def consume_expr(self, expr : 'JSValue', consumed_refs:Set[int]=None) -> None:
"""
Look for references in value, and remove them from pending set.
If consumed_refs is not None, defer the removal and put ref id in consumed_refs instead
:param JSValue expr: The value to examine
:param Set[int] consumed_refs: Store removed ref-id here if not None, instead of removing from pending
"""
#TODO faire une fonction pour recuperer les ref id
refs_to_add = set()
if isinstance(expr, JSRef):
refs_to_add.add(expr)
elif isinstance(expr, JSOr):
for c in expr.choices:
if isinstance(c, JSRef):
refs_to_add.add(c)
if consumed_refs is None:
for expr in refs_to_add:
self.pending.discard(expr.target())
if expr.is_bound() and type(expr.this()) is int:
self.pending.discard(expr.this())
else:
for expr in refs_to_add:
consumed_refs.add(expr.target())
if expr.is_bound() and type(expr.this()) is int:
consumed_refs.add(expr.this())
def cleanup(self, verbose : bool = False) -> None:
"""
Garbage-collect state
:param bool verbose: Display debug info
"""
debug("State before GC: ", self)
if self.is_bottom:
debug("GC: Not doing anything\n")
return
#first, unlink top objects
changes = config.clean_top_objects
while changes:
changes = False
for obj_id in self.objs:
bye = []
for p in self.objs[obj_id].properties:
if isinstance(self.objs[obj_id].properties[p], JSRef):
i = self.objs[obj_id].properties[p].target()
if self.objs[i].missing_mode == MissingMode.MISSING_IS_TOP and len([v for v in self.objs[i].properties.values() if v is not JSTop]) == 0:
bye.append(p)
if len(bye) > 0:
changes = True
for b in bye:
if self.objs[obj_id].missing_mode == MissingMode.MISSING_IS_TOP:
del self.objs[obj_id].properties[b]
else:
self.objs[obj_id].properties[b] = JSTop
reachable = set()
def visit(ref_id):
if ref_id in reachable:
return
reachable.add(ref_id)
if ref_id not in self.objs:
print(self)
raise ValueError
obj = self.objs[ref_id]
for k,p in obj.properties.items():
if isinstance(p, JSOr):
fields = p.choices
else:
fields = set([p])
for v in fields:
if v.target() is None:
continue
visit(v.target())
if v.is_bound():
visit(v.this())
if obj.is_closure():
visit(obj.closure_env())
visit(self.lref) #local context gc root
visit(self.gref) #global context gc root
if isinstance(self.value, JSRef): #end-value is a gc root
visit(self.value.target())
#callstack local contexts gc root
for ref in self.stack_frames:
visit(ref)
#pending expressions gc root
for ref in self.pending:
visit(ref)
#preexisting objects gc root
for ref, obj in GCConfig.preexisting_objects:
visit(ref)
if verbose or config.debug:
print("GC: Reachable nodes: ", reachable)
bye = set()
for o,v in self.objs.items():
if o not in reachable:
bye.add(o)
for b in bye:
del self.objs[b]
## Classes for wrapping JS values
class JSValue(object):
"""
This is an abstract class representing any abstract JS value
"""
def is_callable(self) -> bool:
"""
Is this value a callable? (function or simfct)
:rtype bool:
:return: True if the value is a callable
"""
return False
def is_simfct(self) -> bool:
"""
Is this value a simfct?
:rtype bool:
:return: True if the value is a simfct
"""
return False
def is_pure_simfct(self) -> bool:
"""
Is this value a simfct without side effects?
:rtype bool:
:return: True if the value is a simfct without side effects
"""
return False
def is_function(self) -> bool:
"""
Is this value a function ? (a real function, not a simfct)
:rtype bool:
:return: True if the value is a function
"""
return False
def is_closure(self) -> bool:
"""
Is this value a closure ? (i.e. a function with captured environment)
:rtype bool:
:return: True if the value is a closure
"""
return False
def is_bound(self) -> bool:
"""
Is this value a reference to a function bound to an object?
:rtype bool:
:return: True if the value is bound
"""
return False
def contains_top(self) -> bool:
"""
Test if this value contains top (i.e.: array containing a TOP value)
:rtype bool:
:return: True if the value contains top
"""
return False
def target(self) -> int:
"""
If this value is a reference, returns ref-id
:rtype int:
:return: the ref-id
"""
return None
def this(self) -> Union[int, str]:
"""
If this value is a bound reference, returns ref-id (if bound to object) or string (if bound to string)
:rtype Union[int, str]:
:return: the ref-id or string
"""
return None
def clone(self) -> 'JSValue':
"""
Returns a copy of the JSValue. Note that this may return either a shallow copy or a deep copy.
A shallow copy is returned if the JSValue is immutable, otherwise a deep copy is performed.
:rtype: JSValue
:return: The copy
"""
raise NotImplementedError
def __hash__(self) -> int:
"""
Returns hash value of the object
:rtype: int
:return: the hash value
"""
raise NotImplementedError
def closure_env(self) -> int:
"""
If this value is a closure, returns the ref-id to the closure environment
:rtype int:
:return: the ref-id
"""
return None
# Represents any simple type (for example: a number)
class JSPrimitive(JSValue):
"""
This class represent any simple (scalar) type.
"""
def __init__(self, val : Union[int, str, float, re.Pattern]) -> None:
"""
Class constructor
:param Union[str, float, re.Pattern] val: The concrete value
"""
self.val : Union[int, str, float, re.Pattern] = val
if type(val) is int:
raise ValueError
"""The concrete value"""
def __eq__(self, other : JSValue) -> bool:
if type(self) != type(other):
return False
return self.val == other.val
def __str__(self) -> str:
return repr(self.val)
def __repr__(self) -> str:
return self.__str__()
def __hash__(self) -> int:
return self.val.__hash__()
def clone(self) -> 'JSPrimitive':
return self
class JSSpecial(JSValue):
"""
Represents special values, such as Top (i.e. any/unknown value), Bottom (no value), undefined, or NaN
"""
def __init__(self, name : str) -> None:
"""
Class constructor
:param str name: Either "Top", "Bot" "Null", or "Undef"
"""
self.name : str = name
""" Either "Top", "Bot" or "JSUndef" """
def clone(self) -> 'JSSpecial':
return self
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return self.__str__()
def __eq__(self, other : JSValue) -> bool:
if type(self) != type(other):
return False
return self.name == other.name
def __hash__(self) -> int:
return self.name.__hash__()
def contains_top(self) -> bool:
return self.name == "Top"
JSUndef = JSSpecial("Undef")
JSNull = JSSpecial("Null")
JSTop = JSSpecial("Top")
JSBot = JSSpecial("Bot")
#
class JSObject(JSValue):
"""
This class represents an object, array, or callable (simfct, or real function)
:members: hooks
"""
hooks : List[Callable] = []
"""
List of hooks, i.e. functions that returns methods that are available in any object
"""
#convenience functions to build obj/simfct/function/closure
@classmethod
def function(cls, body : esprima.nodes.Node, params : esprima.nodes.Node) -> 'JSObject':
"""
Build a JSObject for a function
:param esprima.nodes.Node body: The function's body
:param esprima.nodes.Node params: The function's formal args
:rtype JSObject:
:return: The JSObject representing the function
"""
return cls({}, body, params, None, None, False)
@classmethod
def closure(cls, body : esprima.nodes.Node, params : esprima.nodes.Node, env : int) -> 'JSObject':
"""
Build a JSObject for a closure
:param esprima.nodes.Node body: The function's body
:param esprima.nodes.Node params: The function's formal args
:param env int: The ref-id to the closure environment
:rtype JSObject:
:return: The JSObject representing the closure
"""
return cls({}, body, params, env, None, False)
@classmethod
def simfct(cls, simfct : function, pure_simfct : bool =False) -> 'JSObject':
"""
Build a JSObject for a simfct
:param function simfct: A python function to simulate the JS function
:param bool pure_simfct: True if the function is pure (i.e. no side effects)
:rtype JSObject: