blob: 917b084860d34aeabc9698e193cd5985b02b5d7c [file]
/* Skipping uninteresting files and functions while stepping.
Copyright (C) 2011-2026 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "skip.h"
#include "event-top.h"
#include "value.h"
#include "valprint.h"
#include "ui-out.h"
#include "symtab.h"
#include "cli/cli-cmds.h"
#include "command.h"
#include "completer.h"
#include "stack.h"
#include "cli/cli-utils.h"
#include "arch-utils.h"
#include "linespec.h"
#include "objfiles.h"
#include "breakpoint.h"
#include "source.h"
#include "filenames.h"
#include "fnmatch.h"
#include "gdbsupport/gdb_regex.h"
#include <optional>
#include <list>
#include "cli/cli-style.h"
#include "gdbsupport/buildargv.h"
#include "safe-ctype.h"
#include "readline/tilde.h"
#include "gdbsupport/selftest.h"
#include <initializer_list>
#include <unordered_set>
#include <vector>
/* True if we want to print debug printouts related to file/function
skipping. */
static bool debug_skip = false;
/* Print a "skip" debug statement. */
#define skip_debug_printf(fmt, ...) \
debug_prefixed_printf_cond (debug_skip, "skip", fmt, ##__VA_ARGS__)
class skiplist_entry
{
public:
/* Create a skiplist_entry object and add it to the chain. */
static void add_entry (bool file_is_glob,
std::string &&file,
bool function_is_regexp,
std::string &&function);
/* Return true if the skip entry has a file or glob-style file
pattern that matches FUNCTION_SAL. */
bool skip_file_p (const symtab_and_line &function_sal) const;
/* Return true if the skip entry has a function or function regexp
that matches FUNCTION_NAME. */
bool skip_function_p (const char *function_name) const;
/* Getters. */
int number () const { return m_number; };
bool enabled () const { return m_enabled; };
bool file_is_glob () const { return m_file_is_glob; }
const std::string &file () const { return m_file; }
const std::string &function () const { return m_function; }
bool function_is_regexp () const { return m_function_is_regexp; }
/* Setters. */
void enable () { m_enabled = true; };
void disable () { m_enabled = false; };
/* Print a gdb command that can be used to recreate this skip. */
void print_recreate (ui_file *stream) const;
private:
/* Key that grants access to the constructor. */
struct private_key {};
public:
/* Public so we can construct with container::emplace_back. Since
it requires a private class key, it can't be called from outside.
Use the add_entry static factory method to construct instead. */
skiplist_entry (bool file_is_glob, std::string &&file,
bool function_is_regexp, std::string &&function,
private_key);
/* Disable copy. */
DISABLE_COPY_AND_ASSIGN (skiplist_entry);
private:
/* Return true if we're stopped at a file to be skipped. */
bool do_skip_file_p (const symtab_and_line &function_sal) const;
/* Return true if we're stopped at a globbed file to be skipped. */
bool do_skip_gfile_p (const symtab_and_line &function_sal) const;
private: /* data */
int m_number = -1;
/* True if FILE is a glob-style pattern.
Otherwise it is the plain file name (possibly with directories). */
bool m_file_is_glob;
/* The name of the file or empty if no name. */
std::string m_file;
/* True if FUNCTION is a regexp.
Otherwise it is a plain function name (possibly with arguments,
for C++). */
bool m_function_is_regexp;
/* The name of the function or empty if no name. */
std::string m_function;
/* If this is a function regexp, the compiled form. */
std::optional<compiled_regex> m_compiled_function_regexp;
/* If this is a file glob, then we convert the glob to a regexp, and
place the compiled form here. */
std::optional<compiled_regex> m_compiled_file_regexp;
/* Enabled/disabled state. */
bool m_enabled = true;
};
static std::list<skiplist_entry> skiplist_entries;
static int highest_skiplist_entry_num = 0;
/* Structure to hold the results of parsing a glob bracket expression. */
struct glob_bracket_expr
{
/* Type used to express a character range, e.g 'a-z'. In this case the
first item in the pair would be 'a', and the second item 'z'. */
using char_range = std::pair<unsigned char, unsigned char>;
/* Parse a bracket expression and return an instance of this class. If
the bracket expression is invalid then return an empty optional. PTR
must point to the '[' character that starts the bracket expression. */
static std::optional<glob_bracket_expr> parse (const char *ptr);
/* Convert the parsed bracket expression into a regular expression, and
return the regular expression as a string. */
std::string to_string () const;
/* Return a pointer to the end of the bracket expression. This will point
to the final ']' that closes the expression. This will never be NULL. */
const char *end () const;
private:
/* When true the character bracket expression is negated, that is we want
to match everything not mentioned in the expression. When false we
only want to match things mentioned in the expression. */
bool m_negated = false;
/* Each string is something like '[:alpha:]' and represents a character
class. If validation is wanted then it should be done before items
are added to this list, the contents of this list are not validated as
they are used. */
std::vector<std::string> m_char_classes;
/* Character ranges stored as pairs, first entry is the range start,
second entry is the range end. Both are inclusive. */
std::vector<char_range> m_char_ranges;
/* Single characters within the bracket expression. */
std::unordered_set<char> m_chars;
/* Points at the closing ']' for the bracket expression. */
const char *m_end = nullptr;
};
/* Return true if PTR is at the start of a character class descriptor,
e.g. "[:alpha:]". This doesn't validate the actual character class name,
just that PTR is at the start of something that looks like a character
class. */
static bool
at_character_class (const char *ptr)
{
if (*ptr != '[')
return false;
++ptr;
if (*ptr != ':')
return false;
++ptr;
/* Require at least one character in the class name. */
if (!c_isalpha (*ptr))
return false;
while (c_isalpha (*ptr))
++ptr;
if (*ptr != ':')
return false;
++ptr;
return *ptr == ']';
}
/* Return true if PTR is pointing to something like 'A-B', a character
range as could be found within a glob's bracket expression. */
static bool
at_character_range (const char *ptr)
{
gdb_assert (*ptr != '\0');
if (*(ptr + 1) != '-')
return false;
if (*(ptr + 2) == '\0' || *(ptr + 2) == ']')
return false;
return true;
}
/* Helper for sanitize_range. Pass RANGE to SHOULD_SPLIT, if it returns
true then call DO_SPLIT to split RANGE, placing the resulting ranges in
RESULTS. If SHOULD_SPLIT returns false then just place RANGE in
RESULTS. */
template<typename Pred, typename Split>
static void
split_a_range (std::vector<glob_bracket_expr::char_range> &results,
const glob_bracket_expr::char_range &range,
Pred should_split, Split do_split)
{
if (should_split (range))
do_split (results, range);
else
results.emplace_back (range);
}
/* Helper for sanitize_range. For each range in RESULTS, if SHOULD_SPLIT
returns true, replace it with the sub-ranges produced by DO_SPLIT.
Otherwise keep it unchanged. */
template<typename Pred, typename Split>
static void
resplit_all_ranges (std::vector<glob_bracket_expr::char_range> &results,
Pred should_split, Split do_split)
{
std::vector<glob_bracket_expr::char_range> temp;
for (const glob_bracket_expr::char_range &p : results)
split_a_range (temp, p, should_split, do_split);
results = std::move (temp);
}
/* The character range START-END is taken from a filename glob. When
converting to a regular expression we cannot allow a directory
separator to appear within the range. So, if a directory separator
does appear between START and END (inclusive), split the range into
multiple ranges, excluding the directory separator.
For Unix systems the only directory separator we check for is '/', but
on DOS like file systems we also check for '\'.
On case insensitive filesystems we also need to be careful about ranges
that span into, or out of, the upper case character range. For glibc
at least, the REG_ICASE matching which we use (for case insensitive
file systems) lowers any uppercase characters it finds. So a range
like [W-_] becomes [w-_], which is invalid as 'w' is after '_'. To
avoid this problem we split ranges at 'A' and 'Z', so the above range
becomes [W-Z[-_], then when glibc lowers the letters this becomes
[w-z[-_] which is still valid.
Return a vector of all the ranges needed to cover START to END but
exclude directory separators. */
static std::vector<glob_bracket_expr::char_range>
sanitize_range (unsigned char start, unsigned char end)
{
/* Ranges that start or end at a directory separator are invalid, and
should have been filtered out before now. */
gdb_assert (!IS_DIR_SEPARATOR (start) && !IS_DIR_SEPARATOR (end));
/* Backward ranges are invalid, and should have been filtered out before
now. */
gdb_assert (start <= end);
std::vector<glob_bracket_expr::char_range> results;
/* Always split the range on '/'. This creates at most two ranges. */
split_a_range (results, { start, end },
[] (const glob_bracket_expr::char_range &p)
{ return p.first < '/' && p.second > '/'; },
[] (std::vector<glob_bracket_expr::char_range> &out,
const glob_bracket_expr::char_range &p)
{
out.emplace_back (p.first, '/' - 1);
out.emplace_back ('/' + 1, p.second);
});
#ifdef HAVE_DOS_BASED_FILE_SYSTEM
/* Exclude '\\' from ranges. The character after '\\' is ']', which
needs to be kept as a single-character range since it has special
meaning within bracket expressions, by keeping the ']' as a single
character range the ']' can be moved within the bracket expression to
a valid location. */
resplit_all_ranges (results,
[] (const glob_bracket_expr::char_range &p)
{ return p.first < '\\' && p.second > '\\'; },
[] (std::vector<glob_bracket_expr::char_range> &out,
const glob_bracket_expr::char_range &p)
{
out.emplace_back (p.first, '\\' - 1);
gdb_assert ('\\' + 1 == ']');
out.emplace_back (']', ']');
if (p.second > ']')
out.emplace_back (']' + 1, p.second);
});
#endif /* HAVE_DOS_BASED_FILE_SYSTEM */
#ifdef HAVE_CASE_INSENSITIVE_FILE_SYSTEM
/* Split ranges at the 'A' and 'Z' boundaries to allow for case
insensitive matching. glibc's ICASE matching lowers upper case
letters within ranges, so the range [W-_] becomes, with ICASE
matching, [w-_]. Unfortunately, 'w' is after '_' so this range is
now invalid. By splitting the range into [W-Z[-_] glibc is now free
to lower this to [w-z[-_] which is still valid. */
resplit_all_ranges (results,
[] (const glob_bracket_expr::char_range &p)
{ return p.first < 'A' && p.second >= 'A'; },
[] (std::vector<glob_bracket_expr::char_range> &out,
const glob_bracket_expr::char_range &p)
{
out.emplace_back (p.first, 'A' - 1);
out.emplace_back ('A', p.second);
});
resplit_all_ranges (results,
[] (const glob_bracket_expr::char_range &p)
{ return p.first <= 'Z' && p.second > 'Z'; },
[] (std::vector<glob_bracket_expr::char_range> &out,
const glob_bracket_expr::char_range &p)
{
out.emplace_back (p.first, 'Z');
out.emplace_back ('Z' + 1, p.second);
});
#endif /* HAVE_CASE_INSENSITIVE_FILE_SYSTEM */
return results;
}
/* CHAR_CLASS should be a character class name like "[:name:]". Return
true if CHAR_CLASS matches a directory separator, which is just '/'
unless we're on a DOS like filesystem, in which case we check for '/'
or '\'. */
static bool
character_class_matches_dir_separator (const std::string &char_class)
{
std::string pattern = string_printf ("[%s]", char_class.c_str ());
compiled_regex re (pattern.c_str (), REG_NOSUB | REG_EXTENDED,
_("character class check"));
#ifdef HAVE_DOS_BASED_FILE_SYSTEM
static constexpr const char *dir_sep_str = "/\\";
#else
static constexpr const char *dir_sep_str = "/";
#endif /* not HAVE_DOS_BASED_FILE_SYSTEM */
return re.exec (dir_sep_str, 0, nullptr, 0) == 0;
}
/* See class declaration above. */
std::optional<glob_bracket_expr>
glob_bracket_expr::parse (const char *ptr)
{
gdb_assert (*ptr == '[');
++ptr;
glob_bracket_expr result;
/* POSIX defines '!' as the negation character within a bracket
expression, and says that a '^' as the first character is undefined.
Both fnmatch and bash treat a leading '^' as negation, which matches
the regexp behaviour. We match this. */
if (*ptr == '!' || *ptr == '^')
{
result.m_negated = true;
result.m_chars.insert ('/');
ptr++;
}
char end_bracket_char = '\0';
for (; *ptr != '\0' && *ptr != end_bracket_char; ++ptr)
{
end_bracket_char = ']';
/* Is this a character range? */
if (at_character_range (ptr))
{
char range_start = *ptr;
char range_end = *(ptr + 2);
/* If the range starts with ']' then the opening ']' must be the
first character in the bracket expression (after any
negation). Insert the ']' as a single character, and
increment the range start. We know the range_end cannot also
be ']' as that would close the bracket expression, and not be
a valid range. Having the ']' as a lone character make it
easier to place this in the correct place within the bracket
expression when we generate the regexp. */
if (range_start == ']')
{
gdb_assert (range_end != ']');
result.m_chars.insert (range_start);
range_start++;
}
if (IS_DIR_SEPARATOR (range_start) || IS_DIR_SEPARATOR (range_end))
return {};
if (range_end < range_start)
{
/* Don't use RANGE_START here as it might have been modified
above. We could use RANGE_END but don't just to be
consistent. */
error (_("skip glob regexp: invalid character range %c-%c"),
*ptr, *(ptr + 2));
}
std::vector<glob_bracket_expr::char_range> ranges
= sanitize_range (range_start, range_end);
for (glob_bracket_expr::char_range p : ranges)
{
if (p.first == p.second)
result.m_chars.insert (p.first);
else
result.m_char_ranges.push_back (std::move (p));
}
ptr += 2;
}
else if (at_character_class (ptr))
{
const char *end = strchr (ptr, ']');
gdb_assert (end != nullptr);
std::string cc (ptr, end - ptr + 1);
if (character_class_matches_dir_separator (cc))
{
/* We are converting a glob to a regexp and trying to
replicate the FNM_PATHNAME flag of fnmatch. If we allow a
character class that matches '/' then this might cause
problems.
We could try to split the class into multiple ranges that
cover all the same characters, but not '/', but for now we
just give an error. */
error (_("skip glob regexp: unsupported character class '%s'"),
cc.c_str ());
}
result.m_char_classes.push_back (std::move (cc));
ptr = end;
}
else
result.m_chars.insert (*ptr);
}
if (*ptr != ']')
return {};
result.m_end = ptr;
return result;
}
/* See class declaration above. */
std::string
glob_bracket_expr::to_string () const
{
std::string result ("[");
if (m_negated)
result += '^';
if (m_chars.find (']') != m_chars.end ())
result += ']';
for (const glob_bracket_expr::char_range &p : m_char_ranges)
{
/* These should be converted to single characters by sanitize_range. */
gdb_assert (p.first != ']' && p.second != ']');
result += p.first;
result += '-';
result += p.second;
}
for (const std::string &s : m_char_classes)
result += s;
for (const char c : m_chars)
{
if (strchr ("-^]", c) == nullptr)
result += c;
}
if (m_chars.find ('^') != m_chars.end ())
result += '^';
if (m_chars.find ('-') != m_chars.end ())
result += '-';
result += ']';
return result;
}
/* See class declaration above. */
const char *
glob_bracket_expr::end () const
{
/* The parse member function always sets to non-NULL before releasing an
instance of this class into the world. */
gdb_assert (m_end != nullptr);
return m_end;
}
/* When we convert a filename glob into a regular expression, these are
the flags to use. If filenames are case insensitive then we add the
ICASE (ignore case) flag. */
static constexpr int file_glob_regexp_flags = (REG_NOSUB
| REG_EXTENDED
#ifdef HAVE_CASE_INSENSITIVE_FILE_SYSTEM
| REG_ICASE
#endif /* HAVE_CASE_INSENSITIVE_FILE_SYSTEM */
);
/* GLOB is the user supplied glob pattern as supplied to the 'skip -gfile
GLOB' command. This function builds and returns a regular expression
that matches GLOB.
On DOS based filesystems, where backslash can serve as a directory
separator, the returned regexp will accept either directory separator
character.
On case insensitive filesystems it is expected that the regexp will be
compiled with the REG_ICASE flag. Some character ranges within bracket
expressions will be split in order to facilitate case insensitive
matching, see sanitize_range for more details. */
static std::string
glob_to_regexp (const std::string& glob)
{
std::string result;
/* If the GLOB is absolute then we add a beginning anchor, thus a glob
'/tmp/file.c' will not match '/blah/tmp/file.c'. If the glob is not
absolute then we add a preceding slash, this means a glob 'a/file.c'
will not match '/aa/file.c', but will match '/tmp/a/file.c'. */
if (!IS_ABSOLUTE_PATH (glob.c_str ()))
result += "/";
else
result += "^";
static const std::string dir_separator_regexp
#ifdef HAVE_DOS_BASED_FILE_SYSTEM
("(\\\\|/)");
#else
("/");
#endif /* not HAVE_DOS_BASED_FILE_SYSTEM */
/* Many patterns want to match any character. Any in this case doesn't
include slash, so lets define a regexp to match anything except a
slash. */
static const std::string any_char
#ifdef HAVE_DOS_BASED_FILE_SYSTEM
("[^\\/]");
#else
("[^/]");
#endif /* not HAVE_DOS_BASED_FILE_SYSTEM */
/* True when we can accept '**', false otherwise. The '**' pattern is
modelled on the bash globstar feature, it matches zero or more
directories. */
bool can_globstar = true;
for (const char *ptr = glob.c_str ();
*ptr != '\0';
++ptr)
{
/* In a position where '**' could appear, but this is not the first
'*', so we can no longer accept '**'. This will reset after the
next slash. */
if (can_globstar && *ptr != '*')
can_globstar = false;
if (strchr (".+()|{}$^", *ptr) != nullptr)
{
/* This is a character that has no special meaning within a glob,
but does within a regexp, this needs to be escaped. */
result = result + '\\' + *ptr;
}
else if (*ptr == '*')
{
/* Are we looking at '**' in a location where this is valid. After
the '**' must be either a slash, or the end of the string. */
if (can_globstar && *(ptr + 1) == '*'
&& (IS_DIR_SEPARATOR (*(ptr + 2)) || *(ptr + 2) == '\0'))
{
/* If the '**' is at the end of the string then we allow it
to match anything. This is inline with bash. */
if (*(ptr + 2) == '\0')
{
result += ".*";
ptr += 1;
}
/* The '**' is followed by a slash. We accept zero or more
directories, which are any character sequence followed by
a slash. */
else
{
result += "(" + any_char + "*" + dir_separator_regexp + ")*";
ptr += 2;
}
}
/* A single '*' or the start of '**' is a location where '**' is
not valid. Match any number (zero or more) non-slash
characters. */
else
result += any_char + '*';
}
else if (*ptr == '?')
{
/* Match a single non-slash character. */
result += any_char;
}
else if (IS_DIR_SEPARATOR (*ptr))
{
/* After a slash we can see '**' again. On builds where
backslash is also a possible directory separator, this
converts the backslash to a forward slash. */
result += dir_separator_regexp;
can_globstar = true;
}
else if (*ptr == '\\')
{
/* The code that this regexp logic replaced used to call fnmatch
with FNM_NOESCAPE flag. This flag means that backslash has no
special meaning. We maintain that here by escaping any
backslashes. This will only trigger if the earlier
IS_DIR_SEPARATOR check doesn't match backslashes. */
result += "\\\\";
}
else if (*ptr == '[')
{
std::optional<glob_bracket_expr> g = glob_bracket_expr::parse (ptr);
if (g.has_value ())
{
/* Add a regexp version of this bracket expression. */
result += g->to_string ();
/* Skip over the bracket expression in the glob. */
ptr = g->end ();
}
else
{
/* Not a bracket expression. Just treat this as a literal
character. */
result += "\\[";
}
}
else
{
/* All other characters are passed through. */
result += *ptr;
}
}
/* Anchor the regexp to the end of the filename. */
result += '$';
return result;
}
skiplist_entry::skiplist_entry (bool file_is_glob,
std::string &&file,
bool function_is_regexp,
std::string &&function,
private_key)
: m_file_is_glob (file_is_glob),
m_file (std::move (file)),
m_function_is_regexp (function_is_regexp),
m_function (std::move (function))
{
gdb_assert (!m_file.empty () || !m_function.empty ());
if (m_file_is_glob)
{
gdb_assert (!m_file.empty ());
m_compiled_file_regexp.emplace (glob_to_regexp (m_file).c_str (),
file_glob_regexp_flags,
_("skip glob regexp"));
}
if (m_function_is_regexp)
{
gdb_assert (!m_function.empty ());
m_compiled_function_regexp.emplace (m_function.c_str (),
REG_NOSUB | REG_EXTENDED,
_("regexp"));
}
}
void
skiplist_entry::add_entry (bool file_is_glob, std::string &&file,
bool function_is_regexp, std::string &&function)
{
skiplist_entries.emplace_back (file_is_glob,
std::move (file),
function_is_regexp,
std::move (function),
private_key {});
/* Incremented after push_back, in case push_back throws. */
skiplist_entries.back ().m_number = ++highest_skiplist_entry_num;
}
/* A helper function for print_recreate that prints a correctly-quoted
string to STREAM. */
static void
print_quoted (ui_file *stream, const std::string &str)
{
gdb_putc ('"', stream);
for (char c : str)
{
if (ISSPACE (c) || c == '\\' || c == '\'' || c == '"')
gdb_putc ('\\', stream);
gdb_putc (c, stream);
}
gdb_putc ('"', stream);
}
void
skiplist_entry::print_recreate (ui_file *stream) const
{
if (!m_file_is_glob && !m_file.empty ()
&& !m_function_is_regexp && m_function.empty ())
gdb_printf (stream, "skip file %s\n", m_file.c_str ());
else if (!m_file_is_glob && m_file.empty ()
&& !m_function_is_regexp && !m_function.empty ())
gdb_printf (stream, "skip function %s\n", m_function.c_str ());
else
{
gdb_printf (stream, "skip ");
if (!m_file.empty ())
{
if (m_file_is_glob)
gdb_printf (stream, "-gfile ");
else
gdb_printf (stream, "-file ");
print_quoted (stream, m_file);
}
if (!m_function.empty ())
{
if (m_function_is_regexp)
gdb_printf (stream, "-rfunction ");
else
gdb_printf (stream, "-function ");
print_quoted (stream, m_function);
}
gdb_printf (stream, "\n");
}
}
static void
skip_file_command (const char *arg, int from_tty)
{
struct symtab *symtab;
const char *filename = NULL;
/* If no argument was given, try to default to the last
displayed codepoint. */
if (arg == NULL)
{
symtab = get_last_displayed_symtab ();
if (symtab == NULL)
error (_("No default file now."));
/* It is not a typo, symtab_to_filename_for_display would be needlessly
ambiguous. */
filename = symtab_to_fullname (symtab);
}
else
filename = arg;
skiplist_entry::add_entry (false, std::string (filename),
false, std::string ());
gdb_printf (_("File %ps will be skipped when stepping.\n"),
styled_string (file_name_style.style (), filename));
}
/* Create a skiplist entry for the given function NAME and add it to the
list. */
static void
skip_function (const char *name)
{
skiplist_entry::add_entry (false, std::string (), false, std::string (name));
gdb_printf (_("Function %ps will be skipped when stepping.\n"),
styled_string (function_name_style.style (), name));
}
static void
skip_function_command (const char *arg, int from_tty)
{
/* Default to the current function if no argument is given. */
if (arg == NULL)
{
frame_info_ptr fi = get_selected_frame (_("No default function now."));
struct symbol *sym = get_frame_function (fi);
const char *name = NULL;
if (sym != NULL)
name = sym->print_name ();
else
error (_("No function found containing current program point %s."),
paddress (get_current_arch (), get_frame_pc (fi)));
skip_function (name);
return;
}
skip_function (arg);
}
/* Storage for the "skip" command option values. */
struct skip_opts
{
/* For the -file option. */
std::string file;
/* For the -gfile option. */
std::string gfile;
/* For the -function option. */
std::string function;
/* For the -rfunction option. */
std::string rfunction;
};
/* The options for the "skip" command. */
static const gdb::option::option_def skip_option_defs[] = {
gdb::option::filename_option_def<skip_opts> {
"file",
[] (skip_opts *opts) { return &opts->file; },
nullptr, /* show_cmd_cb */
nullptr, /* set_doc */
},
gdb::option::filename_option_def<skip_opts> {
"gfile",
[] (skip_opts *opts) { return &opts->gfile; },
nullptr, /* show_cmd_cb */
nullptr, /* set_doc */
},
gdb::option::string_option_def<skip_opts> {
"function",
[] (skip_opts *opts) { return &opts->function; },
nullptr, /* show_cmd_cb */
nullptr, /* set_doc */
},
gdb::option::string_option_def<skip_opts> {
"rfunction",
[] (skip_opts *opts) { return &opts->rfunction; },
nullptr, /* show_cmd_cb */
nullptr, /* set_doc */
},
};
/* Create the option_def_group for the "skip" command. */
static inline gdb::option::option_def_group
make_skip_options_def_group (skip_opts *opts)
{
return {{skip_option_defs}, opts};
}
/* Completion for the "skip" command. */
static void
skip_command_completer (struct cmd_list_element *cmd,
completion_tracker &tracker,
const char *text, const char * /* word */)
{
/* We only need to handle option completion here. The sub-commands of
'skip' are handled automatically by the command system. */
const auto group = make_skip_options_def_group (nullptr);
gdb::option::complete_options
(tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group);
}
/* Process "skip ..." that does not match "skip file" or "skip function". */
static void
skip_command (const char *arg, int from_tty)
{
if (arg == nullptr)
{
skip_function_command (arg, from_tty);
return;
}
/* Parse command line options. */
skip_opts opts;
const auto group = make_skip_options_def_group (&opts);
gdb::option::process_options
(&arg, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group);
/* Handle invalid argument combinations. */
if (!opts.file.empty () && !opts.gfile.empty ())
error (_("Cannot specify both -file and -gfile."));
if (!opts.function.empty () && !opts.rfunction.empty ())
error (_("Cannot specify both -function and -rfunction."));
/* If there's anything left on the command line then this is an error if
a valid command line flag was also given, or we assume that it's a
function name if the user entered 'skip blah'. */
if (*arg != '\0')
{
if (!opts.file.empty () || !opts.gfile.empty ()
|| !opts.function.empty () || !opts.rfunction.empty ())
error (_("Junk after arguments: %s"), arg);
else if (*arg == '-')
{
const char *after_arg = skip_to_space (arg);
error (_("Invalid skip option: %.*s"), (int) (after_arg - arg), arg);
}
else
{
/* Assume the user entered "skip FUNCTION-NAME". FUNCTION-NAME
may be `foo (int)', and therefore we pass the complete
original ARG to skip_function_command as if the user typed
"skip function arg". */
skip_function_command (arg, from_tty);
return;
}
}
/* This shouldn't happen as "skip" by itself gets punted to
skip_function_command, and if the user entered "skip blah" then this
will have been handled above. */
gdb_assert (!opts.file.empty () || !opts.gfile.empty ()
|| !opts.function.empty () || !opts.rfunction.empty ());
/* Create the skip list entry. */
std::string entry_file;
if (!opts.file.empty ())
entry_file = opts.file;
else if (!opts.gfile.empty ())
entry_file = opts.gfile;
std::string entry_function;
if (!opts.function.empty ())
entry_function = opts.function;
else if (!opts.rfunction.empty ())
entry_function = opts.rfunction;
skiplist_entry::add_entry (!opts.gfile.empty (),
std::move (entry_file),
!opts.rfunction.empty (),
std::move (entry_function));
/* I18N concerns drive some of the choices here (we can't piece together
the output too much). OTOH we want to keep this simple. Therefore the
only polish we add to the output is to append "(s)" to "File" or
"Function" if they're a glob/regexp. */
{
std::string &file_to_print
= !opts.file.empty () ? opts.file : opts.gfile;
std::string &function_to_print
= !opts.function.empty () ? opts.function : opts.rfunction;
const char *file_text = !opts.gfile.empty () ? _("File(s)") : _("File");
const char *lower_file_text = !opts.gfile.empty () ? _("file(s)") : _("file");
const char *function_text
= !opts.rfunction.empty () ? _("Function(s)") : _("Function");
if (function_to_print.empty ())
{
gdb_printf (_("%s %ps will be skipped when stepping.\n"),
file_text,
styled_string (file_name_style.style (),
file_to_print.c_str ()));
}
else if (file_to_print.empty ())
{
gdb_printf (_("%s %ps will be skipped when stepping.\n"),
function_text,
styled_string (function_name_style.style (),
function_to_print.c_str ()));
}
else
{
gdb_printf (_("%s %ps in %s %ps will be skipped"
" when stepping.\n"),
function_text,
styled_string (function_name_style.style (),
function_to_print.c_str ()),
lower_file_text,
styled_string (file_name_style.style (),
file_to_print.c_str ()));
}
}
}
static void
info_skip_command (const char *arg, int from_tty)
{
int num_printable_entries = 0;
struct value_print_options opts;
get_user_print_options (&opts);
/* Count the number of rows in the table and see if we need space for a
64-bit address anywhere. */
for (const skiplist_entry &e : skiplist_entries)
if (arg == NULL || number_is_in_list (arg, e.number ()))
num_printable_entries++;
if (num_printable_entries == 0)
{
if (arg == NULL)
current_uiout->message (_("Not skipping any files or functions.\n"));
else
current_uiout->message (
_("No skiplist entries found with number %s.\n"), arg);
return;
}
ui_out_emit_table table_emitter (current_uiout, 6, num_printable_entries,
"SkiplistTable");
current_uiout->table_header (5, ui_left, "number", "Num"); /* 1 */
current_uiout->table_header (3, ui_left, "enabled", "Enb"); /* 2 */
current_uiout->table_header (4, ui_right, "regexp", "Glob"); /* 3 */
current_uiout->table_header (20, ui_left, "file", "File"); /* 4 */
current_uiout->table_header (2, ui_right, "regexp", "RE"); /* 5 */
current_uiout->table_header (40, ui_noalign, "function", "Function"); /* 6 */
current_uiout->table_body ();
for (const skiplist_entry &e : skiplist_entries)
{
QUIT;
if (arg != NULL && !number_is_in_list (arg, e.number ()))
continue;
ui_out_emit_tuple tuple_emitter (current_uiout, "blklst-entry");
current_uiout->field_signed ("number", e.number ()); /* 1 */
if (e.enabled ())
current_uiout->field_string ("enabled", "y"); /* 2 */
else
current_uiout->field_string ("enabled", "n"); /* 2 */
if (e.file_is_glob ())
current_uiout->field_string ("regexp", "y"); /* 3 */
else
current_uiout->field_string ("regexp", "n"); /* 3 */
current_uiout->field_string ("file",
e.file ().empty () ? "<none>"
: e.file ().c_str (),
e.file ().empty ()
? metadata_style.style ()
: file_name_style.style ()); /* 4 */
if (e.function_is_regexp ())
current_uiout->field_string ("regexp", "y"); /* 5 */
else
current_uiout->field_string ("regexp", "n"); /* 5 */
current_uiout->field_string ("function",
e.function ().empty () ? "<none>"
: e.function ().c_str (),
e.function ().empty ()
? metadata_style.style ()
: function_name_style.style ()); /* 6 */
current_uiout->text ("\n");
}
}
static void
skip_enable_command (const char *arg, int from_tty)
{
bool found = false;
for (skiplist_entry &e : skiplist_entries)
if (arg == NULL || number_is_in_list (arg, e.number ()))
{
e.enable ();
found = true;
}
if (!found)
error (_("No skiplist entries found with number %s."), arg);
}
static void
skip_disable_command (const char *arg, int from_tty)
{
bool found = false;
for (skiplist_entry &e : skiplist_entries)
if (arg == NULL || number_is_in_list (arg, e.number ()))
{
e.disable ();
found = true;
}
if (!found)
error (_("No skiplist entries found with number %s."), arg);
}
static void
skip_delete_command (const char *arg, int from_tty)
{
bool found = false;
for (auto it = skiplist_entries.begin (),
end = skiplist_entries.end ();
it != end;)
{
const skiplist_entry &e = *it;
if (arg == NULL || number_is_in_list (arg, e.number ()))
{
it = skiplist_entries.erase (it);
found = true;
}
else
++it;
}
if (!found)
error (_("No skiplist entries found with number %s."), arg);
}
bool
skiplist_entry::do_skip_file_p (const symtab_and_line &function_sal) const
{
bool result;
/* Check first sole SYMTAB->FILENAME. It may not be a substring of
symtab_to_fullname as it may contain "./" etc. */
if (compare_filenames_for_search (function_sal.symtab->filename (),
m_file.c_str ()))
result = true;
/* Before we invoke realpath, which can get expensive when many
files are involved, do a quick comparison of the basenames. */
else if (!basenames_may_differ
&& filename_cmp (lbasename (function_sal.symtab->filename ()),
lbasename (m_file.c_str ())) != 0)
result = false;
else
{
/* Note: symtab_to_fullname caches its result, thus we don't have to. */
const char *fullname = symtab_to_fullname (function_sal.symtab);
result = compare_filenames_for_search (fullname, m_file.c_str ());
}
return result;
}
/* The implementation of skiplist_entry::do_skip_gfile_p. This exists as a
separate function so that this function can be unit tested.
PATTERN is the user supplied pattern held within the skiplist_entry, and
RE is the compiled regexp version of PATTERN, created when the
skiplist_entry was created.
The two callbacks GET_FILENAME and GET_FULLNAME return the result of
symtab::filename and symtab_to_fullname respectively. These are
provided as callbacks though so that the self tests don't need to create
fake symtabs.
Returns true if PATTERN matches the filename or fullname, and false
otherwise.
This function tries to avoid calling GET_FULLNAME as this can be more
expensive. The PATTERN will first be matched against the result of
calling GET_FILENAME if possible. */
static bool
do_skip_gfile_p (const std::string &pattern, const compiled_regex &re,
gdb::function_view<const char * ()> get_filename,
gdb::function_view<const char * ()> get_fullname)
{
/* If basenames don't match then the full pattern cannot match. The
gdb_filename_fnmatch already handles case insensitive filesystems, and
as we're only checking the basenames here, directory separators are
not a problem. */
if (!basenames_may_differ
&& gdb_filename_fnmatch (lbasename (pattern.c_str ()),
lbasename (get_filename ()),
FNM_FILE_NAME | FNM_NOESCAPE) != 0)
return false;
/* If the pattern is absolute, e.g. starts with '/', then we're going to
have to compare against the full filename, we can skip the check
against the symtab filename. */
bool is_absolute_pattern = IS_ABSOLUTE_PATH (pattern.c_str ());
/* The symtab's filename might not be the full filename, this will depend
on how the symtab was compiled and/or how the DWARF was generated.
But with gcc at least, compiling a relative filename results in a
symtab with a relative filename. However, in many cases, the skip
pattern is also only a partial filename, and matches against the end
part of the symtab filename, so rather than the (relatively expensive)
fullname lookup, check first against the symtab filename. */
if (!basenames_may_differ && !is_absolute_pattern)
{
if (re.exec (get_filename (), 0, nullptr, 0) == 0)
return true;
}
return re.exec (get_fullname (), 0, nullptr, 0) == 0;
}
bool
skiplist_entry::do_skip_gfile_p (const symtab_and_line &function_sal) const
{
gdb_assert (m_compiled_file_regexp.has_value ());
return ::do_skip_gfile_p (m_file, m_compiled_file_regexp.value (),
[&] () {
return function_sal.symtab->filename ();
},
[&] () {
return symtab_to_fullname (function_sal.symtab);
});
}
bool
skiplist_entry::skip_file_p (const symtab_and_line &function_sal) const
{
if (m_file.empty ())
return false;
if (function_sal.symtab == NULL)
return false;
skip_debug_printf ("checking if file %s matches %sglob %s",
function_sal.symtab->filename (),
(m_file_is_glob ? "" : "non-"), m_file.c_str ());
bool result;
if (m_file_is_glob)
result = do_skip_gfile_p (function_sal);
else
result = do_skip_file_p (function_sal);
skip_debug_printf (result ? "yes" : "no");
return result;
}
bool
skiplist_entry::skip_function_p (const char *function_name) const
{
if (m_function.empty ())
return false;
bool result;
if (m_function_is_regexp)
{
skip_debug_printf ("checking if function %s matches regex %s",
function_name, m_function.c_str ());
gdb_assert (m_compiled_function_regexp);
result
= (m_compiled_function_regexp->exec (function_name, 0, NULL, 0) == 0);
}
else
{
skip_debug_printf ("checking if function %s matches non-regex %s",
function_name, m_function.c_str ());
result = (strcmp_iw (function_name, m_function.c_str ()) == 0);
}
skip_debug_printf (result ? "yes" : "no");
return result;
}
/* See skip.h. */
bool
function_name_is_marked_for_skip (const char *function_name,
const symtab_and_line &function_sal)
{
if (function_name == NULL)
return false;
for (const skiplist_entry &e : skiplist_entries)
{
if (!e.enabled ())
continue;
bool skip_by_file = e.skip_file_p (function_sal);
bool skip_by_function = e.skip_function_p (function_name);
/* If both file and function must match, make sure we don't errantly
exit if only one of them match. */
if (!e.file ().empty () && !e.function ().empty ())
{
if (skip_by_file && skip_by_function)
return true;
}
/* Only one of file/function is specified. */
else if (skip_by_file || skip_by_function)
return true;
}
return false;
}
/* Completer for skip numbers. */
static void
complete_skip_number (cmd_list_element *cmd,
completion_tracker &completer,
const char *text, const char *word)
{
size_t word_len = strlen (word);
for (const skiplist_entry &entry : skiplist_entries)
{
gdb::unique_xmalloc_ptr<char> name = xstrprintf ("%d", entry.number ());
if (strncmp (word, name.get (), word_len) == 0)
completer.add_completion (std::move (name));
}
}
/* Implementation of 'save skip' command. */
static void
save_skip_command (const char *filename, int from_tty)
{
if (filename == nullptr || *filename == '\0')
error (_("Argument required (file name in which to save)"));
gdb::unique_xmalloc_ptr<char> expanded_filename (tilde_expand (filename));
stdio_file fp;
if (!fp.open (expanded_filename.get (), "w"))
error (_("Unable to open file '%ps' for saving (%s)"),
styled_string (file_name_style.style (), expanded_filename.get ()),
safe_strerror (errno));
for (const auto &entry : skiplist_entries)
entry.print_recreate (&fp);
}
#if GDB_SELF_TEST
namespace selftests {
/* Define a single test of the do_skip_gfile_p function. */
struct skip_gfile_test
{
/* The glob pattern to match against the filename. */
const char *pattern;
/* The filename is split into PREFIX and SUFFIX. This reflects how GDB
stores only part of the filename (as found in the DWARF) within the
symtab as the 'filename'. The PREFIX is the part GDB figures out from
the compilation directory. The full filename is created by
concatenating PREFIX to SUFFIX. */
const char *prefix;
const char *suffix;
/* True if we expect PATTERN to match against the filename created from
PREFIX and SUFFIX. */
bool expect_match;
};
/* Some tests check that case sensitivity works, these tests expect a
glob to not match a particular filename. On case insensitive file
systems these globs will match. We define this constant to use for
those tests. */
#ifdef HAVE_CASE_INSENSITIVE_FILE_SYSTEM
static constexpr bool false_if_case_sensitive = true;
#else
static constexpr bool false_if_case_sensitive = false;
#endif /* ! HAVE_CASE_INSENSITIVE_FILE_SYSTEM */
/* List of all do_skip_gfile_p tests. */
static constexpr std::initializer_list<skip_gfile_test> skip_gfile_tests = {
/* Basic glob feature testing. */
{ "*", "/tmp/", "foo/hello.c", true },
{ "xx", "/tmp/", "foo/hello.c", false },
{ "hello.?", "/tmp/", "foo/hello.c", true },
{ "hello.?", "/tmp/", "foo/hello.cc", false },
{ "aa/bb*/file*.*c*", "/tmp/", "aa/bb/file.c", true },
{ "*.c", "/tmp/", "aa/bb/file.c", true },
{ "bb/*.c", "/tmp/", "aa/bb/file.c", true },
{ "ee/*.c", "/tmp/", "ee/bb/file.c", false },
{ "b/*.c", "/tmp/", "aa/bb/file.c", false },
/* Testing the globstar '**' feature. */
{ "ee/**/*.c", "/tmp/", "ee/bb/file.c", true },
{ "dd/**/**/*.c", "/tmp/", "dd/file.c", true },
{ "dd/**/**/*.c", "/tmp/", "dd/aa/file.c", true },
{ "dd/**/**/*.c", "/tmp/", "dd/aa/bb/file.c", true },
{ "dd/**/**/*.c", "/tmp/", "dd/aa/bb/cc/file.c", true },
{ "dd/**/**/*.c", "/tmp/", "dd/aa/bb/cc/dd/file.c", true },
{ "**/**/**/*.c", "/tmp/", "file.c", true },
{ "**/**/**/*.c", "/tmp/aa/", "file.c", true },
{ "**/**/**/*.c", "/tmp/aa/bb/", "file.c", true },
{ "**/**/**/*.c", "/tmp/aa/bb/cc/", "file.c", true },
{ "**/**/**/*.c", "/tmp/aa/bb/cc/dd/", "file.c", true },
{ "/tmp/**", "/tmp/", "file.c", true },
{ "/tmp/**", "/tmp/aa/", "file.c", true },
/* When '**' appears in the middle of a path component (not after
'/' or at the start), it is not globstar but two regular '*'
wildcards. */
{ "a**b/*.c", "/tmp/", "ab/foo.c", true },
{ "a**b/*.c", "/tmp/", "axxb/foo.c", true },
{ "a**b/*.c", "/tmp/", "a/b/foo.c", false },
{ "[a-c]*.h", "/tmp/", "axxx.h", true },
{ "*[[:digit:]]*.h", "/tmp/", "xx1xx.h", true },
{ "[!a-c]*.h", "/tmp/", "axxx.h", false },
{ "*[]].h", "/tmp/", "xx].h", true },
{ "*[!]].h", "/tmp/", "xx].h", false },
/* An absolute pattern must match the full filename. */
{ "/tmp/foo.cc", "/blah/tmp/", "foo.cc", false },
{ "/blah/tmp/foo.cc", "/blah/tmp/", "foo.cc", true },
/* Characters that are special in regular expressions but should be
treated as literal characters in glob patterns. */
/* The '+' character means 'one or more' in a regular expression. */
{ "file+.c", "/tmp/", "file+.c", true },
{ "dir+/*.c", "/tmp/", "dir+/foo.c", true },
{ "dir+/*.c", "/tmp/", "dirr/foo.c", false },
{ "dir+/*.c", "/tmp/", "dirrr/foo.c", false },
/* The '(' and ')' characters form capture groups in regexp. */
{ "(foo)/*.c", "/tmp/", "(foo)/bar.c", true },
{ "(foo)/*.c", "/tmp/", "foo/bar.c", false },
/* The '|' character means alternation in a regexp. */
{ "a|b/*.c", "/tmp/", "a|b/foo.c", true },
{ "a|b/*.c", "/tmp/", "b/foo.c", false },
/* The '{' and '}' characters form interval expressions in regexp.
(e.g., a{2} matches "aa"). */
{ "a{2}/*.c", "/tmp/", "a{2}/foo.c", true },
{ "a{2}/*.c", "/tmp/", "aa/foo.c", false },
/* The '$' and '^' characters are anchors in regexp. */
{ "file$.c", "/tmp/", "file$.c", true },
{ "^file.c", "/tmp/", "^file.c", true },
/* Bracket expressions that mention a '/' are not valid within a glob and
POSIX requires that they be treated as literal content. */
{ "aa[.-/]bb/*.c", "/tmp/", "aa.bb/foo.c", false },
{ "aa[.-/]bb/*.c", "/tmp/", "aa[.-/]bb/foo.c", true },
{ "aa[/-/]bb/*.c", "/tmp/", "aa/bb/foo.c", false },
{ "aa[/-/]bb/*.c", "/tmp/", "aa[/-/]bb/foo.c", true },
{ "aa[/-1]bb/*.c", "/tmp/", "aa[/-1]bb/foo.c", true },
{ "aa[/-1]bb/*.c", "/tmp/", "aa0bb/foo.c", false },
/* Within bracket expressions, ranges that span '/' should not match the
'/' character. */
{ "aa[.-0]bb/*.c", "/tmp/", "aa/bb/foo.c", false },
{ "aa[.-0]bb/*.c", "/tmp/", "aa.bb/foo.c", true },
{ "aa[.-0]bb/*.c", "/tmp/", "aa0bb/foo.c", true },
{ "aa[.-1]bb/*.c", "/tmp/", "aa.bb/foo.c", true },
{ "aa[.-1]bb/*.c", "/tmp/", "aa0bb/foo.c", true },
{ "aa[.-1]bb/*.c", "/tmp/", "aa1bb/foo.c", true },
{ "aa[.-1]bb/*.c", "/tmp/", "aa/bb/foo.c", false },
{ "aa[.-1]bb/*.c", "/tmp/", "aa2bb/foo.c", false },
{ "aa[,-0]bb/*.c", "/tmp/", "aa,bb/foo.c", true },
{ "aa[,-0]bb/*.c", "/tmp/", "aa-bb/foo.c", true },
{ "aa[,-0]bb/*.c", "/tmp/", "aa.bb/foo.c", true },
{ "aa[,-0]bb/*.c", "/tmp/", "aa0bb/foo.c", true },
{ "aa[,-0]bb/*.c", "/tmp/", "aa/bb/foo.c", false },
{ "aa[,-0]bb/*.c", "/tmp/", "aa1bb/foo.c", false },
/* Negated bracket expressions should also not match '/'. POSIX says
that, within a glob, a bracket expression starting with '^' is
undefined, but fnmatch (and bash) treat '^' the same as '!'. */
{ "a[!a]b/*.c", "/tmp/", "a/b/foo.c", false },
{ "a[!a]b/*.c", "/tmp/", "axb/foo.c", true },
{ "a[^a]b/*.c", "/tmp/", "a/b/foo.c", false },
{ "a[^a]b/*.c", "/tmp/", "axb/foo.c", true },
/* Historically GDB used fnmatch to perform glob matching, and passed
FNM_NOESCAPE as an option to fnmatch, which means that '\' is not an
escape character, but should be treated as a literal character. When
we switch to using regexp instead of fnmatch we preserved this
behaviour.
Given the above '\*' in a glob doesn't escape the '*', the '\' is
literal and '*' is still a wildcard. */
{ "a\\b.c", "/tmp/", "a\\b.c", true },
{ "a\\*.c", "/tmp/", "a\\foo.c", true },
{ "a\\*.c", "/tmp/", "a*.c", false },
/* As with the previous test, historically FNM_PERIOD was not used, so
'*' and '?' should match a leading period in a filename. */
{ "*.c", "/tmp/", ".hidden.c", true },
{ "?idden.c", "/tmp/", ".idden.c", true },
/* An unmatched '[' should be treated as a literal character. */
{ "[.c", "/tmp/", "[.c", true },
{ "[.c", "/tmp/", "x.c", false },
/* A '-' at the start or end of a bracket expression is literal. */
{ "[-ab]*.c", "/tmp/", "-foo.c", true },
{ "[ab-]*.c", "/tmp/", "-foo.c", true },
/* A '-' immediately after a range is literal, not a second range
operator. So [a-c-f] is the range 'a'-'c', a literal '-', and
a literal 'f'. Characters between 'c' and 'f' (like 'e') that
are outside the range should not match. */
{ "[a-c-f]dir/*.c", "/tmp/", "bdir/foo.c", true },
{ "[a-c-f]dir/*.c", "/tmp/", "-dir/foo.c", true },
{ "[a-c-f]dir/*.c", "/tmp/", "fdir/foo.c", true },
{ "[a-c-f]dir/*.c", "/tmp/", "edir/foo.c", false },
/* In a POSIX glob bracket expression, a leading '^' is undefined.
However fnmatch (and bash) treat this the same as '!', that is, as
match negation. GDB copies this behaviour. */
{ "[^abc]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "[^abc]dir/*.c", "/tmp/", "^dir/foo.c", true },
{ "[^abc]dir/*.c", "/tmp/", "xdir/foo.c", true },
/* Due to the above '^^' within a bracket means match everything except
'^' (and '/' of course). */
{ "[^^]dir/*.c", "/tmp/", "^dir/foo.c", false },
{ "[^^]dir/*.c", "/tmp/", "xdir/foo.c", true },
{ "aa[^^]bb/*.c", "/tmp/", "aa/bb/foo.c", false },
{ "aa[^^]bb/*.c", "/tmp/", "aa^bb/foo.c", false },
{ "aa[^^]bb/*.c", "/tmp/", "aa-bb/foo.c", true },
/* The glob '[!^]' is the same as the previous, but uses the official
POSIX glob '!' character for negation. */
{ "[!^]dir/*.c", "/tmp/", "^dir/foo.c", false },
{ "[!^]dir/*.c", "/tmp/", "adir/foo.c", true },
/* The globs '[]' and '[!]' are invalid as the ']' is considered a
character within the bracket expression, this means that the bracket
expression is never terminated. This is handled by treating the
characters as literals. */
{ "[!]dir/*.c", "/tmp/", "[!]dir/foo.c", true },
{ "[!]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "[^]dir/*.c", "/tmp/", "[^]dir/foo.c", true },
{ "[^]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "aa[]bb/*.c", "/tmp/", "aabb/foo.c", false },
{ "aa[]bb/*.c", "/tmp/", "aa[]bb/foo.c", true },
/* When '^' is NOT the first character in a bracket expression, it is
just a character to match or not match. */
{ "[abc^]dir/*.c", "/tmp/", "^dir/foo.c", true },
{ "[abc^]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[abc^]dir/*.c", "/tmp/", "xdir/foo.c", false },
{ "[!abc^]dir/*.c", "/tmp/", "^dir/foo.c", false },
{ "[!abc^]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "[!abc^]dir/*.c", "/tmp/", "xdir/foo.c", true },
/* Negated bracket expression with ']' as the first element. Test with
both '!' and '^' for the reasons discussed above. */
{ "[!]a]dir/*.c", "/tmp/", "bdir/foo.c", true },
{ "[!]a]dir/*.c", "/tmp/", "]dir/foo.c", false },
{ "[!]a]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "[^]a]dir/*.c", "/tmp/", "bdir/foo.c", true },
{ "[^]a]dir/*.c", "/tmp/", "]dir/foo.c", false },
{ "[^]a]dir/*.c", "/tmp/", "adir/foo.c", false },
/* Within a bracket expression, a range that starts with the negation
character is not treated like a range, so [!-a] means match everything
except '-' and 'a'. Test with both '!' and '^' for the reasons
discussed above. */
{ "[!-a]dir/*.c", "/tmp/", "_dir/foo.c", true },
{ "[!-a]dir/*.c", "/tmp/", "^dir/foo.c", true },
{ "[!-a]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "[!-a]dir/*.c", "/tmp/", "bdir/foo.c", true },
{ "[^-a]dir/*.c", "/tmp/", "_dir/foo.c", true },
{ "[^-a]dir/*.c", "/tmp/", "^dir/foo.c", true },
{ "[^-a]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "[^-a]dir/*.c", "/tmp/", "bdir/foo.c", true },
{ "[!-]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[!-]dir/*.c", "/tmp/", "-dir/foo.c", false },
{ "[^-]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[^-]dir/*.c", "/tmp/", "-dir/foo.c", false },
/* Within a bracket expression, test ranges starting and ending with
'-'. */
{ "aa[--0]bb/*.c", "/tmp/", "aa-bb/foo.c", true },
{ "aa[--0]bb/*.c", "/tmp/", "aa.bb/foo.c", true },
{ "aa[--0]bb/*.c", "/tmp/", "aa0bb/foo.c", true },
{ "aa[--0]bb/*.c", "/tmp/", "aa/bb/foo.c", false },
{ "aa[+--]bb/*.c", "/tmp/", "aa-bb/foo.c", true },
{ "aa[+--]bb/*.c", "/tmp/", "aa+bb/foo.c", true },
{ "aa[+--]bb/*.c", "/tmp/", "aa,bb/foo.c", true },
{ "aa[+--]bb/*.c", "/tmp/", "aa.bb/foo.c", false },
/* Basic character class matching. [[:digit:]] matches any decimal
digit character. */
{ "[[:digit:]]dir/*.c", "/tmp/", "1dir/foo.c", true },
{ "[[:digit:]]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "adir/[[:digit:]]*.c", "/tmp/", "adir/1foo.c", true },
{ "[[:digit:]]*.c", "/tmp/", "adir/1foo.c", true },
/* [[:alpha:]] matches any alphabetic character. */
{ "[[:alpha:]]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[[:alpha:]]dir/*.c", "/tmp/", "1dir/foo.c", false },
/* [[:upper:]] and [[:lower:]] match upper- and lowercase letters
respectively. */
{ "[[:upper:]]dir/*.c", "/tmp/", "Adir/foo.c", true },
{ "[[:upper:]]dir/*.c", "/tmp/", "adir/foo.c", false_if_case_sensitive },
{ "[[:lower:]]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[[:lower:]]dir/*.c", "/tmp/", "Adir/foo.c", false_if_case_sensitive },
/* Negated character class: [![:digit:]] matches non-digit characters,
but should not match '/'. */
{ "a[![:digit:]]b/*.c", "/tmp/", "axb/foo.c", true },
{ "a[![:digit:]]b/*.c", "/tmp/", "a1b/foo.c", false },
{ "a[![:digit:]]b/*.c", "/tmp/", "a/b/foo.c", false },
/* Character class combined with literal characters. [[:digit:]ab]
should match any digit, or 'a', or 'b'. */
{ "[[:digit:]ab]dir/*.c", "/tmp/", "1dir/foo.c", true },
{ "[[:digit:]ab]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[[:digit:]ab]dir/*.c", "/tmp/", "bdir/foo.c", true },
{ "[[:digit:]ab]dir/*.c", "/tmp/", "xdir/foo.c", false },
/* Character class combined with a character range. [[:digit:]a-f]
should match any digit or any letter from 'a' to 'f'. */
{ "[[:digit:]a-f]dir/*.c", "/tmp/", "1dir/foo.c", true },
{ "[[:digit:]a-f]dir/*.c", "/tmp/", "cdir/foo.c", true },
{ "[[:digit:]a-f]dir/*.c", "/tmp/", "gdir/foo.c", false },
/* Multiple character classes in one bracket expression.
[[:digit:][:alpha:]] should match digits and letters. */
{ "[[:digit:][:alpha:]]dir/*.c", "/tmp/", "1dir/foo.c", true },
{ "[[:digit:][:alpha:]]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[[:digit:][:alpha:]]dir/*.c", "/tmp/", "-dir/foo.c", false },
/* Character class appearing in the basename portion of the
pattern. */
{ "dir/file[[:digit:]].c", "/tmp/", "dir/file1.c", true },
{ "dir/file[[:digit:]].c", "/tmp/", "dir/filea.c", false },
{ "[W-_]dir/*.c", "/tmp/", "_dir/foo.c", true },
{ "[A-C]dir/*.c", "/tmp/", "Adir/foo.c", true },
{ "[]-_]dir/*.c", "/tmp/", "^dir/foo.c", true },
#ifdef HAVE_CASE_INSENSITIVE_FILE_SYSTEM
/* Basic case insensitive matching. */
{ "Dir/*.c", "/tmp/", "dir/foo.c", true },
{ "DIR/*.c", "/tmp/", "dir/foo.c", true },
{ "Dir/*.c", "/tmp/", "other/foo.c", false },
{ "dir/*.c", "/tmp/", "dir/FOO.c", true },
{ "dir/*.c", "/tmp/", "DIR/FOO.c", true },
/* Upper case character range [A-C] is converted to [a-c] on a case
insensitive file system. */
{ "[A-C]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[A-C]dir/*.c", "/tmp/", "cdir/foo.c", true },
{ "[A-C]dir/*.c", "/tmp/", "Bdir/foo.c", true },
{ "[A-C]dir/*.c", "/tmp/", "ddir/foo.c", false },
/* A range that overlaps into the upper case characters [=-D] will be
split into two: [=-@] and [A-D]. With REG_ICASE the [A-D] will match
both [A-D] and [a-d]. */
{ "[=-D]dir/*.c", "/tmp/", "@dir/foo.c", true },
{ "[=-D]dir/*.c", "/tmp/", "=dir/foo.c", true },
{ "[=-D]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[=-D]dir/*.c", "/tmp/", "ddir/foo.c", true },
{ "[=-D]dir/*.c", "/tmp/", "Adir/foo.c", true },
{ "[=-D]dir/*.c", "/tmp/", "Ddir/foo.c", true },
/* A similar thing happens with ranges that overlap the end of the
upper case character range. */
{ "[W-_]dir/*.c", "/tmp/", "[dir/foo.c", true },
{ "[W-_]dir/*.c", "/tmp/", "_dir/foo.c", true },
{ "[W-_]dir/*.c", "/tmp/", "wdir/foo.c", true },
{ "[W-_]dir/*.c", "/tmp/", "zdir/foo.c", true },
{ "[W-_]dir/*.c", "/tmp/", "Wdir/foo.c", true },
{ "[W-_]dir/*.c", "/tmp/", "Zdir/foo.c", true },
/* A full upper case range [A-Z] is converted to [a-z]. */
{ "[A-Z]dir/*.c", "/tmp/", "mdir/foo.c", true },
{ "[A-Z]dir/*.c", "/tmp/", "1dir/foo.c", false },
/* Negated bracket expression with an upper case range. */
{ "[!A-C]dir/*.c", "/tmp/", "ddir/foo.c", true },
{ "[!A-C]dir/*.c", "/tmp/", "adir/foo.c", false },
{ "[!A-C]dir/*.c", "/tmp/", "Bdir/foo.c", false },
/* Upper case literal characters in bracket expressions are
converted to lower case. */
{ "[ABCD]dir/*.c", "/tmp/", "adir/foo.c", true },
{ "[ABCD]dir/*.c", "/tmp/", "Cdir/foo.c", true },
{ "[ABCD]dir/*.c", "/tmp/", "edir/foo.c", false },
{ "[[:upper:]]dir/*.c", "/tmp/", "adir/foo.c", true },
#endif /* HAVE_CASE_INSENSITIVE_FILE_SYSTEM */
#ifdef HAVE_DOS_BASED_FILE_SYSTEM
/* Tests for DOS-based file systems. On a DOS-based file system
the backslash is treated as a directory separator in addition to
the forward slash. */
/* Backslash in the pattern is treated as a directory separator. */
{ "dir\\*.c", "/tmp/", "dir\\foo.c", true },
{ "dir\\*.c", "/tmp/", "dir\\bar.c", true },
{ "dir\\*.c", "/tmp/", "other\\foo.c", false },
/* The regexp should match both backslash and forward slashes. */
{ "dir/*.c", "/tmp/", "dir\\foo.c", true },
/* Globstar with backslash as the directory separator. */
{ "dir\\**\\*.c", "/tmp/", "dir\\sub\\foo.c", true },
{ "dir\\**\\*.c", "/tmp/", "dir\\a\\b\\foo.c", true },
/* An absolute DOS path with a drive letter. */
{ "C:\\dir\\*.c", "", "C:\\dir\\foo.c", true },
{ "C:\\dir\\*.c", "", "C:\\other\\foo.c", false },
#endif /* HAVE_DOS_BASED_FILE_SYSTEM */
#if defined HAVE_DOS_BASED_FILE_SYSTEM \
&& defined HAVE_CASE_INSENSITIVE_FILE_SYSTEM
/* Combine case insensitivity together with backslash directory
separators. The pattern has upper case and forward slash, the
filename has mixed case and backslash. */
{ "Dir/*.c", "/tmp/", "dir\\foo.c", true },
{ "dir/*.c", "/tmp/", "Dir\\foo.c", true },
{ "Dir\\sub\\*.c", "/tmp/", "Dir\\sub\\bar.c", true },
#endif /* HAVE_DOS_BASED_FILE_SYSTEM && HAVE_CASE_INSENSITIVE_FILE_SYSTEM */
};
/* The skip_gfile_matching unit tests. */
static void
test_skip_gfile_matching ()
{
int failure_count = 0;
bool first = true;
for (const auto &test : skip_gfile_tests)
{
std::string pattern (test.pattern);
std::string filename (test.suffix);
std::string fullname = std::string (test.prefix) + filename;
if (run_verbose ())
{
if (!first)
debug_printf ("\n");
else
first = false;
debug_printf ("Pattern (%s)\n", pattern.c_str ());
debug_printf ("Filename (%s)\n", filename.c_str ());
debug_printf ("Fullname (%s)\n", fullname.c_str ());
}
std::string file_re = glob_to_regexp (pattern);
if (run_verbose ())
debug_printf ("Regexp (%s)\n", file_re.c_str ());
compiled_regex re (file_re.c_str (), file_glob_regexp_flags,
_("skip glob testing"));
bool matched = do_skip_gfile_p (pattern, re,
[&] () { return filename.c_str (); },
[&] () { return fullname.c_str (); });
bool success = matched == test.expect_match;
if (run_verbose ())
debug_printf ("Matched: %s%s\n", (matched ? "Yes" : "No"),
(success ? ""
: string_printf ("\t[Expected: %s]",
(test.expect_match
? "Yes" : "No")).c_str ()));
if (matched != test.expect_match)
failure_count++;
}
if (run_verbose ())
debug_printf ("Failed: %d\n", failure_count);
SELF_CHECK (failure_count == 0);
}
} /* namespace selftests */
#endif /* GDB_SELF_TEST */
INIT_GDB_FILE (step_skip)
{
static struct cmd_list_element *skiplist = NULL;
struct cmd_list_element *c;
c = add_prefix_cmd ("skip", class_breakpoint, skip_command, _("\
Ignore a function while stepping.\n\
\n\
Usage: skip [FUNCTION-NAME]\n\
skip [FILE-SPEC] [FUNCTION-SPEC]\n\
If no arguments are given, ignore the current function.\n\
\n\
FILE-SPEC is one of:\n\
-file FILE-NAME\n\
-gfile GLOB-FILE-PATTERN\n\
FUNCTION-SPEC is one of:\n\
-function FUNCTION-NAME\n\
-rfunction FUNCTION-NAME-REGULAR-EXPRESSION"),
&skiplist, 1, &cmdlist);
set_cmd_completer_handle_brkchars (c, skip_command_completer);
c = add_cmd ("file", class_breakpoint, skip_file_command, _("\
Ignore a file while stepping.\n\
Usage: skip file [FILE-NAME]\n\
If no filename is given, ignore the current file."),
&skiplist);
set_cmd_completer (c, deprecated_filename_completer);
c = add_cmd ("function", class_breakpoint, skip_function_command, _("\
Ignore a function while stepping.\n\
Usage: skip function [FUNCTION-NAME]\n\
If no function name is given, skip the current function."),
&skiplist);
set_cmd_completer (c, location_completer);
c = add_cmd ("enable", class_breakpoint, skip_enable_command, _("\
Enable skip entries.\n\
Usage: skip enable [NUMBER | RANGE]...\n\
You can specify numbers (e.g. \"skip enable 1 3\"),\n\
ranges (e.g. \"skip enable 4-8\"), or both (e.g. \"skip enable 1 3 4-8\").\n\n\
If you don't specify any numbers or ranges, we'll enable all skip entries."),
&skiplist);
set_cmd_completer (c, complete_skip_number);
add_alias_cmd ("skip", c, class_breakpoint, 0, &enablelist);
c = add_cmd ("disable", class_breakpoint, skip_disable_command, _("\
Disable skip entries.\n\
Usage: skip disable [NUMBER | RANGE]...\n\
You can specify numbers (e.g. \"skip disable 1 3\"),\n\
ranges (e.g. \"skip disable 4-8\"), or both (e.g. \"skip disable 1 3 4-8\").\n\n\
If you don't specify any numbers or ranges, we'll disable all skip entries."),
&skiplist);
set_cmd_completer (c, complete_skip_number);
add_alias_cmd ("skip", c, class_breakpoint, 0, &disablelist);
c = add_cmd ("delete", class_breakpoint, skip_delete_command, _("\
Delete skip entries.\n\
Usage: skip delete [NUMBER | RANGES]...\n\
You can specify numbers (e.g. \"skip delete 1 3\"),\n\
ranges (e.g. \"skip delete 4-8\"), or both (e.g. \"skip delete 1 3 4-8\").\n\n\
If you don't specify any numbers or ranges, we'll delete all skip entries."),
&skiplist);
set_cmd_completer (c, complete_skip_number);
add_alias_cmd ("skip", c, class_breakpoint, 0, &deletelist);
add_info ("skip", info_skip_command, _("\
Display the status of skips.\n\
Usage: info skip [NUMBER | RANGES]...\n\
You can specify numbers (e.g. \"info skip 1 3\"),\n\
ranges (e.g. \"info skip 4-8\"), or both (e.g. \"info skip 1 3 4-8\").\n\n\
If you don't specify any numbers or ranges, we'll show all skips."));
set_cmd_completer (c, complete_skip_number);
add_setshow_boolean_cmd ("skip", class_maintenance,
&debug_skip, _("\
Set whether to print the debug output about skipping files and functions."),
_("\
Show whether the debug output about skipping files and functions is printed."),
_("\
When non-zero, debug output about skipping files and functions is displayed."),
NULL, NULL,
&setdebuglist, &showdebuglist);
c = add_cmd ("skip", no_class, save_skip_command, _("\
Save current skips as a script.\n\
Usage: save skip FILE\n\
Use the 'source' command in another debug session to restore them."),
&save_cmdlist);
set_cmd_completer (c, deprecated_filename_completer);
#if GDB_SELF_TEST
selftests::register_test ("skip_gfile_matching",
selftests::test_skip_gfile_matching);
#endif
}