xazax.hun updated this revision to Diff 101695.
xazax.hun edited the summary of this revision.
xazax.hun added a comment.

- Migrate to use USR instead of mangled names. Name mangling related changes 
are reverted.
- Better error handling in some cases.
- File paths containing spaces are now handled correctly.
- Fixes to support scripts.


https://reviews.llvm.org/D30691

Files:
  include/clang/AST/ASTContext.h
  include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
  include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
  include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
  lib/AST/ASTContext.cpp
  lib/AST/ASTImporter.cpp
  lib/Basic/SourceManager.cpp
  lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
  lib/StaticAnalyzer/Core/CMakeLists.txt
  lib/StaticAnalyzer/Core/CallEvent.cpp
  lib/StaticAnalyzer/Core/ExprEngine.cpp
  lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
  test/Analysis/Inputs/ctu-chain.cpp
  test/Analysis/Inputs/ctu-other.cpp
  test/Analysis/Inputs/externalFnMap.txt
  test/Analysis/ctu-main.cpp
  test/Analysis/func-mapping-test.cpp
  test/CMakeLists.txt
  test/lit.cfg
  tools/CMakeLists.txt
  tools/clang-func-mapping/CMakeLists.txt
  tools/clang-func-mapping/ClangFnMapGen.cpp
  tools/ctu-analysis/ctu-analyze.py
  tools/ctu-analysis/ctu-build.py
  tools/scan-build-py/libscanbuild/analyze.py

Index: tools/scan-build-py/libscanbuild/analyze.py
===================================================================
--- tools/scan-build-py/libscanbuild/analyze.py
+++ tools/scan-build-py/libscanbuild/analyze.py
@@ -383,7 +383,8 @@
 
     def target():
         """ Creates output file name for reports. """
-        if opts['output_format'] in {'plist', 'plist-html'}:
+        if opts['output_format'] in {'plist', 'plist-html',
+                                     'plist-multi-file'}:
             (handle, name) = tempfile.mkstemp(prefix='report-',
                                               suffix='.plist',
                                               dir=opts['output_dir'])
