contrib: add dg-lint and libgdiagnostics.py [PR116163]

Changed in v2:
- eliminated COMMON_MISSPELLINGS in favor of retesting with a regexp
  that adds underscores
- add a list of KNOWN_DIRECTIVES, and complain if we see a directive
  that isn't in the list
- various refactorings to reduce the nesting within the script
- skip more kinds of file ('README', 'Makefile.am', 'Makefile.in',
  'gen_directive_tests')
- keep track of the number of files scanned and report it and the end
  with a note

This patch adds a new dg-lint subdirectory below contrib, containing
a "dg-lint" script for detecting common mistakes made in our DejaGnu
tests.

Specifically, DejaGnu's dg.exp's dg-get-options has a regexp for
detecting dg- directives
  https://git.savannah.gnu.org/gitweb/?p=dejagnu.git;a=blob;f=lib/dg.exp
here's the current:

    set tmp [grep $prog "{\[ \t\]\+dg-\[-a-z\]\+\[ \t\]\+.*\[ \t\]\+}" line]

which if I'm reading it right requires a "{", then one or more tab/space
chars, then a "dg-" directive name, then one of more tab/space
characters, then anything (for arguments to the directive), then one of
more tab/space character, then a "}".

There are numerous places in our testsuite which look like attempts to
use a directive, but which don't match this regexp.

The script warns about such places, along with a list of misspelled
directives (currently just "dg_options" for "dg-options"), and a warning
if a dg-do appears after a dg-require-* (as per
https://gcc.gnu.org/onlinedocs/gccint/Directives.html
"This directive must appear after any dg-do directive in the test
and before any dg-additional-sources directive." for
dg-require-effective-target.

dg-lint uses libgdiagnostics to report its results; the patch adds a
new libgdiagnostics.py script below contrib/dg-lint.  This uses Python's
ctypes module to expose libgdianostics.so to Python via FFI.  Hence
the warnings have colorization, quote the pertinent parts of the tested
file, can have fix-it hints, etc.  Here's the output from the tests, run
from the top-level directory:

$ LD_LIBRARY_PATH=../build/gcc/ ./contrib/dg-lint/dg-lint contrib/dg-lint/test-*.c
contrib/dg-lint/test-1.c:6:6: warning: misspelled directive: 'dg_final'; did you mean 'dg-final'?
    6 | /* { dg_final { scan_assembler_times "vmsumudm" 2 } } */
      |      ^~~~~~~~
      |      dg-final
contrib/dg-lint/test-1.c:15:4: warning: directive 'dg-output-file' appears not to match dg.exp's regexp
   15 |    dg-output-file "m4.out"
      |    ^~~~~~~~~~~~~~
contrib/dg-lint/test-1.c:18:4: warning: directive 'dg-output-file' appears not to match dg.exp's regexp
   18 |    dg-output-file "m4.out" }
      |    ^~~~~~~~~~~~~~
contrib/dg-lint/test-1.c:21:6: warning: directive 'dg-output-file' appears not to match dg.exp's regexp
   21 |    { dg-output-file "m4.out"
      |      ^~~~~~~~~~~~~~
contrib/dg-lint/test-1.c:24:5: warning: directive 'dg-output-file' appears not to match dg.exp's regexp
   24 |    {dg-output-file "m4.out"}
      |     ^~~~~~~~~~~~~~
contrib/dg-lint/test-1.c:27:6: warning: directive 'dg-output-file' appears not to match dg.exp's regexp
   27 |    { dg-output-file, "m4.out" }
      |      ^~~~~~~~~~~~~~
contrib/dg-lint/test-2.c:4:6: warning: 'dg-do' after 'dg-require-effective-target'
    4 | /* { dg-do compile } */
      |      ^~~~~
contrib/dg-lint/test-2.c:3:6: note: 'dg-require-effective-target' was here
    3 | /* { dg-require-effective-target c++11 } */
      |      ^~~~~~~~~~~~~~~~~~~~~~~~~~~

I don't yet have a way to verify these tests (clearly we can't use
DejaGnu for this).

These Python bindings could be used by other projects, but so far I only
implemented what I needed for dg-lint.

Running the test on the GCC source tree finds dozens of issues, which
followup patches address.

Tested with Python 3.8

contrib/ChangeLog:
	PR testsuite/116163
	* dg-lint/dg-lint: New file.
	* dg-lint/libgdiagnostics.py: New file.
	* dg-lint/test-1.c: New file.
	* dg-lint/test-2.c: New file.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
David Malcolm 2025-03-27 19:46:20 -04:00
parent 72ecfe355a
commit 8d6de758cc
4 changed files with 703 additions and 0 deletions

404
contrib/dg-lint/dg-lint Executable file
View file

@ -0,0 +1,404 @@
#!/usr/bin/env python3
# Script to detect common mistakes in DejaGnu tests.
# Contributed by David Malcolm <dmalcolm@redhat.com>
#
# Copyright (C) 2024-2025 Free Software Foundation, Inc.
#
# This file is part of GCC.
#
# GCC 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, or (at your option)
# any later version.
#
# GCC 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 GCC; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
import argparse
import difflib
import os
import pathlib
import re
import sys
import libgdiagnostics
KNOWN_DIRECTIVES = {
# Directives, organized by HTML documentation file:
# https://gcc.gnu.org/onlinedocs/gccint/Directives.html
'dg-do',
'dg-options',
'dg-add-options',
'dg-remove-options',
'dg-additional-options',
'dg-timeout',
'dg-timeout-factor',
'dg-do-if',
'dg-skip-if',
'dg-require-effective-target',
'dg-require-support',
'dg-xfail-if',
'dg-xfail-run-if',
'dg-ice',
'dg-shouldfail',
'dg-error',
'dg-warning',
'dg-message',
'dg-note',
'dg-bogus',
'dg-line',
'dg-excess-errors',
'dg-prune-output',
'dg-output',
'dg-output-file',
'dg-set-compiler-env-var',
'dg-set-target-env-var',
'dg-additional-files',
'dg-final',
# https://gcc.gnu.org/onlinedocs/gccint/Require-Support.html
'dg-require-iconv',
'dg-require-profiling',
'dg-require-stack-check',
'dg-require-stack-size',
'dg-require-visibility',
'dg-require-alias',
'dg-require-ascii-locale',
'dg-require-compat-dfp',
'dg-require-cxa-atexit',
'dg-require-dll',
'dg-require-dot',
'dg-require-fork',
'dg-require-gc-sections',
'dg-require-host-local',
'dg-require-linker-plugin',
'dg-require-mkfifo',
'dg-require-named-sections',
'dg-require-weak',
'dg-require-weak-override',
# https://gcc.gnu.org/onlinedocs/gccint/LTO-Testing.html
'dg-lto-do',
'dg-lto-options',
'dg-extra-ld-options',
'dg-suppress-ld-options',
# https://gcc.gnu.org/onlinedocs/gccint/profopt-Testing.html
'dg-final-generate',
'dg-final-use',
# https://gcc.gnu.org/onlinedocs/gccint/Final-Actions.html
'dg-keep-saved-temps',
# Other directives I couldn't find docs for,
# organized by implementation file:
# gcc/testsuite/lib/target-supports-dg.exp
'dg-require-ifunc',
'dg-require-python-h',
# gcc/testsuite/lib/multiline.exp:
'dg-begin-multiline-output',
'dg-end-multiline-output',
'dg-enable-nn-line-numbers',
# gcc/testsuite/lib/gcc-dg.exp:
'dg-allow-blank-lines-in-output',
'dg-locus',
'dg-optimized',
'dg-missed',
# In gcc/testsuite/lib/gcc-defs.exp:
'dg-additional-sources',
'dg-regexp',
'dg-compile-aux-modules',
# implemented in various places:
# gcc/testsuite/gcc.target/powerpc/ppc-fortran/ppc-fortran.exp
# gcc/testsuite/gfortran.dg/c-interop/c-interop.exp
# gcc/testsuite/gfortran.dg/coarray/caf.exp
# gcc/testsuite/gfortran.dg/dg.exp
# gcc/testsuite/gfortran.dg/f202y/f202y.exp
# gcc/testsuite/gfortran.dg/goacc/goacc.exp
# gcc/testsuite/lib/lto.exp
'dg-lto-warning',
'dg-lto-message',
'dg-lto-note',
# gcc/testsuite/lib/profopt.exp
'dg-final-use-autofdo',
'dg-final-use-not-autofdo',
# gcc/testsuite/lib/scanasm.exp
'dg-function-on-line',
# gcc/testsuite/g++.dg/modules/modules.exp:
'dg-module-cmi',
'dg-module-do',
# gcc/testsuite/gcc.target/bfin/bfin.exp
'dg-bfin-options',
'dg-bfin-processors',
# gcc/testsuite/gcc.target/csky/csky.exp
'dg-csky-options',
# libstdc++-v3/testsuite/lib/dg-options.exp
'dg-require-c-std',
'dg-require-debug-mode',
'dg-require-profile-mode',
'dg-require-normal-mode',
'dg-require-normal-namespace',
'dg-require-parallel-mode',
'dg-require-fileio',
'dg-require-namedlocale',
'dg-require-sharedlib',
'dg-require-time',
'dg-require-cstdint',
'dg-require-cmath',
'dg-require-thread-fence',
'dg-require-atomic-cmpxchg-word',
'dg-require-atomic-builtins',
'dg-require-gthreads',
'dg-require-gthreads-timed',
'dg-require-sleep',
'dg-require-sched-yield',
'dg-require-string-conversions',
'dg-require-swprintf',
'dg-require-binary-io',
'dg-require-nprocs',
'dg-require-static-libstdcxx',
'dg-require-little-endian',
'dg-require-filesystem-ts',
'dg-require-target-fs-symlinks',
'dg-require-target-fs-space',
'dg-require-target-fs-lwt',
'dg-require-cpp-feature-test'
}
class Directive:
@staticmethod
def from_match(path, line_num, line, m):
return Directive(path, line_num,
m.group(1), m.span(1),
m.group(2), m.span(2))
def __init__(self, path, line_num,
name, name_span,
args_str, args_str_span):
self.path = path
self.line_num = line_num
self.name = name
self.name_span = name_span
self.args_str = args_str
self.args_str_span = args_str_span
class FileState:
def __init__(self):
# Map from directive name to list of directives
self.directives = {}
def add_directive(self, d):
if d.name not in self.directives:
self.directives[d.name] = []
self.directives[d.name].append(d)
class Context:
def __init__(self):
self.mgr = libgdiagnostics.Manager()
self.mgr.set_tool_name('dg-lint')
self.mgr.add_text_sink()
self.num_files_checked = 0
def check_file(self, f_in):
file_state = FileState()
try:
for line_idx, line in enumerate(f_in):
self.check_line(file_state, f_in.name, line_idx + 1, line)
self.num_files_checked += 1
except UnicodeDecodeError:
return # FIXME
def check_line(self, file_state, path, line_num, line):
if 0:
phys_loc = self.get_file_and_line(path, line_num)
self.report_warning(phys_loc, "line = '%s'", line.rstrip())
# Look for directives
# Compare with dg.exp's dg-get-options
m = re.search(r"{[ \t]+(dg-[-a-z]+)[ \t]+(.*)[ \t]+}", line)
if m:
d = Directive.from_match(path, line_num, line, m)
self.on_directive(file_state, d)
else:
# Look for things that look like attempts to use directives,
# but didn't match the regexp
m = re.search(r"(dg-[-a-z]+)", line)
if m:
phys_loc \
= self.get_file_line_and_range_for_match_span(path,
line_num,
m.span(1))
self.make_warning() \
.at(phys_loc) \
.emit('directive %qs appears not to match dg.exp\'s regexp',
m.group(1))
else:
# Look for things that look like misspellings of directives,
# via underscores rather than dashes
m = re.search(r"{[ \t]+(dg[-_][-_a-z]+)(.*)", line)
if m:
bogus_d = Directive.from_match(path, line_num, line, m)
self.report_unknown_directive(bogus_d)
def on_directive(self, file_state, d):
if 0:
phys_loc = self.get_file_and_line(d.path, d.line_num)
self.make_note() \
.at(phys_loc) \
.emit('directive %qs with arguments %qs',
d.name, d.args_str)
if d.name not in KNOWN_DIRECTIVES:
self.report_unknown_directive(d)
# Apparently we can't have a dg-do after a dg-require;
# see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116163#c5
if d.name == 'dg-do':
for existing_name in file_state.directives:
if existing_name.startswith('dg-require'):
other_d = file_state.directives[existing_name][0]
self.complain_about_order(other_d, d)
file_state.add_directive(d)
def report_unknown_directive(self, d):
phys_loc = self.get_phys_loc_for_directive(d)
w = self.make_warning() \
.at(phys_loc)
candidates = difflib.get_close_matches(d.name, KNOWN_DIRECTIVES)
if candidates:
suggestion = candidates[0]
w.with_fix_it_replace (phys_loc, suggestion) \
.emit("unknown directive: %qs; did you mean %qs?",
d.name, suggestion)
else:
w.emit("unknown directive: %qs",
d.name)
def complain_about_order(self, existing_d: Directive, new_d: Directive):
"""
Complain about new_d occurring after existing_d.
"""
with self.mgr.make_group() as g:
self.make_warning() \
.at(self.get_phys_loc_for_directive(new_d))\
.emit("%qs after %qs", new_d.name, existing_d.name)
self.make_note() \
.at(self.get_phys_loc_for_directive(existing_d))\
.emit("%qs was here", existing_d.name)
def get_file_and_line(self,
path: str,
line_num: int):
"""
Get a libgdiagnostics.c_diagnostic_physical_location_ptr for the given line.
"""
c_diag_file = self.mgr.get_file(path)
return self.mgr.get_file_and_line(c_diag_file, line_num)
def get_phys_loc_for_directive(self, d: Directive) \
-> libgdiagnostics.c_diagnostic_physical_location_ptr:
return self.get_file_line_and_range_for_match_span(d.path,
d.line_num,
d.name_span)
def get_file_line_and_range_for_match_span(self,
path: str,
line_num: int,
span):
return self.get_file_line_and_range(path, line_num,
start_column=span[0] + 1,
end_column=span[1])
def get_file_line_and_range(self,
path: str,
line_num: int,
start_column: int,
end_column: int):
"""
Get a libgdiagnostics.c_diagnostic_physical_location_ptr for the range
of columns on the given line.
"""
c_diag_file = self.mgr.get_file(path)
start_loc = self.mgr.get_file_line_and_column(c_diag_file, line_num, start_column)
end_loc = self.mgr.get_file_line_and_column(c_diag_file, line_num, end_column)
return self.mgr.get_range(start_loc, start_loc, end_loc)
def report_warning(self, phys_loc, msg, *args):
diag = libgdiagnostics.Diagnostic(self.mgr,
libgdiagnostics.DIAGNOSTIC_LEVEL_WARNING)
diag.set_location (phys_loc)
diag.finish(msg, *args)
def make_warning(self):
return libgdiagnostics.Diagnostic(self.mgr,
libgdiagnostics.DIAGNOSTIC_LEVEL_WARNING)
def make_note(self):
return libgdiagnostics.Diagnostic(self.mgr,
libgdiagnostics.DIAGNOSTIC_LEVEL_NOTE)
def report_stats(self):
self.make_note ()\
.emit('%i files checked', self.num_files_checked)
def skip_file(filename):
if 'ChangeLog' in filename:
return True
if 'README' in filename:
return True
if filename.endswith('Makefile.am') or filename.endswith('Makefile.in'):
return True
if filename.endswith('.exp'):
return True
if 'gen_directive_tests' in filename:
return True
return False
def main(argv):
parser = argparse.ArgumentParser()#usage=__doc__)
parser.add_argument('paths', nargs='+', type=pathlib.Path)
opts = parser.parse_args(argv[1:])
ctxt = Context()
for path in opts.paths:
if path.is_dir():
for dirpath, dirnames, filenames in os.walk(path):
for f in filenames:
if skip_file(f):
continue
p = os.path.join(dirpath, f)
with open(p) as f:
ctxt.check_file(f)
else:
with open(path) as f:
ctxt.check_file(f)
ctxt.report_stats()
# TODO: how to unit test?
if __name__ == '__main__':
retval = main(sys.argv)
sys.exit(retval)

