mirror of https://gitlab.com/cppit/jucipp
34 changed files with 1565 additions and 298 deletions
@ -1 +1 @@ |
|||||||
Subproject commit 955c113c73ecdfbf5fecc1c0b8fef537693c2f25 |
Subproject commit b242f8d6a73cb211ea9faa9bbfd4f444e0afbf0a |
||||||
@ -0,0 +1,714 @@ |
|||||||
|
#include "usages_clang.h" |
||||||
|
#include "compile_commands.h" |
||||||
|
#include "config.h" |
||||||
|
#include "dialogs.h" |
||||||
|
#include "filesystem.h" |
||||||
|
#include <chrono> |
||||||
|
#include <fstream> |
||||||
|
#include <regex> |
||||||
|
#include <thread> |
||||||
|
|
||||||
|
// #include <iostream> //TODO: remove
|
||||||
|
|
||||||
|
const boost::filesystem::path Usages::Clang::cache_folder = ".usages_clang"; |
||||||
|
std::map<boost::filesystem::path, Usages::Clang::Cache> Usages::Clang::caches; |
||||||
|
std::mutex Usages::Clang::caches_mutex; |
||||||
|
std::atomic<size_t> Usages::Clang::cache_in_progress_count(0); |
||||||
|
|
||||||
|
bool Usages::Clang::Cache::Cursor::operator==(const Cursor &o) { |
||||||
|
for(auto &usr : usrs) { |
||||||
|
if(o.usrs.count(usr)) |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
Usages::Clang::Cache::Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||||
|
std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens) |
||||||
|
: project_path(project_path), build_path(build_path) { |
||||||
|
for(auto &clang_token : *clang_tokens) { |
||||||
|
tokens.emplace_back(Token{clang_token.get_spelling(), clang_token.get_source_range().get_offsets(), static_cast<size_t>(-1)}); |
||||||
|
|
||||||
|
if(clang_token.is_identifier()) { |
||||||
|
auto clang_cursor = clang_token.get_cursor().get_referenced(); |
||||||
|
if(clang_cursor) { |
||||||
|
Cursor cursor{clang_cursor.get_kind(), clang_cursor.get_all_usr_extended()}; |
||||||
|
for(size_t c = 0; c < cursors.size(); ++c) { |
||||||
|
if(cursor == cursors[c]) { |
||||||
|
tokens.back().cursor_id = c; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if(tokens.back().cursor_id == static_cast<size_t>(-1)) { |
||||||
|
cursors.emplace_back(cursor); |
||||||
|
tokens.back().cursor_id = cursors.size() - 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
boost::system::error_code ec; |
||||||
|
auto last_write_time = boost::filesystem::last_write_time(path, ec); |
||||||
|
if(ec) |
||||||
|
last_write_time = 0; |
||||||
|
if(last_write_time > before_parse_time) |
||||||
|
last_write_time = 0; |
||||||
|
paths_and_last_write_times.emplace(path, last_write_time); |
||||||
|
|
||||||
|
class VisitorData { |
||||||
|
public: |
||||||
|
const boost::filesystem::path &project_path; |
||||||
|
const boost::filesystem::path &path; |
||||||
|
std::time_t before_parse_time; |
||||||
|
std::map<boost::filesystem::path, std::time_t> &paths_and_last_write_times; |
||||||
|
}; |
||||||
|
VisitorData visitor_data{project_path, path, before_parse_time, paths_and_last_write_times}; |
||||||
|
|
||||||
|
clang_getInclusions(translation_unit->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData data) { |
||||||
|
auto visitor_data = static_cast<VisitorData *>(data); |
||||||
|
auto path = filesystem::get_normal_path(clangmm::to_string(clang_getFileName(included_file))); |
||||||
|
if(filesystem::file_in_path(path, visitor_data->project_path)) { |
||||||
|
for(unsigned c = 0; c < include_len; ++c) { |
||||||
|
auto from_path = filesystem::get_normal_path(clangmm::SourceLocation(inclusion_stack[c]).get_path()); |
||||||
|
if(from_path == visitor_data->path) { |
||||||
|
boost::system::error_code ec; |
||||||
|
auto last_write_time = boost::filesystem::last_write_time(path, ec); |
||||||
|
if(ec) |
||||||
|
last_write_time = 0; |
||||||
|
if(last_write_time > visitor_data->before_parse_time) |
||||||
|
last_write_time = 0; |
||||||
|
visitor_data->paths_and_last_write_times.emplace(path, last_write_time); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
&visitor_data); |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, |
||||||
|
const std::unordered_set<std::string> &usrs) const { |
||||||
|
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; |
||||||
|
for(auto &token : tokens) { |
||||||
|
if(token.cursor_id != static_cast<size_t>(-1)) { |
||||||
|
auto &cursor = cursors[token.cursor_id]; |
||||||
|
if(clangmm::Cursor::is_similar_kind(cursor.kind, kind) && token.spelling == spelling) { |
||||||
|
for(auto &usr : cursor.usrs) { |
||||||
|
if(usrs.count(usr)) { |
||||||
|
offsets.emplace_back(token.offsets); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return offsets; |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<Usages::Clang::Usages> Usages::Clang::get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path, |
||||||
|
const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units) { |
||||||
|
std::vector<Usages> usages; |
||||||
|
|
||||||
|
if(spelling.empty()) |
||||||
|
return usages; |
||||||
|
|
||||||
|
PathSet visited; |
||||||
|
|
||||||
|
auto usr_extended = cursor.get_usr_extended(); |
||||||
|
if(!usr_extended.empty() && usr_extended[0] >= '0' && usr_extended[0] <= '9') { //if declared within a function, return
|
||||||
|
if(!translation_units.empty()) |
||||||
|
add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_units.front(), false); |
||||||
|
return usages; |
||||||
|
} |
||||||
|
|
||||||
|
for(auto &translation_unit : translation_units) |
||||||
|
add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_unit, false); |
||||||
|
|
||||||
|
for(auto &translation_unit : translation_units) |
||||||
|
add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, translation_unit, false); |
||||||
|
|
||||||
|
if(project_path.empty()) |
||||||
|
return usages; |
||||||
|
|
||||||
|
auto paths = find_paths(project_path, build_path, debug_path); |
||||||
|
auto pair = parse_paths(spelling, paths); |
||||||
|
auto pair2 = find_potential_paths(cursor.get_canonical().get_source_location().get_path(), project_path, pair.first, pair.second); |
||||||
|
auto &potential_paths = pair2.first; |
||||||
|
auto &all_includes = pair2.second; |
||||||
|
|
||||||
|
// Remove visited paths
|
||||||
|
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||||
|
if(visited.find(*it) != visited.end()) |
||||||
|
it = potential_paths.erase(it); |
||||||
|
else |
||||||
|
++it; |
||||||
|
} |
||||||
|
|
||||||
|
// Wait for current caching to finish
|
||||||
|
std::unique_ptr<Dialog::Message> message; |
||||||
|
const std::string message_string = "Please wait while finding usages"; |
||||||
|
if(cache_in_progress_count != 0) { |
||||||
|
message = std::make_unique<Dialog::Message>(message_string); |
||||||
|
while(cache_in_progress_count != 0) { |
||||||
|
while(Gtk::Main::events_pending()) |
||||||
|
Gtk::Main::iteration(false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Use cache
|
||||||
|
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||||
|
std::unique_lock<std::mutex> lock(caches_mutex); |
||||||
|
auto caches_it = caches.find(*it); |
||||||
|
|
||||||
|
// Load cache from file if not found in memory and if cache file exists
|
||||||
|
if(caches_it == caches.end()) { |
||||||
|
auto cache = read_cache(project_path, build_path, *it); |
||||||
|
if(cache) { |
||||||
|
auto pair = caches.emplace(*it, std::move(cache)); |
||||||
|
caches_it = pair.first; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(caches_it != caches.end()) { |
||||||
|
if(add_usages_from_cache(caches_it->first, usages, visited, spelling, cursor, caches_it->second)) |
||||||
|
it = potential_paths.erase(it); |
||||||
|
else { |
||||||
|
caches.erase(caches_it); |
||||||
|
++it; |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
++it; |
||||||
|
} |
||||||
|
|
||||||
|
// Remove paths that has been included
|
||||||
|
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||||
|
if(all_includes.find(*it) != all_includes.end()) |
||||||
|
it = potential_paths.erase(it); |
||||||
|
else |
||||||
|
++it; |
||||||
|
} |
||||||
|
|
||||||
|
// Parse potential paths
|
||||||
|
if(!potential_paths.empty()) { |
||||||
|
if(!message) |
||||||
|
message = std::make_unique<Dialog::Message>(message_string); |
||||||
|
|
||||||
|
std::vector<std::thread> threads; |
||||||
|
auto it = potential_paths.begin(); |
||||||
|
auto number_of_threads = Config::get().source.clang_usages_threads; |
||||||
|
if(number_of_threads == static_cast<unsigned>(-1)) { |
||||||
|
number_of_threads = std::thread::hardware_concurrency(); |
||||||
|
if(number_of_threads == 0) |
||||||
|
number_of_threads = 1; |
||||||
|
} |
||||||
|
for(unsigned thread_id = 0; thread_id < number_of_threads; ++thread_id) { |
||||||
|
threads.emplace_back([&potential_paths, &it, &build_path, |
||||||
|
&project_path, &usages, &visited, &spelling, &cursor] { |
||||||
|
while(true) { |
||||||
|
boost::filesystem::path path; |
||||||
|
{ |
||||||
|
static std::mutex mutex; |
||||||
|
std::unique_lock<std::mutex> lock(mutex); |
||||||
|
if(it == potential_paths.end()) |
||||||
|
return; |
||||||
|
path = *it; |
||||||
|
++it; |
||||||
|
} |
||||||
|
clangmm::Index index(0, 0); |
||||||
|
|
||||||
|
{ |
||||||
|
static std::mutex mutex; |
||||||
|
std::unique_lock<std::mutex> lock(mutex); |
||||||
|
// std::cout << "parsing: " << path << std::endl;
|
||||||
|
} |
||||||
|
// auto before_time = std::chrono::system_clock::now();
|
||||||
|
|
||||||
|
std::ifstream stream(path.string(), std::ifstream::binary); |
||||||
|
std::string buffer; |
||||||
|
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); |
||||||
|
|
||||||
|
auto arguments = CompileCommands::get_arguments(build_path, path); |
||||||
|
arguments.emplace_back("-w"); // Disable all warnings
|
||||||
|
for(auto it = arguments.begin(); it != arguments.end();) { // remove comments from system headers
|
||||||
|
if(*it == "-fretain-comments-from-system-headers") |
||||||
|
it = arguments.erase(it); |
||||||
|
else |
||||||
|
++it; |
||||||
|
} |
||||||
|
int flags = CXTranslationUnit_Incomplete; |
||||||
|
#if CINDEX_VERSION_MAJOR > 0 || (CINDEX_VERSION_MAJOR == 0 && CINDEX_VERSION_MINOR >= 35) |
||||||
|
flags |= CXTranslationUnit_KeepGoing; |
||||||
|
#endif |
||||||
|
|
||||||
|
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer, flags); |
||||||
|
|
||||||
|
{ |
||||||
|
static std::mutex mutex; |
||||||
|
std::unique_lock<std::mutex> lock(mutex); |
||||||
|
add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, true); |
||||||
|
add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, true); |
||||||
|
} |
||||||
|
|
||||||
|
// auto time = std::chrono::system_clock::now();
|
||||||
|
// std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(time - before_time).count() << std::endl;
|
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
for(auto &thread : threads) |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
|
||||||
|
if(message) |
||||||
|
message->hide(); |
||||||
|
|
||||||
|
return usages; |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||||
|
std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens) { |
||||||
|
class ScopeExit { |
||||||
|
public: |
||||||
|
std::function<void()> f; |
||||||
|
~ScopeExit() { |
||||||
|
f(); |
||||||
|
} |
||||||
|
}; |
||||||
|
ScopeExit scope_exit{[] { |
||||||
|
--cache_in_progress_count; |
||||||
|
}}; |
||||||
|
|
||||||
|
if(project_path.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
{ |
||||||
|
std::unique_lock<std::mutex> lock(caches_mutex); |
||||||
|
if(project_paths_in_use.count(project_path)) { |
||||||
|
caches.erase(path); |
||||||
|
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); |
||||||
|
} |
||||||
|
else |
||||||
|
write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); |
||||||
|
} |
||||||
|
|
||||||
|
class VisitorData { |
||||||
|
public: |
||||||
|
const boost::filesystem::path &project_path; |
||||||
|
PathSet paths; |
||||||
|
}; |
||||||
|
VisitorData visitor_data{project_path, {}}; |
||||||
|
|
||||||
|
auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); |
||||||
|
clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { |
||||||
|
auto visitor_data = static_cast<VisitorData *>(data); |
||||||
|
|
||||||
|
auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); |
||||||
|
if(filesystem::file_in_path(path, visitor_data->project_path)) |
||||||
|
visitor_data->paths.emplace(path); |
||||||
|
|
||||||
|
return CXChildVisit_Continue; |
||||||
|
}, |
||||||
|
&visitor_data); |
||||||
|
|
||||||
|
visitor_data.paths.erase(path); |
||||||
|
|
||||||
|
for(auto &path : visitor_data.paths) { |
||||||
|
boost::system::error_code ec; |
||||||
|
auto file_size = boost::filesystem::file_size(path, ec); |
||||||
|
if(file_size == static_cast<boost::uintmax_t>(-1) || ec) |
||||||
|
continue; |
||||||
|
auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); |
||||||
|
std::unique_lock<std::mutex> lock(caches_mutex); |
||||||
|
if(project_paths_in_use.count(project_path)) { |
||||||
|
caches.erase(path); |
||||||
|
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); |
||||||
|
} |
||||||
|
else |
||||||
|
write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::erase_unused_caches(const PathSet &project_paths_in_use) { |
||||||
|
std::unique_lock<std::mutex> lock(caches_mutex); |
||||||
|
for(auto it = caches.begin(); it != caches.end();) { |
||||||
|
bool found = false; |
||||||
|
for(auto &project_path : project_paths_in_use) { |
||||||
|
if(filesystem::file_in_path(it->first, project_path)) { |
||||||
|
found = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if(!found) { |
||||||
|
write_cache(it->first, it->second); |
||||||
|
it = caches.erase(it); |
||||||
|
} |
||||||
|
else |
||||||
|
++it; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::erase_cache(const boost::filesystem::path &path) { |
||||||
|
std::unique_lock<std::mutex> lock(caches_mutex); |
||||||
|
|
||||||
|
auto it = caches.find(path); |
||||||
|
if(it == caches.end()) |
||||||
|
return; |
||||||
|
|
||||||
|
auto paths_and_last_write_times = std::move(it->second.paths_and_last_write_times); |
||||||
|
for(auto &path_and_last_write_time : paths_and_last_write_times) |
||||||
|
caches.erase(path_and_last_write_time.first); |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path) { |
||||||
|
if(project_path.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
if(cache_in_progress_count != 0) |
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10)); |
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(caches_mutex); |
||||||
|
boost::system::error_code ec; |
||||||
|
auto usages_clang_path = build_path / cache_folder; |
||||||
|
if(boost::filesystem::exists(usages_clang_path, ec) && boost::filesystem::is_directory(usages_clang_path, ec)) { |
||||||
|
for(boost::filesystem::directory_iterator it(usages_clang_path), end; it != end; ++it) { |
||||||
|
if(it->path().extension() == ".usages") |
||||||
|
boost::filesystem::remove(it->path(), ec); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for(auto it = caches.begin(); it != caches.end();) { |
||||||
|
if(filesystem::file_in_path(it->first, project_path)) |
||||||
|
it = caches.erase(it); |
||||||
|
else |
||||||
|
++it; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::cache_in_progress() { |
||||||
|
++cache_in_progress_count; |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_, |
||||||
|
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, |
||||||
|
clangmm::TranslationUnit *translation_unit, bool store_in_cache) { |
||||||
|
std::unique_ptr<clangmm::Tokens> tokens; |
||||||
|
boost::filesystem::path path; |
||||||
|
auto before_parse_time = std::time(nullptr); |
||||||
|
auto all_usr_extended = cursor.get_all_usr_extended(); |
||||||
|
if(path_.empty()) { |
||||||
|
path = clangmm::to_string(clang_getTranslationUnitSpelling(translation_unit->cx_tu)); |
||||||
|
if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) |
||||||
|
return; |
||||||
|
tokens = translation_unit->get_tokens(); |
||||||
|
} |
||||||
|
else { |
||||||
|
path = path_; |
||||||
|
if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) |
||||||
|
return; |
||||||
|
boost::system::error_code ec; |
||||||
|
auto file_size = boost::filesystem::file_size(path, ec); |
||||||
|
if(file_size == static_cast<boost::uintmax_t>(-1) || ec) |
||||||
|
return; |
||||||
|
tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); |
||||||
|
} |
||||||
|
|
||||||
|
auto offsets = tokens->get_similar_token_offsets(cursor.get_kind(), spelling, all_usr_extended); |
||||||
|
std::vector<std::string> lines; |
||||||
|
for(auto &offset : offsets) { |
||||||
|
std::string line; |
||||||
|
auto line_nr = offset.second.line; |
||||||
|
for(auto &token : *tokens) { |
||||||
|
auto offset = token.get_source_location().get_offset(); |
||||||
|
if(offset.line == line_nr) { |
||||||
|
while(line.size() < offset.index - 1) |
||||||
|
line += ' '; |
||||||
|
line += token.get_spelling(); |
||||||
|
} |
||||||
|
} |
||||||
|
lines.emplace_back(std::move(line)); |
||||||
|
} |
||||||
|
|
||||||
|
if(store_in_cache && filesystem::file_in_path(path, project_path)) { |
||||||
|
std::unique_lock<std::mutex> lock(caches_mutex); |
||||||
|
caches.erase(path); |
||||||
|
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); |
||||||
|
} |
||||||
|
|
||||||
|
visited.emplace(path); |
||||||
|
if(!offsets.empty()) |
||||||
|
usages.emplace_back(Usages{std::move(path), std::move(offsets), lines}); |
||||||
|
} |
||||||
|
|
||||||
|
bool Usages::Clang::add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, |
||||||
|
const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache) { |
||||||
|
for(auto &path_and_last_write_time : cache.paths_and_last_write_times) { |
||||||
|
boost::system::error_code ec; |
||||||
|
auto last_write_time = boost::filesystem::last_write_time(path_and_last_write_time.first, ec); |
||||||
|
if(ec || last_write_time > path_and_last_write_time.second) { |
||||||
|
// std::cout << "updated file: " << path_and_last_write_time.first << ", included from " << path << std::endl;
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto offsets = cache.get_similar_token_offsets(cursor.get_kind(), spelling, cursor.get_all_usr_extended()); |
||||||
|
|
||||||
|
std::vector<std::string> lines; |
||||||
|
for(auto &offset : offsets) { |
||||||
|
std::string line; |
||||||
|
auto line_nr = offset.second.line; |
||||||
|
for(auto &token : cache.tokens) { |
||||||
|
auto &offset = token.offsets.first; |
||||||
|
if(offset.line == line_nr) { |
||||||
|
while(line.size() < offset.index - 1) |
||||||
|
line += ' '; |
||||||
|
line += token.spelling; |
||||||
|
} |
||||||
|
} |
||||||
|
lines.emplace_back(std::move(line)); |
||||||
|
} |
||||||
|
|
||||||
|
visited.emplace(path); |
||||||
|
if(!offsets.empty()) |
||||||
|
usages.emplace_back(Usages{path, std::move(offsets), lines}); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, |
||||||
|
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, |
||||||
|
clangmm::TranslationUnit *translation_unit, bool store_in_cache) { |
||||||
|
if(project_path.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
class VisitorData { |
||||||
|
public: |
||||||
|
const boost::filesystem::path &project_path; |
||||||
|
const std::string &spelling; |
||||||
|
PathSet &visited; |
||||||
|
PathSet paths; |
||||||
|
}; |
||||||
|
VisitorData visitor_data{project_path, spelling, visited, {}}; |
||||||
|
|
||||||
|
auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); |
||||||
|
clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { |
||||||
|
auto visitor_data = static_cast<VisitorData *>(data); |
||||||
|
|
||||||
|
auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); |
||||||
|
if(visitor_data->visited.find(path) == visitor_data->visited.end() && filesystem::file_in_path(path, visitor_data->project_path)) |
||||||
|
visitor_data->paths.emplace(path); |
||||||
|
|
||||||
|
return CXChildVisit_Continue; |
||||||
|
}, |
||||||
|
&visitor_data); |
||||||
|
|
||||||
|
for(auto &path : visitor_data.paths) |
||||||
|
add_usages(project_path, build_path, path, usages, visited, spelling, cursor, translation_unit, store_in_cache); |
||||||
|
} |
||||||
|
|
||||||
|
Usages::Clang::PathSet Usages::Clang::find_paths(const boost::filesystem::path &project_path, |
||||||
|
const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path) { |
||||||
|
PathSet paths; |
||||||
|
|
||||||
|
CompileCommands compile_commands(build_path); |
||||||
|
|
||||||
|
for(boost::filesystem::recursive_directory_iterator it(project_path), end; it != end; ++it) { |
||||||
|
auto &path = it->path(); |
||||||
|
if(!boost::filesystem::is_regular_file(path)) { |
||||||
|
if(path == build_path || path == debug_path || path.filename() == ".git") |
||||||
|
it.no_push(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if(is_header(path)) |
||||||
|
paths.emplace(path); |
||||||
|
else if(is_source(path)) { |
||||||
|
for(auto &command : compile_commands.commands) { |
||||||
|
if(filesystem::get_normal_path(command.file) == path) { |
||||||
|
paths.emplace(path); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return paths; |
||||||
|
} |
||||||
|
|
||||||
|
bool Usages::Clang::is_header(const boost::filesystem::path &path) { |
||||||
|
auto ext = path.extension(); |
||||||
|
if(ext == ".h" || // c headers
|
||||||
|
ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc") // c++ headers
|
||||||
|
return true; |
||||||
|
else |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
bool Usages::Clang::is_source(const boost::filesystem::path &path) { |
||||||
|
auto ext = path.extension(); |
||||||
|
if(ext == ".c" || // c sources
|
||||||
|
ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++") // c++ sources
|
||||||
|
return true; |
||||||
|
else |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
std::pair<std::map<boost::filesystem::path, Usages::Clang::PathSet>, Usages::Clang::PathSet> Usages::Clang::parse_paths(const std::string &spelling, const PathSet &paths) { |
||||||
|
std::map<boost::filesystem::path, PathSet> paths_includes; |
||||||
|
PathSet paths_with_spelling; |
||||||
|
|
||||||
|
const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[\"]([^\"]+)[\"].*$"); |
||||||
|
|
||||||
|
auto is_spelling_char = [](char chr) { |
||||||
|
return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_'; |
||||||
|
}; |
||||||
|
|
||||||
|
for(auto &path : paths) { |
||||||
|
auto paths_includes_it = paths_includes.emplace(path, PathSet()).first; |
||||||
|
bool paths_with_spelling_emplaced = false; |
||||||
|
|
||||||
|
std::ifstream stream(path.string(), std::ifstream::binary); |
||||||
|
if(!stream) |
||||||
|
continue; |
||||||
|
std::string line; |
||||||
|
while(std::getline(stream, line)) { |
||||||
|
std::smatch sm; |
||||||
|
if(std::regex_match(line, sm, include_regex)) { |
||||||
|
boost::filesystem::path path(sm[1].str()); |
||||||
|
boost::filesystem::path include_path; |
||||||
|
// remove .. and .
|
||||||
|
for(auto &part : path) { |
||||||
|
if(part == "..") |
||||||
|
include_path = include_path.parent_path(); |
||||||
|
else if(part == ".") |
||||||
|
continue; |
||||||
|
else |
||||||
|
include_path /= part; |
||||||
|
} |
||||||
|
auto distance = std::distance(include_path.begin(), include_path.end()); |
||||||
|
for(auto &path : paths) { |
||||||
|
auto path_distance = std::distance(path.begin(), path.end()); |
||||||
|
if(path_distance >= distance) { |
||||||
|
auto it = path.begin(); |
||||||
|
std::advance(it, path_distance - distance); |
||||||
|
if(std::equal(it, path.end(), include_path.begin(), include_path.end())) |
||||||
|
paths_includes_it->second.emplace(path); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else if(!paths_with_spelling_emplaced) { |
||||||
|
auto pos = line.find(spelling); |
||||||
|
if(pos != std::string::npos && |
||||||
|
((!spelling.empty() && !is_spelling_char(spelling[0])) || |
||||||
|
((pos == 0 || !is_spelling_char(line[pos - 1])) && |
||||||
|
(pos + spelling.size() >= line.size() - 1 || !is_spelling_char(line[pos + spelling.size()]))))) { |
||||||
|
paths_with_spelling.emplace(path); |
||||||
|
paths_with_spelling_emplaced = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return {paths_includes, paths_with_spelling}; |
||||||
|
} |
||||||
|
|
||||||
|
Usages::Clang::PathSet Usages::Clang::get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes) { |
||||||
|
PathSet all_includes; |
||||||
|
|
||||||
|
class Recursive { |
||||||
|
public: |
||||||
|
static void f(PathSet &all_includes, const boost::filesystem::path &path, |
||||||
|
const std::map<boost::filesystem::path, PathSet> &paths_includes) { |
||||||
|
auto paths_includes_it = paths_includes.find(path); |
||||||
|
if(paths_includes_it != paths_includes.end()) { |
||||||
|
for(auto &include : paths_includes_it->second) { |
||||||
|
auto pair = all_includes.emplace(include); |
||||||
|
if(pair.second) |
||||||
|
f(all_includes, include, paths_includes); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
Recursive::f(all_includes, path, paths_includes); |
||||||
|
|
||||||
|
return all_includes; |
||||||
|
} |
||||||
|
|
||||||
|
std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> Usages::Clang::find_potential_paths(const boost::filesystem::path &path, const boost::filesystem::path &project_path, |
||||||
|
const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling) { |
||||||
|
PathSet potential_paths; |
||||||
|
PathSet all_includes; |
||||||
|
|
||||||
|
if(filesystem::file_in_path(path, project_path)) { |
||||||
|
for(auto &path_with_spelling : paths_with_spelling) { |
||||||
|
auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); |
||||||
|
if((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) { |
||||||
|
potential_paths.emplace(path_with_spelling); |
||||||
|
|
||||||
|
for(auto &include : path_all_includes) |
||||||
|
all_includes.emplace(include); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
for(auto &path_with_spelling : paths_with_spelling) { |
||||||
|
potential_paths.emplace(path_with_spelling); |
||||||
|
|
||||||
|
auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); |
||||||
|
for(auto &include : path_all_includes) |
||||||
|
all_includes.emplace(include); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return {potential_paths, all_includes}; |
||||||
|
} |
||||||
|
|
||||||
|
void Usages::Clang::write_cache(const boost::filesystem::path &path, const Clang::Cache &cache) { |
||||||
|
auto cache_path = cache.build_path / cache_folder; |
||||||
|
boost::system::error_code ec; |
||||||
|
if(!boost::filesystem::exists(cache_path, ec)) { |
||||||
|
boost::filesystem::create_directory(cache_path, ec); |
||||||
|
if(ec) |
||||||
|
return; |
||||||
|
} |
||||||
|
else if(!boost::filesystem::is_directory(cache_path, ec) || ec) |
||||||
|
return; |
||||||
|
|
||||||
|
auto path_str = filesystem::get_relative_path(path, cache.project_path).string(); |
||||||
|
for(auto &chr : path_str) { |
||||||
|
if(chr == '/' || chr == '\\') |
||||||
|
chr = '_'; |
||||||
|
} |
||||||
|
auto full_cache_path = cache_path / (path_str + ".usages"); |
||||||
|
|
||||||
|
std::ofstream stream(full_cache_path.string()); |
||||||
|
if(stream) { |
||||||
|
try { |
||||||
|
boost::archive::text_oarchive text_oarchive(stream); |
||||||
|
text_oarchive << cache; |
||||||
|
} |
||||||
|
catch(...) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Usages::Clang::Cache Usages::Clang::read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path) { |
||||||
|
auto path_str = filesystem::get_relative_path(path, project_path).string(); |
||||||
|
for(auto &chr : path_str) { |
||||||
|
if(chr == '/' || chr == '\\') |
||||||
|
chr = '_'; |
||||||
|
} |
||||||
|
auto cache_path = build_path / cache_folder / (path_str + ".usages"); |
||||||
|
|
||||||
|
boost::system::error_code ec; |
||||||
|
if(boost::filesystem::exists(cache_path, ec)) { |
||||||
|
std::ifstream stream(cache_path.string()); |
||||||
|
if(stream) { |
||||||
|
Cache cache; |
||||||
|
boost::archive::text_iarchive text_iarchive(stream); |
||||||
|
try { |
||||||
|
text_iarchive >> cache; |
||||||
|
return cache; |
||||||
|
} |
||||||
|
catch(...) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return Cache(); |
||||||
|
} |
||||||
@ -0,0 +1,151 @@ |
|||||||
|
#ifndef JUCI_USAGES_CLANG_H_ |
||||||
|
#define JUCI_USAGES_CLANG_H_ |
||||||
|
#include "clangmm.h" |
||||||
|
#include <atomic> |
||||||
|
#include <boost/archive/text_iarchive.hpp> |
||||||
|
#include <boost/archive/text_oarchive.hpp> |
||||||
|
#include <boost/filesystem.hpp> |
||||||
|
#include <boost/serialization/map.hpp> |
||||||
|
#include <boost/serialization/unordered_set.hpp> |
||||||
|
#include <boost/serialization/vector.hpp> |
||||||
|
#include <map> |
||||||
|
#include <mutex> |
||||||
|
#include <regex> |
||||||
|
#include <set> |
||||||
|
#include <unordered_set> |
||||||
|
|
||||||
|
namespace boost { |
||||||
|
namespace serialization { |
||||||
|
template <class Archive> |
||||||
|
void serialize(Archive &ar, boost::filesystem::path &path, const unsigned int version) { |
||||||
|
std::string path_str; |
||||||
|
if(Archive::is_saving::value) |
||||||
|
path_str = path.string(); |
||||||
|
ar &path_str; |
||||||
|
if(Archive::is_loading::value) |
||||||
|
path = path_str; |
||||||
|
} |
||||||
|
} // namespace serialization
|
||||||
|
} // namespace boost
|
||||||
|
|
||||||
|
namespace Usages { |
||||||
|
class Clang { |
||||||
|
public: |
||||||
|
typedef std::set<boost::filesystem::path> PathSet; |
||||||
|
|
||||||
|
class Usages { |
||||||
|
public: |
||||||
|
boost::filesystem::path path; |
||||||
|
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; |
||||||
|
std::vector<std::string> lines; |
||||||
|
}; |
||||||
|
|
||||||
|
class Cache { |
||||||
|
friend class boost::serialization::access; |
||||||
|
template <class Archive> |
||||||
|
void serialize(Archive &ar, const unsigned int version) { |
||||||
|
ar &project_path; |
||||||
|
ar &build_path; |
||||||
|
ar &tokens; |
||||||
|
ar &cursors; |
||||||
|
ar &paths_and_last_write_times; |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
class Cursor { |
||||||
|
friend class boost::serialization::access; |
||||||
|
template <class Archive> |
||||||
|
void serialize(Archive &ar, const unsigned int version) { |
||||||
|
ar &kind; |
||||||
|
ar &usrs; |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
clangmm::Cursor::Kind kind; |
||||||
|
std::unordered_set<std::string> usrs; |
||||||
|
|
||||||
|
bool operator==(const Cursor &o); |
||||||
|
}; |
||||||
|
|
||||||
|
class Token { |
||||||
|
friend class boost::serialization::access; |
||||||
|
template <class Archive> |
||||||
|
void serialize(Archive &ar, const unsigned int version) { |
||||||
|
ar &spelling; |
||||||
|
ar &offsets.first.line &offsets.first.index; |
||||||
|
ar &offsets.second.line &offsets.second.index; |
||||||
|
ar &cursor_id; |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
std::string spelling; |
||||||
|
std::pair<clangmm::Offset, clangmm::Offset> offsets; |
||||||
|
size_t cursor_id; |
||||||
|
}; |
||||||
|
|
||||||
|
boost::filesystem::path project_path; |
||||||
|
boost::filesystem::path build_path; |
||||||
|
|
||||||
|
std::vector<Token> tokens; |
||||||
|
std::vector<Cursor> cursors; |
||||||
|
std::map<boost::filesystem::path, std::time_t> paths_and_last_write_times; |
||||||
|
|
||||||
|
Cache() {} |
||||||
|
Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||||
|
std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens); |
||||||
|
|
||||||
|
operator bool() const { return !paths_and_last_write_times.empty(); } |
||||||
|
|
||||||
|
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, |
||||||
|
const std::unordered_set<std::string> &usrs) const; |
||||||
|
}; |
||||||
|
|
||||||
|
private: |
||||||
|
const static boost::filesystem::path cache_folder; |
||||||
|
|
||||||
|
static std::map<boost::filesystem::path, Cache> caches; |
||||||
|
static std::mutex caches_mutex; |
||||||
|
|
||||||
|
static std::atomic<size_t> cache_in_progress_count; |
||||||
|
|
||||||
|
public: |
||||||
|
static std::vector<Usages> get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path, |
||||||
|
const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units); |
||||||
|
|
||||||
|
static void cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||||
|
std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens); |
||||||
|
static void erase_unused_caches(const PathSet &project_paths_in_use); |
||||||
|
static void erase_cache(const boost::filesystem::path &path); |
||||||
|
static void erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path); |
||||||
|
static void cache_in_progress(); |
||||||
|
|
||||||
|
private: |
||||||
|
static void add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_, |
||||||
|
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, |
||||||
|
clangmm::TranslationUnit *translation_unit, bool store_in_cache); |
||||||
|
|
||||||
|
static bool add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, |
||||||
|
const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache); |
||||||
|
|
||||||
|
static void add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, |
||||||
|
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, |
||||||
|
clangmm::TranslationUnit *translation_unit, bool store_in_cache); |
||||||
|
|
||||||
|
static PathSet find_paths(const boost::filesystem::path &project_path, |
||||||
|
const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path); |
||||||
|
|
||||||
|
static bool is_header(const boost::filesystem::path &path); |
||||||
|
static bool is_source(const boost::filesystem::path &path); |
||||||
|
|
||||||
|
static std::pair<std::map<boost::filesystem::path, PathSet>, PathSet> parse_paths(const std::string &spelling, const PathSet &paths); |
||||||
|
|
||||||
|
static PathSet get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes); |
||||||
|
|
||||||
|
static std::pair<Clang::PathSet, Clang::PathSet> find_potential_paths(const boost::filesystem::path &project_path, const boost::filesystem::path &include_path, |
||||||
|
const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling); |
||||||
|
|
||||||
|
static void write_cache(const boost::filesystem::path &path, const Cache &cache); |
||||||
|
static Cache read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path); |
||||||
|
}; |
||||||
|
} // namespace Usages
|
||||||
|
#endif // JUCI_USAGES_CLANG_H_
|
||||||
@ -0,0 +1,273 @@ |
|||||||
|
#include "clangmm.h" |
||||||
|
#include "compile_commands.h" |
||||||
|
#include "meson.h" |
||||||
|
#include "project.h" |
||||||
|
#include "usages_clang.h" |
||||||
|
#include <cassert> |
||||||
|
#include <fstream> |
||||||
|
|
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
int main() { |
||||||
|
auto tests_path = boost::filesystem::canonical(JUCI_TESTS_PATH); |
||||||
|
auto project_path = boost::filesystem::canonical(tests_path / "usages_clang_test_files"); |
||||||
|
auto build_path = project_path / "build"; |
||||||
|
|
||||||
|
auto build = Project::Build::create(project_path); |
||||||
|
g_assert(build->project_path == project_path); |
||||||
|
|
||||||
|
{ |
||||||
|
clangmm::Index index(0, 0); |
||||||
|
auto path = project_path / "main.cpp"; |
||||||
|
std::ifstream stream(path.string(), std::ifstream::binary); |
||||||
|
assert(stream); |
||||||
|
std::string buffer; |
||||||
|
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); |
||||||
|
auto arguments = CompileCommands::get_arguments(build_path, path); |
||||||
|
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer); |
||||||
|
auto tokens = translation_unit.get_tokens(); |
||||||
|
clangmm::Token *found_token = nullptr; |
||||||
|
for(auto &token : *tokens) { |
||||||
|
if(token.get_spelling() == "a") { |
||||||
|
found_token = &token; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
assert(found_token); |
||||||
|
auto spelling = found_token->get_spelling(); |
||||||
|
auto cursor = found_token->get_cursor().get_referenced(); |
||||||
|
std::vector<Usages::Clang::Usages> usages; |
||||||
|
Usages::Clang::PathSet visited; |
||||||
|
|
||||||
|
Usages::Clang::add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, &translation_unit, false); |
||||||
|
assert(usages.size() == 1); |
||||||
|
assert(usages[0].path == path); |
||||||
|
assert(usages[0].lines.size() == 1); |
||||||
|
assert(usages[0].lines[0] == " test.a=2;"); |
||||||
|
assert(usages[0].offsets.size() == 1); |
||||||
|
assert(usages[0].offsets[0].first.line == 6); |
||||||
|
assert(usages[0].offsets[0].first.index == 8); |
||||||
|
assert(usages[0].offsets[0].second.line == 6); |
||||||
|
assert(usages[0].offsets[0].second.index == 9); |
||||||
|
|
||||||
|
Usages::Clang::add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, false); |
||||||
|
assert(usages.size() == 2); |
||||||
|
assert(usages[1].path == project_path / "test.hpp"); |
||||||
|
assert(usages[1].lines.size() == 2); |
||||||
|
assert(usages[1].lines[0] == " int a=0;"); |
||||||
|
assert(usages[1].lines[1] == " ++a;"); |
||||||
|
assert(usages[1].offsets.size() == 2); |
||||||
|
assert(usages[1].offsets[0].first.line == 6); |
||||||
|
assert(usages[1].offsets[0].first.index == 7); |
||||||
|
assert(usages[1].offsets[0].second.line == 6); |
||||||
|
assert(usages[1].offsets[0].second.index == 8); |
||||||
|
assert(usages[1].offsets[1].first.line == 8); |
||||||
|
assert(usages[1].offsets[1].first.index == 7); |
||||||
|
assert(usages[1].offsets[1].second.line == 8); |
||||||
|
assert(usages[1].offsets[1].second.index == 8); |
||||||
|
|
||||||
|
auto paths = Usages::Clang::find_paths(project_path, build_path, build_path / "debug"); |
||||||
|
auto pair = Usages::Clang::parse_paths(spelling, paths); |
||||||
|
|
||||||
|
auto &paths_includes = pair.first; |
||||||
|
assert(paths_includes.size() == 3); |
||||||
|
assert(paths_includes.find(project_path / "main.cpp") != paths_includes.end()); |
||||||
|
assert(paths_includes.find(project_path / "test.hpp") != paths_includes.end()); |
||||||
|
assert(paths_includes.find(project_path / "test2.hpp") != paths_includes.end()); |
||||||
|
|
||||||
|
auto &paths_with_spelling = pair.second; |
||||||
|
assert(paths_with_spelling.size() == 3); |
||||||
|
assert(paths_with_spelling.find(project_path / "main.cpp") != paths_with_spelling.end()); |
||||||
|
assert(paths_with_spelling.find(project_path / "test.hpp") != paths_with_spelling.end()); |
||||||
|
assert(paths_with_spelling.find(project_path / "test2.hpp") != paths_with_spelling.end()); |
||||||
|
|
||||||
|
auto pair2 = Usages::Clang::find_potential_paths(cursor.get_canonical().get_source_location().get_path(), project_path, pair.first, pair.second); |
||||||
|
|
||||||
|
auto &potential_paths = pair2.first; |
||||||
|
assert(potential_paths.size() == 3); |
||||||
|
assert(potential_paths.find(project_path / "main.cpp") != potential_paths.end()); |
||||||
|
assert(potential_paths.find(project_path / "test.hpp") != potential_paths.end()); |
||||||
|
assert(potential_paths.find(project_path / "test2.hpp") != potential_paths.end()); |
||||||
|
|
||||||
|
auto &all_includes = pair2.second; |
||||||
|
assert(all_includes.size() == 1); |
||||||
|
assert(*all_includes.begin() == project_path / "test.hpp"); |
||||||
|
|
||||||
|
// Remove visited paths
|
||||||
|
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||||
|
if(visited.find(*it) != visited.end()) |
||||||
|
it = potential_paths.erase(it); |
||||||
|
else |
||||||
|
++it; |
||||||
|
} |
||||||
|
|
||||||
|
assert(potential_paths.size() == 1); |
||||||
|
assert(*potential_paths.begin() == project_path / "test2.hpp"); |
||||||
|
|
||||||
|
{ |
||||||
|
auto path = *potential_paths.begin(); |
||||||
|
std::ifstream stream(path.string(), std::ifstream::binary); |
||||||
|
std::string buffer; |
||||||
|
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); |
||||||
|
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer); |
||||||
|
Usages::Clang::add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, true); |
||||||
|
Usages::Clang::add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, true); |
||||||
|
assert(usages.size() == 3); |
||||||
|
assert(usages[2].path == path); |
||||||
|
assert(usages[2].lines.size() == 1); |
||||||
|
assert(usages[2].lines[0] == " ++a;"); |
||||||
|
assert(usages[2].offsets.size() == 1); |
||||||
|
assert(usages[2].offsets[0].first.line == 5); |
||||||
|
assert(usages[2].offsets[0].first.index == 7); |
||||||
|
assert(usages[2].offsets[0].second.line == 5); |
||||||
|
assert(usages[2].offsets[0].second.index == 8); |
||||||
|
} |
||||||
|
|
||||||
|
assert(Usages::Clang::caches.size() == 1); |
||||||
|
auto cache_it = Usages::Clang::caches.begin(); |
||||||
|
assert(cache_it->first == project_path / "test2.hpp"); |
||||||
|
assert(cache_it->second.build_path == build_path); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.size() == 2); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test2.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||||
|
assert(cache_it->second.tokens.size()); |
||||||
|
assert(cache_it->second.cursors.size()); |
||||||
|
{ |
||||||
|
std::vector<Usages::Clang::Usages> usages; |
||||||
|
Usages::Clang::PathSet visited; |
||||||
|
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||||
|
assert(usages.size() == 1); |
||||||
|
assert(usages[0].path == cache_it->first); |
||||||
|
assert(usages[0].lines.size() == 1); |
||||||
|
assert(usages[0].lines[0] == " ++a;"); |
||||||
|
assert(usages[0].offsets.size() == 1); |
||||||
|
assert(usages[0].offsets[0].first.line == 5); |
||||||
|
assert(usages[0].offsets[0].first.index == 7); |
||||||
|
assert(usages[0].offsets[0].second.line == 5); |
||||||
|
assert(usages[0].offsets[0].second.index == 8); |
||||||
|
} |
||||||
|
|
||||||
|
Usages::Clang::erase_unused_caches({project_path}); |
||||||
|
Usages::Clang::cache(project_path, build_path, path, time(nullptr), {project_path}, &translation_unit, tokens.get()); |
||||||
|
assert(Usages::Clang::caches.size() == 3); |
||||||
|
assert(Usages::Clang::caches.find(project_path / "main.cpp") != Usages::Clang::caches.end()); |
||||||
|
assert(Usages::Clang::caches.find(project_path / "test.hpp") != Usages::Clang::caches.end()); |
||||||
|
assert(Usages::Clang::caches.find(project_path / "test2.hpp") != Usages::Clang::caches.end()); |
||||||
|
|
||||||
|
Usages::Clang::erase_unused_caches({}); |
||||||
|
Usages::Clang::cache(project_path, build_path, path, time(nullptr), {}, &translation_unit, tokens.get()); |
||||||
|
assert(Usages::Clang::caches.size() == 0); |
||||||
|
|
||||||
|
auto cache = Usages::Clang::read_cache(project_path, build_path, project_path / "main.cpp"); |
||||||
|
assert(cache); |
||||||
|
Usages::Clang::caches.emplace(project_path / "main.cpp", std::move(cache)); |
||||||
|
assert(!cache); |
||||||
|
assert(Usages::Clang::caches.size() == 1); |
||||||
|
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test.hpp"); |
||||||
|
assert(cache); |
||||||
|
Usages::Clang::caches.emplace(project_path / "test.hpp", std::move(cache)); |
||||||
|
assert(!cache); |
||||||
|
assert(Usages::Clang::caches.size() == 2); |
||||||
|
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test2.hpp"); |
||||||
|
assert(cache); |
||||||
|
Usages::Clang::caches.emplace(project_path / "test2.hpp", std::move(cache)); |
||||||
|
assert(!cache); |
||||||
|
assert(Usages::Clang::caches.size() == 3); |
||||||
|
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test_not_existing.hpp"); |
||||||
|
assert(!cache); |
||||||
|
assert(Usages::Clang::caches.size() == 3); |
||||||
|
{ |
||||||
|
auto cache_it = Usages::Clang::caches.find(project_path / "main.cpp"); |
||||||
|
assert(cache_it != Usages::Clang::caches.end()); |
||||||
|
assert(cache_it->first == project_path / "main.cpp"); |
||||||
|
assert(cache_it->second.build_path == build_path); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.size() == 2); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.find(project_path / "main.cpp") != cache_it->second.paths_and_last_write_times.end()); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||||
|
assert(cache_it->second.tokens.size()); |
||||||
|
assert(cache_it->second.cursors.size()); |
||||||
|
{ |
||||||
|
std::vector<Usages::Clang::Usages> usages; |
||||||
|
Usages::Clang::PathSet visited; |
||||||
|
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||||
|
assert(usages.size() == 1); |
||||||
|
assert(usages[0].path == cache_it->first); |
||||||
|
assert(usages[0].lines.size() == 1); |
||||||
|
assert(usages[0].lines[0] == " test.a=2;"); |
||||||
|
assert(usages[0].offsets.size() == 1); |
||||||
|
assert(usages[0].offsets[0].first.line == 6); |
||||||
|
assert(usages[0].offsets[0].first.index == 8); |
||||||
|
assert(usages[0].offsets[0].second.line == 6); |
||||||
|
assert(usages[0].offsets[0].second.index == 9); |
||||||
|
} |
||||||
|
} |
||||||
|
{ |
||||||
|
auto cache_it = Usages::Clang::caches.find(project_path / "test.hpp"); |
||||||
|
assert(cache_it != Usages::Clang::caches.end()); |
||||||
|
assert(cache_it->first == project_path / "test.hpp"); |
||||||
|
assert(cache_it->second.build_path == build_path); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.size() == 1); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||||
|
assert(cache_it->second.tokens.size()); |
||||||
|
assert(cache_it->second.cursors.size()); |
||||||
|
{ |
||||||
|
std::vector<Usages::Clang::Usages> usages; |
||||||
|
Usages::Clang::PathSet visited; |
||||||
|
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||||
|
assert(usages.size() == 1); |
||||||
|
assert(usages[0].path == cache_it->first); |
||||||
|
assert(usages[0].lines.size() == 2); |
||||||
|
assert(usages[0].lines[0] == " int a=0;"); |
||||||
|
assert(usages[0].lines[1] == " ++a;"); |
||||||
|
assert(usages[0].offsets.size() == 2); |
||||||
|
assert(usages[0].offsets[0].first.line == 6); |
||||||
|
assert(usages[0].offsets[0].first.index == 7); |
||||||
|
assert(usages[0].offsets[0].second.line == 6); |
||||||
|
assert(usages[0].offsets[0].second.index == 8); |
||||||
|
assert(usages[0].offsets[1].first.line == 8); |
||||||
|
assert(usages[0].offsets[1].first.index == 7); |
||||||
|
assert(usages[0].offsets[1].second.line == 8); |
||||||
|
assert(usages[0].offsets[1].second.index == 8); |
||||||
|
} |
||||||
|
} |
||||||
|
{ |
||||||
|
auto cache_it = Usages::Clang::caches.find(project_path / "test2.hpp"); |
||||||
|
assert(cache_it != Usages::Clang::caches.end()); |
||||||
|
assert(cache_it->first == project_path / "test2.hpp"); |
||||||
|
assert(cache_it->second.build_path == build_path); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.size() == 2); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test2.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||||
|
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||||
|
assert(cache_it->second.tokens.size()); |
||||||
|
assert(cache_it->second.cursors.size()); |
||||||
|
{ |
||||||
|
std::vector<Usages::Clang::Usages> usages; |
||||||
|
Usages::Clang::PathSet visited; |
||||||
|
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||||
|
assert(usages.size() == 1); |
||||||
|
assert(usages[0].path == cache_it->first); |
||||||
|
assert(usages[0].lines.size() == 1); |
||||||
|
assert(usages[0].lines[0] == " ++a;"); |
||||||
|
assert(usages[0].offsets.size() == 1); |
||||||
|
assert(usages[0].offsets[0].first.line == 5); |
||||||
|
assert(usages[0].offsets[0].first.index == 7); |
||||||
|
assert(usages[0].offsets[0].second.line == 5); |
||||||
|
assert(usages[0].offsets[0].second.index == 8); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
{ |
||||||
|
assert(!Usages::Clang::caches.empty()); |
||||||
|
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder)); |
||||||
|
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"main.cpp.usages")); |
||||||
|
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test.hpp.usages")); |
||||||
|
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test2.hpp.usages")); |
||||||
|
|
||||||
|
Usages::Clang::erase_all_caches_for_project(project_path, build_path); |
||||||
|
assert(Usages::Clang::caches.empty()); |
||||||
|
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder)); |
||||||
|
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"main.cpp.usages")); |
||||||
|
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test.hpp.usages")); |
||||||
|
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test2.hpp.usages")); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"directory": "jucipp/tests/usages_clang_test_files/build", |
||||||
|
"command": "c++ -Ihello@exe -I. -I.. -Xclang -fcolor-diagnostics -pipe -Wall -Winvalid-pch -Wnon-virtual-dtor -O0 -g -std=c++11 -Wall -Wextra -MMD -MQ 'hello@exe/main.cpp.o' -MF 'hello@exe/main.cpp.o.d' -o 'hello@exe/main.cpp.o' -c ../main.cpp", |
||||||
|
"file": "../main.cpp" |
||||||
|
} |
||||||
|
] |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
#include <iostream> |
||||||
|
#include "test.hpp" |
||||||
|
|
||||||
|
int main() { |
||||||
|
Test test; |
||||||
|
test.a=2; |
||||||
|
test.b(); |
||||||
|
test.c(); |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
project('hello.world', 'cpp') |
||||||
|
|
||||||
|
compiler_args = ['-std=c++11', '-Wall', '-Wextra'] |
||||||
|
|
||||||
|
executable('hello', 'main.cpp', cpp_args: compiler_args) |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
class Test { |
||||||
|
public: |
||||||
|
Test() {} |
||||||
|
~Test() {} |
||||||
|
|
||||||
|
int a=0; |
||||||
|
void b() { |
||||||
|
++a; |
||||||
|
} |
||||||
|
void c() { |
||||||
|
b(); |
||||||
|
} |
||||||
|
}; |
||||||
Loading…
Reference in new issue