@ -13,8 +13,11 @@
# ifdef JUCI_ENABLE_DEBUG
# include "debug_lldb.hpp"
# endif
# include "compile_commands.hpp"
# include "ctags.hpp"
# include "dialog.hpp"
# include "info.hpp"
# include "sarif.hpp"
# include "snippets.hpp"
# include "source_clang.hpp"
# include "usages_clang.hpp"
@ -228,6 +231,10 @@ void Project::Base::recreate_build() {
Info : : get ( ) . print ( " Could not find a supported project " ) ;
}
void Project : : Base : : analyze ( ) {
Info : : get ( ) . print ( " Could not find a supported project " ) ;
}
void Project : : Base : : show_symbols ( ) {
Ctags ctags ( get_preferably_view_folder ( ) ) ;
if ( ! ctags ) {
@ -859,6 +866,155 @@ void Project::Clang::recreate_build() {
}
}
void Project : : Clang : : analyze ( ) {
auto default_build_path = build - > get_default_path ( ) ;
if ( default_build_path . empty ( ) | | ! build - > update_default ( ) )
return ;
compiling = true ;
if ( Config : : get ( ) . terminal . clear_on_compile )
Terminal : : get ( ) . clear ( ) ;
Terminal : : get ( )
. print ( " \ e[37mAnalyzing project: " + filesystem : : get_short_path ( build - > project_path ) . string ( ) + " \ e[m \n " ) ;
CompileCommands db { default_build_path } ;
if ( db . commands . empty ( ) )
return ;
const auto base_cmd = [ ] {
std : : vector < std : : string > analyzer_cmd = { " clang++ " , " --analyze " , " -Xanalyzer " , " -analyzer-output=sarif " } ;
for ( const auto & checker : Config : : get ( ) . project . clang_analyze_checks ) {
analyzer_cmd . push_back ( " -Xanalyzer " ) ;
analyzer_cmd . push_back ( " -analyzer-checker= " + checker ) ;
}
return analyzer_cmd ;
} ( ) ;
std : : vector < boost : : filesystem : : path > result_files ;
std : : vector < std : : pair < std : : string , std : : string > > command_lines ;
for ( const auto & file : db . commands ) {
auto output_file = file . directory / ( file . file . filename ( ) . string ( ) + " .sarif.json " ) ;
auto cmd = base_cmd ;
cmd . push_back ( " -o " ) ;
cmd . push_back ( output_file . string ( ) ) ;
auto compile_args = CompileCommands : : get_arguments ( build - > get_default_path ( ) , file . file ) ;
for ( const auto & arg : compile_args ) {
cmd . push_back ( arg ) ;
}
cmd . push_back ( file . file . string ( ) ) ;
std : : stringstream command_line ;
for ( const auto & arg : cmd ) {
command_line < < std : : quoted ( arg ) < < ' ' ;
}
command_lines . push_back ( std : : make_pair ( file . file . string ( ) , command_line . str ( ) ) ) ;
result_files . push_back ( output_file ) ;
}
std : : size_t num_finished_jobs = 0 ;
bool canceled = false ;
Dialog : : Message message (
" Clang-analyzing project, this can take a while... " , [ & canceled ] { canceled = true ; } , true /* progress */ ) ;
const auto run_analyzer = [ & ] ( const std : : string & file , const std : : string & command_line ) {
Terminal : : get ( ) . print ( " Analyzing file: " + file + " ... \n " , true ) ;
return Terminal : : get ( ) . async_process ( command_line , default_build_path , [ & ] ( int exit_status ) {
// After one job finished, schedule the next or the final collection job
+ + num_finished_jobs ;
message . set_fraction ( static_cast < double > ( num_finished_jobs ) / static_cast < double > ( command_lines . size ( ) ) ) ;
if ( exit_status > 0 ) {
Terminal : : get ( ) . print ( " Analyzing file ' " + file + " ' returned " + std : : to_string ( exit_status ) + " , aborting! \n " , true ) ;
// Do not start any new analyzers
canceled = true ;
}
} ) ;
} ;
const auto concurrent_processes = std : : min ( command_lines . size ( ) , std : : max ( std : : size_t { 1 } , std : : size_t { std : : thread : : hardware_concurrency ( ) - 1U } ) ) ;
std : : vector < std : : pair < std : : string , std : : shared_ptr < TinyProcessLib : : Process > > > processes ;
processes . reserve ( concurrent_processes ) ;
// Schedule the first few jobs
auto next_job_it = command_lines . begin ( ) ;
for ( std : : size_t i = 0 ; ! canceled & & i < concurrent_processes ; + + i ) {
processes . emplace_back ( next_job_it - > first , run_analyzer ( next_job_it - > first , next_job_it - > second ) ) ;
+ + next_job_it ;
}
// Schedule remainder of jobs
while ( next_job_it ! = command_lines . end ( ) ) {
if ( canceled ) {
for ( auto & process : processes ) {
if ( process . second )
process . second - > kill ( ) ;
}
break ;
}
while ( Gtk : : Main : : events_pending ( ) )
Gtk : : Main : : iteration ( ) ;
int exit_status = 0 ;
for ( auto & process : processes ) {
if ( ! process . second | | process . second - > try_get_exit_status ( exit_status ) ) {
if ( ! canceled & & next_job_it ! = command_lines . end ( ) ) {
process = std : : make_pair ( next_job_it - > first , run_analyzer ( next_job_it - > first , next_job_it - > second ) ) ;
+ + next_job_it ;
}
}
}
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 10 ) ) ;
}
// Wait for all jobs to finish
while ( ! processes . empty ( ) ) {
int exit_status = 0 ;
for ( auto it = processes . begin ( ) ; it ! = processes . end ( ) ; ) {
if ( ! it - > second | | it - > second - > try_get_exit_status ( exit_status ) )
it = processes . erase ( it ) ;
else
+ + it ;
}
while ( Gtk : : Main : : events_pending ( ) )
Gtk : : Main : : iteration ( ) ;
}
// Combine results
if ( ! canceled ) {
auto results = SARIF : : merge_files ( default_build_path / " clang-analyze.sarif.json " , result_files ) ;
if ( ! results . empty ( ) ) {
Terminal : : get ( ) . print ( " Summary: \n " , true ) ;
for ( const auto & result : results ) {
for ( const auto & location : result . locations ) {
auto text = location . artifact_location . string ( ) + " : " ;
if ( location . region . start_line ) {
text . append ( std : : to_string ( location . region . start_line ) ) . push_back ( ' : ' ) ;
if ( location . region . start_column )
text . append ( std : : to_string ( location . region . start_column ) ) . push_back ( ' : ' ) ;
}
text . append ( " " ) . append ( to_string ( result . level ) ) . append ( " : " ) . append ( result . get_message ( location ) ) ;
Terminal : : get ( ) . print ( text + " \n " , true ) ;
}
}
}
for ( const auto & file : result_files ) {
boost : : system : : error_code error { } ;
boost : : filesystem : : remove ( file , error ) ;
}
Terminal : : get ( )
. print ( " \ e[37mFinished analyzing with " + std : : to_string ( results . size ( ) ) + " findings! " + " \ e[m \n " ) ;
}
Project : : compiling = false ;
message . hide ( ) ;
if ( auto view = Notebook : : get ( ) . get_current_view ( ) )
view - > soft_reparse ( true ) ;
}
void Project : : Markdown : : compile_and_run ( const boost : : filesystem : : path & file_path ) {
std : : string command ;