Index: tools/ctu-analysis/ctu-build.py
===================================================================
--- /dev/null
+++ tools/ctu-analysis/ctu-build.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+
+import argparse
+import json
+import glob
+import logging
+import multiprocessing
+import os
+import re
+import signal
+import subprocess
+import shlex
+import shutil
+import tempfile
+
+SOURCE_PATTERN = re.compile('.*\.(C|c|cc|cpp|cxx|ii|m|mm)$', re.IGNORECASE)
+TIMEOUT = 86400
+EXTERNAL_FUNCTION_MAP_FILENAME = 'externalFnMap.txt'
+TEMP_EXTERNAL_FNMAP_FOLDER = 'tmpExternalFnMaps'
+
+
+def get_args():
+    parser = argparse.ArgumentParser(
+        description='Executes 1st pass of CTU analysis where we preprocess '
+                    'all files in the compilation database and generate '
+                    'AST dumps and other necessary information from those '
+                    'to be used later by the 2nd pass of '
+                    'Cross Translation Unit analysis',
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument('-b', required=True, dest='buildlog',
+                        metavar='build.json',
+                        help='JSON Compilation Database to be used')
+    parser.add_argument('-p', metavar='preanalyze-dir', dest='ctuindir',
+                        help='Target directory for preanalyzation data',
+                        default='.ctu')
+    parser.add_argument('-j', metavar='threads', dest='threads', type=int,
+                        help='Number of threads to be used',
+                        default=int(multiprocessing.cpu_count() * 1.0))
+    parser.add_argument('-v', dest='verbose', action='store_true',
+                        help='Verbose output')
+    parser.add_argument('--clang-path', metavar='clang-path',
+                        dest='clang_path',
+                        help='Set path to directory of clang binaries used '
+                             '(default taken from CLANG_PATH envvar)',
+                        default=os.environ.get('CLANG_PATH'))
+    mainargs = parser.parse_args()
+
+    if mainargs.verbose:
+        logging.getLogger().setLevel(logging.INFO)
+
+    if mainargs.clang_path is None:
+        clang_path = ''
+    else:
+        clang_path = os.path.abspath(mainargs.clang_path)
+    logging.info('CTU uses clang dir: ' +
+                 (clang_path if clang_path != '' else '<taken from PATH>'))
+
+    return mainargs, clang_path
+
+
+def process_buildlog(buildlog_filename, src_2_dir, src_2_cmd, src_order,
+                     cmd_2_src, cmd_order):
+    with open(buildlog_filename, 'r') as buildlog_file:
+        buildlog = json.load(buildlog_file)
+    for step in buildlog:
+        if SOURCE_PATTERN.match(step['file']):
+            if step['file'] not in src_2_dir:
+                src_2_dir[step['file']] = step['directory']
+                src_2_cmd[step['file']] = step['command']
+                src_order.append(step['file'])
+            if step['command'] not in cmd_2_src:
+                cmd_2_src[step['command']] = [step['file']]
+                cmd_order.append(step['command'])
+            else:
+                cmd_2_src[step['command']].append(step['file'])
+
+
+def clear_file(filename):
+    try:
+        os.remove(filename)
+    except OSError:
+        pass
+
+
+def init_temp_stuff(ctuindir):
+    os.mkdir(os.path.join(ctuindir, TEMP_EXTERNAL_FNMAP_FOLDER))
+
+
+def clear_temp_stuff(ctuindir):
+    shutil.rmtree(os.path.join(ctuindir, TEMP_EXTERNAL_FNMAP_FOLDER),
+                  ignore_errors=True)
+
+
+def clear_workspace(ctuindir):
+    clear_temp_stuff(ctuindir)
+    clear_file(os.path.join(ctuindir, EXTERNAL_FUNCTION_MAP_FILENAME))
+
+
+def get_command_arguments(cmd):
+    had_command = False
+    args = []
+    for arg in shlex.split(cmd):
+        if had_command and not SOURCE_PATTERN.match(arg):
+            args.append(arg)
+        if not had_command and arg.find('=') == -1:
+            had_command = True
+    return args
+
+
+def get_triple_arch(clang_path, clang_args, source):
+    """Returns the architecture part of the target triple in a compilation
+    command. """
+    arch = ""
+    clang_cmd = [os.path.join(clang_path, 'clang'), "-###"]
+    clang_cmd.extend(clang_args)
+    clang_cmd.append(source)
+    clang_out = subprocess.check_output(clang_cmd, stderr=subprocess.STDOUT,
+                                        shell=False)
+    clang_params = shlex.split(clang_out)
+    i = 0
+    while i < len(clang_params) and clang_params[i] != "-triple":
+        i += 1
+    if i < (len(clang_params) - 1):
+        arch = clang_params[i + 1].split("-")[0]
+    return arch
+
+
+def generate_ast(params):
+    source, command, directory, clang_path, ctuindir = params
+    args = get_command_arguments(command)
+    arch = get_triple_arch(clang_path, args, source)
+    ast_joined_path = os.path.join(ctuindir, 'ast', arch,
+                                   os.path.realpath(source)[1:] + '.ast')
+    ast_path = os.path.abspath(ast_joined_path)
+    try:
+        os.makedirs(os.path.dirname(ast_path))
+    except OSError:
+        if os.path.isdir(os.path.dirname(ast_path)):
+            pass
+        else:
+            raise
+    ast_command = [os.path.join(clang_path, 'clang'), '-emit-ast']
+    ast_command.extend(args)
+    ast_command.append('-w')
+    ast_command.append(source)
+    ast_command.append('-o')
+    ast_command.append(ast_path)
+    os.chdir(directory)
+    logging.info(ast_command)
+    subprocess.call(ast_command, shell=False)
+
+
+def map_functions(params):
+    command, sources, directory, clang_path, ctuindir = params
+    args = get_command_arguments(command)
+    arch = get_triple_arch(clang_path, args, sources[0])
+    funcmap_command = [os.path.join(clang_path, 'clang-func-mapping')]
+    funcmap_command.extend(sources)
+    funcmap_command.append('--')
+    funcmap_command.extend(args)    
+    output = []
+    os.chdir(directory);
+    logging.info("map_functions: ")
+    logging.info(funcmap_command)
+    fn_out = subprocess.check_output(funcmap_command)
+    fn_list = fn_out.splitlines()
+    for fn_txt in fn_list:
+        dpos = fn_txt.find(" ")
+        mangled_name = fn_txt[0:dpos]
+        path = fn_txt[dpos + 1:]
+        ast_path = os.path.join("ast", arch, path[1:] + ".ast")
+        output.append(mangled_name + "@" + arch + " " + ast_path)
+    extern_fns_map_folder = os.path.join(ctuindir,
+                                         TEMP_EXTERNAL_FNMAP_FOLDER)
+    if output:
+        with tempfile.NamedTemporaryFile(dir=extern_fns_map_folder,
+                                         delete=False) as out_file:
+            out_file.write("\n".join(output) + "\n")
+
+
+def create_external_fn_maps(ctuindir):
+    files = glob.glob(os.path.join(ctuindir, TEMP_EXTERNAL_FNMAP_FOLDER,
+                                   '*'))
+    extern_fns_map_file = os.path.join(ctuindir,
+                                       EXTERNAL_FUNCTION_MAP_FILENAME)
+    mangled_to_asts = {}
+    for filename in files:
+        with open(filename, 'rb') as in_file:
+            for line in in_file:
+                mangled_name, ast_file = line.strip().split(' ', 1)
+                if mangled_name not in mangled_to_asts:
+                    mangled_to_asts[mangled_name] = {ast_file}
+                else:
+                    mangled_to_asts[mangled_name].add(ast_file)
+    with open(extern_fns_map_file, 'wb') as out_file:
+        for mangled_name, ast_files in mangled_to_asts.iteritems():
+            if len(ast_files) == 1:
+                out_file.write('%s %s\n' % (mangled_name, ast_files.pop()))
+
+
+def run_parallel(threads, workfunc, funcparams):
+    original_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+    workers = multiprocessing.Pool(processes=threads)
+    signal.signal(signal.SIGINT, original_handler)
+    try:
+        # Block with timeout so that signals don't get ignored, python bug 8296
+        workers.map_async(workfunc, funcparams).get(TIMEOUT)
+    except KeyboardInterrupt:
+        workers.terminate()
+        workers.join()
+        exit(1)
+    else:
+        workers.close()
+        workers.join()
+
+
+def main():
+    mainargs, clang_path = get_args()
+    clear_workspace(mainargs.ctuindir)
+
+    src_2_dir = {}
+    src_2_cmd = {}
+    src_order = []
+    cmd_2_src = {}
+    cmd_order = []
+    process_buildlog(mainargs.buildlog, src_2_dir, src_2_cmd, src_order,
+                     cmd_2_src, cmd_order)
+
+    run_parallel(mainargs.threads, generate_ast,
+                 [(src, src_2_cmd[src], src_2_dir[src], clang_path,
+                   mainargs.ctuindir) for src in src_order])
+
+    init_temp_stuff(mainargs.ctuindir)
+    run_parallel(mainargs.threads, map_functions,
+                 [(cmd, cmd_2_src[cmd], src_2_dir[cmd_2_src[cmd][0]],
+                   clang_path, mainargs.ctuindir) for cmd in cmd_order])
+
+    create_external_fn_maps(mainargs.ctuindir)
+    clear_temp_stuff(mainargs.ctuindir)
+
+
+if __name__ == "__main__":
+    main()
Index: tools/ctu-analysis/ctu-analyze.py
===================================================================
--- /dev/null
+++ tools/ctu-analysis/ctu-analyze.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python
+
+import argparse
+import json
+import logging
+import multiprocessing
+import os
+import re
+import signal
+import subprocess
+import threading
+import time
+
+
+SOURCE_PATTERN = re.compile('.*\.(C|c|cc|cpp|cxx|ii|m|mm)$', re.IGNORECASE)
+DIRCMD_SEPARATOR = ': '
+
+
+def get_args():
+    analyser_output_formats = ['plist-multi-file', 'plist', 'plist-html',
+                               'html', 'text']
+    analyser_output_format = analyser_output_formats[0]
+    parser = argparse.ArgumentParser(
+        description='Executes 2nd pass of CTU analysis where we do the '
+                    'static analysis taking all cross calls between '
+                    'translation units into account',
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument('-b', required=True, dest='buildlog',
+                        metavar='build.json',
+                        help='JSON Compilation Database to be used')
+    parser.add_argument('-p', metavar='preanalyze-dir', dest='ctuindir',
+                        help='Use directory for reading preanalyzation data ',
+                        default='.ctu')
+    parser.add_argument('-o', metavar='output-dir', dest='ctuoutdir',
+                        help='Target directory for analyzation results',
+                        default='.ctu-out')
+    parser.add_argument('-e', metavar='enabled-checker', nargs='+',
+                        dest='enabled_checkers',
+                        help='List all enabled checkers')
+    parser.add_argument('-d', metavar='disabled-checker', nargs='+',
+                        dest='disabled_checkers',
+                        help='List all disabled checkers')
+    parser.add_argument('-j', metavar='threads', dest='threads', type=int,
+                        help='Number of threads to be used',
+                        default=int(multiprocessing.cpu_count() * 1.0))
+    parser.add_argument('-v', dest='verbose', action='store_true',
+                        help='Verbose output')
+    parser.add_argument('--clang-path', metavar='clang-path',
+                        dest='clang_path',
+                        help='Set path to directory of clang binaries used '
+                             '(default taken from CLANG_PATH envvar)',
+                        default=os.environ.get('CLANG_PATH'))
+    parser.add_argument('--analyze-cc-path', metavar='analyze-cc-path',
+                        dest='analyze_path',
+                        help='Set path to directory of analyze-cc used '
+                             '(default is taken from CLANG_ANALYZE_CC_PATH '
+                             'environment variable)',
+                        default=os.environ.get('CLANG_ANALYZE_CC_PATH'))
+    parser.add_argument('--output-format', metavar='format',
+                        choices=analyser_output_formats,
+                        default=analyser_output_format,
+                        help='Format for analysis reports '
+                             '(one of %s; default is "%s").' %
+                        (', '.join(analyser_output_formats),
+                         analyser_output_format))
+    parser.add_argument('--no-ctu', dest='no_ctu', action='store_true',
+                        help='Do not use CTU at all, '
+                             'only do normal static analysis')
+    mainargs = parser.parse_args()
+
+    if mainargs.verbose:
+        logging.getLogger().setLevel(logging.INFO)
+
+    mainargs.ctuindir = os.path.abspath(mainargs.ctuindir)
+    mainargs.ctuoutdir = os.path.abspath(mainargs.ctuoutdir)
+
+    if mainargs.clang_path is None:
+        clang_path = ''
+    else:
+        clang_path = os.path.abspath(mainargs.clang_path)
+    logging.info('CTU uses clang dir: ' +
+                 (clang_path if clang_path != '' else '<taken from PATH>'))
+
+    if mainargs.analyze_path is None:
+        analyze_path = ''
+    else:
+        analyze_path = os.path.abspath(mainargs.analyze_path)
+    logging.info('CTU uses analyze-cc dir: ' +
+                 (analyze_path if analyze_path != '' else '<taken from PATH>'))
+
+    return mainargs, clang_path, analyze_path
+
+
+def get_analyzer_env(mainargs, clang_path):
+    analyzer_params = []
+    if mainargs.enabled_checkers:
+        analyzer_params.append('-analyzer-checker')
+        analyzer_params.append(",".join(mainargs.enabled_checkers))
+    if mainargs.disabled_checkers:
+        analyzer_params.append('-analyzer-disable-checker')
+        analyzer_params.append(",".join(mainargs.disabled_checkers))
+    if not mainargs.no_ctu:
+        analyzer_params.append('-analyzer-config')
+        analyzer_params.append('ctu-dir=' + mainargs.ctuindir)
+    analyzer_params.append('-analyzer-config')
+    analyzer_params.append('reanalyze-ctu-visited=true')
+    analyzer_params.append('-analyzer-stats')
+    passthru_analyzer_params = []
+    for param in analyzer_params:
+        passthru_analyzer_params.append('-Xanalyzer')
+        passthru_analyzer_params.append(param)
+    passthru_analyzer_params.append('--analyzer-output')
+    passthru_analyzer_params.append(mainargs.output_format)
+    analyzer_env = os.environ.copy()
+    analyzer_env['ANALYZE_BUILD_CLANG'] = os.path.join(clang_path, 'clang')
+    analyzer_env['ANALYZE_BUILD_REPORT_DIR'] = mainargs.ctuoutdir
+    analyzer_env['ANALYZE_BUILD_REPORT_FORMAT'] = mainargs.output_format
+    analyzer_env['ANALYZE_BUILD_REPORT_FAILURES'] = 'yes'
+    if mainargs.verbose:
+        analyzer_env['ANALYZE_BUILD_FORCE_DEBUG'] = 'yes'
+    analyzer_env['ANALYZE_BUILD_PARAMETERS'] = \
+        ' '.join(passthru_analyzer_params)
+    return analyzer_env
+
+
+def process_buildlog(buildlog_filename):
+    with open(buildlog_filename, 'r') as buildlog_file:
+        buildlog = json.load(buildlog_file)
+    dircmd_2_orders = {}
+    src_build_steps = 0
+    for step in buildlog:
+        if SOURCE_PATTERN.match(step['file']):
+            uid = step['directory'] + DIRCMD_SEPARATOR + step['command']
+            if uid not in dircmd_2_orders:
+                dircmd_2_orders[uid] = [src_build_steps]
+            else:
+                dircmd_2_orders[uid].append(src_build_steps)
+            src_build_steps += 1
+    return dircmd_2_orders
+
+
+def prepare_workspace(mainargs):
+    try:
+        os.makedirs(mainargs.ctuoutdir)
+    except OSError:
+        logging.warning('Output directory %s already exists!',
+                        mainargs.ctuoutdir)
+        exit(1)
+
+
+def clean_up_workspace(mainargs):
+    try:
+        os.removedirs(mainargs.ctuoutdir)
+        logging.info('Removing directory %s because it contains no reports',
+                     mainargs.ctuoutdir)
+    except OSError:
+        pass
+
+
+def get_compiler_and_arguments(cmd):
+    had_command = False
+    args = []
+    for arg in cmd.split():
+        if had_command:
+            args.append(arg)
+        if not had_command and arg.find('=') == -1:
+            had_command = True
+            compiler = arg
+    return compiler, args
+
+
+def analyze(mainargs, analyze_path, analyzer_env, directory, command):
+    compiler, args = get_compiler_and_arguments(command)
+    cmdenv = analyzer_env.copy()
+    cmdenv['INTERCEPT_BUILD'] = json.dumps({
+        'verbose': 10 if mainargs.verbose else 1,
+        'cc': [compiler],
+        'cxx': [compiler]
+        })
+    analyze_cmd_str = os.path.join(analyze_path, 'analyze-cc') + ' ' + \
+        ' '.join(args)
+    logging.info(analyze_cmd_str)
+
+    # Buffer output of subprocess and dump it out at the end, so that
+    # the subprocess doesn't continue to write output after the user
+    # sends SIGTERM
+    run_ok = True
+    out = '******* Error running command'
+    try:
+        popen = subprocess.Popen(analyze_cmd_str, shell=True,
+                                 stderr=subprocess.STDOUT,
+                                 stdout=subprocess.PIPE,
+                                 universal_newlines=True,
+                                 cwd=directory,
+                                 env=cmdenv)
+        out, _ = popen.communicate()
+        run_ok = not popen.returncode
+    except OSError:
+        run_ok = False
+    logging.info('Compile status: %s', 'ok' if run_ok else 'failed')
+    logging.info(out)
+
+
+def analyze_worker(mainargs, analyze_path, analyzer_env, lock,
+                   dircmd_2_orders):
+    while len(dircmd_2_orders) > 0:
+        lock.acquire()
+        if len(dircmd_2_orders) > 0:
+            dircmd, orders = dircmd_2_orders.popitem()
+            lock.release()
+            dircmdsplit = dircmd.split(DIRCMD_SEPARATOR, 2)
+            assert len(dircmdsplit) == 2 and len(dircmdsplit[0]) > 0 and \
+                len(dircmdsplit[1]) > 0
+            assert len(orders) > 0
+            directory = dircmdsplit[0]
+            command = dircmdsplit[1]
+            analyze(mainargs, analyze_path, analyzer_env, directory, command)
+        else:
+            lock.release()
+            time.sleep(0.125)
+
+
+def run_parallel(mainargs, analyze_path, analyzer_env, dircmd_2_orders):
+    original_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+    signal.signal(signal.SIGINT, original_handler)
+    lock = threading.Lock()
+    workers = []
+    for _ in range(mainargs.threads):
+        workers.append(threading.Thread(target=analyze_worker,
+                                        args=(mainargs, analyze_path,
+                                              analyzer_env, lock,
+                                              dircmd_2_orders)))
+    for worker in workers:
+        worker.start()
+    try:
+        for worker in workers:
+            worker.join(9999999999)
+    except KeyboardInterrupt:
+        exit(1)
+
+
+def main():
+    mainargs, clang_path, analyze_path = get_args()
+    analyzer_env = get_analyzer_env(mainargs, clang_path)
+    dircmd_2_orders = process_buildlog(mainargs.buildlog)
+    prepare_workspace(mainargs)
+    run_parallel(mainargs, analyze_path, analyzer_env, dircmd_2_orders)
+    clean_up_workspace(mainargs)
+
+
+if __name__ == "__main__":
+    main()
Index: tools/clang-func-mapping/ClangFnMapGen.cpp
===================================================================
--- /dev/null
+++ tools/clang-func-mapping/ClangFnMapGen.cpp
@@ -0,0 +1,127 @@
+//===- ClangFnMapGen.cpp -----------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===--------------------------------------------------------------------===//
+//
+// Clang tool which creates a list of defined functions and the files in which
+// they are defined.
+//
+//===--------------------------------------------------------------------===//
+
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/GlobalDecl.h"
+#include "clang/AST/Mangle.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Index/USRGeneration.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Signals.h"
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace llvm;
+using namespace clang;
+using namespace clang::tooling;
+
+static cl::OptionCategory ClangFnMapGenCategory("clang-fnmapgen options");
+
+class MapFunctionNamesConsumer : public ASTConsumer {
+public:
+  MapFunctionNamesConsumer(ASTContext &Context) : Ctx(Context) {}
+
+  ~MapFunctionNamesConsumer() {
+    // Flush results to standard output.
+    llvm::outs() << DefinedFuncsStr.str();
+  }
+
+  virtual void HandleTranslationUnit(ASTContext &Ctx) {
+    handleDecl(Ctx.getTranslationUnitDecl());
+  }
+
+private:
+  std::string getLookupName(const FunctionDecl *FD);
+  void handleDecl(const Decl *D);
+
+  ASTContext &Ctx;
+  std::stringstream DefinedFuncsStr;
+  std::string CurrentFileName;
+};
+
+void MapFunctionNamesConsumer::handleDecl(const Decl *D) {
+  if (!D)
+    return;
+
+  if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
+    if (FD->isThisDeclarationADefinition()) {
+      if (const Stmt *Body = FD->getBody()) {
+        SmallString<128> LookupName;
+        bool Res = index::generateUSRForDecl(D, LookupName);
+        assert(!Res);
+        const SourceManager &SM = Ctx.getSourceManager();
+        if (CurrentFileName.empty()) {
+          StringRef SMgrName =
+              SM.getFileEntryForID(SM.getMainFileID())->getName();
+          char *Path = realpath(SMgrName.str().c_str(), nullptr);
+          CurrentFileName = Path;
+          free(Path);
+        }
+
+        switch (FD->getLinkageInternal()) {
+        case ExternalLinkage:
+        case VisibleNoLinkage:
+        case UniqueExternalLinkage:
+          if (SM.isInMainFile(Body->getLocStart()))
+            DefinedFuncsStr << LookupName.str().str() << " " << CurrentFileName
+                            << "\n";
+        default:
+          break;
+        }
+      }
+    }
+  }
+
+  if (const auto *DC = dyn_cast<DeclContext>(D))
+    for (const Decl *D : DC->decls())
+      handleDecl(D);
+}
+
+class MapFunctionNamesAction : public ASTFrontendAction {
+protected:
+  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
+                                                 llvm::StringRef) {
+    std::unique_ptr<ASTConsumer> PFC(
+        new MapFunctionNamesConsumer(CI.getASTContext()));
+    return PFC;
+  }
+};
+
+static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
+
+int main(int argc, const char **argv) {
+  // Print a stack trace if we signal out.
+  sys::PrintStackTraceOnErrorSignal(argv[0], false);
+  PrettyStackTraceProgram X(argc, argv);
+
+  const char *Overview = "\nThis tool collects the USR name and location "
+                         "of all functions definitions in the source files "
+                         "(excluding headers).\n";
+  CommonOptionsParser OptionsParser(argc, argv, ClangFnMapGenCategory,
+                                    cl::ZeroOrMore, Overview);
+
+  ClangTool Tool(OptionsParser.getCompilations(),
+                 OptionsParser.getSourcePathList());
+  Tool.run(newFrontendActionFactory<MapFunctionNamesAction>().get());
+  return 0;
+}
Index: tools/clang-func-mapping/CMakeLists.txt
===================================================================
--- /dev/null
+++ tools/clang-func-mapping/CMakeLists.txt
@@ -0,0 +1,22 @@
+set(LLVM_LINK_COMPONENTS
+  ${LLVM_TARGETS_TO_BUILD}
+  asmparser
+  support
+  mc
+  )
+
+add_clang_executable(clang-func-mapping
+  ClangFnMapGen.cpp
+  )
+
+target_link_libraries(clang-func-mapping
+  clangTooling
+  clangAST
+  clangBasic
+  clangFrontend
+  clangIndex
+  clangRewriteFrontend
+  )
+
+install(TARGETS clang-func-mapping
+  RUNTIME DESTINATION bin)
Index: tools/CMakeLists.txt
===================================================================
--- tools/CMakeLists.txt
+++ tools/CMakeLists.txt
@@ -19,6 +19,7 @@
   add_clang_subdirectory(clang-check)
   add_clang_subdirectory(scan-build)
   add_clang_subdirectory(scan-view)
+  add_clang_subdirectory(clang-func-mapping)
 endif()
 
 # We support checking out the clang-tools-extra repository into the 'extra'
Index: test/lit.cfg
===================================================================
--- test/lit.cfg
+++ test/lit.cfg
@@ -271,6 +271,7 @@
                               ' --driver-mode=cl '))
 config.substitutions.append( ('%clangxx', ' ' + config.clang +
                               ' --driver-mode=g++ '))
+config.substitutions.append( ('%clang_func_map', ' ' + lit.util.which('clang-func-mapping', config.environment['PATH']) + ' ') )
 config.substitutions.append( ('%clang', ' ' + config.clang + ' ') )
 config.substitutions.append( ('%test_debuginfo', ' ' + config.llvm_src_root + '/utils/test_debuginfo.pl ') )
 config.substitutions.append( ('%itanium_abi_triple', makeItaniumABITriple(config.target_triple)) )
Index: test/CMakeLists.txt
===================================================================
--- test/CMakeLists.txt
+++ test/CMakeLists.txt
@@ -47,6 +47,7 @@
   clang-tblgen
   clang-offload-bundler
   clang-import-test
+  clang-func-mapping
   )
   
 if(CLANG_ENABLE_STATIC_ANALYZER)
Index: test/Analysis/func-mapping-test.cpp
===================================================================
--- /dev/null
+++ test/Analysis/func-mapping-test.cpp
@@ -0,0 +1,7 @@
+// RUN: %clang_func_map %s -- | FileCheck %s
+
+int f(int) {
+  return 0;
+}
+
+// CHECK: c:@F@f#I#
Index: test/Analysis/ctu-main.cpp
===================================================================
--- /dev/null
+++ test/Analysis/ctu-main.cpp
@@ -0,0 +1,58 @@
+// RUN: mkdir -p %T/ctudir
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -emit-pch -o %T/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -emit-pch -o %T/ctudir/ctu-chain.cpp.ast %S/Inputs/ctu-chain.cpp
+// RUN: cp %S/Inputs/externalFnMap.txt %T/ctudir/
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fsyntax-only -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-config ctu-dir=%T/ctudir -analyzer-config reanalyze-ctu-visited=true -verify %s
+
+void clang_analyzer_eval(int);
+
+int f(int);
+int g(int);
+int h(int);
+
+int callback_to_main(int x) { return x + 1; }
+
+namespace myns {
+int fns(int x);
+
+namespace embed_ns {
+int fens(int x);
+}
+
+class embed_cls {
+public:
+  int fecl(int x);
+};
+}
+
+class mycls {
+public:
+  int fcl(int x);
+  static int fscl(int x);
+
+  class embed_cls2 {
+  public:
+    int fecl2(int x);
+  };
+};
+
+namespace chns {
+int chf1(int x);
+}
+
+int main() {
+  clang_analyzer_eval(f(3) == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(f(4) == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(f(5) == 3); // expected-warning{{FALSE}}
+  clang_analyzer_eval(g(4) == 6); // expected-warning{{TRUE}}
+  clang_analyzer_eval(h(2) == 8); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(myns::fns(2) == 9);                   // expected-warning{{TRUE}}
+  clang_analyzer_eval(myns::embed_ns::fens(2) == -1);       // expected-warning{{TRUE}}
+  clang_analyzer_eval(mycls().fcl(1) == 6);                 // expected-warning{{TRUE}}
+  clang_analyzer_eval(mycls::fscl(1) == 7);                 // expected-warning{{TRUE}}
+  clang_analyzer_eval(myns::embed_cls().fecl(1) == -6);     // expected-warning{{TRUE}}
+  clang_analyzer_eval(mycls::embed_cls2().fecl2(0) == -11); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(chns::chf1(4) == 12); // expected-warning{{TRUE}}
+}
Index: test/Analysis/Inputs/externalFnMap.txt
===================================================================
--- /dev/null
+++ test/Analysis/Inputs/externalFnMap.txt
@@ -0,0 +1,13 @@
+c:@N@chns@F@chf1#I#@x86_64 ctu-other.cpp.ast
+c:@N@myns@N@embed_ns@F@fens#I#@x86_64 ctu-other.cpp.ast
+c:@F@g#I#@x86_64 ctu-other.cpp.ast
+c:@S@mycls@F@fscl#I#S@x86_64 ctu-other.cpp.ast
+c:@S@mycls@F@fcl#I#@x86_64 ctu-other.cpp.ast
+c:@N@myns@S@embed_cls@F@fecl#I#@x86_64 ctu-other.cpp.ast
+c:@S@mycls@S@embed_cls2@F@fecl2#I#@x86_64 ctu-other.cpp.ast
+c:@F@f#I#@x86_64 ctu-other.cpp.ast
+c:@N@myns@F@fns#I#@x86_64 ctu-other.cpp.ast
+c:@F@h#I#@x86_64 ctu-other.cpp.ast
+c:@F@h_chain#I#@x86_64 ctu-chain.cpp.ast
+c:@N@chns@S@chcls@F@chf4#I#@x86_64 ctu-chain.cpp.ast
+c:@N@chns@F@chf2#I#@x86_64 ctu-chain.cpp.ast
Index: test/Analysis/Inputs/ctu-other.cpp
===================================================================
--- /dev/null
+++ test/Analysis/Inputs/ctu-other.cpp
@@ -0,0 +1,67 @@
+int callback_to_main(int x);
+int f(int x) {
+  return x - 1;
+}
+
+int g(int x) {
+  return callback_to_main(x) + 1;
+}
+
+int h_chain(int);
+
+int h(int x) {
+  return 2 * h_chain(x);
+}
+
+namespace myns {
+int fns(int x) {
+  return x + 7;
+}
+
+namespace embed_ns {
+int fens(int x) {
+  return x - 3;
+}
+}
+
+class embed_cls {
+public:
+  int fecl(int x) {
+    return x - 7;
+  }
+};
+}
+
+class mycls {
+public:
+  int fcl(int x) {
+    return x + 5;
+  }
+  static int fscl(int x) {
+    return x + 6;
+  }
+
+  class embed_cls2 {
+  public:
+    int fecl2(int x) {
+      return x - 11;
+    }
+  };
+};
+
+namespace chns {
+int chf2(int x);
+
+class chcls {
+public:
+  int chf4(int x);
+};
+
+int chf3(int x) {
+  return chcls().chf4(x);
+}
+
+int chf1(int x) {
+  return chf2(x);
+}
+}
Index: test/Analysis/Inputs/ctu-chain.cpp
===================================================================
--- /dev/null
+++ test/Analysis/Inputs/ctu-chain.cpp
@@ -0,0 +1,20 @@
+int h_chain(int x) {
+  return x * 2;
+}
+
+namespace chns {
+int chf3(int x);
+
+int chf2(int x) {
+  return chf3(x);
+}
+
+class chcls {
+public:
+  int chf4(int x);
+};
+
+int chcls::chf4(int x) {
+  return x * 3;
+}
+}
Index: lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
===================================================================
--- lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
+++ lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
@@ -16,13 +16,16 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
+#include "clang/AST/Mangle.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/Analysis/Analyses/LiveVariables.h"
 #include "clang/Analysis/CFG.h"
 #include "clang/Analysis/CallGraph.h"
 #include "clang/Analysis/CodeInjector.h"
 #include "clang/Basic/SourceManager.h"
+#include "clang/Basic/TargetInfo.h"
 #include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/TextDiagnosticPrinter.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h"
 #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
@@ -40,9 +43,11 @@
 #include "llvm/Support/Program.h"
 #include "llvm/Support/Timer.h"
 #include "llvm/Support/raw_ostream.h"
+#include <cassert>
 #include <memory>
 #include <queue>
 #include <utility>
+#include <fstream>
 
 using namespace clang;
 using namespace ento;
@@ -168,6 +173,7 @@
   AnalyzerOptionsRef Opts;
   ArrayRef<std::string> Plugins;
   CodeInjector *Injector;
+  CompilerInstance &CI;
 
   /// \brief Stores the declarations from the local translation unit.
   /// Note, we pre-compute the local declarations at parse time as an
@@ -192,12 +198,12 @@
   /// translation unit.
   FunctionSummariesTy FunctionSummaries;
 
-  AnalysisConsumer(const Preprocessor &pp, const std::string &outdir,
-                   AnalyzerOptionsRef opts, ArrayRef<std::string> plugins,
-                   CodeInjector *injector)
+  AnalysisConsumer(CompilerInstance &CI, const Preprocessor &pp,
+                   const std::string &outdir, AnalyzerOptionsRef opts,
+                   ArrayRef<std::string> plugins, CodeInjector *injector)
       : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), PP(pp),
         OutDir(outdir), Opts(std::move(opts)), Plugins(plugins),
-        Injector(injector) {
+        Injector(injector), CI(CI) {
     DigestAnalyzerOptions();
     if (Opts->PrintStats) {
       llvm::EnableStatistics(false);
@@ -415,6 +421,8 @@
   }
 }
 
+std::string getMangledName(const NamedDecl *ND, MangleContext *MangleCtx);
+
 static bool shouldSkipFunction(const Decl *D,
                                const SetOfConstDecls &Visited,
                                const SetOfConstDecls &VisitedAsTopLevel) {
@@ -706,7 +714,8 @@
   if (!Mgr->getAnalysisDeclContext(D)->getAnalysis<RelaxedLiveVariables>())
     return;
 
-  ExprEngine Eng(*Mgr, ObjCGCEnabled, VisitedCallees, &FunctionSummaries,IMode);
+  ExprEngine Eng(CI, *Mgr, ObjCGCEnabled, VisitedCallees, &FunctionSummaries,
+                 IMode);
 
   // Set the graph auditor.
   std::unique_ptr<ExplodedNode::Auditor> Auditor;
@@ -764,7 +773,7 @@
   bool hasModelPath = analyzerOpts->Config.count("model-path") > 0;
 
   return llvm::make_unique<AnalysisConsumer>(
-      CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts,
+      CI, CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts,
       CI.getFrontendOpts().Plugins,
       hasModelPath ? new ModelInjector(CI) : nullptr);
 }
Index: lib/StaticAnalyzer/Core/ExprEngine.cpp
===================================================================
--- lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -67,25 +67,19 @@
 
 static const char* TagProviderName = "ExprEngine";
 
-ExprEngine::ExprEngine(AnalysisManager &mgr, bool gcEnabled,
-                       SetOfConstDecls *VisitedCalleesIn,
-                       FunctionSummariesTy *FS,
-                       InliningModes HowToInlineIn)
-  : AMgr(mgr),
-    AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()),
-    Engine(*this, FS),
-    G(Engine.getGraph()),
-    StateMgr(getContext(), mgr.getStoreManagerCreator(),
-             mgr.getConstraintManagerCreator(), G.getAllocator(),
-             this),
-    SymMgr(StateMgr.getSymbolManager()),
-    svalBuilder(StateMgr.getSValBuilder()),
-    currStmtIdx(0), currBldrCtx(nullptr),
-    ObjCNoRet(mgr.getASTContext()),
-    ObjCGCEnabled(gcEnabled), BR(mgr, *this),
-    VisitedCallees(VisitedCalleesIn),
-    HowToInline(HowToInlineIn)
-{
+ExprEngine::ExprEngine(CompilerInstance &CI, AnalysisManager &mgr,
+                       bool gcEnabled, SetOfConstDecls *VisitedCalleesIn,
+                       FunctionSummariesTy *FS, InliningModes HowToInlineIn)
+    : CI(CI), AMgr(mgr),
+      AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()),
+      Engine(*this, FS), G(Engine.getGraph()),
+      StateMgr(getContext(), mgr.getStoreManagerCreator(),
+               mgr.getConstraintManagerCreator(), G.getAllocator(), this),
+      SymMgr(StateMgr.getSymbolManager()),
+      svalBuilder(StateMgr.getSValBuilder()), currStmtIdx(0),
+      currBldrCtx(nullptr), ObjCNoRet(mgr.getASTContext()),
+      ObjCGCEnabled(gcEnabled), BR(mgr, *this),
+      VisitedCallees(VisitedCalleesIn), HowToInline(HowToInlineIn) {
   unsigned TrimInterval = mgr.options.getGraphTrimInterval();
   if (TrimInterval != 0) {
     // Enable eager node reclaimation when constructing the ExplodedGraph.
Index: lib/StaticAnalyzer/Core/CallEvent.cpp
===================================================================
--- lib/StaticAnalyzer/Core/CallEvent.cpp
+++ lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -16,6 +16,10 @@
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/AST/ParentMap.h"
 #include "clang/Analysis/ProgramPoint.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/TextDiagnosticPrinter.h"
+#include "clang/Index/USRGeneration.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h"
 #include "llvm/ADT/SmallSet.h"
@@ -355,6 +359,49 @@
                                D->parameters());
 }
 
+RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const {
+  const FunctionDecl *FD = getDecl();
+  // Note that the AnalysisDeclContext will have the FunctionDecl with
+  // the definition (if one exists).
+  if (!FD)
+    return RuntimeDefinition();
+
+  AnalysisDeclContext *AD =
+    getLocationContext()->getAnalysisDeclContext()->
+    getManager()->getContext(FD);
+  if (AD->getBody())
+    return RuntimeDefinition(AD->getDecl());
+
+  auto Engine = static_cast<ExprEngine *>(
+      getState()->getStateManager().getOwningEngine());
+  CompilerInstance &CI = Engine->getCompilerInstance();
+
+  auto ASTLoader = [&](StringRef ASTFileName) {
+    IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
+    TextDiagnosticPrinter *DiagClient =
+        new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
+    IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
+    IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
+        new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
+    return ASTUnit::LoadFromASTFile(
+               ASTFileName, CI.getPCHContainerOperations()->getRawReader(),
+               Diags, CI.getFileSystemOpts());
+  };
+
+  const FunctionDecl *CTUDecl = AD->getASTContext().getCTUDefinition(
+      FD, Engine->getAnalysisManager().options.getCTUDir(),
+      [](const Decl *D) {
+        SmallString<128> DeclUSR;
+        bool Ret = index::generateUSRForDecl(D, DeclUSR);
+        assert(!Ret);
+        return DeclUSR.str().str();
+      },
+      ASTLoader);
+
+  return RuntimeDefinition(CTUDecl);
+}
+
+
 bool AnyFunctionCall::argumentsMayEscape() const {
   if (CallEvent::argumentsMayEscape() || hasVoidPointerToNonConstArg())
     return true;
Index: lib/StaticAnalyzer/Core/CMakeLists.txt
===================================================================
--- lib/StaticAnalyzer/Core/CMakeLists.txt
+++ lib/StaticAnalyzer/Core/CMakeLists.txt
@@ -56,6 +56,8 @@
   clangAST
   clangAnalysis
   clangBasic
+  clangFrontend
+  clangIndex
   clangLex
   clangRewrite
   ${Z3_LINK_FILES}
Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
===================================================================
--- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
+++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
@@ -370,3 +370,9 @@
         getBooleanOption("notes-as-events", /*Default=*/false);
   return DisplayNotesAsEvents.getValue();
 }
+
+StringRef AnalyzerOptions::getCTUDir() {
+  if (!CTUDir.hasValue() || !llvm::sys::fs::is_directory(*CTUDir))
+    CTUDir = getOptionAsString("ctu-dir", "");
+  return CTUDir.getValue();
+}
Index: lib/Basic/SourceManager.cpp
===================================================================
--- lib/Basic/SourceManager.cpp
+++ lib/Basic/SourceManager.cpp
@@ -2034,6 +2034,8 @@
 }
 
 /// \brief Determines the order of 2 source locations in the translation unit.
