diff --git a/infer/src/backend/symExec.ml b/infer/src/backend/symExec.ml index ae694fc18..43989fb50 100644 --- a/infer/src/backend/symExec.ml +++ b/infer/src/backend/symExec.ml @@ -2380,5 +2380,24 @@ module ModelBuiltins = struct Builtin.register_procname (Procname.mangled_c_method "NSArray" "arrayWithObjects:" method_kind) execute_NSArray_arrayWithObjects + + let execute_objc_NSDictionary_alloc_no_fail cfg pdesc tenv symb_state ret_ids loc = + let nsdictionary_typ = Sil.Tvar (Sil.TN_csu (Sil.Class, Mangled.from_string "NSDictionary")) in + let nsdictionary_typ = Sil.expand_type tenv nsdictionary_typ in + execute_objc_alloc_no_fail cfg pdesc tenv symb_state ret_ids nsdictionary_typ loc + + let execute___objc_dictionary_literal cfg pdesc instr tenv prop path ret_ids args callee_pname loc = + let n_formals = 1 in + let res' = + sym_exe_check_variadic_sentinel ~fails_on_nil: true cfg pdesc tenv prop path + n_formals args (0,1) callee_pname loc in + execute_objc_NSDictionary_alloc_no_fail cfg pdesc tenv res' ret_ids loc + + let __objc_dictionary_literal = + let method_kind = Procname.mangled_of_objc_method_kind Procname.Class_objc_method in + let pname = Procname.mangled_c_method "NSDictionary" "__objc_dictionary_literal:" method_kind in + Builtin.register_procname pname execute___objc_dictionary_literal; + pname + end (* ============== END of ModelBuiltins ============== *) diff --git a/infer/src/backend/symExec.mli b/infer/src/backend/symExec.mli index 048f6aaf9..4a948f0f9 100644 --- a/infer/src/backend/symExec.mli +++ b/infer/src/backend/symExec.mli @@ -55,5 +55,6 @@ module ModelBuiltins : sig val __set_autorelease_attribute : Procname.t val __objc_release_autorelease_pool : Procname.t val __objc_cast : Procname.t + val __objc_dictionary_literal : Procname.t val malloc_no_fail : Procname.t end diff --git a/infer/src/clang/cFrontend_config.ml b/infer/src/clang/cFrontend_config.ml index 4e9a54448..700cb8d30 100644 --- a/infer/src/clang/cFrontend_config.ml +++ b/infer/src/clang/cFrontend_config.ml @@ -17,8 +17,6 @@ let array_with_objects_count_m = "arrayWithObjects:count:" let object_at_indexed_subscript_m = "objectAtIndexedSubscript:" -let dict_with_objects_and_keys_m = "dictionaryWithObjectsAndKeys:" - let string_with_utf8_m = "stringWithUTF8String:" let nsstring_cl = "NSString" diff --git a/infer/src/clang/cFrontend_config.mli b/infer/src/clang/cFrontend_config.mli index e1bbe23a4..ca3d5c032 100644 --- a/infer/src/clang/cFrontend_config.mli +++ b/infer/src/clang/cFrontend_config.mli @@ -75,8 +75,6 @@ val array_with_objects_count_m : string val object_at_indexed_subscript_m : string -val dict_with_objects_and_keys_m : string - val emtpy_name_category : string val objc_class : string diff --git a/infer/src/clang/cTrans.ml b/infer/src/clang/cTrans.ml index a29356220..ae8a0f732 100644 --- a/infer/src/clang/cTrans.ml +++ b/infer/src/clang/cTrans.ml @@ -1697,8 +1697,10 @@ struct and objCDictionaryLiteral_trans trans_state info stmt_info stmts = let typ = CTypes_decl.class_from_pointer_type trans_state.context.CContext.tenv info.Clang_ast_t.ei_type_ptr in + let dictionary_literal_pname = SymExec.ModelBuiltins.__objc_dictionary_literal in + let dictionary_literal_s = Procname.c_get_method dictionary_literal_pname in let obj_c_message_expr_info = - Ast_expressions.make_obj_c_message_expr_info_class CFrontend_config.dict_with_objects_and_keys_m typ in + Ast_expressions.make_obj_c_message_expr_info_class dictionary_literal_s typ in let stmts = General_utils.swap_elements_list stmts in let stmts = stmts @ [Ast_expressions.create_nil stmt_info] in let message_stmt = Clang_ast_t.ObjCMessageExpr (stmt_info, stmts, info, obj_c_message_expr_info) in diff --git a/infer/tests/codetoanalyze/objc/errors/npe/nil_in_dictionary_literal.m b/infer/tests/codetoanalyze/objc/errors/npe/nil_in_dictionary_literal.m new file mode 100644 index 000000000..cdcbfed11 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/nil_in_dictionary_literal.m @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2014 - present Facebook, Inc. +* All rights reserved. +* +* This source code is licensed under the BSD style license found in the +* LICENSE file in the root directory of this source tree. An additional grant +* of patent rights can be found in the PATENTS file in the same directory. +*/ + +#import + +@interface A : NSObject { + +} +@end + +@implementation A + +-(void) noProblem { + NSDictionary *foo = @{@"aaa":@"a value", @"bbb":@"b value"}; + // check that dictionary literals create valid objects + NSArray *foofoo = @[foo]; +} + +-(void) nilInDictionaryLiteralKey0 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{str: @"a value"}; +} + +-(void) nilInDictionaryLiteralValue0 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{@"aaa": str}; +} + +-(void) nilInDictionaryLiteralKey1 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{str: @"a value", @"bbb":@"b value"}; +} + +-(void) nilInDictionaryLiteralValue1 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{@"aaa": str, @"bbb":@"b value"}; +} + +-(void) nilInDictionaryLiteralKey2 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{@"aaa":@"a value", str:@"b value", @"ccc":@"c value"}; +} + +-(void) nilInDictionaryLiteralValue2 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{@"aaa":@"a value", @"bbb":str, @"ccc":@"c value"}; +} + +-(void) nilInDictionaryLiteralKey3 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{@"aaa":@"a value", @"bbb":@"b value", str:@"c value"}; +} + +-(void) nilInDictionaryLiteralValue3 { + NSString *str = nil; + + // nil argument in dictionary literal crashes + NSDictionary *foo = @{@"aaa":@"a value", @"bbb":@"b value", @"ccc":str}; +} + +@end + +int main() { + A *a = [A alloc]; + [a noProblem]; + [a nilInDictionaryLiteralKey0]; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.dot b/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.dot index ba4a0b126..b9f8c389c 100644 --- a/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.dot +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.dot @@ -1,5 +1,5 @@ digraph iCFG { -6 [label="6: Return Stmt \n n$1=_fun_NSString_stringWithUTF8String:(\"Matt\":char *) [line 23]\n n$2=_fun_NSString_stringWithUTF8String:(\"firstName\":char *) [line 23]\n n$3=_fun_NSString_stringWithUTF8String:(\"Galloway\":char *) [line 23]\n n$4=_fun_NSString_stringWithUTF8String:(\"lastName\":char *) [line 23]\n n$5=_fun_NSNumber_numberWithInt:(28:int ) [line 23]\n n$6=_fun_NSString_stringWithUTF8String:(\"age\":char *) [line 23]\n n$0=_fun_NSDictionary_dictionaryWithObjectsAndKeys:(n$1:struct objc_object *,n$2:struct objc_object *,n$3:struct objc_object *,n$4:struct objc_object *,n$5:struct objc_object *,n$6:struct objc_object *,0:struct objc_object *) [line 23]\n *&return:class NSDictionary *=n$0 [line 23]\n REMOVE_TEMPS(n$0,n$1,n$2,n$3,n$4,n$5,n$6); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] +6 [label="6: Return Stmt \n n$1=_fun_NSString_stringWithUTF8String:(\"Matt\":char *) [line 23]\n n$2=_fun_NSString_stringWithUTF8String:(\"firstName\":char *) [line 23]\n n$3=_fun_NSString_stringWithUTF8String:(\"Galloway\":char *) [line 23]\n n$4=_fun_NSString_stringWithUTF8String:(\"lastName\":char *) [line 23]\n n$5=_fun_NSNumber_numberWithInt:(28:int ) [line 23]\n n$6=_fun_NSString_stringWithUTF8String:(\"age\":char *) [line 23]\n n$0=_fun_NSDictionary___objc_dictionary_literal:(n$1:struct objc_object *,n$2:struct objc_object *,n$3:struct objc_object *,n$4:struct objc_object *,n$5:struct objc_object *,n$6:struct objc_object *,0:struct objc_object *) [line 23]\n *&return:class NSDictionary *=n$0 [line 23]\n REMOVE_TEMPS(n$0,n$1,n$2,n$3,n$4,n$5,n$6); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] 6 -> 5 ; diff --git a/infer/tests/endtoend/objc/NPEDictionaryLiteralTest.java b/infer/tests/endtoend/objc/NPEDictionaryLiteralTest.java new file mode 100644 index 000000000..e3bfa4224 --- /dev/null +++ b/infer/tests/endtoend/objc/NPEDictionaryLiteralTest.java @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2013 - present Facebook, Inc. +* All rights reserved. +* +* This source code is licensed under the BSD style license found in the +* LICENSE file in the root directory of this source tree. An additional grant +* of patent rights can be found in the PATENTS file in the same directory. +*/ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsExactly.containsExactly; + +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 NPEDictionaryLiteralTest { + + public static final String PREMATURE_NIL_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/nil_in_dictionary_literal.m"; + + private static ImmutableList inferCmd; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommandWithMLBuckets( + folderNPD, + PREMATURE_NIL_FILE, + "cf", + true); + } + + @Test + public void whenInferRunsOnTestThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + PREMATURE_NIL_FILE, + "no_problem")); + + String[] expectedNPEProcedures = { + "nilInDictionaryLiteralKey0", + "nilInDictionaryLiteralKey1", + "nilInDictionaryLiteralKey2", + "nilInDictionaryLiteralKey3", + "nilInDictionaryLiteralValue0", + "nilInDictionaryLiteralValue1", + "nilInDictionaryLiteralValue2", + "nilInDictionaryLiteralValue3" + }; + assertThat( + "Only NPE should be found", inferResults, + containsExactly( + NULL_DEREFERENCE, + PREMATURE_NIL_FILE, + expectedNPEProcedures)); + } +}