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:
parent
72ecfe355a
commit
8d6de758cc
4 changed files with 703 additions and 0 deletions
404
contrib/dg-lint/dg-lint
Executable file
404
contrib/dg-lint/dg-lint
Executable 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)
|
250
contrib/dg-lint/libgdiagnostics.py
Normal file
250
contrib/dg-lint/libgdiagnostics.py
Normal 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
41
contrib/dg-lint/test-1.c
Normal 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
8
contrib/dg-lint/test-2.c
Normal 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 } */
|
Loading…
Add table
Reference in a new issue