Last active
June 4, 2018 12:39
-
-
Save yak1ex/547e3bfb733e88b5190f3e83236ab455 to your computer and use it in GitHub Desktop.
Convert plain C array to C++ std::array by using clang::Tooling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Applies edits generated by a clang tool that was run on Chromium code. | |
| Synopsis: | |
| cat run_tool.out | extract_edits.py | apply_edits.py <build dir> <filters...> | |
| For example - to apply edits only to WTF sources: | |
| ... | apply_edits.py out/gn third_party/WebKit/Source/wtf | |
| In addition to filters specified on the command line, the tool also skips edits | |
| that apply to files that are not covered by git. | |
| """ | |
| import argparse | |
| import collections | |
| import functools | |
| import multiprocessing | |
| import os | |
| import os.path | |
| import subprocess | |
| import sys | |
| # monkey patch for cygwin | |
| realpath = os.path.realpath | |
| def myrealpath(path): | |
| prev = None | |
| while prev != path: | |
| prev = path | |
| path = realpath(path) | |
| return path | |
| os.path.realpath = myrealpath | |
| script_dir = os.path.dirname(os.path.realpath(__file__)) | |
| #tool_dir = os.path.abspath(os.path.join(script_dir, '../pylib')) | |
| #sys.path.insert(0, tool_dir) | |
| #from clang import compile_db | |
| Edit = collections.namedtuple('Edit', | |
| ('edit_type', 'offset', 'length', 'replacement')) | |
| def _GetFilesFromGit(paths=None): | |
| """Gets the list of files in the git repository. | |
| Args: | |
| paths: Prefix filter for the returned paths. May contain multiple entries. | |
| """ | |
| args = [] | |
| if sys.platform == 'win32': | |
| args.append('git.bat') | |
| else: | |
| args.append('git') | |
| args.append('ls-files') | |
| if paths: | |
| args.extend(paths) | |
| command = subprocess.Popen(args, stdout=subprocess.PIPE) | |
| output, _ = command.communicate() | |
| return [os.path.realpath(p) for p in output.splitlines()] | |
| def _GetFilesFromSvn(paths=None): | |
| """Gets the list of files in the svn repository. | |
| Args: | |
| paths: Currently not supported. | |
| """ | |
| args = ['svn', 'ls'] | |
| if paths: | |
| args.extend(paths) | |
| command = subprocess.Popen(args, stdout=subprocess.PIPE) | |
| output, _ = command.communicate() | |
| return [os.path.realpath(p) for p in output.splitlines()] | |
| def _GetFilesFromFind(paths=None): | |
| """Gets the list of files in the directory. | |
| Args: | |
| paths: Arguments passed to find. "-type f" is always prepended. | |
| """ | |
| args = ['find', '.', '-type', 'f'] | |
| if paths: | |
| args.extend(paths) | |
| command = subprocess.Popen(args, stdout=subprocess.PIPE) | |
| output, _ = command.communicate() | |
| return [os.path.realpath(p) for p in output.splitlines()] | |
| def _ParseEditsFromStdin(build_directory): | |
| """Extracts generated list of edits from the tool's stdout. | |
| The expected format is documented at the top of this file. | |
| Args: | |
| build_directory: Directory that contains the compile database. Used to | |
| normalize the filenames. | |
| stdout: The stdout from running the clang tool. | |
| Returns: | |
| A dictionary mapping filenames to the associated edits. | |
| """ | |
| path_to_resolved_path = {} | |
| def _ResolvePath(path): | |
| if path in path_to_resolved_path: | |
| return path_to_resolved_path[path] | |
| if not os.path.isfile(path): | |
| resolved_path = os.path.realpath(os.path.join(build_directory, path)) | |
| else: | |
| resolved_path = path | |
| if not os.path.isfile(resolved_path): | |
| sys.stderr.write('Edit applies to a non-existent file: %s\n' % path) | |
| resolved_path = None | |
| path_to_resolved_path[path] = resolved_path | |
| return resolved_path | |
| edits = collections.defaultdict(list) | |
| for line in sys.stdin: | |
| line = line.rstrip("\n\r") | |
| try: | |
| edit_type, path, offset, length, replacement = line.split(':::', 4) | |
| replacement = replacement.replace('\0', '\n') | |
| path = _ResolvePath(path) | |
| if not path: continue | |
| edits[path].append(Edit(edit_type, int(offset), int(length), replacement)) | |
| except ValueError: | |
| sys.stderr.write('Unable to parse edit: %s\n' % line) | |
| return edits | |
| def _ApplyEditsToSingleFile(filename, edits): | |
| # Sort the edits and iterate through them in reverse order. Sorting allows | |
| # duplicate edits to be quickly skipped, while reversing means that | |
| # subsequent edits don't need to have their offsets updated with each edit | |
| # applied. | |
| edit_count = 0 | |
| error_count = 0 | |
| edits.sort() | |
| last_edit = None | |
| with open(filename, 'rb+') as f: | |
| contents = bytearray(f.read()) | |
| for edit in reversed(edits): | |
| if edit == last_edit: | |
| continue | |
| if (last_edit is not None and edit.edit_type == last_edit.edit_type and | |
| edit.offset == last_edit.offset and edit.length == last_edit.length): | |
| sys.stderr.write( | |
| 'Conflicting edit: %s at offset %d, length %d: "%s" != "%s"\n' % | |
| (filename, edit.offset, edit.length, edit.replacement, | |
| last_edit.replacement)) | |
| error_count += 1 | |
| continue | |
| last_edit = edit | |
| contents[edit.offset:edit.offset + edit.length] = edit.replacement | |
| if not edit.replacement: | |
| _ExtendDeletionIfElementIsInList(contents, edit.offset) | |
| edit_count += 1 | |
| f.seek(0) | |
| f.truncate() | |
| f.write(contents) | |
| return (edit_count, error_count) | |
| def _ApplyEdits(edits): | |
| """Apply the generated edits. | |
| Args: | |
| edits: A dict mapping filenames to Edit instances that apply to that file. | |
| """ | |
| edit_count = 0 | |
| error_count = 0 | |
| done_files = 0 | |
| for k, v in edits.iteritems(): | |
| tmp_edit_count, tmp_error_count = _ApplyEditsToSingleFile(k, v) | |
| edit_count += tmp_edit_count | |
| error_count += tmp_error_count | |
| done_files += 1 | |
| percentage = (float(done_files) / len(edits)) * 100 | |
| sys.stdout.write('Applied %d edits (%d errors) to %d files [%.2f%%]\r' % | |
| (edit_count, error_count, done_files, percentage)) | |
| sys.stdout.write('\n') | |
| return -error_count | |
| _WHITESPACE_BYTES = frozenset((ord('\t'), ord('\n'), ord('\r'), ord(' '))) | |
| def _ExtendDeletionIfElementIsInList(contents, offset): | |
| """Extends the range of a deletion if the deleted element was part of a list. | |
| This rewriter helper makes it easy for refactoring tools to remove elements | |
| from a list. Even if a matcher callback knows that it is removing an element | |
| from a list, it may not have enough information to accurately remove the list | |
| element; for example, another matcher callback may end up removing an adjacent | |
| list element, or all the list elements may end up being removed. | |
| With this helper, refactoring tools can simply remove the list element and not | |
| worry about having to include the comma in the replacement. | |
| Args: | |
| contents: A bytearray with the deletion already applied. | |
| offset: The offset in the bytearray where the deleted range used to be. | |
| """ | |
| char_before = char_after = None | |
| left_trim_count = 0 | |
| for byte in reversed(contents[:offset]): | |
| left_trim_count += 1 | |
| if byte in _WHITESPACE_BYTES: | |
| continue | |
| if byte in (ord(','), ord(':'), ord('('), ord('{')): | |
| char_before = chr(byte) | |
| break | |
| right_trim_count = 0 | |
| for byte in contents[offset:]: | |
| right_trim_count += 1 | |
| if byte in _WHITESPACE_BYTES: | |
| continue | |
| if byte == ord(','): | |
| char_after = chr(byte) | |
| break | |
| if char_before: | |
| if char_after: | |
| del contents[offset:offset + right_trim_count] | |
| elif char_before in (',', ':'): | |
| del contents[offset - left_trim_count:offset] | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| '-p', | |
| required=True, | |
| help='path to the build dir (dir that edit paths are relative to)') | |
| parser.add_argument( | |
| '-t', | |
| default='git', | |
| help='target path check method git, svn, find (default: git)') | |
| parser.add_argument( | |
| 'path_filter', | |
| nargs='*', | |
| help='optional paths to filter what files the tool is run on') | |
| args = parser.parse_args() | |
| if args.t == 'git': | |
| filenames = set(_GetFilesFromGit(args.path_filter)) | |
| elif args.t == 'svn': | |
| filenames = set(_GetFilesFromSvn(args.path_filter)) | |
| elif args.t == 'find': | |
| filenames = set(_GetFilesFromFind(args.path_filter)) | |
| else: | |
| sys.stderr.write('Unknown -t argument: %s' % (args.t)) | |
| sys.exit(1) | |
| edits = _ParseEditsFromStdin(args.p) | |
| return _ApplyEdits( | |
| {k: v for k, v in edits.iteritems() | |
| if os.path.realpath(k) in filenames}) | |
| if __name__ == '__main__': | |
| sys.exit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import sys | |
| import re | |
| import os.path | |
| import itertools | |
| import subprocess | |
| import xml.etree.ElementTree as ET | |
| import ntpath | |
| # TODO: Set C++ compile mode by using etree like replacing | |
| # <ItemGroup> | |
| # <ClCompile Include="Wrapper\g_ior\g_iorfb_cmng.c" /> | |
| # </ItemGroup> | |
| # with | |
| # <ItemGroup> | |
| # <ClCompile Include="Wrapper\g_ior\g_iorfb_cmng.c"> | |
| # <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">CompileAsCpp</CompileAs> | |
| # </ClCompile> | |
| # </ItemGroup> | |
| def find_upward(file, target): | |
| dir = '' | |
| newdir = os.path.dirname(file) | |
| while dir != newdir: | |
| dir = newdir | |
| path = dir + '/' + target | |
| if os.path.exists(path): | |
| return path | |
| else: | |
| newdir = os.path.dirname(dir) | |
| return '' | |
| def process(file): | |
| targets = [] | |
| input = open(file) | |
| line = input.readline() | |
| while line: | |
| m = re.match(r"#include \"([^.]*\.c)\"", line) | |
| if m: | |
| targets.append(m.group(1)) | |
| line = input.readline() | |
| return targets | |
| def make_compiler_flags(gpj): | |
| input = open(gpj) | |
| dir = os.path.dirname(gpj) | |
| print dir + '/compile_flags.txt' | |
| output = open(dir + '/compiler_flags.txt', 'w') | |
| line = input.readline() | |
| while line: | |
| m = re.match(r"\s*-I\s*(.*)", line) | |
| if m: | |
| output.write('-I'+m.group(1)+"\n") | |
| line = input.readline() | |
| output.write("-D__farcall=\n-D__ELF__=1\n") | |
| def make_pathmap(root): | |
| return {f: d+'/'+f for d, sd, fl in os.walk(root + '/User/App') for f in fl if f[-2:] == '.c'} | |
| def main(): | |
| # prj = find_upward(sys.argv[1], 'APA.gpj') | |
| # make_compiler_flags(prj) | |
| # root = os.path.dirname(prj) | |
| # pathmap = make_pathmap(root) | |
| # for i in itertools.chain.from_iterable(map(process, sys.argv[1:])): | |
| # subprocess.call(['./bin/tool-template.exe', pathmap[i]]) | |
| # FIXME: wrong targets, should be map(basename, sys.argv[1:]) | |
| target = {f: 1 for f in itertools.chain.from_iterable(map(process, sys.argv[1:]))} | |
| vcxproj = find_upward(sys.argv[1], 'CSRApp.vcxproj') | |
| tree = ET.parse(vcxproj) | |
| ns = {'msb': 'http://schemas.microsoft.com/developer/msbuild/2003'} | |
| for e in tree.getroot().findall('./msb:ItemGroup/msb:ClCompile', ns): | |
| if ntpath.basename(e.get('Include')) in target: | |
| e.append(ET.fromstring('<CompileAs xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Condition="'+"'"+'$(Configuration)|$(Platform)'+"'"+'=='+"'"+'Debug|Win32'+"'"+'">CompileAsCpp</CompileAs>')) | |
| tree.write('test.xml') | |
| # FIXME: xmlns:ns0 and ns0: | |
| if __name__ == '__main__': | |
| sys.exit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| # Copyright (c) 2016 The Chromium Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Script to extract edits from clang tool output. | |
| If a clang tool emits edits, then the edits should look like this: | |
| ... | |
| ==== BEGIN EDITS ==== | |
| <edit1> | |
| <edit2> | |
| ... | |
| ==== END EDITS ==== | |
| ... | |
| extract_edits.py takes input that is concatenated from multiple tool invocations | |
| and extract just the edits. In other words, given the following input: | |
| ... | |
| ==== BEGIN EDITS ==== | |
| <edit1> | |
| <edit2> | |
| ==== END EDITS ==== | |
| ... | |
| ==== BEGIN EDITS ==== | |
| <yet another edit1> | |
| <yet another edit2> | |
| ==== END EDITS ==== | |
| ... | |
| extract_edits.py would emit the following output: | |
| <edit1> | |
| <edit2> | |
| <yet another edit1> | |
| <yet another edit2> | |
| This python script is mainly needed on Windows. | |
| On unix this script can be replaced with running sed as follows: | |
| $ cat run_tool.debug.out \ | |
| | sed '/^==== BEGIN EDITS ====$/,/^==== END EDITS ====$/{//!b};d' | |
| | sort | uniq | |
| """ | |
| import sys | |
| def main(): | |
| # TODO(dcheng): extract_edits.py should normalize paths. Doing this in | |
| # apply_edits.py is too late, as a common use case is to apply edits from many | |
| # different platforms. | |
| unique_lines = set() | |
| inside_marker_lines = False | |
| for line in sys.stdin: | |
| line = line.rstrip("\n\r") | |
| if line == '==== BEGIN EDITS ====': | |
| inside_marker_lines = True | |
| continue | |
| if line == '==== END EDITS ====': | |
| inside_marker_lines = False | |
| continue | |
| if inside_marker_lines and line not in unique_lines: | |
| unique_lines.add(line) | |
| print line | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //===---- tools/extra/ToolTemplate.cpp - Template for refactoring tool ----===// | |
| // | |
| // The LLVM Compiler Infrastructure | |
| // | |
| // This file is distributed under the University of Illinois Open Source | |
| // License. See LICENSE.TXT for details. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements an empty refactoring tool using the clang tooling. | |
| // The goal is to lower the "barrier to entry" for writing refactoring tools. | |
| // | |
| // Usage: | |
| // tool-template <cmake-output-dir> <file1> <file2> ... | |
| // | |
| // Where <cmake-output-dir> is a CMake build directory in which a file named | |
| // compile_commands.json exists (enable -DCMAKE_EXPORT_COMPILE_COMMANDS in | |
| // CMake to get this output). | |
| // | |
| // <file1> ... specify the paths of files in the CMake source tree. This path | |
| // is looked up in the compile command database. If the path of a file is | |
| // absolute, it needs to point into CMake's source tree. If the path is | |
| // relative, the current working directory needs to be in the CMake source | |
| // tree and the file must be in a subdirectory of the current working | |
| // directory. "./" prefixes in the relative files will be automatically | |
| // removed, but the rest of a relative path must be a suffix of a path in | |
| // the compile command line database. | |
| // | |
| // For example, to use tool-template on all files in a subtree of the | |
| // source tree, use: | |
| // | |
| // /path/in/subtree $ find . -name '*.cpp'| | |
| // xargs tool-template /path/to/build | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "clang/ASTMatchers/ASTMatchFinder.h" | |
| #include "clang/ASTMatchers/ASTMatchers.h" | |
| #include "clang/Basic/SourceManager.h" | |
| #include "clang/Frontend/FrontendActions.h" | |
| #include "clang/Frontend/CompilerInstance.h" | |
| #include "clang/Lex/Lexer.h" | |
| #include "clang/Lex/PPCallbacks.h" | |
| #include "clang/Tooling/CommonOptionsParser.h" | |
| #include "clang/Tooling/Execution.h" | |
| #include "clang/Tooling/Refactoring.h" | |
| #include "clang/Tooling/Refactoring/AtomicChange.h" | |
| #include "clang/Tooling/Tooling.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/Support/MemoryBuffer.h" | |
| #include "llvm/Support/Signals.h" | |
| namespace clang { | |
| namespace ast_matchers { | |
| AST_POLYMORPHIC_MATCHER(isExpansionInMainFileBase, | |
| AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc)) { | |
| auto &SourceManager = Finder->getASTContext().getSourceManager(); | |
| auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart()); | |
| if (ExpansionLoc.isInvalid()) { | |
| return false; | |
| } | |
| auto FileEntry = | |
| SourceManager.getFileEntryForID(SourceManager.getFileID(ExpansionLoc)); | |
| if (!FileEntry) { | |
| return false; | |
| } | |
| auto MainFileEntry = SourceManager.getFileEntryForID(SourceManager.getMainFileID()); | |
| if (!MainFileEntry) { | |
| return false; | |
| } | |
| // FIXME: Make sure to be absolute path, instead of just calling tryGetRealPathName() | |
| // ref: https://github.com/llvm-mirror/clang-tools-extra/blob/master/clang-move/ClangMove.cpp | |
| auto MainFilename = MainFileEntry->tryGetRealPathName(); | |
| auto Filename = FileEntry->tryGetRealPathName(); | |
| auto LastDotMain = MainFilename.find_last_of('.'); | |
| auto LastDot = Filename.find_last_of('.'); | |
| return MainFilename.substr(0, LastDotMain) == Filename.substr(0, LastDot); | |
| } | |
| } // namespace ast_matchers | |
| } // namespace clang | |
| using namespace clang; | |
| using namespace clang::ast_matchers; | |
| using namespace clang::tooling; | |
| using namespace llvm; | |
| // TODO: Fix passed to arrayify function with reference to std::array | |
| // ref. https://chromium.googlesource.com/chromium/src/+/lkcr/docs/clang_tool_refactoring.md | |
| // https://chromium.googlesource.com/chromium/src/+/master/tools/clang/scripts/extract_edits.py | |
| // https://chromium.googlesource.com/chromium/src/+/master/tools/clang/scripts/apply_edits.py | |
| namespace { | |
| typedef std::map<std::string, Replacements> AllReplacements; // one Replacements holds changes in one file | |
| class InclusionCheck : public PPCallbacks { | |
| public: | |
| InclusionCheck(SourceManager &SourceManager_, AllReplacements &AllReplacements) : SourceManager_(SourceManager_), AllReplacements_(AllReplacements), Level(0), Seen(false), Left(false) {} | |
| void InclusionDirective (SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, StringRef SearchPath, StringRef RelativePath, const clang::Module *Imported) override { | |
| if(SourceManager_.isInMainFile(HashLoc)) { | |
| if(!Seen) { | |
| AtomicChange Change(SourceManager_, HashLoc); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(SourceManager_, CharSourceRange::getCharRange(SourceRange(HashLoc, HashLoc)), std::string("#ifdef __cplusplus")+'\0'+"extern \"C\" {"+'\0'+"#endif"+'\0')); | |
| } | |
| if(Level == 0) { | |
| End = FilenameRange.getEnd(); | |
| Token tok; | |
| if(!Lexer::getRawToken(End, tok, SourceManager_, LangOptions(), true)) { | |
| End = tok.getLocation(); | |
| } | |
| } else { | |
| Left = true; | |
| } | |
| Seen = true; | |
| } | |
| } | |
| void If(SourceLocation Loc, SourceRange ConditionRange, ConditionValueKind ConditionValue) override { | |
| if(SourceManager_.isInMainFile(Loc)) ++Level; | |
| } | |
| void Endif(SourceLocation Loc, SourceLocation IfLoc) override { | |
| if(SourceManager_.isInMainFile(Loc)) --Level; | |
| if(Left && Level == 0) { | |
| Token tok; | |
| Lexer::getRawToken(Loc, tok, SourceManager_, LangOptions(), true); | |
| End = tok.getEndLoc(); | |
| if(!Lexer::getRawToken(End, tok, SourceManager_, LangOptions(), true)) { | |
| End = tok.getLocation(); | |
| } | |
| Left = false; | |
| } | |
| } | |
| void EndOfMainFile() override { | |
| AtomicChange Change(SourceManager_, End); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(SourceManager_, CharSourceRange::getCharRange(SourceRange(End, End)), std::string("#ifdef __cplusplus")+'\0'+"}"+'\0'+"#endif"+'\0')); | |
| } | |
| private: | |
| SourceManager &SourceManager_; | |
| AllReplacements &AllReplacements_; | |
| SourceLocation End; | |
| int Level; | |
| bool Seen; | |
| bool Left; | |
| }; | |
| class InclusionChecker : public SourceFileCallbacks { | |
| public: | |
| InclusionChecker(AllReplacements &AllReplacements) : AllReplacements_(AllReplacements) {} | |
| bool handleBeginSource(CompilerInstance &Compiler) override { | |
| std::unique_ptr<PPCallbacks> p = make_unique<InclusionCheck>(Compiler.getSourceManager(), AllReplacements_); | |
| Compiler.getPreprocessor().addPPCallbacks(std::move(p)); | |
| return true; | |
| } | |
| private: | |
| AllReplacements &AllReplacements_; | |
| }; | |
| class ToolTemplateCallback : public MatchFinder::MatchCallback { | |
| public: | |
| ToolTemplateCallback(ExecutionContext &Context, AllReplacements &AllReplacements) : Context(Context), AllReplacements_(AllReplacements), first(true) {} | |
| void run(const MatchFinder::MatchResult &Result) override { | |
| #if 0 /* emitting #include <array> is placed at wrapper */ | |
| if (first) { | |
| auto loc = Result.SourceManager->getLocForStartOfFile(Result.SourceManager->getMainFileID()); | |
| AtomicChange Change(*Result.SourceManager, loc); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(*Result.SourceManager, CharSourceRange::getCharRange(SourceRange(loc, loc)), std::string("#include <array>")+'\0')); | |
| first = false; | |
| } | |
| #endif | |
| // TODO: This routine will get called for each thing that the matchers | |
| // find. | |
| // At this point, you can examine the match, and do whatever you want, | |
| // including replacing the matched text with other text | |
| auto *D = Result.Nodes.getNodeAs<ValueDecl>("decl"); | |
| // assert(D); | |
| // Use AtomicChange to get a key. | |
| if (D && D->getLocStart().isValid()) { // Declarations | |
| AtomicChange Change(*Result.SourceManager, D->getLocStart()); | |
| // Context.reportResult(Change.getKey(), D->getNameAsString()); | |
| // Context.reportResult(Change.getKey()+"QN", D->getQualifiedNameAsString()); | |
| // Context.reportResult(Change.getKey()+"type", D->getType().getAsString()); | |
| // Context.reportResult(Change.getKey()+"convtype", convType(D->getType().getAsString(), false)); | |
| Context.reportResult(Change.getKey()+"decl", getSourceFragment(D, *Result.SourceManager)); | |
| auto *ot = D->getType().getTypePtr()->getAs<DecayedType>(); | |
| if (ot) { // (decaying) parameter | |
| // Context.reportResult(Change.getKey()+"otype", ot->getOriginalType().getAsString()); | |
| if (ot->getOriginalType().getTypePtr()->isConstantArrayType()) { | |
| // Context.reportResult(Change.getKey()+"convotype", convType(ot->getOriginalType().getAsString(), true)); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(*Result.SourceManager, CharSourceRange::getTokenRange(D->getSourceRange()), convType(ot->getOriginalType().getAsString(), true) + " " + D->getNameAsString())); | |
| } else if (ot->getOriginalType().getTypePtr()->isArrayType()) { | |
| Context.reportResult(Change.getKey()+"WARN", "Unknown bounds array parameter"); | |
| } | |
| } else { // not (decaying) parameter | |
| auto *DD = Result.Nodes.getNodeAs<VarDecl>("decl"); | |
| if (DD && DD->hasInit()) { // variable declaration with initializer | |
| // auto init = DD->getInit(); | |
| // Context.reportResult(Change.getKey()+"init", getSourceFragment(init, *Result.SourceManager)); | |
| auto range = getRangeWithInit(DD, *Result.SourceManager); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(*Result.SourceManager, CharSourceRange::getTokenRange(range[0]), convType(DD->getType().getAsString(), false) + " " + DD->getNameAsString())); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(*Result.SourceManager, CharSourceRange::getTokenRange(range[1]), "{{")); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(*Result.SourceManager, CharSourceRange::getTokenRange(range[2]), "}}")); | |
| } else { // variable declaration w/o initializer or field declaration | |
| AllReplacements_[Change.getFilePath()].add(Replacement(*Result.SourceManager, CharSourceRange::getTokenRange(D->getSourceRange()), convType(D->getType().getAsString(), false) + " " + D->getNameAsString())); | |
| } | |
| } | |
| } | |
| auto *E = Result.Nodes.getNodeAs<Expr>("expr"); | |
| auto *RE = Result.Nodes.getNodeAs<Expr>("base"); | |
| if (E && E->getLocStart().isValid() && RE) { // address taken // TODO: limit in call expression | |
| AtomicChange Change(*Result.SourceManager, E->getLocStart()); | |
| Context.reportResult(Change.getKey()+"expr", getSourceFragment(E, *Result.SourceManager)); | |
| // Context.reportResult(Change.getKey()+"base", getSourceFragment(RE, *Result.SourceManager)); | |
| AllReplacements_[Change.getFilePath()].add(Replacement(*Result.SourceManager, CharSourceRange::getTokenRange(E->getSourceRange()), getSourceFragment(RE, *Result.SourceManager))); | |
| } | |
| } | |
| void onStartOfTranslationUnit() override { | |
| Context.reportResult("START", "Start of TU."); | |
| } | |
| void onEndOfTranslationUnit() override { | |
| Context.reportResult("END", "End of TU."); | |
| } | |
| private: | |
| // based on https://stackoverflow.com/a/23081770 | |
| template<typename T> | |
| std::string getSourceFragment(const T* t, SourceManager &sm) { | |
| if (!t) return {}; | |
| std::string text = Lexer::getSourceText(CharSourceRange::getTokenRange(t->getSourceRange()), sm, LangOptions(), 0); | |
| if (text.size() > 0 && (text.at(text.size()-1) == ',')) // the text can be "" | |
| return Lexer::getSourceText(CharSourceRange::getCharRange(t->getSourceRange()), sm, LangOptions(), 0); | |
| return text; | |
| } | |
| std::array<SourceRange, 3> getRangeWithInit(const VarDecl *d, SourceManager &sm) { | |
| auto loc = d->getLocStart(), locEnd = d->getLocEnd(); | |
| auto initStart = d->getInit()->getLocStart(); | |
| Token tok; | |
| SourceLocation slEnd; | |
| SourceRange srLBrace, srRBrace; | |
| bool inInit = false, passedLBrace = false; | |
| while (sm.isBeforeInTranslationUnit(loc, locEnd)) { | |
| Lexer::getRawToken(loc, tok, sm, LangOptions(), true); | |
| if (!passedLBrace && tok.is(tok::l_brace)) { | |
| srLBrace = SourceRange(tok.getLocation(), tok.getLastLoc()); | |
| passedLBrace = true; | |
| } | |
| if (tok.is(tok::r_brace)) { | |
| srRBrace = SourceRange(tok.getLocation(), tok.getLastLoc()); | |
| } | |
| if (!inInit && sm.isBeforeInTranslationUnit(loc, initStart)) { | |
| if (!tok.is(tok::equal) && !tok.is(tok::l_brace)) { | |
| slEnd = tok.getLastLoc(); | |
| } | |
| } else { | |
| inInit = true; | |
| } | |
| loc = tok.getEndLoc(); | |
| } | |
| return { SourceRange(d->getLocStart(), slEnd), srLBrace, srRBrace }; | |
| } | |
| std::string convType(const std::string &s, bool isRef) { | |
| if (s.size() == 0) return {}; | |
| std::vector<std::string> bounds; | |
| auto itTemp = s.end(); | |
| bool inBounds = false; | |
| for (auto it = --s.end(); it != s.begin(); --it) { | |
| switch (*it) { | |
| case ']': | |
| inBounds = true; | |
| itTemp = it; | |
| break; | |
| case '[': | |
| bounds.emplace_back(it + 1, itTemp); | |
| inBounds = false; | |
| break; | |
| case ' ': | |
| case '\t': | |
| // skip | |
| break; | |
| default: | |
| if (!inBounds) { | |
| itTemp = it + 1; | |
| it = s.begin() + 1; // break for loop | |
| } | |
| break; | |
| } | |
| } | |
| std::string elemType(s.begin(), itTemp); | |
| for(auto bound: bounds) { | |
| elemType = std::string("std::array<") + elemType + ", " + bound +">"; | |
| } | |
| if (isRef && bounds.size()) elemType += " &"; | |
| return elemType; | |
| } | |
| ExecutionContext &Context; | |
| AllReplacements &AllReplacements_; | |
| bool first; | |
| }; | |
| } // end anonymous namespace | |
| // Set up the command line options | |
| static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); | |
| static cl::OptionCategory ToolTemplateCategory("tool-template options"); | |
| int main(int argc, const char **argv) { | |
| llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); | |
| auto Executor = clang::tooling::createExecutorFromCommandLineArgs( | |
| argc, argv, ToolTemplateCategory); | |
| if (!Executor) { | |
| llvm::errs() << llvm::toString(Executor.takeError()) << "\n"; | |
| return 1; | |
| } | |
| ast_matchers::MatchFinder Finder; | |
| AllReplacements AllReplacements_; | |
| ToolTemplateCallback Callback(*Executor->get()->getExecutionContext(), AllReplacements_); | |
| // TODO: Put your matchers here. | |
| // Use Finder.addMatcher(...) to define the patterns in the AST that you | |
| // want to match against. You are not limited to just one matcher! | |
| // | |
| // Finder.addMatcher( | |
| // varDecl(hasType(qualType(hasCanonicalType(pointerType(pointee(constantArrayType())))))).bind("decl"), | |
| // &Callback); | |
| // for arrays in parameter | |
| Finder.addMatcher( | |
| parmVarDecl(isExpansionInMainFileBase(), hasType(decayedType(hasDecayedType(pointerType(unless(pointee(functionType()))))))).bind("decl"), | |
| &Callback); | |
| // for array vars | |
| Finder.addMatcher( | |
| varDecl(isExpansionInMainFileBase(), hasType(constantArrayType())).bind("decl"), | |
| &Callback); | |
| // for array as fields | |
| Finder.addMatcher( | |
| fieldDecl(isExpansionInMainFileBase(), hasType(constantArrayType())).bind("decl"), | |
| &Callback); | |
| // for pass by &array[0] | |
| Finder.addMatcher( | |
| expr(isExpansionInMainFileBase(), unaryOperator(hasOperatorName("&"), hasUnaryOperand(arraySubscriptExpr(hasBase(implicitCastExpr(hasSourceExpression(declRefExpr().bind("base")))), hasIndex(integerLiteral(equals(0))))))).bind("expr"), | |
| &Callback); | |
| // for pass by &array[]...[0] | |
| Finder.addMatcher( | |
| expr(isExpansionInMainFileBase(), unaryOperator(hasOperatorName("&"), hasUnaryOperand(arraySubscriptExpr(hasBase(implicitCastExpr(hasSourceExpression(arraySubscriptExpr().bind("base")))), hasIndex(integerLiteral(equals(0))))))).bind("expr"), | |
| &Callback); | |
| InclusionChecker InclusionChecker_(AllReplacements_); | |
| auto Err = Executor->get()->execute(newFrontendActionFactory(&Finder, &InclusionChecker_)); | |
| if (Err) { | |
| llvm::errs() << llvm::toString(std::move(Err)) << "\n"; | |
| } | |
| Executor->get()->getToolResults()->forEachResult( | |
| [](llvm::StringRef key, llvm::StringRef value) { | |
| llvm::errs() << "----" << key.str() << "\n" << value.str() << "\n"; | |
| }); | |
| llvm::outs() << "==== BEGIN EDITS ====\n"; | |
| for (const auto& Replacements_ : AllReplacements_) { | |
| for (const auto &r : Replacements_.second) { | |
| llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset() | |
| << ":::" << r.getLength() << ":::" << r.getReplacementText() | |
| << "\n"; | |
| } | |
| } | |
| llvm::outs() << "==== END EDITS ====\n"; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment