From 247b3641911a4b33294106dbe7a4f192271cde21 Mon Sep 17 00:00:00 2001 From: ychenfo Date: Tue, 7 Sep 2021 17:30:15 +0800 Subject: [PATCH] nac3core: top level fix cyclic ancestor analysis, add tests --- nac3core/src/toplevel/helper.rs | 50 ++++++++++++++ nac3core/src/toplevel/mod.rs | 45 ++++++++---- nac3core/src/toplevel/test.rs | 118 ++++++++++++++++++++++++++++---- 3 files changed, 186 insertions(+), 27 deletions(-) diff --git a/nac3core/src/toplevel/helper.rs b/nac3core/src/toplevel/helper.rs index 012f20412..ec4be79fe 100644 --- a/nac3core/src/toplevel/helper.rs +++ b/nac3core/src/toplevel/helper.rs @@ -1,5 +1,55 @@ use super::*; +impl TopLevelDef { + pub fn to_string(&self, unifier: &mut Unifier, obj_to_name: &mut F, var_to_name: &mut G) -> String + where + F: FnMut(usize) -> String, + G: FnMut(u32) -> String, + { + match self { + TopLevelDef::Class { + name, + ancestors, + fields, + methods, + object_id, + type_vars, + .. + } =>{ + let fields_str = fields + .iter() + .map(|(n, ty)| (n.to_string(), unifier.stringify(*ty, obj_to_name, var_to_name))) + .collect_vec(); + + let methods_str = methods + .iter() + .map(|(n, ty, id)| + (n.to_string(), unifier.stringify(*ty, obj_to_name, var_to_name), *id) + ) + .collect_vec(); + + format!( + "Class {{\nname: {:?},\ndef_id: {:?},\nancestors: {:?},\nfields: {:?},\nmethods: {:?},\ntype_vars: {:?}\n}}", + name, + object_id, + ancestors, + fields_str, + methods_str, + type_vars, + ) + } + TopLevelDef::Function { name, signature, var_id, .. } => + format!( + "Function {{\nname: {:?},\nsig: {:?},\nvar_id: {:?}\n}}", + name, + unifier.stringify(*signature, obj_to_name, var_to_name), + var_id + ), + TopLevelDef::Initializer { class_id } => format!("Initializer {{ {:?} }}", class_id) + } + } +} + impl TopLevelComposer { pub fn make_primitives() -> (PrimitiveStore, Unifier) { let mut unifier = Unifier::new(); diff --git a/nac3core/src/toplevel/mod.rs b/nac3core/src/toplevel/mod.rs index 9dac9314f..3591a837c 100644 --- a/nac3core/src/toplevel/mod.rs +++ b/nac3core/src/toplevel/mod.rs @@ -1,4 +1,4 @@ -use std::{borrow::BorrowMut, collections::{HashMap, HashSet}, iter::FromIterator, ops::{Deref, DerefMut}, sync::Arc}; +use std::{borrow::BorrowMut, collections::{HashMap, HashSet}, fmt::Debug, iter::FromIterator, ops::{Deref, DerefMut}, sync::Arc}; use super::typecheck::type_inferencer::PrimitiveStore; use super::typecheck::typedef::{FunSignature, FuncArg, SharedUnifier, Type, TypeEnum, Unifier}; @@ -6,7 +6,7 @@ use crate::{ symbol_resolver::SymbolResolver, typecheck::{type_inferencer::CodeLocation, typedef::CallId}, }; -use itertools::Itertools; +use itertools::{Itertools, izip}; use parking_lot::{Mutex, RwLock}; use rustpython_parser::ast::{self, Stmt}; @@ -104,11 +104,19 @@ impl TopLevelComposer { let primitives = Self::make_primitives(); TopLevelComposer { - definition_ast_list: Default::default(), + definition_ast_list: { + let top_level_def_list = vec![ + Arc::new(RwLock::new(Self::make_top_level_class_def(0, None, "int32"))), + Arc::new(RwLock::new(Self::make_top_level_class_def(1, None, "int64"))), + Arc::new(RwLock::new(Self::make_top_level_class_def(2, None, "float"))), + Arc::new(RwLock::new(Self::make_top_level_class_def(3, None, "bool"))), + Arc::new(RwLock::new(Self::make_top_level_class_def(4, None, "none"))), + ]; + let ast_list: Vec>> = vec![None, None, None, None, None]; + izip!(top_level_def_list, ast_list).collect_vec() + }, primitives_ty: primitives.0, unifier: primitives.1, - // class_method_to_def_id: Default::default(), - // to_be_analyzed_class: Default::default(), keyword_list: HashSet::from_iter(vec![ "Generic".into(), "virtual".into(), @@ -303,7 +311,8 @@ impl TopLevelComposer { let unifier = self.unifier.borrow_mut(); let primitives_store = &self.primitives_ty; - for (class_def, class_ast) in def_list { + // skip 5 to skip analyzing the primitives + for (class_def, class_ast) in def_list.iter().skip(5) { // only deal with class def here let mut class_def = class_def.write(); let (class_bases_ast, class_def_type_vars, class_resolver) = { @@ -403,7 +412,8 @@ impl TopLevelComposer { let unifier = self.unifier.borrow_mut(); // first, only push direct parent into the list - for (class_def, class_ast) in self.definition_ast_list.iter_mut() { + // skip 5 to skip analyzing the primitives + for (class_def, class_ast) in self.definition_ast_list.iter_mut().skip(5) { let mut class_def = class_def.write(); let (class_bases, class_ancestors, class_resolver) = { if let TopLevelDef::Class { ancestors, resolver, .. } = class_def.deref_mut() { @@ -463,10 +473,11 @@ impl TopLevelComposer { // second, get all ancestors let mut ancestors_store: HashMap> = Default::default(); - for (class_def, _) in self.definition_ast_list.iter_mut() { - let mut class_def = class_def.write(); + // skip 5 to skip analyzing the primitives + for (class_def, _) in self.definition_ast_list.iter().skip(5) { + let class_def = class_def.read(); let (class_ancestors, class_id) = { - if let TopLevelDef::Class { ancestors, object_id, .. } = class_def.deref_mut() { + if let TopLevelDef::Class { ancestors, object_id, .. } = class_def.deref() { (ancestors, *object_id) } else { continue; @@ -484,7 +495,8 @@ impl TopLevelComposer { } // insert the ancestors to the def list - for (class_def, _) in self.definition_ast_list.iter_mut() { + // skip 5 to skip analyzing the primitives + for (class_def, _) in self.definition_ast_list.iter_mut().skip(5) { let mut class_def = class_def.write(); let (class_ancestors, class_id, class_type_vars) = { if let TopLevelDef::Class { ancestors, object_id, type_vars, .. } = class_def.deref_mut() { @@ -514,7 +526,8 @@ impl TopLevelComposer { let mut type_var_to_concrete_def: HashMap = HashMap::new(); - for (class_def, class_ast) in def_ast_list { + // skip 5 to skip analyzing the primitives + for (class_def, class_ast) in def_ast_list.iter().skip(5) { if matches!(&*class_def.read(), TopLevelDef::Class { .. }) { Self::analyze_single_class_methods_fields( class_def.clone(), @@ -533,7 +546,7 @@ impl TopLevelComposer { loop { let mut finished = true; - for (class_def, _) in def_ast_list { + for (class_def, _) in def_ast_list.iter().skip(5) { let mut class_def = class_def.write(); if let TopLevelDef::Class { ancestors, .. } = class_def.deref() { // if the length of the ancestor is equal to the current depth @@ -580,7 +593,8 @@ impl TopLevelComposer { let unifier = self.unifier.borrow_mut(); let primitives_store = &self.primitives_ty; - for (function_def, function_ast) in def_list { + // skip 5 to skip analyzing the primitives + for (function_def, function_ast) in def_list.iter().skip(5) { let function_def = function_def.read(); let function_def = function_def.deref(); let function_ast = if let Some(function_ast) = function_ast { @@ -802,6 +816,9 @@ impl TopLevelComposer { and names thould not be the same as the keywords" .into()); } + if name == "__init__" && !defined_paramter_name.contains("self") { + return Err("class __init__ function must contain the `self` parameter".into()); + } let mut result = Vec::new(); for x in &args.args { diff --git a/nac3core/src/toplevel/test.rs b/nac3core/src/toplevel/test.rs index f4c82eb9f..dde4755f4 100644 --- a/nac3core/src/toplevel/test.rs +++ b/nac3core/src/toplevel/test.rs @@ -145,27 +145,102 @@ fn test_simple_function_analyze(source: Vec<&str>, tys: Vec<&str>, names: Vec<&s #[test_case( vec![ indoc! {" - class A: - def __init__(): + class A(): + def __init__(self): + self.a: int32 = 3 + def fun(self, b: B): pass "}, indoc! {" class B(C): - def __init__(): + def __init__(self): pass "}, indoc! {" class C(A): - def __init__(): + def __init__(self): + pass + def fun(self, b: B): + a = 1 pass "}, indoc! {" def foo(a: A): pass "}, + ], + vec![ + indoc! {"5: Class { + name: \"A\", + def_id: DefinitionId(5), + ancestors: [CustomClassKind { id: DefinitionId(5), params: [] }], + fields: [(\"a\", \"0\")], + methods: [(\"__init__\", \"fn[[self=5], 5]\", DefinitionId(6)), (\"fun\", \"fn[[self=5, b=9], 4]\", DefinitionId(7))], + type_vars: [] + }"}, + + indoc! {"6: Function { + name: \"A__init__\", + sig: \"fn[[self=5], 5]\", + var_id: [] + }"}, + + indoc! {"7: Function { + name: \"Afun\", + sig: \"fn[[self=5, b=9], 4]\", + var_id: [] + }"}, + + indoc! {"8: Initializer { DefinitionId(5) }"}, + + indoc! {"9: Class { + name: \"B\", + def_id: DefinitionId(9), + ancestors: [CustomClassKind { id: DefinitionId(9), params: [] }, CustomClassKind { id: DefinitionId(12), params: [] }, CustomClassKind { id: DefinitionId(5), params: [] }], + fields: [(\"a\", \"0\")], + methods: [(\"__init__\", \"fn[[self=9], 9]\", DefinitionId(10)), (\"fun\", \"fn[[self=12, b=9], 4]\", DefinitionId(14))], + type_vars: [] + }"}, + + indoc! {"10: Function { + name: \"B__init__\", + sig: \"fn[[self=9], 9]\", + var_id: [] + }"}, + + indoc! {"11: Initializer { DefinitionId(9) }"}, + + indoc! {"12: Class { + name: \"C\", + def_id: DefinitionId(12), + ancestors: [CustomClassKind { id: DefinitionId(12), params: [] }, CustomClassKind { id: DefinitionId(5), params: [] }], + fields: [(\"a\", \"0\")], + methods: [(\"__init__\", \"fn[[self=12], 12]\", DefinitionId(13)), (\"fun\", \"fn[[self=12, b=9], 4]\", DefinitionId(14))], + type_vars: [] + }"}, + + indoc! {"13: Function { + name: \"C__init__\", + sig: \"fn[[self=12], 12]\", + var_id: [] + }"}, + + indoc! {"14: Function { + name: \"Cfun\", + sig: \"fn[[self=12, b=9], 4]\", + var_id: [] + }"}, + + indoc! {"15: Initializer { DefinitionId(12) }"}, + + indoc! {"16: Function { + name: \"foo\", + sig: \"fn[[a=5], 4]\", + var_id: [] + }"}, ] )] -fn test_simple_class_analyze(source: Vec<&str>) { +fn test_simple_class_analyze(source: Vec<&str>, res: Vec<&str>) { let mut composer = TopLevelComposer::new(); let resolver = Arc::new(Mutex::new(Box::new(Resolver { @@ -184,12 +259,29 @@ fn test_simple_class_analyze(source: Vec<&str>) { composer.start_analysis().unwrap(); - // for (i, (def, _)) in composer.definition_ast_list.into_iter().enumerate() { - // let def = &*def.read(); - // if let TopLevelDef::Function { signature, name, .. } = def { - // let ty_str = composer.unifier.stringify(*signature, &mut |id| id.to_string(), &mut |id| id.to_string()); - // assert_eq!(ty_str, tys[i]); - // assert_eq!(name, names[i]); - // } - // } + // skip 5 to skip primitives + for (i, (def, _)) in composer.definition_ast_list.iter().skip(5).enumerate() { + let def = &*def.read(); + // println!( + // "{}: {}\n", + // i + 5, + // def.to_string( + // composer.unifier.borrow_mut(), + // &mut |id| id.to_string(), + // &mut |id| id.to_string() + // ) + // ); + assert_eq!( + format!( + "{}: {}", + i + 5, + def.to_string( + composer.unifier.borrow_mut(), + &mut |id| id.to_string(), + &mut |id| id.to_string() + ) + ), + res[i] + ) + } } \ No newline at end of file