Reviewed By: sblackshear Differential Revision: D5970391 fbshipit-source-id: 6148094master
parent
4255d918ad
commit
923a15fa60
@ -0,0 +1,121 @@
|
||||
(*
|
||||
* 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.
|
||||
*)
|
||||
|
||||
module L = Logging
|
||||
module MF = MarkupFormatter
|
||||
module CallSites = AbstractDomain.FiniteSet (CallSite)
|
||||
module Domain = AbstractDomain.Map (AccessPath) (CallSites)
|
||||
|
||||
module TransferFunctions (CFG : ProcCfg.S) = struct
|
||||
module CFG = CFG
|
||||
module Domain = Domain
|
||||
|
||||
type extras = Specs.summary
|
||||
|
||||
let is_instance_method callee_pname =
|
||||
if Typ.Procname.is_java callee_pname then not (Typ.Procname.java_is_static callee_pname)
|
||||
else
|
||||
Option.exists
|
||||
~f:(fun attributes ->
|
||||
attributes.ProcAttributes.is_objc_instance_method
|
||||
|| attributes.ProcAttributes.is_cpp_instance_method)
|
||||
(Specs.proc_resolve_attributes callee_pname)
|
||||
|
||||
let report_nullable_dereference ap call_sites {ProcData.pdesc; extras} loc =
|
||||
let pname = Procdesc.get_proc_name pdesc in
|
||||
let annotation = Localise.nullable_annotation_name pname in
|
||||
let issue_kind = IssueType.nullable_dereference.unique_id in
|
||||
let call_site =
|
||||
try CallSites.min_elt call_sites
|
||||
with Not_found ->
|
||||
L.(die InternalError)
|
||||
"Expecting a least one element in the set of call sites when analyzing %a"
|
||||
Typ.Procname.pp pname
|
||||
in
|
||||
let message =
|
||||
Format.asprintf
|
||||
"Variable %a is indirectly annotated with %a (source %a) and is dereferenced without being checked for null"
|
||||
(MF.wrap_monospaced AccessPath.pp) ap MF.pp_monospaced annotation
|
||||
(MF.wrap_monospaced CallSite.pp) call_site
|
||||
in
|
||||
let exn = Exceptions.Checkers (issue_kind, Localise.verbatim_desc message) in
|
||||
let summary = extras in
|
||||
let trace =
|
||||
let with_origin_site =
|
||||
let callee_pname = CallSite.pname call_site in
|
||||
match Specs.proc_resolve_attributes callee_pname with
|
||||
| None
|
||||
-> []
|
||||
| Some attributes
|
||||
-> let description =
|
||||
Format.asprintf "definition of %s" (Typ.Procname.get_method callee_pname)
|
||||
in
|
||||
let trace_element =
|
||||
Errlog.make_trace_element 1 attributes.ProcAttributes.loc description []
|
||||
in
|
||||
[trace_element]
|
||||
in
|
||||
let with_assignment_site =
|
||||
let call_site_loc = CallSite.loc call_site in
|
||||
if Location.equal call_site_loc loc then with_origin_site
|
||||
else
|
||||
let trace_element =
|
||||
Errlog.make_trace_element 0 call_site_loc "assignment of the nullable value" []
|
||||
in
|
||||
trace_element :: with_origin_site
|
||||
in
|
||||
let dereference_site =
|
||||
let description = Format.asprintf "deference of %a" AccessPath.pp ap in
|
||||
Errlog.make_trace_element 0 loc description []
|
||||
in
|
||||
dereference_site :: with_assignment_site
|
||||
in
|
||||
Reporting.log_error summary ~loc ~ltr:trace exn
|
||||
|
||||
let exec_instr (astate: Domain.astate) proc_data _ (instr: HilInstr.t) : Domain.astate =
|
||||
match instr with
|
||||
| Call (Some ret_var, Direct callee_pname, _, _, loc)
|
||||
when Annotations.pname_has_return_annot callee_pname
|
||||
~attrs_of_pname:Specs.proc_resolve_attributes Annotations.ia_is_nullable
|
||||
-> let call_site = CallSite.make callee_pname loc in
|
||||
Domain.add (ret_var, []) (CallSites.singleton call_site) astate
|
||||
| Call (_, Direct callee_pname, (HilExp.AccessPath receiver) :: _, _, loc)
|
||||
when is_instance_method callee_pname -> (
|
||||
match Domain.find_opt receiver astate with
|
||||
| None
|
||||
-> astate
|
||||
| Some call_sites
|
||||
-> report_nullable_dereference receiver call_sites proc_data loc ;
|
||||
Domain.remove receiver astate )
|
||||
| Call (Some ret_var, _, _, _, _)
|
||||
-> Domain.remove (ret_var, []) astate
|
||||
| Assign (lhs, _, loc) when Domain.mem lhs astate
|
||||
-> report_nullable_dereference lhs (Domain.find lhs astate) proc_data loc ;
|
||||
Domain.remove lhs astate
|
||||
| Assign (lhs, HilExp.AccessPath rhs, _) when Domain.mem rhs astate
|
||||
-> Domain.add lhs (Domain.find rhs astate) astate
|
||||
| Assign (lhs, _, _)
|
||||
-> Domain.remove lhs astate
|
||||
| Assume (HilExp.AccessPath ap, _, _, _)
|
||||
-> Domain.remove ap astate
|
||||
| Assume (HilExp.BinaryOperator (Binop.Ne, HilExp.AccessPath ap, exp), _, _, _)
|
||||
when HilExp.is_null_literal exp
|
||||
-> Domain.remove ap astate
|
||||
| _
|
||||
-> astate
|
||||
end
|
||||
|
||||
module Analyzer =
|
||||
AbstractInterpreter.Make (ProcCfg.Exceptional) (LowerHil.MakeDefault (TransferFunctions))
|
||||
|
||||
let checker {Callbacks.summary; proc_desc; tenv} =
|
||||
let initial = (Domain.empty, IdAccessPathMapDomain.empty) in
|
||||
let proc_data = ProcData.make proc_desc tenv summary in
|
||||
ignore (Analyzer.compute_post proc_data ~initial ~debug:false) ;
|
||||
summary
|
@ -0,0 +1,10 @@
|
||||
(*
|
||||
* 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.
|
||||
*)
|
||||
|
||||
val checker : Callbacks.proc_callback_t
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
bool star();
|
||||
|
||||
class T {
|
||||
|
||||
public:
|
||||
int* _Nullable mayReturnNullPointer() {
|
||||
if (star()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return new int;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
T* _Nullable mayReturnNullObject() {
|
||||
if (star()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void doSomething() {}
|
||||
};
|
||||
|
||||
void assignNullableValueBad(T* t) {
|
||||
int* p = t->mayReturnNullPointer();
|
||||
*p = 42;
|
||||
}
|
||||
|
||||
void FP_reAssigningNullableValueOk(T* t) {
|
||||
int* p = t->mayReturnNullPointer();
|
||||
p = new int;
|
||||
*p = 42;
|
||||
}
|
||||
|
||||
void callMethodOnNullableObjectBad(T* t) {
|
||||
t->mayReturnNullObject()->doSomething();
|
||||
}
|
||||
|
||||
void callMethodOnNullableObjectOk(T* t) {
|
||||
T* p = t->mayReturnNullObject();
|
||||
if (p != nullptr) {
|
||||
p->doSomething();
|
||||
}
|
||||
}
|
Loading…
Reference in new issue