View file

@ -0,0 +1,250 @@
#!/usr/bin/env python3
# Python bindings for libgdiagnostics, using ctypes
# Contributed by David Malcolm <dmalcolm@redhat.com>
#
# Copyright (C) 2025 Free Software Foundation, Inc.
#
# This file is part of GCC.
#
# GCC 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, or (at your option)
# any later version.
#
# GCC 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 GCC; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
from contextlib import contextmanager
import ctypes
# Lower-level API: use ctypes and FFI to wrap the C API directly
cdll = ctypes.cdll.LoadLibrary('libgdiagnostics.so')
libc = ctypes.CDLL(None)
c_stderr = ctypes.c_void_p.in_dll(libc, 'stderr')
# Opaque structs
class c_diagnostic_manager(ctypes.Structure):
pass
c_diagnostic_manager_ptr = ctypes.POINTER(c_diagnostic_manager)
class c_diagnostic(ctypes.Structure):
pass
c_diagnostic_ptr = ctypes.POINTER(c_diagnostic)
class c_diagnostic_file(ctypes.Structure):
pass
c_diagnostic_file_ptr = ctypes.POINTER(c_diagnostic_file)
class c_diagnostic_physical_location(ctypes.Structure):
pass
c_diagnostic_physical_location_ptr = ctypes.POINTER(c_diagnostic_physical_location)
# Enums
DIAGNOSTIC_COLORIZE_IF_TTY = 0
DIAGNOSTIC_LEVEL_ERROR = 0
DIAGNOSTIC_LEVEL_WARNING = 1
DIAGNOSTIC_LEVEL_NOTE = 2
DIAGNOSTIC_LEVEL_SORRY = 3
# Entrypoints
cdll.diagnostic_manager_new.argtypes = []
cdll.diagnostic_manager_new.restype = c_diagnostic_manager_ptr
cdll.diagnostic_manager_release.argtypes = [c_diagnostic_manager_ptr]
cdll.diagnostic_manager_release.restype = None
cdll.diagnostic_manager_set_tool_name.argtypes = [c_diagnostic_manager_ptr,
ctypes.c_char_p]
cdll.diagnostic_manager_set_tool_name.restype = None
cdll.diagnostic_manager_begin_group.argtypes = [c_diagnostic_manager_ptr]
cdll.diagnostic_manager_begin_group.restype = None
cdll.diagnostic_manager_end_group.argtypes = [c_diagnostic_manager_ptr]
cdll.diagnostic_manager_end_group.restype = None
cdll.diagnostic_begin.argtypes = [c_diagnostic_manager_ptr,
ctypes.c_int]
cdll.diagnostic_begin.restype = c_diagnostic_ptr
cdll.diagnostic_finish.argtypes = [c_diagnostic_ptr,
ctypes.c_char_p]#, ctypes.c_char_p] # FIXME: should be variadic
cdll.diagnostic_finish.restype = None
cdll.diagnostic_manager_new_file.argtypes = [c_diagnostic_manager_ptr,
ctypes.c_char_p,
ctypes.c_char_p]
cdll.diagnostic_manager_new_file.restype = c_diagnostic_file_ptr
cdll.diagnostic_manager_new_location_from_file_and_line.argtypes \
= [c_diagnostic_manager_ptr,
c_diagnostic_file_ptr,
ctypes.c_int]
cdll.diagnostic_manager_new_location_from_file_and_line.restype \
= c_diagnostic_physical_location_ptr
cdll.diagnostic_manager_new_location_from_file_line_column.argtypes \
= [c_diagnostic_manager_ptr,
c_diagnostic_file_ptr,
ctypes.c_int,
ctypes.c_int]
cdll.diagnostic_manager_new_location_from_file_line_column.restype \
= c_diagnostic_physical_location_ptr
cdll.diagnostic_manager_new_location_from_range.argtypes\
= [c_diagnostic_manager_ptr,
c_diagnostic_physical_location_ptr,
c_diagnostic_physical_location_ptr,
c_diagnostic_physical_location_ptr]
cdll.diagnostic_manager_new_location_from_range.restype \
= c_diagnostic_physical_location_ptr
cdll.diagnostic_set_location.argtypes = [c_diagnostic_ptr,
c_diagnostic_physical_location_ptr]
cdll.diagnostic_set_location.restype = None
cdll.diagnostic_add_fix_it_hint_replace.argtypes \
= [c_diagnostic_ptr,
c_diagnostic_physical_location_ptr,
ctypes.c_char_p]
cdll.diagnostic_add_fix_it_hint_replace.restype = None
# Helper functions
def _to_utf8(s: str):
if s is None:
return None
return s.encode('utf-8')
# Higher-level API, a more pythonic approach, with classes
class Manager:
def __init__(self):
self.c_mgr = cdll.diagnostic_manager_new()
if 0:
print('__init__: %r' % self.c_mgr)
def __del__(self):
if 0:
print('__del__: %r' % self.c_mgr)
self.c_mgr = cdll.diagnostic_manager_release(self.c_mgr)
def set_tool_name(self, name: str):
assert self.c_mgr
assert str
cdll.diagnostic_manager_set_tool_name (self.c_mgr, _to_utf8(name))
def add_text_sink(self):
assert self.c_mgr
# FIXME: hardcode the args for now
cdll.diagnostic_manager_add_text_sink (self.c_mgr,
c_stderr,
DIAGNOSTIC_COLORIZE_IF_TTY)
def get_file(self, path: str, sarif_lang: str = None):
assert self.c_mgr
assert path
c_file = cdll.diagnostic_manager_new_file (self.c_mgr,
_to_utf8(path),
_to_utf8(sarif_lang))
return c_file
def get_file_and_line(self,
c_file: c_diagnostic_file_ptr,
line_num: int):
assert self.c_mgr
assert c_file
c_phys_loc = cdll.diagnostic_manager_new_location_from_file_and_line (self.c_mgr,
c_file,
line_num)
return c_phys_loc
def get_file_line_and_column(self,
c_file: c_diagnostic_file_ptr,
line_num: int,
column_num: int):
assert self.c_mgr
assert c_file
c_phys_loc = cdll.diagnostic_manager_new_location_from_file_line_column (self.c_mgr,
c_file,
line_num,
column_num)
return c_phys_loc
def get_range(self,
caret: c_diagnostic_physical_location_ptr,
start: c_diagnostic_physical_location_ptr,
finish: c_diagnostic_physical_location_ptr):
assert self.c_mgr
c_phys_loc = cdll.diagnostic_manager_new_location_from_range (self.c_mgr,
caret,
start,
finish)
return c_phys_loc
@contextmanager
def make_group(self):
assert self.c_mgr
cdll.diagnostic_manager_begin_group (self.c_mgr)
try:
yield
finally:
assert self.c_mgr
cdll.diagnostic_manager_end_group (self.c_mgr)
class Diagnostic:
def __init__(self, mgr: Manager, level: int):
self.c_diag = cdll.diagnostic_begin (mgr.c_mgr, level)
if 0:
print('__init__: %r' % self.c_diag)
def finish(self, fmt: str, *args):
assert self.c_diag
c_args = []
for arg in args:
if type(arg) is str:
arg = _to_utf8(arg)
c_args.append(arg)
cdll.diagnostic_finish (self.c_diag, _to_utf8(fmt), *c_args)
self.c_diagnostic = None
def set_location(self,
phys_loc: c_diagnostic_physical_location_ptr):
assert self.c_diag
cdll.diagnostic_set_location(self.c_diag, phys_loc)
# Chaining-style API
def at(self,
phys_loc: c_diagnostic_physical_location_ptr):
self.set_location(phys_loc)
return self
def with_fix_it_replace(self,
phys_loc: c_diagnostic_physical_location_ptr,
replacement: str):
assert self.c_diag
assert replacement
cdll.diagnostic_add_fix_it_hint_replace(self.c_diag,
phys_loc,
_to_utf8(replacement))
return self
def emit(self, fmt: str, *args):
self.finish(fmt, *args)

