From 5503487704b2d490c080827a0318b8346104c401 Mon Sep 17 00:00:00 2001 From: Andrzej Kotulski Date: Thu, 6 Apr 2017 03:03:25 -0700 Subject: [PATCH] [C++] Introduce mechanism to write generic models Summary: Make it possible to write one model which will be used by all template instantiations. There is one big missing piece: infer never tries to do template instantiation by itself. With current code, it's possible to use generic models as long as header contains `__infer_generic_model` annotation (see the test as an example). This is not viable to modify all headers with this annotation hence infer will try to do template instantiation for generic models in later diffs. Reviewed By: jberdine Differential Revision: D4826365 fbshipit-source-id: 2233e42 --- infer/src/IR/BuiltinDecl.ml | 2 +- infer/src/IR/Typ.re | 38 ++++++++++++---- infer/src/IR/Typ.rei | 10 ++++- infer/src/backend/taint.ml | 3 +- infer/src/clang/CProcname.ml | 44 +++++++++++++------ infer/tests/codetoanalyze/cpp/errors/Makefile | 1 + .../errors/generic_models/generic_model.cpp | 35 +++++++++++++++ .../cpp/errors/generic_models/generic_model.h | 23 ++++++++++ .../generic_models/generic_model_test.cpp | 37 ++++++++++++++++ .../tests/codetoanalyze/cpp/errors/issues.exp | 2 + 10 files changed, 168 insertions(+), 27 deletions(-) create mode 100644 infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.cpp create mode 100644 infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.h create mode 100644 infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model_test.cpp diff --git a/infer/src/IR/BuiltinDecl.ml b/infer/src/IR/BuiltinDecl.ml index 14bf16cb0..088465727 100644 --- a/infer/src/IR/BuiltinDecl.ml +++ b/infer/src/IR/BuiltinDecl.ml @@ -25,7 +25,7 @@ let create_objc_class_method class_name method_name = let method_kind = Typ.Procname.ObjCClassMethod in let tname = Typ.Name.Objc.from_string class_name in let pname = Typ.Procname.ObjC_Cpp - (Typ.Procname.objc_cpp tname method_name method_kind Typ.NoTemplate) in + (Typ.Procname.objc_cpp tname method_name method_kind Typ.NoTemplate ~is_generic_model:false) in register pname; pname diff --git a/infer/src/IR/Typ.re b/infer/src/IR/Typ.re index 7ee248713..92fcd00d1 100644 --- a/infer/src/IR/Typ.re +++ b/infer/src/IR/Typ.re @@ -406,7 +406,12 @@ let module Procname = { [@@deriving compare]; /** Type of c procedure names. */ - type c = {name: QualifiedCppName.t, mangled: option string, template_args: template_spec_info} + type c = { + name: QualifiedCppName.t, + mangled: option string, + template_args: template_spec_info, + is_generic_model: bool + } [@@deriving compare]; type objc_cpp_method_kind = | CPPMethod (option string) /** with mangling */ @@ -421,7 +426,8 @@ let module Procname = { method_name: string, class_name: Name.t, kind: objc_cpp_method_kind, - template_args: template_spec_info + template_args: template_spec_info, + is_generic_model: bool } [@@deriving compare]; @@ -491,13 +497,19 @@ let module Procname = { | None => (None, package_classname) }; let split_typename typename => split_classname (Name.name typename); - let c (name: QualifiedCppName.t) (mangled: string) (template_args: template_spec_info) => { + let c name mangled template_args is_generic_model::is_generic_model => { name, mangled: Some mangled, - template_args + template_args, + is_generic_model }; let from_string_c_fun (name: string) => - C {name: QualifiedCppName.of_qual_string name, mangled: None, template_args: NoTemplate}; + C { + name: QualifiedCppName.of_qual_string name, + mangled: None, + template_args: NoTemplate, + is_generic_model: false + }; let java class_name return_type method_name parameters kind => { class_name, return_type, @@ -507,14 +519,16 @@ let module Procname = { }; /** Create an objc procedure name from a class_name and method_name. */ - let objc_cpp class_name method_name kind template_args => { + let objc_cpp class_name method_name kind template_args is_generic_model::is_generic_model => { class_name, method_name, kind, - template_args + template_args, + is_generic_model }; let get_default_objc_class_method objc_class => { - let objc_cpp = objc_cpp objc_class "__find_class_" ObjCInternalMethod NoTemplate; + let objc_cpp = + objc_cpp objc_class "__find_class_" ObjCInternalMethod NoTemplate is_generic_model::false; ObjC_Cpp objc_cpp }; @@ -921,7 +935,13 @@ let module Procname = { String.concat sep::"#"; Escape.escape_filename @@ SourceFile.append_crc_cutoff proc_id }; - let to_filename = to_concrete_filename; + let to_filename pname => + switch pname { + | C {is_generic_model} + | ObjC_Cpp {is_generic_model} when Bool.equal is_generic_model true => + to_generic_filename pname + | _ => to_concrete_filename pname + }; }; diff --git a/infer/src/IR/Typ.rei b/infer/src/IR/Typ.rei index ce4ab106b..590f4ecbb 100644 --- a/infer/src/IR/Typ.rei +++ b/infer/src/IR/Typ.rei @@ -269,7 +269,7 @@ let module Procname: { let module Set: PrettyPrintable.PPSet with type elt = t; /** Create a C procedure name from plain and mangled name. */ - let c: QualifiedCppName.t => string => template_spec_info => c; + let c: QualifiedCppName.t => string => template_spec_info => is_generic_model::bool => c; /** Empty block name. */ let empty_block: t; @@ -324,7 +324,13 @@ let module Procname: { let mangled_objc_block: string => t; /** Create an objc procedure name from a class_name and method_name. */ - let objc_cpp: Name.t => string => objc_cpp_method_kind => template_spec_info => objc_cpp; + let objc_cpp: + Name.t => + string => + objc_cpp_method_kind => + template_spec_info => + is_generic_model::bool => + objc_cpp; let get_default_objc_class_method: Name.t => t; /** Get the class name of a Objective-C/C++ procedure name. */ diff --git a/infer/src/backend/taint.ml b/infer/src/backend/taint.ml index cd93b6b09..ed527abd3 100644 --- a/infer/src/backend/taint.ml +++ b/infer/src/backend/taint.ml @@ -275,7 +275,8 @@ let objc_method_to_procname objc_method = let method_kind = Typ.Procname.objc_method_kind_of_bool (not objc_method.is_static) in let typename = Typ.Name.Objc.from_string objc_method.classname in Typ.Procname.ObjC_Cpp - (Typ.Procname.objc_cpp typename objc_method.method_name method_kind Typ.NoTemplate) + (Typ.Procname.objc_cpp typename objc_method.method_name method_kind + Typ.NoTemplate ~is_generic_model:false) let taint_spec_to_taint_info taint_spec = let taint_source = diff --git a/infer/src/clang/CProcname.ml b/infer/src/clang/CProcname.ml index f03cb20dc..6a4bd4444 100644 --- a/infer/src/clang/CProcname.ml +++ b/infer/src/clang/CProcname.ml @@ -38,6 +38,13 @@ let get_template_info tenv (fdi : Clang_ast_t.function_decl_info) : Typ.template | _ -> None)) | None -> Typ.NoTemplate +let is_decl_info_generic_model {Clang_ast_t.di_attributes} = + let f = function + | Clang_ast_t.AnnotateAttr {ai_parameters=[_; name; _]} + when String.equal name "__infer_generic_model" -> true + | _ -> false in + List.exists ~f di_attributes + let mk_c_function translation_unit_context ?tenv name function_decl_info_opt = let file = match function_decl_info_opt with @@ -55,14 +62,15 @@ let mk_c_function translation_unit_context ?tenv name function_decl_info_opt = let mangled_name = match mangled_opt with | Some m when CGeneral_utils.is_cpp_translation translation_unit_context -> m | _ -> "" in - let template_info = match function_decl_info_opt, tenv with - | Some (_, function_decl_info), Some t -> get_template_info t function_decl_info - | _ -> Typ.NoTemplate in + let template_info, is_generic_model = match function_decl_info_opt, tenv with + | Some (decl_info, function_decl_info), Some t -> + get_template_info t function_decl_info, is_decl_info_generic_model decl_info + | _ -> Typ.NoTemplate, false in let mangled = file ^ mangled_name in if String.is_empty mangled then Typ.Procname.from_string_c_fun (QualifiedCppName.to_qual_string name) else - Typ.Procname.C (Typ.Procname.c name mangled template_info) + Typ.Procname.C (Typ.Procname.c name mangled template_info ~is_generic_model) let mk_cpp_method ?tenv class_name method_name ?meth_decl mangled = let open Clang_ast_t in @@ -71,22 +79,30 @@ let mk_cpp_method ?tenv class_name method_name ?meth_decl mangled = Typ.Procname.CPPConstructor (mangled, xmdi_is_constexpr) | _ -> Typ.Procname.CPPMethod mangled in - let template_info = match meth_decl with - | Some (CXXMethodDecl (_, _, _, fdi, _)) - | Some (CXXConstructorDecl (_, _, _, fdi, _)) - | Some (CXXConversionDecl (_, _, _, fdi, _)) - | Some (CXXDestructorDecl (_, _, _, fdi, _)) -> ( - match tenv with + let template_info, is_generic_model = match meth_decl with + | Some (CXXMethodDecl (di, _, _, fdi, _)) + | Some (CXXConstructorDecl (di, _, _, fdi, _)) + | Some (CXXConversionDecl (di, _, _, fdi, _)) + | Some (CXXDestructorDecl (di, _, _, fdi, _)) -> ( + let templ_info = match tenv with | Some t -> get_template_info t fdi - | None -> Typ.NoTemplate + | None -> Typ.NoTemplate in + let is_gen_model = is_decl_info_generic_model di || + (* read whether parent class is annoatated as generic model *) + di.di_parent_pointer + |> Option.value_map ~f:CAst_utils.get_decl ~default:None + |> Option.map ~f:Clang_ast_proj.get_decl_tuple + |> Option.value_map ~f:is_decl_info_generic_model ~default:false in + templ_info, is_gen_model ) - | _ -> Typ.NoTemplate in + | _ -> Typ.NoTemplate, false in Typ.Procname.ObjC_Cpp - (Typ.Procname.objc_cpp class_name method_name method_kind template_info) + (Typ.Procname.objc_cpp class_name method_name method_kind template_info ~is_generic_model) let mk_objc_method class_typename method_name method_kind = Typ.Procname.ObjC_Cpp - (Typ.Procname.objc_cpp class_typename method_name method_kind Typ.NoTemplate) + (Typ.Procname.objc_cpp class_typename method_name method_kind Typ.NoTemplate + ~is_generic_model:false) let block_procname_with_index defining_proc i = Config.anonymous_block_prefix ^ diff --git a/infer/tests/codetoanalyze/cpp/errors/Makefile b/infer/tests/codetoanalyze/cpp/errors/Makefile index fdf9f84c7..18c4d66ec 100644 --- a/infer/tests/codetoanalyze/cpp/errors/Makefile +++ b/infer/tests/codetoanalyze/cpp/errors/Makefile @@ -19,6 +19,7 @@ SOURCES = \ include_header/include_templ.cpp \ $(wildcard memory_leaks/*.cpp) \ $(wildcard models/*.cpp) \ + $(wildcard generic_models/*.cpp) \ $(wildcard mutex/*.cpp) \ $(wildcard npe/*.cpp) \ $(wildcard numeric/*.cpp) \ diff --git a/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.cpp b/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.cpp new file mode 100644 index 000000000..cde4cee12 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 - 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. + */ +#include "generic_model.h" + +template +T* GenericModelClass::get() { + return nullptr; +} + +template +T* NonGenericModelClass::get() { + return nullptr; +} + +template +T* genericModelFunction() { + return nullptr; +} + +template +T* nonGenericModelFunction() { + return nullptr; +} + +// explicit instantiations with as template argument +template class GenericModelClass; +template class NonGenericModelClass; +template long long* genericModelFunction(); +template long long* nonGenericModelFunction(); diff --git a/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.h b/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.h new file mode 100644 index 000000000..e61b80551 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017 - 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. + */ +template +struct __attribute__((annotate("__infer_generic_model"))) GenericModelClass { + T* get(); +}; + +template +struct NonGenericModelClass { + T* get(); +}; + +template +__attribute__((annotate("__infer_generic_model"))) T* genericModelFunction(); + +template +T* nonGenericModelFunction(); diff --git a/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model_test.cpp b/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model_test.cpp new file mode 100644 index 000000000..421679463 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/errors/generic_models/generic_model_test.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017 - 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. + */ +#include "generic_model.h" + +/* This code uses template instantiations, but those are never + instantiated because there is no implementation of templated code in + "generic_model.h" + However, there is instantation for coming from + "generic_model.cpp". If generic model is truly generic, then infer will pick + up specs for those and use them +*/ +int genericModelNPE() { + GenericModelClass x; + auto ptr = x.get(); + return *ptr; +} + +int nonGenericModelNoNPE() { + NonGenericModelClass x; + auto ptr = x.get(); // this will be skip function + return *ptr; +} + +int genericModelFunctionNPE() { + auto ptr = genericModelFunction(); + return *ptr; +} +int nonGenericModelFunctionNPE() { + auto ptr = nonGenericModelFunction(); // this will be skip function + return *ptr; +} diff --git a/infer/tests/codetoanalyze/cpp/errors/issues.exp b/infer/tests/codetoanalyze/cpp/errors/issues.exp index 2e4541bf2..347c9ab6e 100644 --- a/infer/tests/codetoanalyze/cpp/errors/issues.exp +++ b/infer/tests/codetoanalyze/cpp/errors/issues.exp @@ -7,6 +7,8 @@ codetoanalyze/cpp/errors/c_tests/c_bugs.cpp, malloc_memory_leak_is_reported, 0, codetoanalyze/cpp/errors/c_tests/c_bugs.cpp, memcpy_spec_is_found, 3, NULL_DEREFERENCE, [start of procedure memcpy_spec_is_found()] codetoanalyze/cpp/errors/c_tests/c_bugs.cpp, resource_leak_is_reported, 0, RESOURCE_LEAK, [start of procedure resource_leak_is_reported()] codetoanalyze/cpp/errors/c_tests/c_bugs.cpp, resource_leak_is_reported, 0, RETURN_VALUE_IGNORED, [start of procedure resource_leak_is_reported()] +codetoanalyze/cpp/errors/generic_models/generic_model_test.cpp, genericModelFunctionNPE, 2, NULL_DEREFERENCE, [start of procedure genericModelFunctionNPE(),start of procedure genericModelFunction(),return from a call to genericModelFunction] +codetoanalyze/cpp/errors/generic_models/generic_model_test.cpp, genericModelNPE, 3, NULL_DEREFERENCE, [start of procedure genericModelNPE(),start of procedure GenericModelClass,return from a call to GenericModelClass_GenericModelClass,start of procedure get,return from a call to GenericModelClass_get] codetoanalyze/cpp/errors/include_header/header.h, header::A_div0, 0, DIVIDE_BY_ZERO, [start of procedure div0] codetoanalyze/cpp/errors/include_header/header.h, header::div0_fun, 0, DIVIDE_BY_ZERO, [start of procedure header::div0_fun()] codetoanalyze/cpp/errors/include_header/header2.h, header2::B_div0, 0, DIVIDE_BY_ZERO, [start of procedure div0]