Skip to content

Commit

Permalink
Compiler default standard detection
Browse files Browse the repository at this point in the history
Some implicit information, like implicit include paths or compiler
targets, may depend on the used C/C++ language standard. This can be
provided by "-std=" flag. However, in some cases this flag is not used
and has a default value which depends on the compiler binary. In this
commit the intention is to detect this default language standard
version.
  • Loading branch information
bruntib committed Sep 6, 2018
1 parent 224ba3d commit 37ce3ec
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 13 deletions.
5 changes: 5 additions & 0 deletions libcodechecker/analyze/analyzers/analyzer_clang_tidy.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ def construct_analyzer_cmd(self, result_handler):

analyzer_cmd.extend(self.buildaction.compiler_includes)

if not next((x for x in analyzer_cmd if x.startswith('-std=') or
x.startswith('--std')),
False):
analyzer_cmd.append(self.buildaction.compiler_standard)

analyzer_cmd.extend(compiler_warnings)

return analyzer_cmd
Expand Down
5 changes: 5 additions & 0 deletions libcodechecker/analyze/analyzers/analyzer_clangsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ def construct_analyzer_cmd(self, result_handler):

analyzer_cmd.extend(self.buildaction.compiler_includes)

if not next((x for x in analyzer_cmd if x.startswith('-std=') or
x.startswith('--std')),
False):
analyzer_cmd.append(self.buildaction.compiler_standard)

analyzer_cmd.append(self.source_file)

return analyzer_cmd
Expand Down
97 changes: 94 additions & 3 deletions libcodechecker/analyze/log_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import shlex
import subprocess
import sys
import tempfile
import traceback

# TODO: This is a cross-subpackage import!
Expand All @@ -40,6 +41,11 @@
compiler_info_dump_file = "compiler_info.json"


def remove_file_if_exists(filename):
if os.path.isfile(filename):
os.remove(filename)


def get_compiler_err(cmd):
"""
Returns the stderr of a compiler invocation as string.
Expand Down Expand Up @@ -222,9 +228,87 @@ def get_compiler_target(parseLogOptions, compiler):
return parse_compiler_target(err)


def remove_file_if_exists(filename):
if os.path.isfile(filename):
os.remove(filename)
def get_compiler_standard(parseLogOptions, compiler, lang):
"""
Returns the default compiler standard of the given compiler. The standard
is determined by the values of __STDC_VERSION__ and __cplusplus predefined
macros. These values are integers indicating the date of the standard.
However, GCC supports a GNU extension for each standard. For sake of
generality we return the GNU extended standard, since it should be a
superset of the non-extended one, thus applicable in a more general manner.
"""
VERSION_C = u"""
#ifdef __STDC_VERSION__
# if __STDC_VERSION__ >= 201710L
# error CC_FOUND_STANDARD_VER#17
# elif __STDC_VERSION__ >= 201112L
# error CC_FOUND_STANDARD_VER#11
# elif __STDC_VERSION__ >= 199901L
# error CC_FOUND_STANDARD_VER#99
# elif __STDC_VERSION__ >= 199409L
# error CC_FOUND_STANDARD_VER#95
# else
# error CC_FOUND_STANDARD_VER#95
# endif
#else
# error CC_FOUND_STANDARD_VER#95
#endif
"""

VERSION_CPP = u"""
#ifdef __cplusplus
# if __cplusplus >= 201703L
# error CC_FOUND_STANDARD_VER#17
# elif __cplusplus >= 201402L
# error CC_FOUND_STANDARD_VER#14
# elif __cplusplus >= 201103L
# error CC_FOUND_STANDARD_VER#11
# elif __cplusplus >= 199711L
# error CC_FOUND_STANDARD_VER#98
# else
# error CC_FOUND_STANDARD_VER#98
# endif
#else
# error CC_FOUND_STANDARD_VER#98
#endif
"""

standard = ""
if parseLogOptions.compiler_info_file is None:
with tempfile.NamedTemporaryFile(
suffix=('.c' if lang == 'c' else '.cpp')) as source:

with source.file as f:
f.write(VERSION_C if lang == 'c' else VERSION_CPP)

try:
proc = subprocess.Popen([compiler, source.name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
_, err = proc.communicate() # Wait for execution.

finding = re.search('CC_FOUND_STANDARD_VER#(.+)', err)

if finding:
standard = finding.group(1)
except OSError:
LOG.error("Error during the compilation of compiler standard "
"detector.")

if standard:
standard = '-std=gnu' + ('' if lang == 'c' else '++') + standard
else:
standard = load_compiler_info(parseLogOptions.compiler_info_file,
compiler,
'default_standard')

if parseLogOptions.output_path is not None and standard:
dump_compiler_info(compiler_info_dump_file,
compiler,
'default_standard',
standard)

return standard


def parse_compile_commands_json(log_data, parseLogOptions):
Expand All @@ -244,6 +328,7 @@ def parse_compile_commands_json(log_data, parseLogOptions):

compiler_includes = {}
compiler_target = {}
compiler_standard = {}

counter = 0
for entry in log_data:
Expand Down Expand Up @@ -337,7 +422,13 @@ def parse_compile_commands_json(log_data, parseLogOptions):
compiler_target[results.compiler] = \
get_compiler_target(parseLogOptions, results.compiler)

if not (results.compiler in compiler_standard):
compiler_standard[results.compiler] = \
get_compiler_standard(parseLogOptions, results.compiler,
results.lang)

action.compiler_includes = compiler_includes[results.compiler]
action.compiler_standard = compiler_standard[results.compiler]
action.target = compiler_target[results.compiler]

if results.action != option_parser.ActionType.COMPILE:
Expand Down
5 changes: 5 additions & 0 deletions libcodechecker/generic_package_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ def path_plist_to_html_dist(self):
return os.path.join(self.package_root,
self.pckg_layout['plist_to_html_dist_path'])

@property
def path_standard_detector(self):
return os.path.join(self.package_root,
self.pckg_layout['standard_detector_path'])

@property
def compiler_resource_dir(self):
resource_dir = self.pckg_layout.get('compiler_resource_dir')
Expand Down
5 changes: 4 additions & 1 deletion libcodechecker/libhandlers/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,14 +427,17 @@ def __init__(self, args=None):
self.output_path = None
self.compiler_includes_file = None
self.compiler_target_file = None
self.compiler_info_file = None
else:
self.output_path = getattr(args, 'output_path', None)
self.compiler_includes_file =\
getattr(args, 'compiler_includes_file', None)
self.compiler_target_file =\
getattr(args, 'compiler_target_file', None)
self.compiler_info_file =\
getattr(args, 'compiler_info_file', None)

if getattr(args, 'compiler_info_file', None) is not None:
if self.compiler_info_file:
self.compiler_includes_file = args.compiler_info_file
self.compiler_target_file = args.compiler_info_file

Expand Down
18 changes: 9 additions & 9 deletions tests/functional/analyze/test_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ def test_compiler_info_files(self):

# Create a compilation database.
build_log = [{"directory": self.test_workspace,
"command": "gcc -c " + source_file,
"command": "g++ -c " + source_file,
"file": source_file
},
{"directory": self.test_workspace,
"command": "clang -c " + source_file,
"command": "clang++ -c " + source_file,
"file": source_file
}
]
Expand Down Expand Up @@ -149,8 +149,8 @@ def test_compiler_info_files(self):
try:
data = json.load(f)
self.assertEquals(len(data), 2)
self.assertTrue("clang" in data)
self.assertTrue("gcc" in data)
self.assertTrue("clang++" in data)
self.assertTrue("g++" in data)
except ValueError:
self.fail("json.load should successfully parse the file %s"
% info_File)
Expand All @@ -169,7 +169,7 @@ def test_compiler_includes_file_is_loaded(self):
# Contents of build log.
build_log = [
{"directory": self.test_workspace,
"command": "clang -c " + source_file,
"command": "clang++ -c " + source_file,
"file": source_file
}
]
Expand All @@ -185,7 +185,7 @@ def test_compiler_includes_file_is_loaded(self):
with open(compiler_includes_file, 'w') as source:
source.write(
# Raw string literal, cannot break the line:
r"""{"clang": "\"\n#include \"...\" search starts here:\n"""\
r"""{"clang++": "\"\n#include \"...\" search starts here:\n"""\
r"""#include <...> search starts here:\n"""\
r""" /TEST_FAKE_INCLUDE_DIR"}"""
)
Expand Down Expand Up @@ -217,7 +217,7 @@ def test_compiler_target_file_is_loaded(self):
# Contents of build log.
build_log = [
{"directory": self.test_workspace,
"command": "clang -c " + source_file,
"command": "clang++ -c " + source_file,
"file": source_file
}
]
Expand All @@ -233,7 +233,7 @@ def test_compiler_target_file_is_loaded(self):
with open(compiler_target_file, 'w') as source:
source.write(
# Raw string literal, cannot break the line:
r"""{"clang": "Target: TEST_FAKE_TARGET\nConfigured with"}"""
r"""{"clang++": "Target: TEST_FAKE_TARGET\nConfigured with"}"""
)

# Create analyze command.
Expand Down Expand Up @@ -397,7 +397,7 @@ def test_incremental_analyze(self):

# Create a compilation database.
build_log = [{"directory": self.test_workspace,
"command": "gcc -c " + source_file,
"command": "g++ -c " + source_file,
"file": source_file
}]

Expand Down

0 comments on commit 37ce3ec

Please sign in to comment.