diff --git a/infer/src/backend/abs.ml b/infer/src/backend/abs.ml index 528c5cc6a..ca3f877e0 100644 --- a/infer/src/backend/abs.ml +++ b/infer/src/backend/abs.ml @@ -1189,7 +1189,8 @@ let remove_opt _prop = | Some (Some p) -> p | _ -> Prop.prop_emp -(* Checks if cycle has fields with property attributes weak/unsafe_unretained *) +(* Checks if cycle has fields (derived from a property or directly defined as ivar) *) +(* with attributes weak/unsafe_unretained/assing *) let cycle_has_weak_or_unretained_or_assign_field cycle = (* returns items annotation for field fn in struct t *) let get_item_annotation t fn = @@ -1203,10 +1204,11 @@ let cycle_has_weak_or_unretained_or_assign_field cycle = let rec has_weak_or_unretained_or_assign params = match params with | [] -> false - | att::_ when Config.unsafe_unret = att || Config.weak = att || Config.assign = att -> true + | att:: _ when Config.unsafe_unret = att || Config.weak = att || Config.assign = att -> true | _:: params' -> has_weak_or_unretained_or_assign params' in let do_annotation (a, _) = - (a.Sil.class_name = Config.property_attributes && has_weak_or_unretained_or_assign a.Sil.parameters) in + ((a.Sil.class_name = Config.property_attributes) || + (a.Sil.class_name = Config.ivar_attributes)) && has_weak_or_unretained_or_assign a.Sil.parameters in let rec do_cycle c = match c with | [] -> false diff --git a/infer/src/backend/config.ml b/infer/src/backend/config.ml index e6e32ae22..e1f8292bb 100644 --- a/infer/src/backend/config.ml +++ b/infer/src/backend/config.ml @@ -372,6 +372,8 @@ let anonymous_block_num_sep = "______" let property_attributes = "property_attributes" +let ivar_attributes = "ivar_attributes" + let unsafe_unret = "<\"Unsafe_unretained\">" let weak = "<\"Weak\">" diff --git a/infer/src/clang/cField_decl.ml b/infer/src/clang/cField_decl.ml index a9fb9d8da..ea5542bb5 100644 --- a/infer/src/clang/cField_decl.ml +++ b/infer/src/clang/cField_decl.ml @@ -47,15 +47,21 @@ let get_field_www name_field fl = (Ident.create_fieldname (Mangled.from_string "NO_FIELD_NAME") 0, Sil.Tvoid) let rec build_sil_field tenv class_name field_name qual_type prop_atts = + let annotation_from_type t = + match t with + | Sil.Tptr(_,Sil.Pk_objc_weak) -> [Config.weak] + | Sil.Tptr(_,Sil.Pk_objc_unsafe_unretained) -> [Config.unsafe_unret] + | _ -> [] in let fname = mk_class_field_name class_name field_name in let typ = CTypes_decl.qual_type_to_sil_type tenv qual_type in let item_annotations = match prop_atts with - | None -> Sil.item_annotation_empty + | None -> [({ Sil.class_name = Config.ivar_attributes; Sil.parameters = annotation_from_type typ }, true)] | Some atts -> [({ Sil.class_name = Config.property_attributes; Sil.parameters = atts }, true)] in fname, typ, item_annotations (* From an ivar look for its property and if it finds it returns its attributes *) let ivar_property curr_class ivar = + Printing.log_out "Checking if a property is defined for the ivar: '%s'@." ivar; match ObjcProperty_decl.Property.find_property_name_from_ivar curr_class ivar with | Some pname' -> (Printing.log_out "Found property name from ivar: '%s'" pname'; @@ -66,7 +72,7 @@ let ivar_property curr_class ivar = with Not_found -> Printing.log_out "Didn't find property for pname '%s'" pname'; None) - | None -> Printing.log_out "Didn't find property for ivar '%s'" ivar; + | None -> Printing.log_out "No property found for ivar '%s'@." ivar; None (* Given a list of declarations in an interface returns a list of fields *) @@ -78,12 +84,12 @@ let rec get_fields tenv curr_class decl_list = let fields = get_fields tenv curr_class decl_list' in (* Doing a post visit here. Adding Ivar after all the declaration have been visited so that *) (* ivar names will be added in the property list. *) - Printing.log_out " ...Adding Instance Variable '%s' \n" field_name; + Printing.log_out " ...Adding Instance Variable '%s' @." field_name; let prop_attributes = ivar_property curr_class field_name in let (fname, typ, ia) = build_sil_field tenv class_name field_name qual_type prop_attributes in - Printing.log_out " ...Resulting sil field: (%s) with attributes:\n" ((Ident.fieldname_to_string fname) ^":"^(Sil.typ_to_string typ)); + Printing.log_out " ...Resulting sil field: (%s) with attributes:@." ((Ident.fieldname_to_string fname) ^":"^(Sil.typ_to_string typ)); list_iter (fun (ia', _) -> - list_iter (fun a -> Printing.log_out " '%s' \n" a) ia'.Sil.parameters) ia; + list_iter (fun a -> Printing.log_out " '%s'@." a) ia'.Sil.parameters) ia; (fname, typ, ia):: fields | ObjCPropertyImplDecl(decl_info, property_impl_decl_info):: decl_list' -> diff --git a/infer/src/clang/cTypes_decl.ml b/infer/src/clang/cTypes_decl.ml index 4bb14e064..83f1b6b05 100644 --- a/infer/src/clang/cTypes_decl.ml +++ b/infer/src/clang/cTypes_decl.ml @@ -92,7 +92,8 @@ let string_type_to_sil_type tenv s = try let t = CTypes_parser.parse (Ast_lexer.token) lexbuf in Printing.log_out - " ...Parsed. Translated with sil TYPE '%s'@." (Sil.typ_to_string t); t + " ...Parsed. Translated with sil TYPE '%a'@." (Sil.pp_typ_full pe_text) t; + t with Parsing.Parse_error -> ( Printing.log_stats "\nXXXXXXX PARSE ERROR for string '%s'. RETURNING Void.TODO@.@." s; diff --git a/infer/src/clang/cTypes_parser.mly b/infer/src/clang/cTypes_parser.mly index cb709164d..b20fb8fe3 100644 --- a/infer/src/clang/cTypes_parser.mly +++ b/infer/src/clang/cTypes_parser.mly @@ -217,8 +217,7 @@ clang_type: | VOLATILE pointer_clang_type { $2 } | ident ANONYM_IDENT { CFrontend_utils.Printing.log_out " ...Found just an identifier modified with a protocol. Ignoring protocol!. Parsing as Named Type!\n"; Sil.Tvar (Sil.TN_typedef(Mangled.from_string $1))} - | ident { CFrontend_utils.Printing.log_out " ...Found just an identifier. Parsing as Named Type %s !\n" $1; - Sil.Tvar (Sil.TN_typedef(Mangled.from_string $1))} + | ident { Sil.Tvar (Sil.TN_typedef(Mangled.from_string $1))} | csu_sil ident_csu { let typename=Sil.TN_csu($1, Mangled.from_string $2) in Sil.Tvar typename } ; diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/retain_cycle2.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/retain_cycle2.m new file mode 100644 index 000000000..cf57f1b04 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/retain_cycle2.m @@ -0,0 +1,131 @@ +#import + +@class Child; +@class ChildW; +@class ChildUU; + +@interface Parent : NSObject + +-(void) setChild: (Child *) c; + +@end + +@implementation Parent { + Child *child; +} + +-(void) setChild: (Child *) c { + + self->child =c; +} + +@end + +@interface ParentW : NSObject + +-(void) setChild: (ChildW *) c; + +@end + +@implementation ParentW { + ChildW *child; +} + +-(void) setChild: (ChildW *) c { + + self->child =c; +} + +@end + +@interface ParentUU : NSObject + +-(void) setChild: (ChildUU *) c; + +@end + +@implementation ParentUU { + ChildUU *child; +} + +-(void) setChild: (ChildUU *) c { + + self->child =c; +} + +@end + + +@interface Child : NSObject + +-(void) setParent: (Parent *) p; + +@end + +@implementation Child { + Parent *parent; +} + +-(void) setParent: (Parent *) p { + self->parent =p; +} + +@end + + +@interface ChildW : NSObject + +-(void) setParent: (ParentW *) p; + +@end + +@implementation ChildW { + ParentW __weak *parent; +} + +-(void) setParent: (ParentW *) p { + self->parent =p; +} + +@end + + +@interface ChildUU: NSObject + +-(void) setParent: (ParentUU *) p; + +@end + +@implementation ChildUU { + ParentUU __unsafe_unretained *parent; +} + +-(void) setParent: (ParentUU *) p { + self->parent =p; +} + +@end + +void strongcycle() { + Parent *parent = [[Parent alloc] init]; + Child *child = [[Child alloc] init]; + [parent setChild:child]; + [child setParent: parent]; +} + +void weakcycle() { + ParentW *parent = [[ParentW alloc] init]; + ChildW *child = [[ChildW alloc] init]; + [parent setChild:child]; + [child setParent: parent]; +} + +void unsafeunretainedcycle() { + ParentUU *parent = [[ParentUU alloc] init]; + ChildUU *child = [[ChildUU alloc] init]; + [parent setChild:child]; + [child setParent: parent]; +} + + + diff --git a/infer/tests/endtoend/objc/RetainCycle2Test.java b/infer/tests/endtoend/objc/RetainCycle2Test.java new file mode 100644 index 000000000..480092f14 --- /dev/null +++ b/infer/tests/endtoend/objc/RetainCycle2Test.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class RetainCycle2Test { + + public static final String retain_cycle_file = + "infer/tests/codetoanalyze/objc/errors/" + + "memory_leaks_benchmark/retain_cycle2.m"; + + private static ImmutableList inferCmd; + + public static final String RETAIN_CYCLE = "RETAIN_CYCLE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createiOSInferCommandWithMLBuckets( + folder, + retain_cycle_file, + "cf", + true); + } + + + + @Test + public void whenInferRunsOnStrongCycleThenRCIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain retain cycle", + inferResults, + containsExactly( + RETAIN_CYCLE, + retain_cycle_file, + new String[]{"strongcycle"})); + } + + +}