+/// FIXME: It also works when two locations are from different translation
+///        units. In that case it will return *some* order.
 ///
 /// \returns true if LHS source location comes before RHS, false otherwise.
 bool SourceManager::isBeforeInTranslationUnit(SourceLocation LHS,
@@ -2130,7 +2132,8 @@
       return LIsScratch;
     return LOffs.second < ROffs.second;
   }
-  llvm_unreachable("Unsortable locations found");
+  // FIXME: Source locations from different translation units.
+  return LOffs.first < ROffs.first;
 }
 
 void SourceManager::PrintStats() const {
Index: lib/AST/ASTImporter.cpp
===================================================================
--- lib/AST/ASTImporter.cpp
+++ lib/AST/ASTImporter.cpp
@@ -1838,6 +1838,8 @@
   if (ToD)
     return ToD;
 
+  const FunctionDecl *FoundWithoutBody = nullptr;
+
   // Try to find a function in our own ("to") context with the same name, same
   // type, and in the same context as the function we're importing.
   if (!LexicalDC->isFunctionOrMethod()) {
@@ -1855,6 +1857,13 @@
           if (Importer.IsStructurallyEquivalent(D->getType(), 
                                                 FoundFunction->getType())) {
             // FIXME: Actually try to merge the body and other attributes.
+            const FunctionDecl *FromBodyDecl = nullptr;
+            D->hasBody(FromBodyDecl);
+            if (D == FromBodyDecl && !FoundFunction->hasBody()) {
+              // This function is needed to merge completely.
+              FoundWithoutBody = FoundFunction;
+              break;
+            }
             return Importer.Imported(D, FoundFunction);
           }
 
@@ -2005,6 +2014,12 @@
   }
   ToFunction->setParams(Parameters);
 
+  if (FoundWithoutBody) {
+    auto *Recent = const_cast<FunctionDecl *>(
+          FoundWithoutBody->getMostRecentDecl());
+    ToFunction->setPreviousDecl(Recent);
+  }
+
   if (usedDifferentExceptionSpec) {
     // Update FunctionProtoType::ExtProtoInfo.
     QualType T = Importer.Import(D->getType());
Index: lib/AST/ASTContext.cpp
===================================================================
--- lib/AST/ASTContext.cpp
+++ lib/AST/ASTContext.cpp
@@ -13,6 +13,7 @@
 
 #include "clang/AST/ASTContext.h"
 #include "CXXABI.h"
+#include "clang/AST/ASTImporter.h"
 #include "clang/AST/ASTMutationListener.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/CharUnits.h"
@@ -34,11 +35,14 @@
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/TargetInfo.h"
+#include "clang/Frontend/ASTUnit.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/Triple.h"
 #include "llvm/Support/Capacity.h"
 #include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
+#include <fstream>
 #include <map>
 
 using namespace clang;
@@ -1421,6 +1425,131 @@
   }
 }
 
