diff --git a/src/analyzer/config/json_config.rs b/src/analyzer/config/json_config.rs index c4767c55..5e741182 100644 --- a/src/analyzer/config/json_config.rs +++ b/src/analyzer/config/json_config.rs @@ -11,6 +11,8 @@ pub struct JsonConfig { #[serde(default)] pub ignore_issue_files: FxHashMap>, #[serde(default)] + pub banned_builtin_functions: FxHashMap, + #[serde(default)] pub security_analysis: JsonSecurityConfig, #[serde(default)] pub allowed_issues: Vec, diff --git a/src/analyzer/config/mod.rs b/src/analyzer/config/mod.rs index 378c7dc1..78e1bfa7 100644 --- a/src/analyzer/config/mod.rs +++ b/src/analyzer/config/mod.rs @@ -5,7 +5,7 @@ use hakana_reflection_info::{ issue::{Issue, IssueKind}, taint::{SinkType, SourceType}, }; -use hakana_str::Interner; +use hakana_str::{Interner, StrId}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::custom_hook::CustomHook; @@ -26,6 +26,7 @@ pub struct Config { pub test_files: Vec, pub ignore_issue_files: FxHashMap>, pub ignore_all_issues_in_files: Vec, + pub banned_builtin_functions: FxHashMap, pub security_config: SecurityConfig, pub root_dir: String, pub hooks: Vec>, @@ -82,6 +83,7 @@ impl Config { ast_diff: false, in_migration: false, in_codegen: false, + banned_builtin_functions: FxHashMap::default(), } } @@ -89,6 +91,7 @@ impl Config { &mut self, cwd: &String, config_path: &Path, + interner: &mut Interner, ) -> Result<(), Box> { let json_config = json_config::read_from_file(config_path)?; @@ -134,6 +137,12 @@ impl Config { ) }; + self.banned_builtin_functions = json_config + .banned_builtin_functions + .into_iter() + .map(|(k, v)| (interner.intern(k), interner.intern(v))) + .collect(); + self.security_config.ignore_files = json_config .security_analysis .ignore_files diff --git a/src/analyzer/custom_hook.rs b/src/analyzer/custom_hook.rs index 9309014d..9583b212 100644 --- a/src/analyzer/custom_hook.rs +++ b/src/analyzer/custom_hook.rs @@ -10,6 +10,7 @@ use oxidized::{ }; use rustc_hash::FxHashMap; +use crate::config::Config; use crate::{ config, function_analysis_data::FunctionAnalysisData, scope::BlockContext, statements_analyzer::StatementsAnalyzer, @@ -154,7 +155,7 @@ pub trait InternalHook { } #[allow(unused_variables)] - fn after_populate(&self, codebase: &CodebaseInfo, interner: &Interner) {} + fn after_populate(&self, codebase: &CodebaseInfo, interner: &Interner, config: &Config) {} } pub trait CustomHook: InternalHook + Send + Sync + core::fmt::Debug {} diff --git a/src/analyzer/expr/call/function_call_analyzer.rs b/src/analyzer/expr/call/function_call_analyzer.rs index 4d03cae5..a0abb626 100644 --- a/src/analyzer/expr/call/function_call_analyzer.rs +++ b/src/analyzer/expr/call/function_call_analyzer.rs @@ -14,8 +14,8 @@ use crate::expr::call_analyzer::{apply_effects, check_template_result}; use crate::expr::{echo_analyzer, exit_analyzer, expression_identifier, isset_analyzer}; use crate::function_analysis_data::FunctionAnalysisData; use crate::reconciler; -use crate::scope_analyzer::ScopeAnalyzer; use crate::scope::BlockContext; +use crate::scope_analyzer::ScopeAnalyzer; use crate::statements_analyzer::StatementsAnalyzer; use crate::stmt_analyzer::AnalysisError; use crate::{expression_analyzer, formula_generator}; @@ -202,6 +202,22 @@ pub(crate) fn analyze( &functionlike_id, ); + if let Some(banned_message) = function_storage.banned_function_message { + analysis_data.maybe_add_issue( + Issue::new( + IssueKind::BannedFunction, + statements_analyzer + .get_interner() + .lookup(&banned_message) + .to_string(), + statements_analyzer.get_hpos(pos), + &context.function_context.calling_functionlike_id, + ), + statements_analyzer.get_config(), + statements_analyzer.get_file_path_actual(), + ); + } + if !function_storage.is_production_code && function_storage.user_defined && context.function_context.is_production(codebase) diff --git a/src/cli/lib.rs b/src/cli/lib.rs index 1df72e93..9312bb1f 100644 --- a/src/cli/lib.rs +++ b/src/cli/lib.rs @@ -683,8 +683,10 @@ fn do_fix( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(&cwd, config_path).ok(); + config.update_from_file(&cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -698,6 +700,7 @@ fn do_fix( threads, Arc::new(logger), header, + interner, None, None, None, @@ -731,8 +734,10 @@ fn do_remove_unused_fixmes( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -750,6 +755,7 @@ fn do_remove_unused_fixmes( threads, Arc::new(logger), header, + interner, None, None, None, @@ -805,8 +811,10 @@ fn do_add_fixmes( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -821,6 +829,7 @@ fn do_add_fixmes( threads, Arc::new(logger), header, + interner, None, None, None, @@ -868,8 +877,10 @@ fn do_migrate( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -906,6 +917,7 @@ fn do_migrate( threads, Arc::new(logger), header, + interner, None, None, None, @@ -954,8 +966,10 @@ fn do_migration_candidates( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -972,6 +986,7 @@ fn do_migration_candidates( threads, Arc::new(logger), header, + interner, None, None, None, @@ -1029,8 +1044,10 @@ fn do_codegen( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -1045,6 +1062,7 @@ fn do_codegen( threads, Arc::new(logger), header, + interner, None, None, None, @@ -1138,8 +1156,10 @@ fn do_find_paths( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -1163,6 +1183,7 @@ fn do_find_paths( threads, Arc::new(logger), header, + interner, None, None, None, @@ -1200,8 +1221,10 @@ fn do_security_check( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } config.allowed_issues = None; @@ -1227,6 +1250,7 @@ fn do_security_check( threads, Arc::new(logger), header, + interner, None, None, None, @@ -1318,8 +1342,10 @@ fn do_analysis( let config_path = config_path.unwrap(); + let mut interner = Interner::default(); + if config_path.exists() { - config.update_from_file(cwd, config_path).ok(); + config.update_from_file(cwd, config_path, &mut interner).ok(); } // do this after we've loaded from file, as they can be overridden @@ -1344,6 +1370,7 @@ fn do_analysis( threads, Arc::new(logger), header, + interner, None, None, None, diff --git a/src/cli/test_runners/test_runner.rs b/src/cli/test_runners/test_runner.rs index b325de5d..bcb818b3 100644 --- a/src/cli/test_runners/test_runner.rs +++ b/src/cli/test_runners/test_runner.rs @@ -6,6 +6,7 @@ use hakana_reflection_info::code_location::FilePath; use hakana_reflection_info::data_flow::graph::GraphKind; use hakana_reflection_info::data_flow::graph::WholeProgramKind; use hakana_reflection_info::issue::IssueKind; +use hakana_str::Interner; use hakana_workhorse::wasm::get_single_file_codebase; use hakana_workhorse::SuccessfulScanData; use rand::seq::SliceRandom; @@ -232,6 +233,8 @@ impl TestRunner { let config = Arc::new(analysis_config); + let interner = Interner::default(); + let result = hakana_workhorse::scan_and_analyze( stub_dirs, None, @@ -248,6 +251,7 @@ impl TestRunner { 1, logger, build_checksum, + interner, previous_scan_data, previous_analysis_result, None, @@ -457,6 +461,7 @@ impl TestRunner { let mut config = self.get_config_for_test(&workdir_base); config.ast_diff = true; config.find_unused_definitions = true; + let interner = Interner::default(); let config = Arc::new(config); let mut stub_dirs = vec![cwd.clone() + "/tests/stubs"]; @@ -479,6 +484,7 @@ impl TestRunner { 1, logger.clone(), build_checksum, + interner.clone(), previous_scan_data, previous_analysis_result, None, diff --git a/src/code_info/functionlike_info.rs b/src/code_info/functionlike_info.rs index cdf86c3f..edcd634f 100644 --- a/src/code_info/functionlike_info.rs +++ b/src/code_info/functionlike_info.rs @@ -161,6 +161,8 @@ pub struct FunctionLikeInfo { pub is_production_code: bool, + pub banned_function_message: Option, + pub is_closure: bool, pub overriding: bool, @@ -204,6 +206,7 @@ impl FunctionLikeInfo { has_throw: false, is_closure: false, overriding: false, + banned_function_message: None, } } diff --git a/src/code_info_builder/functionlike_scanner.rs b/src/code_info_builder/functionlike_scanner.rs index 31b4a5ef..ed606b95 100644 --- a/src/code_info_builder/functionlike_scanner.rs +++ b/src/code_info_builder/functionlike_scanner.rs @@ -366,6 +366,14 @@ pub(crate) fn get_functionlike( StrId::HAKANA_TEST_ONLY => { functionlike_info.is_production_code = false; } + StrId::HAKANA_BANNED_FUNCTION => { + if let Some(attribute_param_expr) = user_attribute.params.first() { + if let aast::Expr_::String(str) = &attribute_param_expr.2 { + functionlike_info.banned_function_message = + Some(interner.intern(str.to_string())); + } + } + } StrId::HAKANA_SECURITY_ANALYSIS_IGNORE_PATH_IF_TRUE => { functionlike_info.ignore_taints_if_true = true; } diff --git a/src/file_scanner_analyzer/lib.rs b/src/file_scanner_analyzer/lib.rs index cea9fc4f..9bdb1942 100644 --- a/src/file_scanner_analyzer/lib.rs +++ b/src/file_scanner_analyzer/lib.rs @@ -82,6 +82,7 @@ pub async fn scan_and_analyze_async( threads: u8, lsp_client: &Client, header: &str, + interner: Arc, previous_scan_data: Option, previous_analysis_result: Option, language_server_changes: Option>, @@ -110,6 +111,7 @@ pub async fn scan_and_analyze_async( threads, Arc::new(Logger::DevNull), header, + &interner, previous_scan_data, language_server_changes, )?; @@ -142,10 +144,11 @@ pub async fn scan_and_analyze_async( &mut cached_analysis.symbol_references, cached_analysis.safe_symbols, cached_analysis.safe_symbol_members, + &config, ); for hook in &config.hooks { - hook.after_populate(&codebase, &interner); + hook.after_populate(&codebase, &interner, &config); } let (analysis_result, arc_scan_data) = get_analysis_ready( @@ -207,6 +210,7 @@ pub fn scan_and_analyze( threads: u8, logger: Arc, header: &str, + interner: Interner, previous_scan_data: Option, previous_analysis_result: Option, language_server_changes: Option>, @@ -233,6 +237,7 @@ pub fn scan_and_analyze( threads, logger.clone(), header, + &Arc::new(interner), previous_scan_data, language_server_changes, )?; @@ -284,10 +289,11 @@ pub fn scan_and_analyze( &mut cached_analysis.symbol_references, cached_analysis.safe_symbols, cached_analysis.safe_symbol_members, + &config, ); for hook in &config.hooks { - hook.after_populate(&codebase, &interner); + hook.after_populate(&codebase, &interner, &config); } let populating_elapsed = populating_now.elapsed(); diff --git a/src/file_scanner_analyzer/populator.rs b/src/file_scanner_analyzer/populator.rs index d9764d6b..b783c668 100644 --- a/src/file_scanner_analyzer/populator.rs +++ b/src/file_scanner_analyzer/populator.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use hakana_analyzer::config::Config; use hakana_reflection_info::classlike_info::{ClassConstantType, ClassLikeInfo}; use hakana_reflection_info::codebase_info::symbols::SymbolKind; use hakana_reflection_info::codebase_info::{CodebaseInfo, Symbols}; @@ -20,6 +21,7 @@ pub fn populate_codebase( symbol_references: &mut SymbolReferences, safe_symbols: FxHashSet, safe_symbol_members: FxHashSet<(StrId, StrId)>, + config: &Config, ) { let new_classlike_names = codebase .classlike_infos @@ -55,6 +57,7 @@ pub fn populate_codebase( }, symbol_references, v.user_defined && !safe_symbols.contains(&name.0), + config, ); } @@ -253,6 +256,7 @@ fn populate_functionlike_storage( reference_source: &ReferenceSource, symbol_references: &mut SymbolReferences, force_type_population: bool, + config: &Config, ) { if storage.is_populated && !force_type_population { return; @@ -260,6 +264,14 @@ fn populate_functionlike_storage( storage.is_populated = true; + if !storage.user_defined && !storage.is_closure { + if let ReferenceSource::Symbol(true, function_id) = reference_source { + if let Some(banned_message) = config.banned_builtin_functions.get(function_id) { + storage.banned_function_message = Some(*banned_message); + } + } + } + for attribute_info in &storage.attributes { match reference_source { ReferenceSource::Symbol(_, a) => { diff --git a/src/file_scanner_analyzer/scanner.rs b/src/file_scanner_analyzer/scanner.rs index 29e8e647..3da85b7b 100644 --- a/src/file_scanner_analyzer/scanner.rs +++ b/src/file_scanner_analyzer/scanner.rs @@ -55,6 +55,7 @@ pub fn scan_files( threads: u8, logger: Arc, build_checksum: &str, + starter_interner: &Arc, starter_data: Option, language_server_changes: Option>, ) -> io::Result { @@ -107,7 +108,7 @@ pub fn scan_files( codebase = starter_data.codebase; resolved_names = starter_data.resolved_names; } else { - interner = Interner::default(); + interner = (**starter_interner).clone(); codebase = CodebaseInfo::new(); resolved_names = FxHashMap::default(); } diff --git a/src/file_scanner_analyzer/wasm.rs b/src/file_scanner_analyzer/wasm.rs index 407f34f4..0f4d9872 100644 --- a/src/file_scanner_analyzer/wasm.rs +++ b/src/file_scanner_analyzer/wasm.rs @@ -64,6 +64,7 @@ pub fn scan_and_analyze_single_file( &mut symbol_references, FxHashSet::default(), FxHashSet::default(), + &analysis_config, ); let mut analysis_result = analyze_single_file( @@ -182,6 +183,7 @@ pub fn get_single_file_codebase( &mut symbol_references, FxHashSet::default(), FxHashSet::default(), + &Config::new("".to_string(), FxHashSet::default()), ); (codebase, interner, file_system) diff --git a/src/language_server/lib.rs b/src/language_server/lib.rs index d6b0516d..e0b2aeab 100644 --- a/src/language_server/lib.rs +++ b/src/language_server/lib.rs @@ -6,6 +6,7 @@ use std::time::Duration; use hakana_analyzer::config::{self, Config}; use hakana_analyzer::custom_hook::CustomHook; use hakana_reflection_info::analysis_result::AnalysisResult; +use hakana_str::Interner; use hakana_workhorse::file::FileStatus; use hakana_workhorse::{scan_and_analyze_async, SuccessfulScanData}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -19,6 +20,7 @@ use tower_lsp::{Client, LanguageServer}; pub struct Backend { client: Client, analysis_config: Arc, + starter_interner: Arc, previous_scan_data: RwLock>, previous_analysis_result: RwLock>, all_diagnostics: RwLock>>>, @@ -27,10 +29,11 @@ pub struct Backend { } impl Backend { - pub fn new(client: Client, analysis_config: Arc) -> Self { + pub fn new(client: Client, analysis_config: Config, starter_interner: Interner) -> Self { Self { client, - analysis_config, + analysis_config: Arc::new(analysis_config), + starter_interner: Arc::new(starter_interner), previous_scan_data: RwLock::new(None), previous_analysis_result: RwLock::new(None), all_diagnostics: RwLock::new(None), @@ -215,6 +218,7 @@ impl Backend { 8, &self.client, "", + self.starter_interner.clone(), successful_scan_data, analysis_result, file_changes, @@ -321,6 +325,7 @@ impl Backend { pub fn get_config( plugins: Vec>, cwd: &String, + interner: &mut Interner, ) -> std::result::Result> { let mut all_custom_issues = vec![]; @@ -348,7 +353,7 @@ pub fn get_config( let config_path = Path::new(&config_path_str); if config_path.exists() { - config.update_from_file(cwd, config_path)?; + config.update_from_file(cwd, config_path, interner)?; } Ok(config) diff --git a/src/str/build.rs b/src/str/build.rs index bf7814d5..6bf6b196 100644 --- a/src/str/build.rs +++ b/src/str/build.rs @@ -238,6 +238,7 @@ fn main() -> Result<()> { "HH\\type_structure_for_alias", "HH\\varray", "HH\\vec", + "Hakana\\BannedFunction", "Hakana\\FindPaths\\Sanitize", "Hakana\\NotTestOnly", "Hakana\\Immutable",