Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Milestone 5: Disallow Invalid Breakpoints #318

Merged
merged 5 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions asteroid/mad.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
###########################################################################################

from os.path import exists, split, basename
from asteroid.support import term2string, get_tail_term, term2verbose
from asteroid.support import term2string, get_tail_term, term2verbose, find_function
from asteroid.version import MAD_VERSION
import copy

Expand Down Expand Up @@ -283,10 +283,10 @@ def _handle_help(self,_):
print("next\t\t\t- step execution across a nested scope")
print("print <name>[@<num>|<name>]+|* [-v]\t\t- print contents of <name>, * lists all vars in scope, recursively access (nested) objects with @, '-v' enables verbose printing of nested data")
print("quit\t\t\t- quit debugger")
print("set [<func>|<line#> [<file>]]\n\t\t\t- set a breakpoint")
print("set [<func>|<line#> [<file>]]\n\t\t\t- set a breakpoint, breakpoints may only be set on valid statements on already loaded files")
print("stack [<num>|* [-v]]\t\t\t- display runtime stack, list all items in specific frame with an index or all frames with '*', '-v' toggles verbose printing")
print("step\t\t\t- step to next executable statement")
print("trace [<num> [<num>]]\t\t\t- display runtime stack trace , display runtime stack trace, can specify either the first n frames or all of the frames between the start and end")
print("trace [<num> [<num>]]\t\t\t- display runtime stack trace, display runtime stack trace, can specify either the first n frames or all of the frames between the start and end")
print("up\t\t\t- move up one stack frame")
print("where\t\t\t- print current program line")
print()
Expand Down Expand Up @@ -383,27 +383,80 @@ def _handle_quit(self,_):
raise SystemExit()

def _handle_set(self,args):
(file,lineno) = self.interp_state.lineinfo
if len(args) == 0:
# set a breakpoint at the current line
(file,lineno) = self.interp_state.lineinfo
self.line_breakpoints.append((lineno,file))
return START_DEBUGGER
elif len(args) == 1:
(file,_) = self.interp_state.lineinfo
if args[0].isnumeric():
self.line_breakpoints.append((int(args[0]),file))
self._load_program_text(file)
if args[0].isnumeric():
if self._validate_breakpoint_line(file, int(args[0])):
self.line_breakpoints.append((int(args[0]),file))
else:
self.function_breakpoints.append((args[0],file))
if self._validate_breakpoint_function(file, args[0]):
self.function_breakpoints.append((args[0],file))
return START_DEBUGGER
elif len(args) == 2:
if args[0].isnumeric():
self.line_breakpoints.append((int(args[0]),args[1]))
self._load_program_text(args[1])
if args[0].isnumeric():
if self._validate_breakpoint_line(args[1], int(args[0])):
self.line_breakpoints.append((int(args[0]),args[1]))
else:
self.function_breakpoints.append((args[0],args[1]))
if self._validate_breakpoint_function(args[1], args[0]):
self.function_breakpoints.append((args[0],args[1]))
return START_DEBUGGER
else:
print("error: too many arguments to set")
return START_DEBUGGER

def _validate_breakpoint_line(self, fname, lineno):
# Load the correct file
file_text = self.program_text[fname]
# If the line is outside the current file, produce an error message and return
if lineno > len(file_text):
print("error: cannot place breakpoint on invalid lines")
return False
# Check if the line is empty
line = file_text[lineno-1].strip()
if len(line) == 0:
print("error: cannot place breakpoints on blank lines")
return False
# If the line starts with any of these tokens, it is a valid statement and can accept a breakpoint
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very fragile, if the language evolves by introducing new tokens this will break and will prevent users from setting break points. Is there a way to do this in the lexer for example where it is natural for the user to extend collections when introducing new tokens. See the parser file where we define collection at the beginning.

for start in ['load', 'global', 'structure', 'let', 'loop', 'for', 'while', 'repeat', 'match', 'if', 'try', 'throw', 'break', 'return', 'function']:
if len(line) >= len(start) and line[:len(start)] == start:
return True
# Get the lines above and below the current if they exist
prev = file_text[lineno-2].strip() if lineno > 1 else None
next = file_text[lineno].strip() if lineno < len(file_text) else None
# If the line starts with any of these tokens, the line is in the middle of a statement or expression and cannot accept a breakpoint
for start in ['system', 'as', 'with', 'end', 'do', 'in', 'until', 'elif', 'else', 'catch', '@', ',', '[', ']', '(', ')', '"', "'"]:
if len(line) >= len(start) and line[:len(start)] == start:
print("error: cannot place breakpoint inside a statement")
return False
# If the previous or next lines begins with any of these symbols, the current line is in the middle of a statement
if prev:
for start in ['@', ',', '[', '(', '"', "'"]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comment above, having these collection arbitrarily inside the code makes the code fragile

