[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
(* 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).
So in general case anonymous class name looks something like
Class$NestedClass$1$17$5, and we need to return Class$NestedClass *)
In general case anonymous class name looks something like
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 outer_class_name, nesting_level = strip_anonymous_suffixes_if_present classname in
if nesting_level > 0 then Some {package; classname= outer_class_name} else None
let without_lambda, was_lambda_stripped = strip_lambda_if_present classname in
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

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

@ -46,7 +46,7 @@ let test_from_string =
let test_anonymous =
"test_from_string"
"test_anonymous"
>:: fun _ ->
(* If it is not an anonymous class, we expect this to return None *)
get_user_defined_class_if_anonymous_inner
@ -80,6 +80,23 @@ let test_anonymous =
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package")
~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 *)
get_user_defined_class_if_anonymous_inner
(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, 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/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, 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"

Loading…
Cancel
Save