41
contrib/dg-lint/test-1.c Normal file
View file

@ -0,0 +1,41 @@
/* Various misspelled DejaGnu directives. */
/* Underscore rather than dash.
Example taken from gcc/testsuite/gcc.target/powerpc/vsx-builtin-msum.c
fixed in r15-3878-gd5864b95ce94d9. */
/* { dg_final { scan_assembler_times "vmsumudm" 2 } } */
/* Correct directive. */
/* { dg-final { scan_assembler_times "vmsumudm" 2 } } */
/* Malformed uses of directives.
Missing braces
dg-output-file "m4.out"
Missing leading brace
dg-output-file "m4.out" }
Missing trailing brace
{ dg-output-file "m4.out"
Missing whitespace.
{dg-output-file "m4.out"}
Trailing comma.
{ dg-output-file, "m4.out" }
*/
/* Unrecognized directives.
{ dg-whatever }
{ dg-whatever }
{ dg-whatever "" }
*/
/* Misspelled directive
{ dg-oupyt-file "m4.out" }
*/

8
contrib/dg-lint/test-2.c Normal file
View file

@ -0,0 +1,8 @@
/* Bad directive ordering; see
https://gcc.gnu.org/onlinedocs/gccint/Directives.html
"This directive must appear after any dg-do directive in the test and
before any dg-additional-sources directive." for
dg-require-effective-target. */
/* { dg-require-effective-target c++11 } */
/* { dg-do compile } */