if len(prev) >= len(start) and prev[:len(start)] == start:
print("error: cannot place breakpoint inside a statement")
return False
if next:
for start in ['@', ',', ']', ')', '"', "'"]:
if len(next) >= len(start) and next[:len(start)] == start:
print("error: cannot place breakpoint inside a statement")
return False
# If it cannot be determined, assume the breakpoint is valid
return True


def _validate_breakpoint_function(self, fname, func_name):
# Loop through every scope and check if the function was found
loaded_syms = self.interp_state.symbol_table.scoped_symtab
for scope in loaded_syms:
for (sym, val) in scope.items():
if find_function(sym, val, fname, func_name): return True
print("error: unable to find function '{}' in file '{}'".format(func_name, fname))
return False

def _handle_frame(self,_):
print("you are looking at frame #{}".format(self.frame_ix))
Expand Down
32 changes: 32 additions & 0 deletions asteroid/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,35 @@ def term2verbose(term, indent_size=0, initial_indent=True):
term_string += ',\n'
term_string += curr_indent + (']' if TYPE == 'list' else ')')
return term_string

def find_function(sym, val, fname, func_name):
# If the function has already been found, check if it is in the correct file
if sym == func_name and val[0] == 'function-val':
if _check_lineinfo_function(val, fname): return True
# If we are looking at a struct, check if the function is one of its members and if it lies in the correct file
if val[0] == 'struct':
(STRUCT,
(MEMBER_NAMES, (LIST, member_names)),
(STRUCT_MEMORY, (LIST, struct_memory))) = val
if func_name in member_names and struct_memory[member_names.index(func_name)][0] == 'function-val':
if _check_lineinfo_function(struct_memory[member_names.index(func_name)], fname): return True
# If we are looking at a loaded module, recurse on everything that is not another module and return True if the function was found
if val[0] == 'module':
(MODULE,
(ID, id),
(SCOPE, scope)) = val
for s in scope[0]:
for (new_sym, new_val) in s.items():
if new_val[0] != 'module' and find_function(new_sym, new_val, fname, func_name):
return True
return False

def _check_lineinfo_function(val, fname):
# Extract the lineinfo field and compare it to the provided filename
(FUNCTION_VAL,
(BODY_LIST,
(LIST, body_list)),
(scopes, globals, global_scope)) = val
(LINEINFO,
(filename, lineno)) = body_list[0]
return filename == fname
2 changes: 1 addition & 1 deletion docs/MAD.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Here is a table of the available commands in the the debugger,
print <name>[@<num>|<name>]+|* [-v]. print contents of <name>, * lists all vars in scope, recursively access (nested) objects with @, '-v' enables verbose printing of nested data
quit ............................... quit debugger
stack [<num>|* [-v]]................ display runtime stack, list all items in specific frame with an index or all frames with '*', '-v' toggles verbose printing
set [<func>|<line#> [<file>]] ...... set a breakpoint
set [<func>|<line#> [<file>]] ...... set a breakpoint, breakpoints may only be set on valid statements on already loaded files
step ............................... step to next executable statement
trace [<num> [<num>]]............... display runtime stack trace, can specify either the first n frames or all of the frames between the start and end
up ................................. move up one stack frame
Expand Down
2 changes: 1 addition & 1 deletion docs/MAD.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Here is a table of the available commands in the the debugger,
print <name>[@<num>|<name>]+|* [-v]. print contents of <name>, * lists all vars in scope, recursively access (nested) objects with @, '-v' enables verbose printing of nested data
quit ............................... quit debugger
stack [<num>|* [-v]]................ display runtime stack, list all items in specific frame with an index or all frames with '*', '-v' toggles verbose printing
set [<func>|<line#> [<file>]] ...... set a breakpoint
set [<func>|<line#> [<file>]] ...... set a breakpoint, breakpoints may only be set on valid statements on already loaded files
step ............................... step to next executable statement
trace [<num> [<num>]]............... display runtime stack trace, can specify either the first n frames or all of the frames between the start and end
up ................................. move up one stack frame
Expand Down
Loading