[nullsafe] Take into account Lambda classes in JavaClassName

Summary:
There are two types of anonymous classes (not user defined classes):
- classic anonymous classes (defined as $<int> suffixes)
- lambda classes (corresponding to lambda expressions). Experimentally,
they all have form `$Lambda$_<int>_<int>`, but the code just uses
`$Lambda$` as a heuristic so it is potentially more robust.

# Problem this diff solves
When generate meta-issues for nullsafe, we are interested only in
user-defined classes, so we merge all nested anonymous stuff to
corresponding user-defined classes and hence aggregate the issues.

Without this diff, for each lambda in the code, we would report this as
a separate meta-issue, which would both screw up stats and be confusing
for the user (when we start reporting mode promo suggestions!).

Reviewed By: artempyanykh

Differential Revision: D21042928

fbshipit-source-id: a7be266af
master
Mitya Lyubarskiy 5 years ago committed by Facebook GitHub Bot
parent 2152af123d
commit 6a75ba6429

@ -72,14 +72,29 @@ let strip_anonymous_suffixes_if_present classname =
strip_recursively classname 0 strip_recursively classname 0
(* Strips everything after $Lambda$ (if it is a lambda-class),
and returns the result string together with if it was stripped *)
let strip_lambda_if_present classname =
match String.substr_index classname ~pattern:"$Lambda$" with
| Some index ->
(String.prefix classname index, true)
| None ->
(classname, false)
(* (*
Anonymous classes have suffixes in form of $<int>; but they can be nested inside of each other. Anonymous classes have two forms:
- classic anonymous classes: suffixes in form of $<int>.
- classes corresponding to lambda-expressions: they are manifested as $Lambda$.
- two forms above nested inside each other.
Also non-anonymous (user-defined) name can be nested as well (Class$NestedClass). Also non-anonymous (user-defined) name can be nested as well (Class$NestedClass).
So in general case anonymous class name looks something like In general case anonymous class name looks something like
Class$NestedClass$1$17$5, and we need to return Class$NestedClass *) Class$NestedClass$1$17$5$Lambda$_1_2, and we need to return Class$NestedClass *)
let get_user_defined_class_if_anonymous_inner {package; classname} = let get_user_defined_class_if_anonymous_inner {package; classname} =
let outer_class_name, nesting_level = strip_anonymous_suffixes_if_present classname in let without_lambda, was_lambda_stripped = strip_lambda_if_present classname in
if nesting_level > 0 then Some {package; classname= outer_class_name} else None let outer_class_name, nesting_level = strip_anonymous_suffixes_if_present without_lambda in
let was_stripped = was_lambda_stripped || nesting_level > 0 in
if was_stripped then Some {package; classname= outer_class_name} else None
let is_anonymous_inner_class_name t = get_user_defined_class_if_anonymous_inner t |> is_some let is_anonymous_inner_class_name t = get_user_defined_class_if_anonymous_inner t |> is_some

@ -30,15 +30,15 @@ val is_external_via_config : t -> bool
(** Considered external based on config flags. *) (** Considered external based on config flags. *)
val is_anonymous_inner_class_name : t -> bool val is_anonymous_inner_class_name : t -> bool
(** True if it is anonymous Java class: (** True if it is either "classic" anonymous Java class:
https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html *) https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html, or a synthetic Java
class corresponding to a lambda expression. *)
val get_user_defined_class_if_anonymous_inner : t -> t option val get_user_defined_class_if_anonymous_inner : t -> t option
(** If the current class is anonymous ([is_anonymous_inner_class_name] is true), Return the (** If the current class is anonymous ([is_anonymous_inner_class_name] is true), return the
corresponding user defined (not anonymous) class this anonymous class belongs to. corresponding user defined (not anonymous) class this anonymous class belongs to.
In general case, BOTH anonymous classes and user-defined classes can be nested, so the most In general case, BOTH anonymous classes and user-defined classes can be nested:
general example looks like So in general case anonymous class name looks something like SomeClass$NestedClass$1$17$5. In this example, we should return SomeClass$NestedClass.
Class$NestedClass$1$17$5. This function should return Class$NestedClass for this case.
If this is not an anonymous class, returns [None]. *) If this is not an anonymous class, returns [None]. *)

