Skip to content

Commit

Permalink
Merge pull request #314 from fandango-fuzzer/alexander
Browse files Browse the repository at this point in the history
Added read_only flag
  • Loading branch information
joszamama authored Feb 10, 2025
2 parents cbc9ab1 + 6a6935b commit d8ae909
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 9 deletions.
1 change: 0 additions & 1 deletion src/fandango/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,6 @@ def fuzz_command(args):
except TypeError:
# Python 3.11 does not know the `delete` argument
temp_dir = tempfile.TemporaryDirectory()

args.directory = temp_dir.name
args.format = "string"
output_population(population, args, file_mode=file_mode, output_on_stdout=False)
Expand Down
2 changes: 2 additions & 0 deletions src/fandango/evolution/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ def __init__(
def fix_individual(self, individual: DerivationTree) -> DerivationTree:
_, failing_trees = self.evaluator.evaluate_individual(individual)
for failing_tree in failing_trees:
if failing_tree.tree.read_only:
continue
for operator, value, side in failing_tree.suggestions:
from fandango.constraints.fitness import Comparison, ComparisonSide

Expand Down
1 change: 1 addition & 0 deletions src/fandango/evolution/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def mutate(

# Collect the failing subtrees
failing_subtrees = [ft.tree for ft in failing_trees]
failing_subtrees = list(filter(lambda x: not x.read_only, failing_subtrees))

# If there is nothing to mutate, return the individual as is.
if not failing_subtrees:
Expand Down
2 changes: 2 additions & 0 deletions src/fandango/evolution/population.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ def fix_individual(
) -> DerivationTree:
fixes_made = 0
for failing_tree in failing_trees:
if failing_tree.tree.read_only:
continue
for operator, value, side in failing_tree.suggestions:
if operator == Comparison.EQUAL and side == ComparisonSide.LEFT:
# LOGGER.debug(f"Parsing {value} into {failing_tree.tree.symbol.symbol!s}")
Expand Down
6 changes: 5 additions & 1 deletion src/fandango/language/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,11 @@ def fuzz(self, grammar: "Grammar", max_nodes: int = 100) -> List[DerivationTree]
if self.symbol not in grammar:
raise ValueError(f"Symbol {self.symbol} not found in grammar")
if self.symbol in grammar.generators:
return [grammar.generate(self.symbol)]
generated = grammar.generate(self.symbol)
# Prevent children from being overwritten without executing generator
generated.set_all_read_only(True)
generated.read_only = False
return [generated]
children = grammar[self.symbol].fuzz(grammar, max_nodes - 1)
return [DerivationTree(self.symbol, children)]

Expand Down
24 changes: 17 additions & 7 deletions src/fandango/language/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ def __init__(
symbol: Symbol,
children: Optional[List["DerivationTree"]] = None,
parent: Optional["DerivationTree"] = None,
read_only: bool = False
):
self.hash_cache = None
self._parent: Optional["DerivationTree"] = parent
self.symbol: Symbol = symbol
self._children: list["DerivationTree"] = []
self.read_only = read_only
self._size = 1
self.set_children(children or [])

Expand All @@ -45,6 +47,11 @@ def invalidate_hash(self):
if self._parent is not None:
self._parent.invalidate_hash()

def set_all_read_only(self, read_only: bool):
self.read_only = read_only
for child in self._children:
child.set_all_read_only(read_only)

def set_children(self, children: List["DerivationTree"]):
self._children = children
self._size = 1 + sum(child.size() for child in self._children)
Expand Down Expand Up @@ -121,7 +128,7 @@ def __deepcopy__(self, memo):
return memo[id(self)]

# Create a new instance without copying the parent
copied = DerivationTree(self.symbol, [])
copied = DerivationTree(self.symbol, [], read_only=self.read_only)
memo[id(self)] = copied

# Deepcopy the children
Expand Down Expand Up @@ -390,7 +397,7 @@ def replace(self, tree_to_replace, new_subtree):
"""
Replace the subtree rooted at the given node with the new subtree.
"""
if self == tree_to_replace:
if self == tree_to_replace and not self.read_only:
return new_subtree
else:
return DerivationTree(
Expand All @@ -399,25 +406,28 @@ def replace(self, tree_to_replace, new_subtree):
child.replace(tree_to_replace, new_subtree)
for child in self._children
],
read_only=self.read_only,
)

def get_non_terminal_symbols(self) -> Set[NonTerminal]:
def get_non_terminal_symbols(self, exclude_read_only=True) -> Set[NonTerminal]:
"""
Retrieve all non-terminal symbols present in the derivation tree.
"""
symbols = set()
if self.symbol.is_non_terminal:
if self.symbol.is_non_terminal and not (exclude_read_only and self.read_only):
symbols.add(self.symbol)
for child in self._children:
symbols.update(child.get_non_terminal_symbols())
symbols.update(child.get_non_terminal_symbols(exclude_read_only))
return symbols

def find_all_nodes(self, symbol: NonTerminal) -> List["DerivationTree"]:
def find_all_nodes(
self, symbol: NonTerminal, exclude_read_only=True
) -> List["DerivationTree"]:
"""
Find all nodes in the derivation tree with the given non-terminal symbol.
"""
nodes = []
if self.symbol == symbol:
if self.symbol == symbol and not (exclude_read_only and self.read_only):
nodes.append(self)
for child in self._children:
nodes.extend(child.find_all_nodes(symbol))
Expand Down

0 comments on commit d8ae909

Please sign in to comment.