Reviewed By: jeremydubreil Differential Revision: D2905842 fb-gh-sync-id: 08b8b58master
parent
d31b041fba
commit
0fbd333cab
@ -0,0 +1,62 @@
|
||||
(*
|
||||
* 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.
|
||||
*)
|
||||
|
||||
(** Make sure callbacks are always unregistered. drive the point home by reporting possible NPE's *)
|
||||
|
||||
module P = Printf
|
||||
open Utils
|
||||
|
||||
let report_error fld fld_typ pname pdesc =
|
||||
let retained_view = "CHECKERS_FRAGMENT_RETAINS_VIEW" in
|
||||
let fld_decl_class, fld_name =
|
||||
Ident.java_fieldname_get_class fld, Ident.java_fieldname_get_field fld in
|
||||
let description =
|
||||
P.sprintf
|
||||
"Fragment %s does not nullify View field %s (type %s) in onDestroyView. If the Fragment is placed on the back stack, a reference to the View may be retained."
|
||||
fld_decl_class
|
||||
fld_name
|
||||
(Sil.typ_to_string (Sil.typ_strip_ptr fld_typ)) in
|
||||
let loc = Cfg.Procdesc.get_loc pdesc in
|
||||
let exn = Exceptions.Checkers (retained_view, Localise.verbatim_desc description) in
|
||||
Reporting.log_error pname ~loc:(Some loc) exn
|
||||
|
||||
let callback_fragment_retains_view all_procs get_procdesc idenv tenv pname pdesc =
|
||||
(* TODO: complain if onDestroyView is not defined, yet the Fragment has View fields *)
|
||||
(* TODO: handle fields nullified in callees in the same file *)
|
||||
let is_on_destroy_view = Procname.java_get_method pname = "onDestroyView" in
|
||||
(* this is needlessly complicated because field types are Tvars instead of Tstructs *)
|
||||
let fld_typ_is_view = function
|
||||
| Sil.Tptr (Sil.Tvar tname, _) ->
|
||||
begin
|
||||
match Sil.tenv_lookup tenv tname with
|
||||
| Some typ -> AndroidFramework.is_view typ tenv
|
||||
| None -> false
|
||||
end
|
||||
| _ -> false in
|
||||
(* is [fldname] a View type declared by [class_typename]? *)
|
||||
let is_declared_view_typ class_typename (fldname, fld_typ, _) =
|
||||
let fld_classname = Typename.Java.from_string (Ident.java_fieldname_get_class fldname) in
|
||||
Typename.equal fld_classname class_typename && fld_typ_is_view fld_typ in
|
||||
if is_on_destroy_view then
|
||||
begin
|
||||
let class_typename = Typename.Java.from_string (Procname.java_get_class pname) in
|
||||
match Sil.tenv_lookup tenv class_typename with
|
||||
| Some (Sil.Tstruct { Sil.csu; struct_name = Some class_name; def_methods; instance_fields }
|
||||
as typ) when AndroidFramework.is_fragment typ tenv ->
|
||||
let declared_view_fields =
|
||||
IList.filter (is_declared_view_typ class_typename) instance_fields in
|
||||
let fields_nullified = PatternMatch.get_fields_nullified pdesc in
|
||||
(* report if a field is declared by C, but not nulled out in C.onDestroyView *)
|
||||
IList.iter
|
||||
(fun (fname, typ, _) ->
|
||||
if not (Ident.FieldSet.mem fname fields_nullified) then
|
||||
report_error fname typ pname pdesc)
|
||||
declared_view_fields
|
||||
| _ -> ()
|
||||
end
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2016 - 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 codetoanalyze.java.checkers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class FragmentDoesNotRetainViewExample extends Fragment {
|
||||
|
||||
class CustomView extends ListView {
|
||||
|
||||
public CustomView(Context c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
View mView1;
|
||||
View mView2;
|
||||
ViewGroup mViewSubclass;
|
||||
CustomView mCustomView;
|
||||
|
||||
boolean b;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
mView1 = inflater.inflate(-1, container, false);
|
||||
mView2 = inflater.inflate(-1, container, false);
|
||||
mViewSubclass = (ViewGroup) inflater.inflate(-1, container, false);
|
||||
mCustomView = (CustomView) inflater.inflate(-1, container, false);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mView1 = null;
|
||||
if (b) {
|
||||
mView2 = null; // conditional nulling is still ok
|
||||
}
|
||||
mCustomView = null;
|
||||
mViewSubclass = null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2016 - 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 codetoanalyze.java.checkers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class FragmentRetainsViewExample extends Fragment {
|
||||
|
||||
class CustomView extends ListView {
|
||||
|
||||
public CustomView(Context c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
View mView;
|
||||
ViewGroup mViewSubclass;
|
||||
CustomView mCustomView;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
mView = inflater.inflate(-1, container, false);
|
||||
mViewSubclass = (ViewGroup) inflater.inflate(-1, container, false);
|
||||
mCustomView = (CustomView) inflater.inflate(-1, container, false);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
// not nulling out anything
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2015 - 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.java.checkers;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import utils.InferException;
|
||||
import utils.InferResults;
|
||||
|
||||
public class FragmentDoesNotRetainViewTest {
|
||||
|
||||
public static final String SOURCE_FILE =
|
||||
"infer/tests/codetoanalyze/java/checkers/FragmentDoesNotRetainViewExample.java";
|
||||
|
||||
public static final String FRAGMENT_RETAINS_VIEW =
|
||||
"CHECKERS_FRAGMENT_RETAINS_VIEW";
|
||||
|
||||
private static InferResults inferResults;
|
||||
|
||||
@BeforeClass
|
||||
public static void loadResults() throws InterruptedException, IOException {
|
||||
inferResults =
|
||||
InferResults.loadCheckersResults(FragmentDoesNotRetainViewTest.class, SOURCE_FILE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchNumberOfErrors()
|
||||
throws IOException, InterruptedException, InferException {
|
||||
assertThat(
|
||||
"Results should contain 0 retained View errors",
|
||||
inferResults,
|
||||
doesNotContain(
|
||||
FRAGMENT_RETAINS_VIEW,
|
||||
SOURCE_FILE,
|
||||
"onDestroyView"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2015 - 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.java.checkers;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static utils.matchers.ResultContainsNumberOfErrorsInMethod.containsNumberOfErrors;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import utils.InferException;
|
||||
import utils.InferResults;
|
||||
|
||||
public class FragmentRetainsViewTest {
|
||||
|
||||
public static final String SOURCE_FILE =
|
||||
"infer/tests/codetoanalyze/java/checkers/FragmentRetainsViewExample.java";
|
||||
|
||||
public static final String FRAGMENT_RETAINS_VIEW =
|
||||
"CHECKERS_FRAGMENT_RETAINS_VIEW";
|
||||
|
||||
private static InferResults inferResults;
|
||||
|
||||
@BeforeClass
|
||||
public static void loadResults() throws InterruptedException, IOException {
|
||||
inferResults =
|
||||
InferResults.loadCheckersResults(FragmentRetainsViewTest.class, SOURCE_FILE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchNumberOfErrors()
|
||||
throws IOException, InterruptedException, InferException {
|
||||
assertThat(
|
||||
"Results should contain 3 retained View errors",
|
||||
inferResults,
|
||||
containsNumberOfErrors(
|
||||
FRAGMENT_RETAINS_VIEW,
|
||||
SOURCE_FILE,
|
||||
"onDestroyView",
|
||||
3
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue