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 Aug 27, 2018
1 parent e9edcbe commit 1b2bec4
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 10 deletions.
4 changes: 4 additions & 0 deletions libcodechecker/analyze/analyzers/analyzer_clang_tidy.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ 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=')),
False):
analyzer_cmd.append(self.buildaction.compiler_standard)

analyzer_cmd.extend(compiler_warnings)

return analyzer_cmd
Expand Down
4 changes: 4 additions & 0 deletions libcodechecker/analyze/analyzers/analyzer_clangsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ 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=')),
False):
analyzer_cmd.append(self.buildaction.compiler_standard)

analyzer_cmd.append(self.source_file)

return analyzer_cmd
Expand Down
108 changes: 108 additions & 0 deletions libcodechecker/analyze/log_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
import os
import re
import shlex
import stat
import subprocess
import sys
import tempfile
import traceback

# TODO: This is a cross-subpackage import!
Expand Down Expand Up @@ -223,6 +225,105 @@ def get_compiler_target(parseLogOptions, compiler):
return parse_compiler_target(err)


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 = """
#include <stdio.h>
int main()
{
int version = 90;
#if defined(__STDC_VERSION__)
# if __STDC_VERSION__ >= 201710L
version = 17;
# elif __STDC_VERSION__ >= 201112L
version = 11;
# elif __STDC_VERSION__ >= 199901L
version = 99;
# elif __STDC_VERSION__ >= 199409L
version = 95;
# endif
#endif
printf("%d\\n", version);
return 0;
}
"""

VERSION_CPP = """
#include <iostream>
int main()
{
int version = 98;
#if defined(__cplusplus)
# if __cplusplus >= 201703L
version = 17;
# elif __cplusplus >= 201402L
version = 14;
# elif __cplusplus >= 201103L
version = 11;
# elif __cplusplus >= 199711L
version = 98;
# endif
#endif
std::cout << version << std::endl;
return 0;
}
"""

err = ""
if parseLogOptions.compiler_info_file is None:
_, temp_file = tempfile.mkstemp()
test_file = temp_file + ('.c' if lang == 'c' else '.cpp')
os.rename(temp_file, test_file)

with open(test_file, 'w') as f:
f.write(VERSION_C if lang == 'c' else VERSION_CPP)

executable = os.path.join(tempfile.gettempdir(),
next(tempfile._get_candidate_names()))

proc = subprocess.Popen([compiler, '-o', executable, test_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.communicate() # Wait for execution.

os.chmod(executable, stat.S_IXUSR)

proc = subprocess.Popen([executable], stdout=subprocess.PIPE)
out, _ = proc.communicate()

os.remove(test_file)
os.remove(executable)

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

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

return err


def remove_file_if_exists(filename):
if os.path.isfile(filename):
os.remove(filename)
Expand All @@ -247,6 +348,7 @@ def parse_compile_commands_json(logfile, parseLogOptions):

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

counter = 0
for entry in data:
Expand Down Expand Up @@ -340,7 +442,13 @@ def parse_compile_commands_json(logfile, 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 @@ -249,6 +249,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 @@ -416,14 +416,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 1b2bec4

Please sign in to comment.