-
Notifications
You must be signed in to change notification settings - Fork 0
/
mutator.py
147 lines (109 loc) · 3.64 KB
/
mutator.py
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
from abc import ABC, abstractmethod
from typing import List, Tuple
import random
from copy import deepcopy
from seed import Seed
class Mutator(ABC):
"""
Mutator interface
"""
@abstractmethod
def mutate(self, seed: Seed) -> Seed:
pass
@abstractmethod
def name(self) -> str:
return "base"
def __repr__(self):
return f"Mutator[{self.name()}]"
class DupMutator(Mutator):
"""
Choose one api call in a seed to duplicate it.
"""
def mutate(self, seed: Seed) -> Seed:
seed = seed.copy()
seed.mutations.append(self.name())
randpos: int = random.randrange(0, seed.len())
seed.insert(randpos, deepcopy(seed[randpos]))
return seed
def name(self) -> str:
return "dup"
class SwapMutator(Mutator):
"""
Choose two api calls in a seed to swap them
"""
def mutate(self, seed: Seed) -> Seed:
seed = seed.copy()
# If the number of API calls in a seed is less than 2,
# it will cause an infinite loop when choosing API calls to exchange
if seed.len() < 2:
return seed
seed.mutations.append(self.name())
randpos1 = random.randrange(0, seed.len())
while (randpos2 := random.randrange(0, seed.len())) == randpos1:
continue
seed[randpos1], seed[randpos2] = seed[randpos2], seed[randpos1]
return seed
def name(self) -> str:
return "swap"
class DelMutator(Mutator):
"""
Choose one api call in a seed to delete it
"""
def mutate(self, seed: Seed) -> Seed:
seed = seed.copy()
seed.mutations.append(self.name())
if seed.len() > 2:
randpos = random.randrange(0, seed.len())
seed.fns.remove(seed[randpos])
return seed
def name(self) -> str:
return "del"
class ArgMutator(Mutator):
"""
Choose one api call in a seed to mutate its arguments
"""
def mutate(self, seed: Seed) -> Seed:
seed = seed.copy()
seed.mutations.append(self.name())
randpos: int = random.randrange(0, seed.len())
fn = seed[randpos]
for arg in fn.args:
if arg.mutable:
arg.mutate()
return seed
def name(self) -> str:
return "arg"
class InsMutator(Mutator):
"""
Randomly insert an api call into a seed
"""
def mutate(self, seed: Seed):
return
def name(self) -> str:
return "ins"
class MutExecutor:
"""
Mutation executor
"""
def __init__(self) -> None:
self.mutator_with_weight: List[Tuple[Mutator, float]] = [
(ArgMutator(), 0.4),
(DupMutator(), 0.2),
(SwapMutator(), 0.2),
(DelMutator(), 0.2),
]
@property
def mutators(self):
return [mutator for mutator, _ in self.mutator_with_weight]
@property
def weights(self):
return [weight for _, weight in self.mutator_with_weight]
def mutate(self, queue: List[Seed], *, top_n: int = 10, mut_limit: int = 5) -> List[Seed]:
"""Given a queue, mutate seeds with `top_n` priority. Perform no more than `mut_limit` mutations."""
# Only mutate seeds with `top_n` priority
# For now, for simplicity, we just use sample to random select #top_n seeds to mutate
# TODO: Use priority algorithm to select seeds
selected_seeds = random.sample(queue, top_n) if top_n < len(queue) else queue
return [mutator.mutate(seed)
for seed in selected_seeds
for mutator in random.choices(self.mutators, self.weights, k=seed.power)]