+
+//===----------------------------------------------------------------------===//
+//                         Cross-translation unit support
+//===----------------------------------------------------------------------===//
+
+std::string getLookupName(const NamedDecl *ND,
+                          std::string getUSR(const Decl *)) {
+  std::string ExtendedUSR;
+  llvm::raw_string_ostream OS(ExtendedUSR);
+  OS << getUSR(ND);
+  ASTContext &Ctx = ND->getASTContext();
+  // We are not going to support vendor and don't support OS and environment.
+  // FIXME: support OS and environment correctly
+  llvm::Triple::ArchType T = Ctx.getTargetInfo().getTriple().getArch();
+  if (T == llvm::Triple::thumb)
+    T = llvm::Triple::arm;
+  OS << "@" << Ctx.getTargetInfo().getTriple().getArchTypeName(T);
+  return OS.str();
+}
+
+/// Recursively visit the funtion decls of a DeclContext, and looks up a
+/// function based on mangled name.
+static const FunctionDecl *
+findFunctionInDeclContext(const DeclContext *DC, StringRef LookupFnName,
+                          std::string getUSR(const Decl *)) {
+  if (!DC)
+    return nullptr;
+  for (const Decl *D : DC->decls()) {
+    const auto *SubDC = dyn_cast<DeclContext>(D);
+    if (const auto *FD =
+            findFunctionInDeclContext(SubDC, LookupFnName, getUSR))
+      return FD;
+
+    const auto *ND = dyn_cast<FunctionDecl>(D);
+    const FunctionDecl *ResultDecl;
+    if (!ND || !ND->hasBody(ResultDecl))
+      continue;
+    // We are already sure that the triple is correct here.
+    if (getLookupName(ResultDecl, getUSR) != LookupFnName)
+      continue;
+    return ResultDecl;
+  }
+  return nullptr;
+}
+
+const FunctionDecl *ASTContext::getCTUDefinition(
+    const FunctionDecl *FD, StringRef CTUDir, std::string getUSR(const Decl *),
+    std::function<std::unique_ptr<clang::ASTUnit>(StringRef)> Loader) {
+  assert(!FD->hasBody() && "FD has a definition in current translation unit!");
+
+  std::string LookupFnName = getLookupName(FD, getUSR);
+  if (LookupFnName.empty())
+    return nullptr;
+  ASTUnit *Unit = nullptr;
+  auto FnUnitCacheEntry = FunctionAstUnitMap.find(LookupFnName);
+  if (FnUnitCacheEntry == FunctionAstUnitMap.end()) {
+    if (FunctionFileMap.empty()) {
+      SmallString<256> ExternalFunctionMap = CTUDir;
+      llvm::sys::path::append(ExternalFunctionMap, "externalFnMap.txt");
+      std::ifstream ExternalFnMapFile(ExternalFunctionMap.c_str());
+      if (!ExternalFnMapFile) {
+        llvm::errs() << "error: '" << ExternalFunctionMap
+                     << "' cannot be opened: falling back to non-CTU mode\n";
+        return nullptr;
+      }
+
+      std::string FunctionName, FileName;
+      std::string line;
+      while (std::getline(ExternalFnMapFile, line)) {
+        size_t pos = line.find(" ");
+        FunctionName = line.substr(0, pos);
+        FileName = line.substr(pos + 1);
+        SmallString<256> FilePath = CTUDir;
+        llvm::sys::path::append(FilePath, FileName);
+        FunctionFileMap[FunctionName] = FilePath.str().str();
+      }
+    }
+
+    StringRef ASTFileName;
+    auto It = FunctionFileMap.find(LookupFnName);
+    if (It == FunctionFileMap.end())
+      return nullptr; // No definition found even in some other build unit.
+    ASTFileName = It->second;
+    auto ASTCacheEntry = FileASTUnitMap.find(ASTFileName);
+    if (ASTCacheEntry == FileASTUnitMap.end()) {
+      std::unique_ptr<ASTUnit> LoadedUnit(Loader(ASTFileName));
+      Unit = LoadedUnit.get();
+      FileASTUnitMap[ASTFileName] = std::move(LoadedUnit);
+    } else {
+      Unit = ASTCacheEntry->second.get();
+    }
+    FunctionAstUnitMap[LookupFnName] = Unit;
+  } else {
+    Unit = FnUnitCacheEntry->second;
+  }
+
+  if (!Unit)
+    return nullptr;
+  assert(&Unit->getFileManager() ==
+         &Unit->getASTContext().getSourceManager().getFileManager());
+  ASTImporter &Importer = getOrCreateASTImporter(Unit->getASTContext());
+  TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl();
+  if (const FunctionDecl *ResultDecl =
+            findFunctionInDeclContext(TU, LookupFnName, getUSR)) {
+    // FIXME: Refactor const_cast
+    auto *ToDecl = cast<FunctionDecl>(
+        Importer.Import(const_cast<FunctionDecl *>(ResultDecl)));
+    assert(ToDecl->hasBody());
+    assert(FD->hasBody() && "Functions already imported should have body.");
+    return ToDecl;
+  }
+  return nullptr;
+}
+
+ASTImporter &ASTContext::getOrCreateASTImporter(ASTContext &From) {
+  auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl());
+  if (I != ASTUnitImporterMap.end())
+    return *I->second;
+  ASTImporter *NewImporter = new ASTImporter(
+        *this, getSourceManager().getFileManager(),
+        From, From.getSourceManager().getFileManager(), false);
+  ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter);
+  return *NewImporter;
+}
+
 CharUnits ASTContext::getDeclAlign(const Decl *D, bool ForAlignof) const {
   unsigned Align = Target->getCharWidth();
 
Index: include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
===================================================================
--- include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -56,6 +56,8 @@
   };
 
 private:
+  CompilerInstance &CI;
+
   AnalysisManager &AMgr;
   
   AnalysisDeclContextManager &AnalysisDeclContexts;
@@ -97,9 +99,8 @@
   InliningModes HowToInline;
 
 public:
-  ExprEngine(AnalysisManager &mgr, bool gcEnabled,
-             SetOfConstDecls *VisitedCalleesIn,
-             FunctionSummariesTy *FS,
+  ExprEngine(CompilerInstance &CI, AnalysisManager &mgr, bool gcEnabled,
+             SetOfConstDecls *VisitedCalleesIn, FunctionSummariesTy *FS,
              InliningModes HowToInlineIn);
 
   ~ExprEngine() override;
@@ -132,6 +133,8 @@
 
   BugReporter& getBugReporter() { return BR; }
 
+  CompilerInstance &getCompilerInstance() { return CI; }
+
   const NodeBuilderContext &getBuilderContext() {
     assert(currBldrCtx);
     return *currBldrCtx;
Index: include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
===================================================================
--- include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -436,20 +436,7 @@
     return cast<FunctionDecl>(CallEvent::getDecl());
   }
 
-  RuntimeDefinition getRuntimeDefinition() const override {
-    const FunctionDecl *FD = getDecl();
-    // Note that the AnalysisDeclContext will have the FunctionDecl with
-    // the definition (if one exists).
-    if (FD) {
-      AnalysisDeclContext *AD =
-        getLocationContext()->getAnalysisDeclContext()->
-        getManager()->getContext(FD);
-      if (AD->getBody())
-        return RuntimeDefinition(AD->getDecl());
-    }
-
-    return RuntimeDefinition();
-  }
+  RuntimeDefinition getRuntimeDefinition() const override;
 
   bool argumentsMayEscape() const override;
 
Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
===================================================================
--- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
+++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
@@ -272,6 +272,9 @@
   /// \sa shouldDisplayNotesAsEvents
   Optional<bool> DisplayNotesAsEvents;
 
+  /// \sa getCTUDir
+  Optional<StringRef> CTUDir;
+
   /// A helper function that retrieves option for a given full-qualified
   /// checker name.
   /// Options for checkers can be specified via 'analyzer-config' command-line
@@ -548,6 +551,9 @@
   /// to false when unset.
   bool shouldDisplayNotesAsEvents();
 
+  /// Returns the directory containing the CTU related files.
+  StringRef getCTUDir();
+
 public:
   AnalyzerOptions() :
     AnalysisStoreOpt(RegionStoreModel),
Index: include/clang/AST/ASTContext.h
===================================================================
--- include/clang/AST/ASTContext.h
+++ include/clang/AST/ASTContext.h
@@ -63,6 +63,7 @@
 #include <cassert>
 #include <cstddef>
 #include <cstdint>
+#include <functional>
 #include <iterator>
 #include <memory>
 #include <new>
@@ -78,12 +79,14 @@
 } // end namespace llvm
 
 namespace clang {
-
+class ASTImporter;
 class ASTMutationListener;
 class ASTRecordLayout;
+class ASTUnit;
 class AtomicExpr;
 class BlockExpr;
 class CharUnits;
+class CompilerInstance;
 class CXXABI;
 class DiagnosticsEngine;
 class Expr;
@@ -1902,6 +1905,23 @@
   }
 
   //===--------------------------------------------------------------------===//
+  //                         Cross-translation unit support
+  //===--------------------------------------------------------------------===//
+private:
+  llvm::StringMap<std::unique_ptr<clang::ASTUnit>> FileASTUnitMap;
+  llvm::StringMap<clang::ASTUnit *> FunctionAstUnitMap;
+  llvm::StringMap<std::string> FunctionFileMap;
+  llvm::DenseMap<TranslationUnitDecl *, std::unique_ptr<ASTImporter>>
+      ASTUnitImporterMap;
+  ASTImporter &getOrCreateASTImporter(ASTContext &From);
+
+public:
+  const FunctionDecl *getCTUDefinition(
+      const FunctionDecl *FD, StringRef CTUDir,
+      std::string getUSR(const Decl *),
+      std::function<std::unique_ptr<clang::ASTUnit>(StringRef)> Loader);
+
+  //===--------------------------------------------------------------------===//
   //                         Type Sizing and Analysis
   //===--------------------------------------------------------------------===//
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to