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