@ -46,7 +46,7 @@ let test_from_string =
let test_anonymous = let test_anonymous =
"test_from_string" "test_anonymous"
>:: fun _ -> >:: fun _ ->
(* If it is not an anonymous class, we expect this to return None *) (* If it is not an anonymous class, we expect this to return None *)
get_user_defined_class_if_anonymous_inner get_user_defined_class_if_anonymous_inner
@ -80,6 +80,23 @@ let test_anonymous =
|> assert_some |> assert_some
|> assert_equal_to ~expected_package:(Some "some.package") |> assert_equal_to ~expected_package:(Some "some.package")
~expected_classname:"SomeClass$NestedClass$AgainNestedClass" ; ~expected_classname:"SomeClass$NestedClass$AgainNestedClass" ;
(* If it is a lambda class, we expect this to be detected *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package") ~classname:"SomeClass$Lambda$_4_1")
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package") ~expected_classname:"SomeClass" ;
(* Lambda might be inside anonymous (or several ones in general case) *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package") ~classname:"SomeClass$1$7$Lambda$_4_1")
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package") ~expected_classname:"SomeClass" ;
(* The most general case: nested class, lambda, and anonymous mixed *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package")
~classname:"SomeClass$NestedClass$7$1$Lambda$_4_1$2$Lambda$_7_18$19$16")
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package")
~expected_classname:"SomeClass$NestedClass" ;
(* If package was empty, everything should still work *) (* If package was empty, everything should still work *)
get_user_defined_class_if_anonymous_inner get_user_defined_class_if_anonymous_inner
(make ~package:None ~classname:"SomeClass$NestedClass$AgainNestedClass$17$23$1") (make ~package:None ~classname:"SomeClass$NestedClass$AgainNestedClass$17$23$1")

@ -328,7 +328,6 @@ codetoanalyze/java/nullsafe-default/PropagatesNullable.java, codetoanalyze.java.
codetoanalyze/java/nullsafe-default/PropagatesNullable.java, codetoanalyze.java.nullsafe_default.TestPropagatesNullable$TestSecondParameter.test(java.lang.String,java.lang.String):void, 7, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`nullable(...)` is nullable and is not locally checked for null when calling `length()`.] codetoanalyze/java/nullsafe-default/PropagatesNullable.java, codetoanalyze.java.nullsafe_default.TestPropagatesNullable$TestSecondParameter.test(java.lang.String,java.lang.String):void, 7, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`nullable(...)` is nullable and is not locally checked for null when calling `length()`.]
codetoanalyze/java/nullsafe-default/PropagatesNullable.java, codetoanalyze.java.nullsafe_default.TestPropagatesNullable$TestSecondParameter.test(java.lang.String,java.lang.String):void, 11, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`nullable(...)` is nullable and is not locally checked for null when calling `length()`.] codetoanalyze/java/nullsafe-default/PropagatesNullable.java, codetoanalyze.java.nullsafe_default.TestPropagatesNullable$TestSecondParameter.test(java.lang.String,java.lang.String):void, 11, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`nullable(...)` is nullable and is not locally checked for null when calling `length()`.]
codetoanalyze/java/nullsafe-default/PropagatesNullable.java, codetoanalyze.java.nullsafe_default.TestPropagatesNullable$TestSecondParameter.test(java.lang.String,java.lang.String):void, 15, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`nullable(...)` is nullable and is not locally checked for null when calling `length()`.] codetoanalyze/java/nullsafe-default/PropagatesNullable.java, codetoanalyze.java.nullsafe_default.TestPropagatesNullable$TestSecondParameter.test(java.lang.String,java.lang.String):void, 15, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`nullable(...)` is nullable and is not locally checked for null when calling `length()`.]
codetoanalyze/java/nullsafe-default/ReturnNotNullable.java, Linters_dummy_method, 1, ERADICATE_META_CLASS_CAN_BE_NULLSAFE, no_bucket, ADVICE, [Congrats! Class codetoanalyze.java.nullsafe_default.ReturnNotNullable$Lambda$_9_1 is free of nullability issues. Mark it `@Nullsafe(Nullsafe.Mode.Local)` to prevent regressions.], ReturnNotNullable$Lambda$_9_1, codetoanalyze.java.nullsafe_default, issues: 0, curr_mode: "Default", promote_mode: "LocalTrustAll"
codetoanalyze/java/nullsafe-default/ReturnNotNullable.java, Linters_dummy_method, 19, ERADICATE_META_CLASS_NEEDS_IMPROVEMENT, no_bucket, INFO, [], ReturnNotNullable, codetoanalyze.java.nullsafe_default, issues: 9, curr_mode: "Default" codetoanalyze/java/nullsafe-default/ReturnNotNullable.java, Linters_dummy_method, 19, ERADICATE_META_CLASS_NEEDS_IMPROVEMENT, no_bucket, INFO, [], ReturnNotNullable, codetoanalyze.java.nullsafe_default, issues: 9, curr_mode: "Default"
codetoanalyze/java/nullsafe-default/ReturnNotNullable.java, Linters_dummy_method, 154, ERADICATE_META_CLASS_CAN_BE_NULLSAFE, no_bucket, ADVICE, [Congrats! Class codetoanalyze.java.nullsafe_default.ReturnNotNullable$E is free of nullability issues. Mark it `@Nullsafe(value = Nullsafe.Mode.LOCAL, trustOnly = @Nullsafe.TrustList({}))` to prevent regressions.], ReturnNotNullable$E, codetoanalyze.java.nullsafe_default, issues: 0, curr_mode: "Default", promote_mode: "Strict" codetoanalyze/java/nullsafe-default/ReturnNotNullable.java, Linters_dummy_method, 154, ERADICATE_META_CLASS_CAN_BE_NULLSAFE, no_bucket, ADVICE, [Congrats! Class codetoanalyze.java.nullsafe_default.ReturnNotNullable$E is free of nullability issues. Mark it `@Nullsafe(value = Nullsafe.Mode.LOCAL, trustOnly = @Nullsafe.TrustList({}))` to prevent regressions.], ReturnNotNullable$E, codetoanalyze.java.nullsafe_default, issues: 0, curr_mode: "Default", promote_mode: "Strict"
codetoanalyze/java/nullsafe-default/ReturnNotNullable.java, Linters_dummy_method, 191, ERADICATE_META_CLASS_NEEDS_IMPROVEMENT, no_bucket, INFO, [], ReturnNotNullable$ConditionalAssignment, codetoanalyze.java.nullsafe_default, issues: 1, curr_mode: "Default" codetoanalyze/java/nullsafe-default/ReturnNotNullable.java, Linters_dummy_method, 191, ERADICATE_META_CLASS_NEEDS_IMPROVEMENT, no_bucket, INFO, [], ReturnNotNullable$ConditionalAssignment, codetoanalyze.java.nullsafe_default, issues: 1, curr_mode: "Default"

Loading…
Cancel
Save