From ca8396d9bc9bc3c2c3201583be12d07e5708fa0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ezgi=20=C3=87i=C3=A7ek?= Date: Fri, 6 Dec 2019 04:39:45 -0800 Subject: [PATCH] [litho] Add new tests for new litho domain Reviewed By: skcho Differential Revision: D18851068 fbshipit-source-id: b34840a63 --- Makefile | 1 + .../java/litho-required-props/Column.java | 50 +++ .../java/litho-required-props/Component.java | 22 ++ .../java/litho-required-props/Makefile | 12 + .../litho-required-props/MyComponent.java | 54 +++ .../MyLithoComponent.java | 43 +++ .../litho-required-props/MyTreeComponent.java | 38 ++ .../java/litho-required-props/Prop.java | 22 ++ .../litho-required-props/RequiredProps.java | 364 ++++++++++++++++++ .../ResPropComponent.java | 69 ++++ .../java/litho-required-props/ResType.java | 12 + .../java/litho-required-props/TreeProp.java | 20 + .../VarArgPropComponent.java | 83 ++++ .../java/litho-required-props/issues.exp | 19 + 14 files changed, 809 insertions(+) create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/Column.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/Component.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/Makefile create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/MyComponent.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/MyLithoComponent.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/MyTreeComponent.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/Prop.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/ResPropComponent.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/ResType.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/TreeProp.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/VarArgPropComponent.java create mode 100644 infer/tests/codetoanalyze/java/litho-required-props/issues.exp diff --git a/Makefile b/Makefile index 3e60f6ca1..f2fefc9f9 100644 --- a/Makefile +++ b/Makefile @@ -161,6 +161,7 @@ DIRECT_TESTS += \ java_inefficientKeysetIterator \ java_infer \ java_litho \ + java_litho-required-props \ java_performance \ java_purity \ java_quandary \ diff --git a/infer/tests/codetoanalyze/java/litho-required-props/Column.java b/infer/tests/codetoanalyze/java/litho-required-props/Column.java new file mode 100644 index 000000000..b63386004 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/Column.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.litho; + +public class Column extends Component { + + static native Builder acquire(); + + public static Builder create() { + Builder builder = acquire(); + if (builder == null) { + builder = new Builder(); + } + return builder; + } + + public static class Builder extends Component.Builder { + + public Builder child(Component child) { + if (child == null) { + return this; + } + return this; + } + + public Builder child(Component.Builder child) { + if (child == null) { + return this; + } + return child(child.build()); + } + + Column mColumn; + + @Override + public Column build() { + return mColumn; + } + + @Override + public Builder getThis() { + return this; + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/Component.java b/infer/tests/codetoanalyze/java/litho-required-props/Component.java new file mode 100644 index 000000000..a72ae84b9 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/Component.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.litho; + +public class Component { + + public abstract static class Builder> { + + public abstract Component build(); + + public abstract T getThis(); + + public T commonProp(Object prop) { + return getThis(); + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/Makefile b/infer/tests/codetoanalyze/java/litho-required-props/Makefile new file mode 100644 index 000000000..da74bc966 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/Makefile @@ -0,0 +1,12 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +TESTS_DIR = ../../.. + +INFER_OPTIONS = --litho-required-props-only --new-litho-domain --debug-exceptions +INFERPRINT_OPTIONS = --issues-tests +SOURCES = $(wildcard *.java) + +include $(TESTS_DIR)/javac.make diff --git a/infer/tests/codetoanalyze/java/litho-required-props/MyComponent.java b/infer/tests/codetoanalyze/java/litho-required-props/MyComponent.java new file mode 100644 index 000000000..6bb4e7c86 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/MyComponent.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package codetoanalyze.java.litho; + +import com.facebook.litho.Component; +import com.facebook.litho.annotations.Prop; + +public class MyComponent extends Component { + @Prop Object prop1; // implicitly non-optional + + @Prop(optional = true) + Object prop2; // explicitly optional + + @Prop(optional = false) + Object prop3; // explicitly non-optional + + Object nonProp; + + public Builder create() { + return new Builder(); + } + + public static class Builder extends Component.Builder { + MyComponent mMyComponent; + + public Builder prop1(Object o) { + this.mMyComponent.prop1 = o; + return this; + } + + public Builder prop2(Object o) { + this.mMyComponent.prop2 = o; + return this; + } + + public Builder prop3(Object o) { + this.mMyComponent.prop3 = o; + return this; + } + + public MyComponent build() { + return mMyComponent; + } + + @Override + public Builder getThis() { + return this; + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/MyLithoComponent.java b/infer/tests/codetoanalyze/java/litho-required-props/MyLithoComponent.java new file mode 100644 index 000000000..a4acb10e5 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/MyLithoComponent.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package com.facebook.litho; + +import com.facebook.litho.annotations.Prop; + +public class MyLithoComponent extends Component { + @Prop Object prop1; // implicitly non-optional + + @Prop(optional = false) + Object prop2; // explicitly non-optional + + public Builder create() { + return new Builder(); + } + + public static class Builder extends Component.Builder { + MyLithoComponent mMyLithoComponent; + + public Builder prop1(Object o) { + this.mMyLithoComponent.prop1 = o; + return this; + } + + public Builder prop2(Object o) { + this.mMyLithoComponent.prop2 = o; + return this; + } + + public MyLithoComponent build() { + return mMyLithoComponent; + } + + @Override + public Builder getThis() { + return this; + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/MyTreeComponent.java b/infer/tests/codetoanalyze/java/litho-required-props/MyTreeComponent.java new file mode 100644 index 000000000..addc1d849 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/MyTreeComponent.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package codetoanalyze.java.litho; + +import com.facebook.litho.Component; +import com.facebook.litho.annotations.TreeProp; + +class MyTreeComponent extends Component { + @TreeProp Object prop1; // implicitly non-optional + + Object nonProp; + + public Builder create() { + return new Builder(); + } + + static class Builder extends Component.Builder { + MyTreeComponent mMyTreeComponent; + + public Builder prop1(Object o) { + this.mMyTreeComponent.prop1 = o; + return this; + } + + public MyTreeComponent build() { + return mMyTreeComponent; + } + + @Override + public Builder getThis() { + return this; + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/Prop.java b/infer/tests/codetoanalyze/java/litho-required-props/Prop.java new file mode 100644 index 000000000..cf49d01e3 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/Prop.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package com.facebook.litho.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.CLASS) +public @interface Prop { + ResType resType() default ResType.NONE; + + boolean optional() default false; + + String varArg() default ""; +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java b/infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java new file mode 100644 index 000000000..0a4d46ead --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java @@ -0,0 +1,364 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package codetoanalyze.java.litho; + +import com.facebook.litho.Column; +import com.facebook.litho.Component; +import com.facebook.litho.MyLithoComponent; +import java.util.ArrayList; + +public class RequiredProps { + + public MyComponent mMyComponent; + public MyLithoComponent mMyLithoComponent; + public ResPropComponent mResPropComponent; + public VarArgPropComponent mVarArgPropComponent; + + public Component buildWithAllOk() { + return mMyComponent + .create() + .prop1(new Object()) + .prop2(new Object()) + .prop3(new Object()) + .build(); + } + + // prop 2 is optional + public Component buildWithout2Ok() { + return mMyComponent.create().prop1(new Object()).prop3(new Object()).build(); + } + + // prop 1 is required + public Component buildWithout1Bad() { + return mMyComponent.create().prop2(new Object()).prop3(new Object()).build(); + } + + // prop3 is required + public Component buildWithout3Bad() { + return mMyComponent.create().prop1(new Object()).prop2(new Object()).build(); + } + + public Component buildWithCommonPropOk() { + return mMyComponent + .create() + .prop1(new Object()) + .commonProp(new Object()) + .prop3(new Object()) + .build(); + } + + private static MyComponent.Builder setProp1(MyComponent.Builder builder) { + return builder.prop1(new Object()); + } + + private static MyComponent.Builder setProp3(MyComponent.Builder builder) { + return builder.prop3(new Object()); + } + + public Component setProp1InCalleeOk() { + return setProp1(mMyComponent.create().prop2(new Object())).prop3(new Object()).build(); + } + + public Component setProp3InCalleeOk() { + return setProp3(mMyComponent.create().prop1(new Object()).prop2(new Object())).build(); + } + + public Component setProp3InCalleeButForgetProp1Bad_FN() { + return setProp3(mMyComponent.create()).prop2(new Object()).build(); + } + + public Component setRequiredOnOneBranchBad(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + if (b) { + builder = builder.prop1(new Object()); + } + return builder.prop2(new Object()).prop3(new Object()).build(); + } + + public Component setRequiredOnBothBranchesOk(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + if (b) { + builder = builder.prop1(new Object()); + } else { + builder = builder.prop1(new Object()); + } + return builder.prop2(new Object()).prop3(new Object()).build(); + } + + public Component setRequiredOnBothBranchesNoAssignOk(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + if (b) { + builder.prop1(new Object()); + } else { + builder.prop1(new Object()); + } + return builder.prop2(new Object()).prop3(new Object()).build(); + } + + // gets confused at cyclic dependency to builder when setting prop1 + public Component setRequiredOk(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + builder = builder.prop1(new Object()); + return builder.prop2(new Object()).prop3(new Object()).build(); + } + + public Component setRequiredEffectful(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + builder.prop1(new Object()); + return builder.prop2(new Object()).prop3(new Object()).build(); + } + + public Component setRequiredOnOneBranchEffectfulBad(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + if (b) { + builder.prop1(new Object()); + } + return builder.prop2(new Object()).prop3(new Object()).build(); + } + + public void buildPropLithoMissingOneInLoopBad(int x) { + + for (int i = 0; i < x; i++) { + Column.create() + .child(mMyLithoComponent.create().prop1(new Object()).commonProp(new Object())) + .build(); + } + } + + public Component doubleSetMissingBad() { + Component.Builder builder = + mMyComponent.create().commonProp(new Object()).prop3(new Object()).commonProp(new Object()); + return builder.build(); + } + + // due to mutual recursion check, we break cycle at seen props + public Component doubleSetCommonOk() { + Component.Builder builder = + mMyComponent + .create() + .prop1(new Object()) + .commonProp(new Object()) + .prop3(new Object()) + .commonProp(new Object()); + return builder.build(); + } + + // only missing prop3 + public Component setRequiredOnBothBranchesMissingProp3Bad(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + if (b) { + builder.prop1(new Object()); + } else { + builder.prop1(new Object()); + } + return builder.prop2(new Object()).build(); + } + + public void buildPropInConditionalOk(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + if (b) { + builder.prop1(new Object()).prop3(new Object()); + } else { + builder.prop1(new Object()).prop3(new Object()); + } + builder.build(); + } + + // should be only missing prop3 + public void buildPropMissingInConditionalBad(boolean b) { + MyComponent.Builder builder = mMyComponent.create(); + if (b) { + builder.prop1(new Object()).prop3(new Object()); + } else { + builder.prop2(new Object()).prop1(new Object()); + } + builder.build(); + } + + public boolean isEmptyOrNull(String str) { + return str == null || str.isEmpty(); + } + + public void buildInterProcUnrelatedBad(boolean b, String s) { + MyComponent.Builder builder = mMyComponent.create().prop1(new Object()); + if (!isEmptyOrNull(s)) { + builder.prop3(new Object()); + } + builder.build(); + } + + // don't want to report here; want to report at clients that don't pass prop1 + private MyComponent buildSuffix(MyComponent.Builder builder) { + return builder.prop2(new Object()).prop3(new Object()).build(); + } + + // shouldn't report here; prop 1 passed + public Component callBuildSuffixWithRequiredOk() { + return buildSuffix(mMyComponent.create().prop1(new Object())); + } + + // should report here; forgot prop 1 + public Component callBuildSuffixWithoutRequiredBad_FN() { + return buildSuffix(mMyComponent.create()); + } + + public Object generalTypeWithout2Ok() { + Component.Builder builder = mMyComponent.create().prop1(new Object()).prop3(new Object()); + return builder.build(); + } + + public Object generalTypeForgot3Bad() { + MyComponent.Builder builder1 = mMyComponent.create(); + Component.Builder builder2 = (Component.Builder) builder1.prop1(new Object()); + // don't fail to find required @Prop's for MyComponent.Builder even though the static type that + // build is invoked on is [builder2] + return builder2.build(); + } + + public void buildWithColumnChildBad_FN() { + Column.Builder builder = Column.create(); + Component.Builder childBuilder = mMyComponent.create().prop1(new Object()); + // forgot prop 3, and builder.child() will invoke build() on childBuilder + builder.child(childBuilder); + } + + public Component buildWithColumnChildOk() { + return Column.create() + .child(mMyComponent.create().prop1(new Object()).prop3(new Object())) + .build(); + } + + public void buildPropResWithNormalOk() { + mResPropComponent.create().prop(new Object()).build(); + } + + public void buildPropResWithResOk() { + mResPropComponent.create().propRes(new Object()).build(); + } + + public void buildPropResWithAttrOk() { + mResPropComponent.create().propAttr(new Object()).build(); + } + + public void buildPropResWithDipOk() { + mResPropComponent.create().propDip(new Object()).build(); + } + + public void buildPropResWithPxOk() { + mResPropComponent.create().propPx(new Object()).build(); + } + + public void buildPropResWithSpOk() { + mResPropComponent.create().propSp(new Object()).build(); + } + + public void buildPropResMissingBad() { + mResPropComponent.create().build(); + } + + public void buildPropResInCondOk(boolean b) { + ResPropComponent.Builder builder = mResPropComponent.create(); + if (b) { + builder.propAttr(new Object()); + } else { + builder.propDip(new Object()); + } + builder.build(); + } + + public void buildPropResInCondOneNormalOk(boolean b) { + ResPropComponent.Builder builder = mResPropComponent.create(); + if (b) { + builder.propAttr(new Object()); + } else { + builder.prop(new Object()); + } + builder.build(); + } + + public void buildPropVarArgNormalOk() { + mVarArgPropComponent.create().props(new ArrayList()).build(); + } + + public void buildPropVarArgElementOk() { + mVarArgPropComponent.create().prop(new Object()).build(); + } + + public void buildPropVarArgAttrElementOk() { + mVarArgPropComponent.create().propAttr(new Object()).build(); + } + + public void buildPropVarArgNormalAttrElementOk() { + mVarArgPropComponent.create().propsAttr(new ArrayList()).build(); + } + + public void buildPropVarArgMissingBad() { + mVarArgPropComponent.create().build(); + } + + public Component buildPropLithoMissingBothBad() { + return mMyLithoComponent.create().build(); + } + + public void buildPropLithoMissingOneBad() { + Column.create() + .child(mMyLithoComponent.create().prop1(new Object()).commonProp(new Object())) + .build(); + } + + public Component buildPropLithoOK() { + Component.Builder layoutBuilder = + mMyLithoComponent.create().prop1(new Object()).prop2(new Object()); + return layoutBuilder.build(); + } + + public void castImpossibleOk_FP(Object o1) { + Component.Builder builder = mMyLithoComponent.create(); + if (builder instanceof MyComponent.Builder) + ((MyComponent.Builder) builder) + .build(); // this branch will never be taken but we can't detect it yet + } + + void castOk(Object o1) { + Component.Builder builder = mMyLithoComponent.create().prop1(new Object()).prop2(new Object()); + if (builder instanceof MyLithoComponent.Builder) + ((MyLithoComponent.Builder) builder).build(); // this branch will be taken + } + + Component.Builder createBuilder() { + return mMyLithoComponent.create(); + } + + void castMissingOneBad_FN(Object o1) { + Component.Builder builder = createBuilder(); + if (builder instanceof MyLithoComponent.Builder) + ((MyLithoComponent.Builder) builder).prop2(new Object()).build(); // this branch will be taken + } + + void buildMissingProp3Bad() { + Component.Builder builder = mMyComponent.create(); + ((MyLithoComponent.Builder) builder).prop1(new Object()).prop2(new Object()).build(); + } + + public class NonRequiredTreeProps { + + public MyTreeComponent mMyTreeComponent; + + public MyTreeComponent buildWithoutOk() { + return mMyTreeComponent.create().build(); + } + } + + public Component buildPropLithoMissingInOneBranchBad(boolean b) { + if (b) { + return mMyLithoComponent.create().prop1(new Object()).build(); + } else { + return mMyLithoComponent.create().prop1(new Object()).prop2(new Object()).build(); + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/ResPropComponent.java b/infer/tests/codetoanalyze/java/litho-required-props/ResPropComponent.java new file mode 100644 index 000000000..2a93a1e98 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/ResPropComponent.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package codetoanalyze.java.litho; + +import com.facebook.litho.Component; +import com.facebook.litho.annotations.Prop; +import com.facebook.litho.annotations.ResType; + +/** + * using @Prop(resType = ..) allows you to set the Prop with any of .propname, .propnameRes, or + * .propnameAttr + */ +public class ResPropComponent extends Component { + + @Prop(resType = ResType.SOME) + Object prop; // implicitly non-optional with resType + + public Builder create() { + return new Builder(); + } + + public static class Builder extends Component.Builder { + + ResPropComponent mResPropComponent; + + public Builder prop(Object o) { + this.mResPropComponent.prop = o; + return this; + } + + public Builder propRes(Object o) { + this.mResPropComponent.prop = o; + return this; + } + + public Builder propAttr(Object o) { + this.mResPropComponent.prop = o; + return this; + } + + public Builder propDip(Object o) { + this.mResPropComponent.prop = o; + return this; + } + + public Builder propPx(Object o) { + this.mResPropComponent.prop = o; + return this; + } + + public Builder propSp(Object o) { + this.mResPropComponent.prop = o; + return this; + } + + public ResPropComponent build() { + return mResPropComponent; + } + + @Override + public Builder getThis() { + return this; + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/ResType.java b/infer/tests/codetoanalyze/java/litho-required-props/ResType.java new file mode 100644 index 000000000..200b1066b --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/ResType.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package com.facebook.litho.annotations; + +public enum ResType { + SOME, + NONE +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/TreeProp.java b/infer/tests/codetoanalyze/java/litho-required-props/TreeProp.java new file mode 100644 index 000000000..2560f427f --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/TreeProp.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package com.facebook.litho.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.CLASS) +public @interface TreeProp { + ResType resType() default ResType.NONE; + + boolean optional() default false; +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/VarArgPropComponent.java b/infer/tests/codetoanalyze/java/litho-required-props/VarArgPropComponent.java new file mode 100644 index 000000000..89a696619 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/VarArgPropComponent.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +package codetoanalyze.java.litho; + +import com.facebook.litho.Component; +import com.facebook.litho.annotations.Prop; +import java.util.ArrayList; +import java.util.List; + +/** varArg test */ +class VarArgPropComponent extends Component { + + @Prop(varArg = "prop") + List props; + + public Builder create() { + return new Builder(); + } + + static class Builder extends Component.Builder { + + VarArgPropComponent mVarArgPropComponent; + + public Builder prop(Object prop) { + if (prop == null) { + return this; + } + if (this.mVarArgPropComponent.props == null) { + this.mVarArgPropComponent.props = new ArrayList(); + } + this.mVarArgPropComponent.props.add(prop); + return this; + } + + public Builder propAttr(Object prop) { + if (prop == null) { + return this; + } + if (this.mVarArgPropComponent.props == null) { + this.mVarArgPropComponent.props = new ArrayList(); + } + this.mVarArgPropComponent.props.add(prop); + return this; + } + + public Builder propsAttr(List props) { + if (props == null) { + return this; + } + if (this.mVarArgPropComponent.props == null || this.mVarArgPropComponent.props.isEmpty()) { + this.mVarArgPropComponent.props = props; + } else { + this.mVarArgPropComponent.props.addAll(props); + } + return this; + } + + public Builder props(List props) { + if (props == null) { + return this; + } + if (this.mVarArgPropComponent.props == null || this.mVarArgPropComponent.props.isEmpty()) { + this.mVarArgPropComponent.props = props; + } else { + this.mVarArgPropComponent.props.addAll(props); + } + return this; + } + + public VarArgPropComponent build() { + return mVarArgPropComponent; + } + + @Override + public Builder getThis() { + return this; + } + } +} diff --git a/infer/tests/codetoanalyze/java/litho-required-props/issues.exp b/infer/tests/codetoanalyze/java/litho-required-props/issues.exp new file mode 100644 index 000000000..f8cec7ffa --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho-required-props/issues.exp @@ -0,0 +1,19 @@ +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildInterProcUnrelatedBad(boolean,java.lang.String):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildMissingProp3Bad():void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop1(Object),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop2(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropLithoMissingBothBad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropLithoMissingBothBad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropLithoMissingInOneBranchBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build(),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop1(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropLithoMissingOneBad():void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build(),calls Component$Builder Component$Builder.commonProp(Object),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop1(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropLithoMissingOneInLoopBad(int):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build(),calls Component$Builder Component$Builder.commonProp(Object),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop1(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropMissingInConditionalBad(boolean):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropResMissingBad():void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop is required for component codetoanalyze.java.litho.ResPropComponent, but is not set before the call to build()] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildPropVarArgMissingBad():void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [Either @Prop props or @Prop(varArg = prop) is required for component codetoanalyze.java.litho.VarArgPropComponent, but is not set before the call to build()] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithout1Bad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithout3Bad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object),calls MyComponent$Builder MyComponent$Builder.prop2(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.castImpossibleOk_FP(java.lang.Object):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.castImpossibleOk_FP(java.lang.Object):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.doubleSetMissingBad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls Component$Builder Component$Builder.commonProp(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.generalTypeForgot3Bad():java.lang.Object, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnBothBranchesMissingProp3Bad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object),calls MyComponent$Builder MyComponent$Builder.prop2(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnOneBranchBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] +codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnOneBranchEffectfulBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]