summaryrefslogtreecommitdiffstats
path: root/Tools/c-analyzer/c_parser/preprocessor/common.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/c-analyzer/c_parser/preprocessor/common.py')
-rw-r--r--Tools/c-analyzer/c_parser/preprocessor/common.py173
1 files changed, 173 insertions, 0 deletions
diff --git a/Tools/c-analyzer/c_parser/preprocessor/common.py b/Tools/c-analyzer/c_parser/preprocessor/common.py
new file mode 100644
index 0000000..6368102
--- /dev/null
+++ b/Tools/c-analyzer/c_parser/preprocessor/common.py
@@ -0,0 +1,173 @@
+import contextlib
+import distutils.ccompiler
+import logging
+import shlex
+import subprocess
+import sys
+
+from ..info import FileInfo, SourceLine
+from .errors import (
+ PreprocessorFailure,
+ ErrorDirectiveError,
+ MissingDependenciesError,
+ OSMismatchError,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+# XXX Add aggregate "source" class(es)?
+# * expose all lines as single text string
+# * expose all lines as sequence
+# * iterate all lines
+
+
+def run_cmd(argv, *,
+ #capture_output=True,
+ stdout=subprocess.PIPE,
+ #stderr=subprocess.STDOUT,
+ stderr=subprocess.PIPE,
+ text=True,
+ check=True,
+ **kwargs
+ ):
+ if isinstance(stderr, str) and stderr.lower() == 'stdout':
+ stderr = subprocess.STDOUT
+
+ kw = dict(locals())
+ kw.pop('argv')
+ kw.pop('kwargs')
+ kwargs.update(kw)
+
+ proc = subprocess.run(argv, **kwargs)
+ return proc.stdout
+
+
+def preprocess(tool, filename, **kwargs):
+ argv = _build_argv(tool, filename, **kwargs)
+ logger.debug(' '.join(shlex.quote(v) for v in argv))
+
+ # Make sure the OS is supported for this file.
+ if (_expected := is_os_mismatch(filename)):
+ error = None
+ raise OSMismatchError(filename, _expected, argv, error, TOOL)
+
+ # Run the command.
+ with converted_error(tool, argv, filename):
+ # We use subprocess directly here, instead of calling the
+ # distutil compiler object's preprocess() method, since that
+ # one writes to stdout/stderr and it's simpler to do it directly
+ # through subprocess.
+ return run_cmd(argv)
+
+
+def _build_argv(
+ tool,
+ filename,
+ incldirs=None,
+ macros=None,
+ preargs=None,
+ postargs=None,
+ executable=None,
+ compiler=None,
+):
+ compiler = distutils.ccompiler.new_compiler(
+ compiler=compiler or tool,
+ )
+ if executable:
+ compiler.set_executable('preprocessor', executable)
+
+ argv = None
+ def _spawn(_argv):
+ nonlocal argv
+ argv = _argv
+ compiler.spawn = _spawn
+ compiler.preprocess(
+ filename,
+ macros=[tuple(v) for v in macros or ()],
+ include_dirs=incldirs or (),
+ extra_preargs=preargs or (),
+ extra_postargs=postargs or (),
+ )
+ return argv
+
+
+@contextlib.contextmanager
+def converted_error(tool, argv, filename):
+ try:
+ yield
+ except subprocess.CalledProcessError as exc:
+ convert_error(
+ tool,
+ argv,
+ filename,
+ exc.stderr,
+ exc.returncode,
+ )
+
+
+def convert_error(tool, argv, filename, stderr, rc):
+ error = (stderr.splitlines()[0], rc)
+ if (_expected := is_os_mismatch(filename, stderr)):
+ logger.debug(stderr.strip())
+ raise OSMismatchError(filename, _expected, argv, error, tool)
+ elif (_missing := is_missing_dep(stderr)):
+ logger.debug(stderr.strip())
+ raise MissingDependenciesError(filename, (_missing,), argv, error, tool)
+ elif '#error' in stderr:
+ # XXX Ignore incompatible files.
+ error = (stderr.splitlines()[1], rc)
+ logger.debug(stderr.strip())
+ raise ErrorDirectiveError(filename, argv, error, tool)
+ else:
+ # Try one more time, with stderr written to the terminal.
+ try:
+ output = run_cmd(argv, stderr=None)
+ except subprocess.CalledProcessError:
+ raise PreprocessorFailure(filename, argv, error, tool)
+
+
+def is_os_mismatch(filename, errtext=None):
+ # See: https://docs.python.org/3/library/sys.html#sys.platform
+ actual = sys.platform
+ if actual == 'unknown':
+ raise NotImplementedError
+
+ if errtext is not None:
+ if (missing := is_missing_dep(errtext)):
+ matching = get_matching_oses(missing, filename)
+ if actual not in matching:
+ return matching
+ return False
+
+
+def get_matching_oses(missing, filename):
+ # OSX
+ if 'darwin' in filename or 'osx' in filename:
+ return ('darwin',)
+ elif missing == 'SystemConfiguration/SystemConfiguration.h':
+ return ('darwin',)
+
+ # Windows
+ elif missing in ('windows.h', 'winsock2.h'):
+ return ('win32',)
+
+ # other
+ elif missing == 'sys/ldr.h':
+ return ('aix',)
+ elif missing == 'dl.h':
+ # XXX The existence of Python/dynload_dl.c implies others...
+ # Note that hpux isn't actual supported any more.
+ return ('hpux', '???')
+
+ # unrecognized
+ else:
+ return ()
+
+
+def is_missing_dep(errtext):
+ if 'No such file or directory' in errtext:
+ missing = errtext.split(': No such file or directory')[0].split()[-1]
+ return missing
+ return False