From f585e238c69dbba22ffc3f6503dd3476a5d6203a Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Thu, 27 Apr 2023 12:27:30 -0400 Subject: [PATCH 01/57] Fix CI on Windows (#759) --- .github/workflows/continuous-integration.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 9bb0d03707..5c682a2fd1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -36,6 +36,12 @@ jobs: steps: - name: Check out NullAway sources uses: actions/checkout@v3 + - name: 'Set up JDK 17 on Windows' + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + if: matrix.os == 'windows-latest' - name: 'Set up JDK ${{ matrix.java }}' uses: actions/setup-java@v3 with: From ffdcf465dfc8276e09d5e77e8f7eb8d7f48c1b55 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sun, 7 May 2023 19:40:02 -0700 Subject: [PATCH 02/57] Clearer printing of types in errors related to generics (#758) Previously, we would print fully-qualified names for all types, including annotation types. Now we use simple names for types, including types in annotations, which generally makes the message easier to read. We update some (but not all) test assertions to check how a type is printed. --- .../com/uber/nullaway/GenericsChecks.java | 75 +++++++++++++++++-- .../NullAwayJSpecifyGenericsTests.java | 12 +-- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index 13ffd147f2..e66430e114 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -1,6 +1,7 @@ package com.uber.nullaway; import static com.uber.nullaway.NullabilityUtil.castToNonNull; +import static java.util.stream.Collectors.joining; import com.google.common.base.Preconditions; import com.google.errorprone.VisitorState; @@ -17,6 +18,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeMetadata; @@ -125,9 +127,9 @@ private static void reportInvalidAssignmentInstantiationError( ErrorMessage.MessageTypes.ASSIGN_GENERIC_NULLABLE, String.format( "Cannot assign from type " - + rhsType + + prettyTypeForError(rhsType) + " to type " - + lhsType + + prettyTypeForError(lhsType) + " due to mismatched nullability of type parameters")); state.reportMatch( errorBuilder.createErrorDescription( @@ -142,9 +144,9 @@ private static void reportInvalidReturnTypeError( ErrorMessage.MessageTypes.RETURN_NULLABLE_GENERIC, String.format( "Cannot return expression of type " - + returnType + + prettyTypeForError(returnType) + " from method with return type " - + methodType + + prettyTypeForError(methodType) + " due to mismatched nullability of type parameters")); state.reportMatch( errorBuilder.createErrorDescription( @@ -159,9 +161,9 @@ private static void reportMismatchedTypeForTernaryOperator( ErrorMessage.MessageTypes.ASSIGN_GENERIC_NULLABLE, String.format( "Conditional expression must have type " - + expressionType + + prettyTypeForError(expressionType) + " but the sub-expression has type " - + subPartType + + prettyTypeForError(subPartType) + ", which has mismatched nullability of type parameters")); state.reportMatch( errorBuilder.createErrorDescription( @@ -179,9 +181,9 @@ private void reportInvalidParametersNullabilityError( new ErrorMessage( ErrorMessage.MessageTypes.PASS_NULLABLE_GENERIC, "Cannot pass parameter of type " - + actualParameterType + + prettyTypeForError(actualParameterType) + ", as formal parameter has type " - + formalParameterType + + prettyTypeForError(formalParameterType) + ", which has mismatched type parameter nullability"); state.reportMatch( errorBuilder.createErrorDescription( @@ -504,4 +506,61 @@ public void compareGenericTypeParameterNullabilityForCall( } } } + + /** + * Returns a pretty-printed representation of type suitable for error messages. The representation + * uses simple names rather than fully-qualified names, and retains all type-use annotations. + */ + public static String prettyTypeForError(Type type) { + return type.accept(PRETTY_TYPE_VISITOR, null); + } + + /** This code is a modified version of code in {@link com.google.errorprone.util.Signatures} */ + private static final Type.Visitor PRETTY_TYPE_VISITOR = + new Types.DefaultTypeVisitor() { + @Override + public String visitWildcardType(Type.WildcardType t, Void unused) { + StringBuilder sb = new StringBuilder(); + sb.append(t.kind); + if (t.kind != BoundKind.UNBOUND) { + sb.append(t.type.accept(this, null)); + } + return sb.toString(); + } + + @Override + public String visitClassType(Type.ClassType t, Void s) { + StringBuilder sb = new StringBuilder(); + for (Attribute.TypeCompound compound : t.getAnnotationMirrors()) { + sb.append('@'); + sb.append(compound.type.accept(this, null)); + sb.append(' '); + } + sb.append(t.tsym.getSimpleName()); + if (t.getTypeArguments().nonEmpty()) { + sb.append('<'); + sb.append( + t.getTypeArguments().stream() + .map(a -> a.accept(this, null)) + .collect(joining(", "))); + sb.append(">"); + } + return sb.toString(); + } + + @Override + public String visitCapturedType(Type.CapturedType t, Void s) { + return t.wildcard.accept(this, null); + } + + @Override + public String visitArrayType(Type.ArrayType t, Void unused) { + return t.elemtype.accept(this, null) + "[]"; + } + + @Override + public String visitType(Type t, Void s) { + return t.toString(); + } + }; } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index b16dd52cc7..2cac6729cc 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -234,7 +234,7 @@ public void genericsChecksForAssignments() { "class Test {", " static class NullableTypeParam {}", " static void testPositive(NullableTypeParam<@Nullable String> t1) {", - " // BUG: Diagnostic contains: Cannot assign from type", + " // BUG: Diagnostic contains: Cannot assign from type NullableTypeParam<@Nullable String>", " NullableTypeParam t2 = t1;", " }", " static void testNegative(NullableTypeParam<@Nullable String> t1) {", @@ -255,7 +255,7 @@ public void nestedChecksForAssignmentsMultipleArguments() { " static class SampleClass {}", " static class SampleClassMultipleArguments {}", " static void testPositive() {", - " // BUG: Diagnostic contains: Cannot assign from type", + " // BUG: Diagnostic contains: Cannot assign from type SampleClassMultipleArguments>", " SampleClassMultipleArguments>, String> t1 =", " new SampleClassMultipleArguments>, String>();", " }", @@ -278,7 +278,7 @@ public void superTypeAssignmentChecksSingleInterface() { " interface Fn

{}", " class FnImpl implements Fn<@Nullable String, @Nullable String> {}", " void testPositive() {", - " // BUG: Diagnostic contains: Cannot assign from type", + " // BUG: Diagnostic contains: Cannot assign from type FnImpl", " Fn<@Nullable String, String> f = new FnImpl();", " }", " void testNegative() {", @@ -499,7 +499,7 @@ public void genericFunctionReturnTypeNewClassTree() { "class Test {", " static class A { }", " static A testPositive1() {", - " // BUG: Diagnostic contains: mismatched nullability of type parameters", + " // BUG: Diagnostic contains: Cannot return expression of type A<@Nullable String>", " return new A<@Nullable String>();", " }", " static A<@Nullable String> testPositive2() {", @@ -567,7 +567,7 @@ public void genericsChecksForTernaryOperator() { "class Test {", "static class A { }", " static A testPositive(A a, boolean t) {", - " // BUG: Diagnostic contains: Conditional expression must have type", + " // BUG: Diagnostic contains: Conditional expression must have type A<@Nullable String>", " A<@Nullable String> t1 = t ? new A() : new A<@Nullable String>();", " // BUG: Diagnostic contains: Conditional expression must have type", " return t ? new A<@Nullable String>() : new A<@Nullable String>();", @@ -678,7 +678,7 @@ public void parameterPassing() { " return a2;", " }", " static void testPositive1(A> a1, A a2) {", - " // BUG: Diagnostic contains: Cannot pass parameter of type", + " // BUG: Diagnostic contains: Cannot pass parameter of type A>", " A a = sampleMethod1(a1, a2);", " }", " static void testPositive2(A> a1, A a2) {", From eea2a9a08dbc01d1b7a9e7c882650a38a79d69b4 Mon Sep 17 00:00:00 2001 From: Christopher Lambert Date: Mon, 15 May 2023 00:09:27 +0200 Subject: [PATCH 03/57] Upgrade to Gradle 8.1.1 (#762) upgraded by running twice: ./gradlew wrapper --gradle-version 8.1.1 --gradle-distribution-sha256-sum e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f see also: https://gradle.org/release-checksums/ https://docs.gradle.org/8.1.1/release-notes.html --- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 8979 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 F{TFmrHLU;u delta 8469 zcmY*q~ZGqoW{=01$bgB@1Nex`%9%S2I04)5Jw9+UyLS&r+9O2bq{gY;dCa zHW3WY0%Dem?S7n5JZO%*yiT9fb!XGk9^Q`o-EO{a^j%&)ZsxsSN@2k2eFx1*psqn0e*crIbAO}Rd~_BifMu*q7SUn{>WD$=7n_$uiQ0wGc$?u1hM%gf??nL?m22h!8{ zYmFMLvx6fjz*nwF^tAqx1uv0yEW9-tcIV5Q{HNh`9PMsuqD8VE%oAs5FsWa0mLV$L zPAF5e^$tJ8_Kwp!$N1M<#Z154n!X6hFpk8)eMLu; zaXS71&`24 zV`x~}yAxBw##Oj@qo_@DcBqc+2TB&=bJyZWTeR55zG<{Z@T^hSbMdm~Ikkr?4{7WT zcjPyu>0sDjl7&?TL@ z)cW?lW@Pfwu#nm7E1%6*nBIzQrKhHl`t54$-m>j8f%0vVr?N0PTz`}VrYAl+8h^O~ zuWQj@aZSZmGPtcVjGq-EQ1V`)%x{HZ6pT-tZttJOQm?q-#KzchbH>>5-jEX*K~KDa z#oO&Qf4$@}ZGQ7gxn<;D$ziphThbi6zL^YC;J#t0GCbjY)NHdqF=M4e(@|DUPY_=F zLcX1HAJ+O-3VkU#LW`4;=6szwwo%^R4#UK}HdAXK` z{m!VZj5q9tVYL=^TqPH*6?>*yr>VxyYF4tY{~?qJ*eIoIU0}-TLepzga4g}}D7#Qu zn;6I;l!`xaL^8r*Tz*h`^(xJCnuVR_O@Gl*Q}y$lp%!kxD`%zN19WTIf`VX*M=cDp z*s4<9wP|ev;PARRV`g$R*QV@rr%Ku~z(2-s>nt{JI$357vnFAz9!ZsiiH#4wOt+!1 zM;h;EN__zBn)*-A^l!`b?b*VI-?)Sj6&Ov3!j9k$5+#w)M>`AExCm0!#XL+E{Bp)s;Hochs+-@@)7_XDMPby#p<9mLu+S{8e2Jn`1`1nrffBfy4u)p7FFQWzgYt zXC}GypRdkTUS+mP!jSH$K71PYI%QI-{m;DvlRb*|4GMPmvURv0uD2bvS%FOSe_$4zc--*>gfRMKN|D ztP^WFfGEkcm?sqXoyRmuCgb?bSG17#QSv4~XsbPH>BE%;bZQ_HQb?q%CjykL7CWDf z!rtrPk~46_!{V`V<;AjAza;w-F%t1^+b|r_um$#1cHZ1|WpVUS&1aq?Mnss|HVDRY z*sVYNB+4#TJAh4#rGbr}oSnxjD6_LIkanNvZ9_#bm?$HKKdDdg4%vxbm-t@ZcKr#x z6<$$VPNBpWM2S+bf5IBjY3-IY2-BwRfW_DonEaXa=h{xOH%oa~gPW6LTF26Y*M)$N z=9i`Y8};Qgr#zvU)_^yU5yB;9@yJjrMvc4T%}a|jCze826soW-d`V~eo%RTh)&#XR zRe<8$42S2oz|NVcB%rG(FP2U&X>3 z4M^}|K{v64>~rob;$GO55t;Nb&T+A3u(>P6;wtp6DBGWbX|3EZBDAM2DCo&4w|WGpi;~qUY?Ofg$pX&`zR~)lr)8}z^U3U38Nrtnmf~e7$i=l>+*R%hQgDrj%P7F zIjyBCj2$Td=Fp=0Dk{=8d6cIcW6zhK!$>k*uC^f}c6-NR$ zd<)oa+_fQDyY-}9DsPBvh@6EvLZ}c)C&O-+wY|}RYHbc2cdGuNcJ7#yE}9=!Vt-Q~ z4tOePK!0IJ0cW*jOkCO? zS-T!bE{5LD&u!I4tqy;dI*)#e^i)uIDxU?8wK1COP3Qk{$vM3Sm8(F2VwM?1A+dle z6`M6bbZye|kew%w9l`GS74yhLluJU5R=#!&zGwB7lmTt}&eCt0g(-a;Mom-{lL6u~ zFgjyUs1$K*0R51qQTW_165~#WRrMxiUx{0F#+tvgtcjV$U|Z}G*JWo6)8f!+(4o>O zuaAxLfUl;GHI}A}Kc>A8h^v6C-9bb}lw@rtA*4Q8)z>0oa6V1>N4GFyi&v69#x&CwK*^!w&$`dv zQKRMKcN$^=$?4to7X4I`?PKGi(=R}d8cv{74o|9FwS zvvTg0D~O%bQpbp@{r49;r~5`mcE^P<9;Zi$?4LP-^P^kuY#uBz$F!u1d{Ens6~$Od zf)dV+8-4!eURXZZ;lM4rJw{R3f1Ng<9nn2_RQUZDrOw5+DtdAIv*v@3ZBU9G)sC&y!vM28daSH7(SKNGcV z&5x#e#W2eY?XN@jyOQiSj$BlXkTG3uAL{D|PwoMp$}f3h5o7b4Y+X#P)0jlolgLn9xC%zr3jr$gl$8?II`DO6gIGm;O`R`bN{;DlXaY4b`>x6xH=Kl@ z!>mh~TLOo)#dTb~F;O z8hpjW9Ga?AX&&J+T#RM6u*9x{&%I8m?vk4eDWz^l2N_k(TbeBpIwcV4FhL(S$4l5p z@{n7|sax){t!3t4O!`o(dYCNh90+hl|p%V_q&cwBzT*?Nu*D0wZ)fPXv z@*;`TO7T0WKtFh8~mQx;49VG_`l`g|&VK}LysK%eU4})Cvvg3YN)%;zI?;_Nr z)5zuU1^r3h;Y+mJov*->dOOj>RV^u2*|RraaQWsY5N?Uu)fKJOCSL2^G=RB%(4K{* zx!^cB@I|kJR`b+5IK}(6)m=O{49P5E^)!XvD5zVuzJH{01^#$@Cn514w41BB;FAoS2SYl3SRrOBDLfl5MvgA3 zU6{T?BW}l~8vU;q@p9IOM(=;WdioeQmt?X|=L9kyM&ZsNc*-Knv8@U*O96T@4ZiJ$ zeFL2}pw_~Tm3d4#q!zZS0km@vYgym33C0h(6D)6|Y)*UXI^T`(QPQh$WF?&h(3QYh zqGw@?BTk@VA_VxK@z?a@UrMhY zUD16oqx4$$6J_k0HnXgARm}N#(^yA1MLdbwmEqHnX*JdHN>$5k2E|^_bL< zGf5Z+D!9dXR>^(5F&5gIew1%kJtFUwI5P1~I$4LL_6)3RPzw|@2vV;Q^MeQUKzc=KxSTTX`}u%z?h~;qI#%dE@OZwehZyDBsWTc&tOC1c%HS#AyTJ= zQixj=BNVaRS*G!;B$}cJljeiVQabC25O+xr4A+32HVb;@+%r}$^u4-R?^3yij)0xb z86i@aoVxa%?bfOE;Bgvm&8_8K(M-ZEj*u9ms_Hk#2eL`PSnD#At!0l{f!v`&Kg}M$n(&R)?AigC5Z?T7Jv^lrDL!yYS{4 zq_H}oezX-Svu>dp)wE@khE@aR5vY=;{C-8Hws++5LDpArYd)U47jc-;f~07_TPa^1 zO`0+uIq)@?^!%JXCDid+nt|c@NG1+ce@ijUX&@rV9UiT|m+t-nqVB7?&UX*|{yDBFw9x52&dTh@;CL)Q?6s1gL=CUQTX7#TJPs9cpw<4>GFMUKo|f{! z&(%2hP6ghr%UFVO-N^v9l|tKy>&e%8us}wT0N*l(tezoctVtLmNdGPOF6oaAGJI5R zZ*|k@z3H!~Mm9fXw{bbP6?lV-j#Rfgnjf++O7*|5vz2#XK;kk ztJbi%r0{U5@QwHYfwdjtqJ6?;X{Ul3?W0O0bZ$k*y z4jWsNedRoCb7_|>nazmq{T3Y_{<5IO&zQ?9&uS@iL+|K|eXy^F>-60HDoVvovHelY zy6p(}H^7b+$gu@7xLn_^oQryjVu#pRE5&-w5ZLCK&)WJ5jJF{B>y;-=)C;xbF#wig zNxN^>TwzZbV+{+M?}UfbFSe#(x$c)|d_9fRLLHH?Xbn!PoM{(+S5IEFRe4$aHg~hP zJYt`h&?WuNs4mVAmk$yeM;8?R6;YBMp8VilyM!RXWj<95=yp=4@y?`Ua8 znR^R?u&g%`$Wa~usp|pO$aMF-en!DrolPjD_g#{8X1f=#_7hH8i|WF+wMqmxUm*!G z*4p980g{sgR9?{}B+a0yiOdR()tWE8u)vMPxAdK)?$M+O_S+;nB34@o<%lGJbXbP` z5)<({mNpHp&45UvN`b&K5SD#W){}6Y_d4v~amZPGg|3GdlWDB;;?a=Z{dd zELTfXnjCqq{Dgbh9c%LjK!Epi1TGI{A7AP|eg2@TFQiUd4Bo!JsCqsS-8ml`j{gM& zEd7yU`djX!EX2I{WZq=qasFzdDWD`Z?ULFVIP!(KQP=fJh5QC9D|$JGV95jv)!sYWY?irpvh06rw&O?iIvMMj=X zr%`aa(|{Ad=Vr9%Q(61{PB-V_(3A%p&V#0zGKI1O(^;tkS{>Y<`Ql@_-b7IOT&@?l zavh?#FW?5otMIjq+Bp?Lq)w7S(0Vp0o!J*~O1>av;)Cdok@h&JKaoHDV6IVtJ?N#XY=lknPN+SN8@3Gb+D-X*y5pQ)wnIpQlRR!Rd)@0LdA85}1 zu7W6tJ*p26ovz+`YCPePT>-+p@T_QsW$uE`McLlXb;k}!wwWuh$YC4qHRd=RS!s>2 zo39VCB-#Ew?PAYOx`x!@0qa5lZKrE?PJEwVfkww#aB_$CLKlkzHSIi4p3#IeyA@u@ z`x^!`0HJxe>#V7+Grku^in>Ppz|TD*`Ca4X%R3Yo|J=!)l$vYks|KhG{1CEfyuzK( zLjCz{5l}9>$J=FC?59^85awK0$;^9t9UxwOU8kP7ReVCc*rPOr(9uMY*aCZi2=JBu z(D0svsJRB&a9nY;6|4kMr1Er5kUVOh1TuBwa3B2C<+rS|xJo&Lnx3K-*P83eXQCJ= z(htQSA3hgOMcs`#NdYB17#zP_1N_P0peHrNo1%NsYn=;PgLXTic6b#{Y0Z~x9Ffav z^3eO+diquPfo1AXW*>G(JcGn{yN?segqKL$Wc9po(Kex z#tw_};zd++we+MPhOOgaXSmguul67JOvBysmg?wRf=OUeh(XyRcyY@8RTV@xck_c~ zLFMWAWb4^7xwR)3iO1PIs1<}L3CMJ1L-}s=>_y!`!FvYf^pJO|&nII{!Dz+b?=bUd zPJUUn))z)-TcpqKF(1tr-x1;lS?SB@mT#O7skl0sER{a|d?&>EKKaw* zQ>D^m*pNgV`54BKv?knU-T5bcvBKnI@KZo^UYjKp{2hpCo?_6v(Sg77@nQa{tSKbn zUgMtF>A3hndGocRY+Snm#)Q4%`|Qq3YTOU^uG}BGlz!B=zb?vB16sN&6J`L(k1r+$ z5G6E9tJ~Iwd!d!NH7Q%Z@BR@0e{p6#XF2))?FLAVG`npIjih*I+0!f6;+DM zLOP-qDsm9=ZrI!lfSDn%XuF17$j~gZE@I}S(Ctw&Te75P5?Fj%FLT;p-tm33FaUQc z5cR;$SwV|N0xmjox3V~XL3sV?YN}U0kkfmygW@a5JOCGgce6JyzGmgN$?NM%4;wEhUMg0uTTB~L==1Fvc(6)KMLmU z(12l^#g&9OpF7+Ll30F6(q=~>NIY=-YUJJ}@&;!RYnq*xA9h!iMi`t;B2SUqbyNGn zye@*0#Uu`OQy%utS%IA%$M1f4B|bOH={!3K1=Tc7Ra|%qZgZ{mjAGKXb)}jUu1mQ_ zRW7<;tkHv(m7E0m>**8D;+2ddTL>EcH_1YqCaTTu_#6Djm z*64!w#=Hz<>Fi1n+P}l#-)0e0P4o+D8^^Mk& zhHeJoh2paKlO+8r?$tx`qEcm|PSt6|1$1q?r@VvvMd1!*zAy3<`X9j?ZI|;jE-F(H zIn1+sm(zAnoJArtytHC|0&F0`i*dy-PiwbD-+j`ezvd4C`%F1y^7t}2aww}ZlPk)t z=Y`tm#jNM$d`pG%F42Xmg_pZnEnvC%avz=xNs!=6b%%JSuc(WObezkCeZ#C|3PpXj zkR8hDPyTIUv~?<%*)6=8`WfPPyB9goi+p$1N2N<%!tS2wopT2x`2IZi?|_P{GA|I5 z?7DP*?Gi#2SJZ!x#W9Npm)T;=;~Swyeb*!P{I^s@o5m_3GS2Lg?VUeBdOeae7&s5$ zSL_VuTJih_fq7g8O8b0g+GbmE+xG}^Wx`g~{mWTyr@=h zKlAymoHeZa`DgR?Pj8Yc+I|MrSB>X*ts#wNFOJxs!3aGE)xeTHlF`fC5^g(DTacl$ zx!ezQJdwIyc$8RyNS~Wh{0pp>8NcW)*J=7AQYdT?(QhJuq4u`QniZ!%6l{KWp-0Xp z4ZC6(E(_&c$$U_cmGFslsyX6(62~m*z8Yx2p+F5xmD%6A7eOnx`1lJA-Mrc#&xZWJ zzXV{{OIgzYaq|D4k^j%z|8JB8GnRu3hw#8Z@({sSmsF(x>!w0Meg5y(zg!Z0S^0k# z5x^g1@L;toCK$NB|Fn Date: Mon, 15 May 2023 00:52:01 +0200 Subject: [PATCH 04/57] Upgrade to Error Prone 2.19.1 (#763) need to fix a few new warnings: - deprecated method usage - not an actual javadoc comment --- .github/workflows/continuous-integration.yml | 10 +++++----- gradle/dependencies.gradle | 2 +- .../com/uber/nullaway/ErrorProneCLIFlagsConfig.java | 2 +- nullaway/src/main/java/com/uber/nullaway/NullAway.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5c682a2fd1..27b8d871a6 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -21,16 +21,16 @@ jobs: epVersion: 2.4.0 - os: macos-latest java: 11 - epVersion: 2.18.0 + epVersion: 2.19.1 - os: ubuntu-latest java: 11 - epVersion: 2.18.0 + epVersion: 2.19.1 - os: windows-latest java: 11 - epVersion: 2.18.0 + epVersion: 2.19.1 - os: ubuntu-latest java: 17 - epVersion: 2.18.0 + epVersion: 2.19.1 fail-fast: false runs-on: ${{ matrix.os }} steps: @@ -76,7 +76,7 @@ jobs: with: arguments: coveralls continue-on-error: true - if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.18.0' && github.repository == 'uber/NullAway' + if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.19.1' && github.repository == 'uber/NullAway' - name: Check that Git tree is clean after build and test run: ./.buildscript/check_git_clean.sh publish_snapshot: diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 528862b5fc..8c8d72a613 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -19,7 +19,7 @@ import org.gradle.util.VersionNumber // The oldest version of Error Prone that we support running on def oldestErrorProneVersion = "2.4.0" // Latest released Error Prone version that we've tested with -def latestErrorProneVersion = "2.18.0" +def latestErrorProneVersion = "2.19.1" // Default to using latest tested Error Prone version, except on Java 8, where 2.10.0 is the last version // that works def defaultErrorProneVersion = JavaVersion.current() >= JavaVersion.VERSION_11 ? latestErrorProneVersion : "2.10.0" diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java index 939166a19a..fe3cf8a3b7 100644 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java @@ -213,7 +213,7 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig { "Invalid -XepOpt:" + FL_SUPPRESS_COMMENT + " value. Comment must be single line."); } skippedLibraryModels = getFlagStringSet(flags, FL_SKIP_LIBRARY_MODELS); - /** --- JarInfer configs --- */ + /* --- JarInfer configs --- */ jarInferEnabled = flags.getBoolean(FL_JI_ENABLED).orElse(false); jarInferUseReturnAnnotations = flags.getBoolean(FL_JI_USE_RETURN).orElse(false); // The defaults of these two options translate to: remove .aar/.jar from the file name, and also diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index 9466745eef..a4e50a328d 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -621,7 +621,7 @@ public Description matchMethod(MethodTree tree, VisitorState state) { // package) Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); handler.onMatchMethod(this, tree, state, methodSymbol); - boolean isOverriding = ASTHelpers.hasAnnotation(methodSymbol, Override.class, state); + boolean isOverriding = ASTHelpers.hasAnnotation(methodSymbol, "java.lang.Override", state); boolean exhaustiveOverride = config.exhaustiveOverride(); if (isOverriding || !exhaustiveOverride) { Symbol.MethodSymbol closestOverriddenMethod = From d09ff9b9e2572e1cb8a5cd97e7a6b3b2218c9c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Clapp?= Date: Thu, 1 Jun 2023 12:28:14 -0400 Subject: [PATCH 05/57] Fix error inside Lombok generated code for @Nullable @Builder.Default (#765) When given code such as: ``` @Builder public class LombokDTO { @Nullable @Builder.Default private String fieldWithNullDefault = null; } ``` Lombok internally generates the following method: ``` @java.lang.SuppressWarnings(value = "all") @lombok.Generated() private static String $default$fieldWithNullDefault() { return null; } ``` which does not propagate `@Nullable` to the method's return type! While this method is marked as `@Generated` code and `@SuppressWarnings("all")`, that does not suppress NullAway under all configurations. In fact, we sometimes want to check generated code (setters/getters for auto-annotation, for example), so just counting all Lombok Generated code as unannotated is not always the desired behavior (it's optional behavior, enabled by the `TreatGeneratedAsUnannotated=true` flag). Instead, we want to internally and implicitly propagate the `@Nullable` annotation from `fieldWithNullDefault` to the generated `$default$fieldWithNullDefault()`. We do this in two steps: 1. We modify our checking of return statements to allow handlers' existing overriding of method return nullability to be taken into account when deciding if `return [nullable expression];` should result in a NullAway error. 2. We add a new handler for fixing the Lombok nullability propagation, by detecting these `$default$foo()` methods and looking at the nullability of `foo` to determine if the method should also be `@Nullable`. In addition to this, we can suggest a fix upstream in Lombok to propagate `@Nullable` to these `$default$` methods when present on the field, but this would not obviate the need for this PR, since we are keeping compatibility with multiple older Lombok releases internally. Edit: Also, note that coverage on `LombokHandler` is bad in terms of unit tests, but that's because coveralls doesn't count `test-java-lib-lombok/` as a test case. We need to build with Lombok to exercise most of this handler, so we can't really exercise it in unit tests (vs integration) without significantly artificial workarounds (i.e. manually replicating the Lombok-generated code). --- .../main/java/com/uber/nullaway/NullAway.java | 106 ++++++++---------- .../nullaway/handlers/BaseNoOpHandler.java | 2 +- .../nullaway/handlers/CompositeHandler.java | 5 +- .../com/uber/nullaway/handlers/Handler.java | 6 +- .../com/uber/nullaway/handlers/Handlers.java | 1 + .../handlers/LibraryModelsHandler.java | 6 +- .../uber/nullaway/handlers/LombokHandler.java | 95 ++++++++++++++++ .../RestrictiveAnnotationHandler.java | 2 +- .../NullAwayCustomLibraryModelsTests.java | 14 ++- .../main/java/com/uber/lombok/LombokDTO.java | 1 + .../testlibrarymodels/TestLibraryModels.java | 2 +- 11 files changed, 166 insertions(+), 74 deletions(-) create mode 100644 nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index a4e50a328d..9222a8b5ba 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -808,6 +808,21 @@ static Trees getTreesInstance(VisitorState state) { return Trees.instance(JavacProcessingEnvironment.instance(state.context)); } + private Nullness getMethodReturnNullness( + Symbol.MethodSymbol methodSymbol, VisitorState state, Nullness defaultForUnannotated) { + final boolean isMethodAnnotated = !codeAnnotationInfo.isSymbolUnannotated(methodSymbol, config); + Nullness methodReturnNullness = + defaultForUnannotated; // Permissive default for unannotated code. + if (isMethodAnnotated) { + methodReturnNullness = + Nullness.hasNullableAnnotation(methodSymbol, config) + ? Nullness.NULLABLE + : Nullness.NONNULL; + } + return handler.onOverrideMethodReturnNullability( + methodSymbol, state, isMethodAnnotated, methodReturnNullness); + } + private Description checkReturnExpression( Tree tree, ExpressionTree retExpr, Symbol.MethodSymbol methodSymbol, VisitorState state) { Type returnType = methodSymbol.getReturnType(); @@ -822,8 +837,7 @@ private Description checkReturnExpression( // support) return Description.NO_MATCH; } - if (codeAnnotationInfo.isSymbolUnannotated(methodSymbol, config) - || Nullness.hasNullableAnnotation(methodSymbol, config)) { + if (getMethodReturnNullness(methodSymbol, state, Nullness.NULLABLE).equals(Nullness.NULLABLE)) { return Description.NO_MATCH; } if (mayBeNullExpr(state, retExpr)) { @@ -908,63 +922,41 @@ private Description checkOverriding( Symbol.MethodSymbol overridingMethod, @Nullable MemberReferenceTree memberReferenceTree, VisitorState state) { - final boolean isOverriddenMethodAnnotated = - !codeAnnotationInfo.isSymbolUnannotated(overriddenMethod, config); - Nullness overriddenMethodReturnNullness = - Nullness.NULLABLE; // Permissive default for unannotated code. - if (isOverriddenMethodAnnotated && !Nullness.hasNullableAnnotation(overriddenMethod, config)) { - overriddenMethodReturnNullness = Nullness.NONNULL; - } - overriddenMethodReturnNullness = - handler.onOverrideMethodInvocationReturnNullability( - overriddenMethod, state, isOverriddenMethodAnnotated, overriddenMethodReturnNullness); - // if the super method returns nonnull, - // overriding method better not return nullable - if (overriddenMethodReturnNullness.equals(Nullness.NONNULL)) { - final boolean isOverridingMethodAnnotated = - !codeAnnotationInfo.isSymbolUnannotated(overridingMethod, config); - // Note that, for the overriding method, the permissive default is non-null. - Nullness overridingMethodReturnNullness = Nullness.NONNULL; - if (isOverridingMethodAnnotated && Nullness.hasNullableAnnotation(overridingMethod, config)) { - overridingMethodReturnNullness = Nullness.NULLABLE; - } - // We must once again check the handler chain, to allow it to update nullability of the - // overriding method - // (e.g. through AcknowledgeRestrictiveAnnotations=true) - overridingMethodReturnNullness = - handler.onOverrideMethodInvocationReturnNullability( - overridingMethod, state, isOverridingMethodAnnotated, overridingMethodReturnNullness); - if (overridingMethodReturnNullness.equals(Nullness.NULLABLE) - && (memberReferenceTree == null - || getComputedNullness(memberReferenceTree).equals(Nullness.NULLABLE))) { - String message; - if (memberReferenceTree != null) { - message = - "referenced method returns @Nullable, but functional interface method " - + ASTHelpers.enclosingClass(overriddenMethod) - + "." - + overriddenMethod.toString() - + " returns @NonNull"; - - } else { - message = - "method returns @Nullable, but superclass method " - + ASTHelpers.enclosingClass(overriddenMethod) - + "." - + overriddenMethod.toString() - + " returns @NonNull"; - } + // if the super method returns nonnull, overriding method better not return nullable + // Note that, for the overriding method, the permissive default is non-null, + // but it's nullable for the overridden one. + if (getMethodReturnNullness(overriddenMethod, state, Nullness.NULLABLE).equals(Nullness.NONNULL) + && getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL) + .equals(Nullness.NULLABLE) + && (memberReferenceTree == null + || getComputedNullness(memberReferenceTree).equals(Nullness.NULLABLE))) { + String message; + if (memberReferenceTree != null) { + message = + "referenced method returns @Nullable, but functional interface method " + + ASTHelpers.enclosingClass(overriddenMethod) + + "." + + overriddenMethod.toString() + + " returns @NonNull"; - Tree errorTree = - memberReferenceTree != null - ? memberReferenceTree - : getTreesInstance(state).getTree(overridingMethod); - return errorBuilder.createErrorDescription( - new ErrorMessage(MessageTypes.WRONG_OVERRIDE_RETURN, message), - buildDescription(errorTree), - state, - overriddenMethod); + } else { + message = + "method returns @Nullable, but superclass method " + + ASTHelpers.enclosingClass(overriddenMethod) + + "." + + overriddenMethod.toString() + + " returns @NonNull"; } + + Tree errorTree = + memberReferenceTree != null + ? memberReferenceTree + : getTreesInstance(state).getTree(overridingMethod); + return errorBuilder.createErrorDescription( + new ErrorMessage(MessageTypes.WRONG_OVERRIDE_RETURN, message), + buildDescription(errorTree), + state, + overriddenMethod); } // if any parameter in the super method is annotated @Nullable, // overriding method cannot assume @Nonnull diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/BaseNoOpHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/BaseNoOpHandler.java index 751f64ffa4..242a96a1dc 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/BaseNoOpHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/BaseNoOpHandler.java @@ -109,7 +109,7 @@ public void onMatchReturn(NullAway analysis, ReturnTree tree, VisitorState state } @Override - public Nullness onOverrideMethodInvocationReturnNullability( + public Nullness onOverrideMethodReturnNullability( Symbol.MethodSymbol methodSymbol, VisitorState state, boolean isAnnotated, diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/CompositeHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/CompositeHandler.java index d426b12c1c..31617da68b 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/CompositeHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/CompositeHandler.java @@ -123,15 +123,14 @@ public void onMatchReturn(NullAway analysis, ReturnTree tree, VisitorState state } @Override - public Nullness onOverrideMethodInvocationReturnNullability( + public Nullness onOverrideMethodReturnNullability( Symbol.MethodSymbol methodSymbol, VisitorState state, boolean isAnnotated, Nullness returnNullness) { for (Handler h : handlers) { returnNullness = - h.onOverrideMethodInvocationReturnNullability( - methodSymbol, state, isAnnotated, returnNullness); + h.onOverrideMethodReturnNullability(methodSymbol, state, isAnnotated, returnNullness); } return returnNullness; } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/Handler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/Handler.java index 7b98fde283..835c01fbfd 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/Handler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/Handler.java @@ -162,12 +162,12 @@ boolean onOverrideMayBeNullExpr( * @param methodSymbol The method symbol for the method in question. * @param state The current visitor state. * @param isAnnotated A boolean flag indicating whether the called method is considered to be - * within isAnnotated or unannotated code, used to avoid querying for this information - * multiple times within the same handler chain. + * within annotated or unannotated code, used to avoid querying for this information multiple + * times within the same handler chain. * @param returnNullness return nullness computed by upstream handlers or NullAway core. * @return Updated return nullability computed by this handler. */ - Nullness onOverrideMethodInvocationReturnNullability( + Nullness onOverrideMethodReturnNullability( Symbol.MethodSymbol methodSymbol, VisitorState state, boolean isAnnotated, diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java index ba46360a87..85e10e644a 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java @@ -74,6 +74,7 @@ public static Handler buildDefault(Config config) { if (config.checkContracts()) { handlerListBuilder.add(new ContractCheckHandler(config)); } + handlerListBuilder.add(new LombokHandler(config)); return new CompositeHandler(handlerListBuilder.build()); } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java index 954651bccc..799ef241ed 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java @@ -109,14 +109,16 @@ public Nullness[] onOverrideMethodInvocationParametersNullability( } @Override - public Nullness onOverrideMethodInvocationReturnNullability( + public Nullness onOverrideMethodReturnNullability( Symbol.MethodSymbol methodSymbol, VisitorState state, boolean isAnnotated, Nullness returnNullness) { OptimizedLibraryModels optLibraryModels = getOptLibraryModels(state.context); if (optLibraryModels.hasNonNullReturn(methodSymbol, state.getTypes(), !isAnnotated)) { - return Nullness.NONNULL; + return NONNULL; + } else if (optLibraryModels.hasNullableReturn(methodSymbol, state.getTypes(), !isAnnotated)) { + return NULLABLE; } return returnNullness; } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java new file mode 100644 index 0000000000..49e5aafaf7 --- /dev/null +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java @@ -0,0 +1,95 @@ +package com.uber.nullaway.handlers; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.VisitorState; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Symbol; +import com.uber.nullaway.Config; +import com.uber.nullaway.NullAway; +import com.uber.nullaway.Nullness; +import java.util.stream.StreamSupport; +import javax.annotation.Nullable; +import javax.lang.model.element.ElementKind; + +/** + * A general handler for Lombok generated code and its internal semantics. + * + *

Currently used to propagate @Nullable in cases where the Lombok annotation processor fails to + * do so consistently. + */ +public class LombokHandler extends BaseNoOpHandler { + + private static String LOMBOK_GENERATED_ANNOTATION_NAME = "lombok.Generated"; + private static String LOMBOK_BUILDER_DEFAULT_METHOD_PREFIX = "$default$"; + + private final Config config; + + public LombokHandler(Config config) { + this.config = config; + } + + @SuppressWarnings("ASTHelpersSuggestions") // Suggested API doesn't exist in EP 2.4.0 + private boolean isLombokMethodWithMissingNullableAnnotation( + Symbol.MethodSymbol methodSymbol, VisitorState state) { + if (!ASTHelpers.hasAnnotation(methodSymbol, LOMBOK_GENERATED_ANNOTATION_NAME, state)) { + return false; + } + String methodNameString = methodSymbol.name.toString(); + if (!methodNameString.startsWith(LOMBOK_BUILDER_DEFAULT_METHOD_PREFIX)) { + return false; + } + String originalFieldName = + methodNameString.substring(LOMBOK_BUILDER_DEFAULT_METHOD_PREFIX.length()); + ImmutableList matchingMembers = + StreamSupport.stream( + methodSymbol + .enclClass() + .members() + .getSymbols( + sym -> + sym.name.contentEquals(originalFieldName) + && sym.getKind().equals(ElementKind.FIELD)) + .spliterator(), + false) + .collect(ImmutableList.toImmutableList()); + Preconditions.checkArgument( + matchingMembers.size() == 1, + String.format( + "Found %d fields matching Lombok generated builder default method %s", + matchingMembers.size(), methodNameString)); + return Nullness.hasNullableAnnotation(matchingMembers.get(0), config); + } + + @Override + public boolean onOverrideMayBeNullExpr( + NullAway analysis, + ExpressionTree expr, + @Nullable Symbol exprSymbol, + VisitorState state, + boolean exprMayBeNull) { + if (exprMayBeNull) { + return true; + } + Tree.Kind exprKind = expr.getKind(); + if (exprSymbol != null && exprKind == Tree.Kind.METHOD_INVOCATION) { + Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) exprSymbol; + return isLombokMethodWithMissingNullableAnnotation(methodSymbol, state); + } + return false; + } + + @Override + public Nullness onOverrideMethodReturnNullability( + Symbol.MethodSymbol methodSymbol, + VisitorState state, + boolean isAnnotated, + Nullness returnNullness) { + if (isLombokMethodWithMissingNullableAnnotation(methodSymbol, state)) { + return Nullness.NULLABLE; + } + return returnNullness; + } +} diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java index 8e6ac8ee64..561ac6ebae 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java @@ -133,7 +133,7 @@ public Nullness[] onOverrideMethodInvocationParametersNullability( } @Override - public Nullness onOverrideMethodInvocationReturnNullability( + public Nullness onOverrideMethodReturnNullability( Symbol.MethodSymbol methodSymbol, VisitorState state, boolean isAnnotated, diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayCustomLibraryModelsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayCustomLibraryModelsTests.java index 0685a6566e..937752c0bc 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayCustomLibraryModelsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayCustomLibraryModelsTests.java @@ -53,20 +53,22 @@ public void allowLibraryModelsOverrideAnnotations() { temporaryFolder.getRoot().getAbsolutePath(), "-XepOpt:NullAway:AnnotatedPackages=com.uber")) .addSourceLines( - "Foo.java", + "AnnotatedWithModels.java", "package com.uber;", - "public class Foo {", + "public class AnnotatedWithModels {", " Object field = new Object();", - " Object bar() {", - " return new Object();", + " // implicitly @Nullable due to library model", + " Object returnsNullFromModel() {", + " // null is valid here only because of the library model", + " return null;", " }", " Object nullableReturn() {", " // BUG: Diagnostic contains: returning @Nullable", - " return bar();", + " return returnsNullFromModel();", " }", " void run() {", " // just to make sure, flow analysis is also impacted by library models information", - " Object temp = bar();", + " Object temp = returnsNullFromModel();", " // BUG: Diagnostic contains: assigning @Nullable", " this.field = temp;", " }", diff --git a/test-java-lib-lombok/src/main/java/com/uber/lombok/LombokDTO.java b/test-java-lib-lombok/src/main/java/com/uber/lombok/LombokDTO.java index 37bedb4c14..44d6b50709 100644 --- a/test-java-lib-lombok/src/main/java/com/uber/lombok/LombokDTO.java +++ b/test-java-lib-lombok/src/main/java/com/uber/lombok/LombokDTO.java @@ -36,4 +36,5 @@ public class LombokDTO { private String field; @Builder.Default private String fieldWithDefault = "Default"; @Nullable private String nullableField; + @Nullable @Builder.Default private String fieldWithNullDefault = null; } diff --git a/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java b/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java index cd6aad1d40..3b84b8f8d5 100644 --- a/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java +++ b/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java @@ -66,7 +66,7 @@ public ImmutableSetMultimap nullImpliesNullParameters() { @Override public ImmutableSet nullableReturns() { return ImmutableSet.of( - methodRef("com.uber.Foo", "bar()"), + methodRef("com.uber.AnnotatedWithModels", "returnsNullFromModel()"), methodRef("com.uber.lib.unannotated.UnannotatedWithModels", "returnsNullUnannotated()"), methodRef("com.uber.lib.unannotated.UnannotatedWithModels", "returnsNullUnannotated2()")); } From 1ff88b64e527cdb529f5df38ab5ade8dc01b0352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Clapp?= Date: Wed, 14 Jun 2023 14:13:27 -0400 Subject: [PATCH 06/57] Revert #762 (Grade upgrade) and add CI checking of publishing flows (#767) A recent upgrade to our gradle version (#762) silently broke our publishing flows. In particular, `publish` and `publishToMavenLocal` are broken on Java 8, resulting in the following error: ``` Execution failed for task ':annotations:publishMavenPublicationToMavenLocal'. > Failed to publish publication 'maven' to repository 'mavenLocal' > Invalid publication 'maven': artifact file does not exist: '/home/runner/work/NullAway/NullAway/annotations/build/publications/maven/module.json.asc' ``` See this [CI job run](https://github.com/uber/NullAway/actions/runs/5261437721/jobs/9509542067?pr=767). There are further errors when the release is signed. Below, we revert the gradle upgrade, and also add a CI job running `publishToMavenLocal` to prevent future regressions (We can't trivially test signing these publications in a secure manner, unfortunately, so we skip that for now. If that specifically breaks in the future, we could provide a test gpg key for signing in CI). --- .github/workflows/continuous-integration.yml | 8 ++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 7 +++---- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 27b8d871a6..020ccfddf5 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -77,6 +77,14 @@ jobs: arguments: coveralls continue-on-error: true if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.19.1' && github.repository == 'uber/NullAway' + - name: Test publishToMavenLocal flow + env: + ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} + ORG_GRADLE_PROJECT_VERSION_NAME: '0.0.0.1-LOCAL' + uses: gradle/gradle-build-action@v2 + with: + arguments: publishToMavenLocal -x signMavenPublication -x signShadowPublication + if: matrix.java == '8' - name: Check that Git tree is clean after build and test run: ./.buildscript/check_git_clean.sh publish_snapshot: diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 8469 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 F{TFmrHLU;u diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2c3425d49e..19acfb4ef2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionSha256Sum=ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb43..79a61d421c 100755 --- a/gradlew +++ b/gradlew @@ -85,6 +85,9 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -194,10 +197,6 @@ if "$cygwin" || "$msys" ; then done fi - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in From 0466a02cbd4898db09a3e9457e60c4495cab6593 Mon Sep 17 00:00:00 2001 From: Asapin <1559761+Asapin@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:23:58 +0900 Subject: [PATCH 07/57] Added support for Apache Validate (#769) Added support for Apache Validate: https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/Validate.java We recently started to use these Apache methods to validate some data but quickly found out that NullAway doesn't support them since they are not properly annotated. Here's a fix that adds support for these methods along with test cases for each method. I noticed that Apache library generally has different validation methods for String, Map and Collection, but they all end up with the same signature when analyzed by NullAway (probably because these methods use generics). Examples of such methods: ```java public static > T notEmpty(final T collection) { } public static > T notEmpty(final T map) { } public static T notEmpty(final T chars) { } ``` But I decided to add a separate unit test for each of these methods in case NullAway will change how it generates method's signature in the future. --- .../handlers/LibraryModelsHandler.java | 48 +++ .../uber/nullaway/NullAwayFrameworkTests.java | 382 ++++++++++++++++++ 2 files changed, 430 insertions(+) diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java index 799ef241ed..1ed32ebac6 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java @@ -452,6 +452,54 @@ private static class DefaultLibraryModels implements LibraryModels { "org.junit.jupiter.api.Assertions", "assertNotNull(java.lang.Object,java.util.function.Supplier)"), 0) + .put(methodRef("org.apache.commons.lang3.Validate", "notNull(T)"), 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "notNull(T,java.lang.String,java.lang.Object...)"), + 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "notEmpty(T[],java.lang.String,java.lang.Object...)"), + 0) + .put(methodRef("org.apache.commons.lang3.Validate", "notEmpty(T[])"), 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "notEmpty(T,java.lang.String,java.lang.Object...)"), + 0) + .put(methodRef("org.apache.commons.lang3.Validate", "notEmpty(T)"), 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "notBlank(T,java.lang.String,java.lang.Object...)"), + 0) + .put(methodRef("org.apache.commons.lang3.Validate", "notBlank(T)"), 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "noNullElements(T[],java.lang.String,java.lang.Object...)"), + 0) + .put(methodRef("org.apache.commons.lang3.Validate", "noNullElements(T[])"), 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "noNullElements(T,java.lang.String,java.lang.Object...)"), + 0) + .put(methodRef("org.apache.commons.lang3.Validate", "noNullElements(T)"), 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "validIndex(T[],int,java.lang.String,java.lang.Object...)"), + 0) + .put(methodRef("org.apache.commons.lang3.Validate", "validIndex(T[],int)"), 0) + .put( + methodRef( + "org.apache.commons.lang3.Validate", + "validIndex(T,int,java.lang.String,java.lang.Object...)"), + 0) + .put(methodRef("org.apache.commons.lang3.Validate", "validIndex(T,int)"), 0) .build(); private static final ImmutableSetMultimap EXPLICITLY_NULLABLE_PARAMETERS = diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java index d3678632e8..8a41d2fd18 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java @@ -454,4 +454,386 @@ public void defaultLibraryModelsClassCast() { "}") .doTest(); } + + @Test + public void apacheValidateNotNull() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.notNull(s);", + " int l = s.length();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateNotNullWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.notNull(s, \"Message\");", + " int l = s.length();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateArrayNotEmptyWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String[] s) {", + " Validate.notEmpty(s, \"Message\");", + " int l = s.length;", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateArrayNotEmpty() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String[] s) {", + " Validate.notEmpty(s);", + " int l = s.length;", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateListNotEmptyWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.List;", + "public class Foo {", + " public void bar(@Nullable List s) {", + " Validate.notEmpty(s, \"Message\");", + " int l = s.size();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateListNotEmpty() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.List;", + "public class Foo {", + " public void bar(@Nullable List s) {", + " Validate.notEmpty(s);", + " int l = s.size();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateMapNotEmptyWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.Map;", + "public class Foo {", + " public void bar(@Nullable Map s) {", + " Validate.notEmpty(s, \"Message\");", + " int l = s.size();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateMapNotEmpty() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.Map;", + "public class Foo {", + " public void bar(@Nullable Map s) {", + " Validate.notEmpty(s);", + " int l = s.size();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateStringNotEmptyWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.notEmpty(s, \"Message\");", + " int l = s.length();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateStringNotEmpty() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.notEmpty(s);", + " int l = s.length();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateStringNotBlankWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.notBlank(s, \"Message\");", + " int l = s.length();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateStringNotBlank() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.notBlank(s);", + " int l = s.length();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateArrayNoNullElementsWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String[] s) {", + " Validate.noNullElements(s, \"Message\");", + " int l = s.length;", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateArrayNoNullElements() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String[] s) {", + " Validate.noNullElements(s);", + " int l = s.length;", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateIterableNoNullElementsWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.Iterator;", + "public class Foo {", + " public void bar(@Nullable Iterable s) {", + " Validate.noNullElements(s, \"Message\");", + " Iterator l = s.iterator();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateIterableNoNullElements() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.Iterator;", + "public class Foo {", + " public void bar(@Nullable Iterable s) {", + " Validate.noNullElements(s);", + " Iterator l = s.iterator();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateArrayValidIndexWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String[] s) {", + " Validate.validIndex(s, 0, \"Message\");", + " int l = s.length;", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateArrayValidIndex() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String[] s) {", + " Validate.validIndex(s, 0);", + " int l = s.length;", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateCollectionValidIndexWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.List;", + "public class Foo {", + " public void bar(@Nullable List s) {", + " Validate.validIndex(s, 0, \"Message\");", + " int l = s.size();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateCollectionValidIndex() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "import java.util.List;", + "public class Foo {", + " public void bar(@Nullable List s) {", + " Validate.validIndex(s, 0);", + " int l = s.size();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateStringValidIndexWithMessage() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.validIndex(s, 0, \"Message\");", + " int l = s.length();", + " }", + "}") + .doTest(); + } + + @Test + public void apacheValidateStringValidIndex() { + defaultCompilationHelper + .addSourceLines( + "Foo.java", + "package com.uber;", + "import org.apache.commons.lang3.Validate;", + "import org.jetbrains.annotations.Nullable;", + "public class Foo {", + " public void bar(@Nullable String s) {", + " Validate.validIndex(s, 0);", + " int l = s.length();", + " }", + "}") + .doTest(); + } } From 37f4bb8a97750adf946c10e4eaaf1ab1813f3d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Clapp?= Date: Wed, 21 Jun 2023 15:33:32 -0700 Subject: [PATCH 08/57] Introduce FluentFutureHandler as a workaround for Guava Futures/FluentFuture (#771) We need this due to our incomplete support for generics, and the fact that #765 fixes a previous false negative which would cause our `@NonNull` model for Guava's `Function` to be ignored when determining the correct overriding of the functional interface by a lambda. After the fix, however, code such as: ``` FluentFuture .from(...) .transform(s -> { if(...) {...} else { return null; } }, executor); ``` will fail with an error about `Function::apply` having a `@NonNull` result. Where nullability should actually depend on the parameter `T` of the `ListenableFuture` in question. Unfortunately, usage of this futures API is common internally for us, and our generics support is far from fully supporting this in a sound manner, so this diff introduces a (hopefully temporary) workaround, in the form of handler for the Futures/FluentFuture Guava APIs: This works by special casing the return nullability of `com.google.common.base.Function` and `com.google.common.util.concurrent.AsyncFunction` to be e.g. `Function<@Nullable T>` whenever these functional interfaces are implemented as a lambda expression passed to a list of specific methods of `com.google.common.util.concurrent.FluentFuture` or `com.google.common.util.concurrent.Futures`. This is unsound, but permits the code example above to pass NullAway checking, while strictly reducing the safety hole of NullAway versions prior to PR #765. --- .../com/uber/nullaway/handlers/Handlers.java | 2 + .../temporary/FluentFutureHandler.java | 91 +++++++++++++++ ...ayFunctionalInterfaceNullabilityTests.java | 110 ++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 nullaway/src/main/java/com/uber/nullaway/handlers/temporary/FluentFutureHandler.java create mode 100644 nullaway/src/test/java/com/uber/nullaway/NullAwayFunctionalInterfaceNullabilityTests.java diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java index 85e10e644a..606a7bb847 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java @@ -28,6 +28,7 @@ import com.uber.nullaway.handlers.contract.ContractHandler; import com.uber.nullaway.handlers.contract.fieldcontract.EnsuresNonNullHandler; import com.uber.nullaway.handlers.contract.fieldcontract.RequiresNonNullHandler; +import com.uber.nullaway.handlers.temporary.FluentFutureHandler; /** Utility static methods for the handlers package. */ public class Handlers { @@ -75,6 +76,7 @@ public static Handler buildDefault(Config config) { handlerListBuilder.add(new ContractCheckHandler(config)); } handlerListBuilder.add(new LombokHandler(config)); + handlerListBuilder.add(new FluentFutureHandler()); return new CompositeHandler(handlerListBuilder.build()); } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/temporary/FluentFutureHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/temporary/FluentFutureHandler.java new file mode 100644 index 0000000000..04ccd6b419 --- /dev/null +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/temporary/FluentFutureHandler.java @@ -0,0 +1,91 @@ +package com.uber.nullaway.handlers.temporary; + +import com.google.errorprone.VisitorState; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.tools.javac.code.Symbol; +import com.uber.nullaway.NullabilityUtil; +import com.uber.nullaway.Nullness; +import com.uber.nullaway.handlers.BaseNoOpHandler; +import java.util.Arrays; +import javax.lang.model.element.Name; + +/** + * This handler provides a temporary workaround due to our lack of support for generics, which + * allows natural usage of Futures/FluentFuture Guava APIs. It can potentially introduce false + * negatives, however, and should be deprecated as soon as full generic support is available. + * + *

This works by special casing the return nullability of {@link com.google.common.base.Function} + * and {@link com.google.common.util.concurrent.AsyncFunction} to be e.g. {@code Function<@Nullable + * T>} whenever these functional interfaces are implemented as a lambda expression passed to a list + * of specific methods of {@link com.google.common.util.concurrent.FluentFuture} or {@link + * com.google.common.util.concurrent.Futures}. We cannot currently check that {@code T} for {@code + * FluentFuture} is a {@code @Nullable} type, so this is unsound. However, we have found many + * cases in practice where these lambdas include {@code null} returns, which were already being + * ignored (due to a bug) before PR #765. This handler offers the best possible support for these + * cases, at least until our generics support is mature enough to handle them. + * + *

Note: Package {@code com.uber.nullaway.handlers.temporary} is meant for this sort of temporary + * workaround handler, to be removed as future NullAway features make them unnecessary. This is a + * hack, but the best of a bunch of bad options. + */ +public class FluentFutureHandler extends BaseNoOpHandler { + + private static final String GUAVA_FUNCTION_CLASS_NAME = "com.google.common.base.Function"; + private static final String GUAVA_ASYNC_FUNCTION_CLASS_NAME = + "com.google.common.util.concurrent.AsyncFunction"; + private static final String FLUENT_FUTURE_CLASS_NAME = + "com.google.common.util.concurrent.FluentFuture"; + private static final String FUTURES_CLASS_NAME = "com.google.common.util.concurrent.Futures"; + private static final String FUNCTION_APPLY_METHOD_NAME = "apply"; + private static final String[] FLUENT_FUTURE_INCLUDE_LIST_METHODS = { + "catching", "catchingAsync", "transform", "transformAsync" + }; + + private static boolean isGuavaFunctionDotApply(Symbol.MethodSymbol methodSymbol) { + Name className = methodSymbol.enclClass().flatName(); + return (className.contentEquals(GUAVA_FUNCTION_CLASS_NAME) + || className.contentEquals(GUAVA_ASYNC_FUNCTION_CLASS_NAME)) + && methodSymbol.name.contentEquals(FUNCTION_APPLY_METHOD_NAME); + } + + private static boolean isFluentFutureIncludeListMethod(Symbol.MethodSymbol methodSymbol) { + Name className = methodSymbol.enclClass().flatName(); + return (className.contentEquals(FLUENT_FUTURE_CLASS_NAME) + || className.contentEquals(FUTURES_CLASS_NAME)) + && Arrays.stream(FLUENT_FUTURE_INCLUDE_LIST_METHODS) + .anyMatch(s -> methodSymbol.name.contentEquals(s)); + } + + @Override + public Nullness onOverrideMethodReturnNullability( + Symbol.MethodSymbol methodSymbol, + VisitorState state, + boolean isAnnotated, + Nullness returnNullness) { + // We only care about lambda's implementing Guava's Function + if (!isGuavaFunctionDotApply(methodSymbol)) { + return returnNullness; + } + // Check if we are inside a lambda passed as an argument to a method call: + LambdaExpressionTree enclosingLambda = + ASTHelpers.findEnclosingNode(state.getPath(), LambdaExpressionTree.class); + if (enclosingLambda == null + || !NullabilityUtil.getFunctionalInterfaceMethod(enclosingLambda, state.getTypes()) + .equals(methodSymbol)) { + return returnNullness; + } + MethodInvocationTree methodInvocation = + ASTHelpers.findEnclosingNode(state.getPath(), MethodInvocationTree.class); + if (methodInvocation == null || !methodInvocation.getArguments().contains(enclosingLambda)) { + return returnNullness; + } + // Check if that method call is one of the FluentFuture APIs we care about + Symbol.MethodSymbol lambdaConsumerMethodSymbol = ASTHelpers.getSymbol(methodInvocation); + if (!isFluentFutureIncludeListMethod(lambdaConsumerMethodSymbol)) { + return returnNullness; + } + return Nullness.NULLABLE; + } +} diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayFunctionalInterfaceNullabilityTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayFunctionalInterfaceNullabilityTests.java new file mode 100644 index 0000000000..6406b2b733 --- /dev/null +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayFunctionalInterfaceNullabilityTests.java @@ -0,0 +1,110 @@ +package com.uber.nullaway; + +import org.junit.Test; + +public class NullAwayFunctionalInterfaceNullabilityTests extends NullAwayTestsBase { + + @Test + public void multipleTypeParametersInstantiation() { + defaultCompilationHelper + .addSourceLines( + "NullableFunction.java", + "package com.uber.unannotated;", // As if a third-party lib, since override is invalid + "import javax.annotation.Nullable;", + "import java.util.function.Function;", + "@FunctionalInterface", + "public interface NullableFunction extends Function {", + " @Override", + " @Nullable", + " T apply(@Nullable F input);", + "}") + .addSourceLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "import java.util.function.Function;", + "import com.uber.unannotated.NullableFunction;", + "class Test {", + " private static void takesNullableFunction(NullableFunction nf) { }", + " private static void takesNonNullableFunction(Function f) { }", + " private static void passesNullableFunction() {", + " takesNullableFunction(s -> { return null; });", + " }", + " private static void passesNullableFunctionToNonNull() {", + " takesNonNullableFunction(s -> { return null; });", + " }", + "}") + .addSourceLines( + "TestGuava.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "import com.google.common.base.Function;", + "import com.uber.unannotated.NullableFunction;", + "class TestGuava {", + " private static void takesNullableFunction(NullableFunction nf) { }", + " private static void takesNonNullableFunction(Function f) { }", + " private static void passesNullableFunction() {", + " takesNullableFunction(s -> { return null; });", + " }", + " private static void passesNullableFunctionToNonNull() {", + " // BUG: Diagnostic contains: returning @Nullable expression", + " takesNonNullableFunction(s -> { return null; });", + " }", + "}") + .doTest(); + } + + @Test + public void futuresFunctionLambdas() { + // See FluentFutureHandler + defaultCompilationHelper + .addSourceLines( + "TestGuava.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "import com.google.common.base.Function;", + "import com.google.common.util.concurrent.FluentFuture;", + "import com.google.common.util.concurrent.Futures;", + "import com.google.common.util.concurrent.ListenableFuture;", + "import java.util.concurrent.Executor;", + "class TestGuava {", + " private static ListenableFuture<@Nullable String> fluentFutureCatching(Executor executor) {", + " return FluentFuture", + " .from(Futures.immediateFuture(\"hi\"))", + " .catching(Throwable.class, e -> { return null; }, executor);", + " }", + " private static ListenableFuture<@Nullable String> fluentFutureCatchingAsync(Executor executor) {", + " return FluentFuture", + " .from(Futures.immediateFuture(\"hi\"))", + " .catchingAsync(Throwable.class, e -> { return null; }, executor);", + " }", + " private static ListenableFuture<@Nullable String> fluentFutureTransform(Executor executor) {", + " return FluentFuture", + " .from(Futures.immediateFuture(\"hi\"))", + " .transform(s -> { return null; }, executor);", + " }", + " private static ListenableFuture<@Nullable String> fluentFutureTransformAsync(Executor executor) {", + " return FluentFuture", + " .from(Futures.immediateFuture(\"hi\"))", + " .transformAsync(s -> { return null; }, executor);", + " }", + " private static ListenableFuture fluentFutureTransformNoNull(Executor executor) {", + " return FluentFuture", + " .from(Futures.immediateFuture(\"hi\"))", + " // Should be an error when we have full generics support, false-negative for now", + " .transform(s -> { return s; }, executor);", + " }", + " private static ListenableFuture fluentFutureUnsafe(Executor executor) {", + " return FluentFuture", + " .from(Futures.immediateFuture(\"hi\"))", + " // Should be an error when we have full generics support, false-negative for now", + " .transform(s -> { return null; }, executor);", + " }", + " private static ListenableFuture<@Nullable String> futuresTransform(Executor executor) {", + " return Futures", + " .transform(Futures.immediateFuture(\"hi\"), s -> { return null; }, executor);", + " }", + "}") + .doTest(); + } +} From 2bfe823661d7f24135f935ed2d2b3c37d6b6b507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Clapp?= Date: Thu, 22 Jun 2023 17:43:55 -0700 Subject: [PATCH 09/57] Upgrade maven publish plugin to 0.21.0 and restore publishing (#773) Without this change, the recent Gradle upgrade (#743) resulted in the following error while trying to publish to maven central: ``` > Task :jar-infer:jar-infer-cli:publishShadowPublicationToLocalRepository FAILED FAILURE: Build failed with an exception. * What went wrong: Some problems were found with the configuration of task ':jar-infer:jar-infer-cli:publishShadowPublicationToLocalRepository' (type 'PublishToMavenRepository'). - Gradle detected a problem with the following location: '/Users/lazaro/Uber/NullAway/jar-infer/jar-infer-cli/build/libs/jar-infer-cli-0.10.11.jar.asc'. Reason: Task ':jar-infer:jar-infer-cli:publishShadowPublicationToLocalRepository' uses this output of task ':jar-infer:jar-infer-cli:signMavenPublication' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. Possible solutions: 1. Declare task ':jar-infer:jar-infer-cli:signMavenPublication' as an input of ':jar-infer:jar-infer-cli:publishShadowPublicationToLocalRepository'. 2. Declare an explicit dependency on ':jar-infer:jar-infer-cli:signMavenPublication' from ':jar-infer:jar-infer-cli:publishShadowPublicationToLocalRepository' using Task#dependsOn. 3. Declare an explicit dependency on ':jar-infer:jar-infer-cli:signMavenPublication' from ':jar-infer:jar-infer-cli:publishShadowPublicationToLocalRepository' using Task#mustRunAfter. ``` This change fixes that issue and also upgrades `com.vanniktech:gradle-maven-publish-plugin` to 0.21.0, the latest compatible version (versions from 0.23.0 onwards don't seem to be compatible with JDK8, and 0.22.0 gives a separate error). Long term, we need to update the CI testing flow for publishing we added in #768 to also cover jar signing workflows. --- build.gradle | 2 +- gradle.properties | 4 ++++ jar-infer/jar-infer-cli/build.gradle | 32 ++++++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index f63ac8f0dc..2843a0bbf4 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.0' - classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2' + classpath 'com.vanniktech:gradle-maven-publish-plugin:0.21.0' // This restriction is needed due to our mix of Android and Java modules; // without it, the build fails with a weird error. // See https://stackoverflow.com/questions/70217853/how-to-include-android-project-in-a-gradle-multi-project-build diff --git a/gradle.properties b/gradle.properties index 863eb5da2c..f1774cbb2c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,3 +28,7 @@ POM_LICENCE_DIST=repo POM_DEVELOPER_ID=uber POM_DEVELOPER_NAME=Uber Technologies POM_DEVELOPER_URL=https://uber.com + +# Publishing configuration for vanniktech/gradle-maven-publish-plugin +SONATYPE_HOST=DEFAULT +RELEASE_SIGNING_ENABLED=true diff --git a/jar-infer/jar-infer-cli/build.gradle b/jar-infer/jar-infer-cli/build.gradle index 0b242595be..8744bc3524 100644 --- a/jar-infer/jar-infer-cli/build.gradle +++ b/jar-infer/jar-infer-cli/build.gradle @@ -21,6 +21,11 @@ dependencies { } } +java { + withJavadocJar() + withSourcesJar() +} + jar { manifest { attributes( @@ -41,6 +46,7 @@ shadowJar { shadowJar.dependsOn jar assemble.dependsOn shadowJar + // We disable the default maven publications to make sure only // our custom shadow publication is used. Since we use the empty // classifier for our fat jar, it would otherwise clash with the @@ -67,8 +73,8 @@ publishing { // Since we are skipping the default maven publication, we append the `:sources` and // `:javadoc` artifacts here. They are also required for Maven Central validation. afterEvaluate { - artifact project.sourcesJar - artifact project.javadocsJar + artifact project.javaSourcesJar + artifact project.javadocJar } // The shadow publication does not auto-configure the pom.xml file for us, so we need to // set it up manually. We use the opportunity to change the name and description from @@ -98,5 +104,27 @@ publishing { } } } + } + + afterEvaluate { + // Below is a series of hacks needed to get publication to work with + // gradle-maven-publish-plugin >= 0.15.0 (itself needed after the upgrade to Gradle 8.0.2). + // Not sure why e.g. publishShadowPublicationToMavenCentralRepository must depend on signMavenPublication + // (rather than just signShadowPublication) + project.tasks.named('generateMetadataFileForMavenPublication').configure { + dependsOn 'javaSourcesJar' + dependsOn 'simpleJavadocJar' + } + project.tasks.named('signShadowPublication').configure { + dependsOn 'sourcesJar' + dependsOn 'simpleJavadocJar' + } + project.tasks.named('publishShadowPublicationToMavenCentralRepository').configure { + dependsOn 'signMavenPublication' + } + project.tasks.named('publishShadowPublicationToMavenLocal').configure { + dependsOn 'sourcesJar' + dependsOn 'simpleJavadocJar' + } } } From b00792641f016dc9fc9cd81e57cf3a6efb09027f Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Wed, 21 Jun 2023 16:27:10 -0700 Subject: [PATCH 10/57] Prepare for release 0.10.11. --- CHANGELOG.md | 24 ++++++++++++++++++++++++ README.md | 4 ++-- gradle.properties | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf48af143..f1168c77dd 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ Changelog ========= +Version 0.10.11 +--------------- +* NULL_LITERAL expressions may always be null (#749) +* Fix error in Lombok generated code for @Nullable @Builder.Default (#765) +* Support for specific libraries/APIs: + - Added support for Apache Validate (#769) + - Introduce FluentFutureHandler as a workaround for Guava FluentFuture (#771) +* Internal code refactorings: + - [Refactor] Pass resolved Symbols into Handler methods (#729) + - Prepare for Nullable ASTHelpers.getSymbol (#733) + - Refactor: streamline mayBeNullExpr flow (#753) + - Refactor LibraryModelsHandler.onOverrideMayBeNullExpr (#754) + - Refactor simple onOverrideMayBeNullExpr handlers (#747) +* Support for JSpecify's 0.3.0 annotation [experimental] + - JSpecify generics checks for conditional expressions (#739) + - Generics checks for parameter passing (#746) + - Clearer printing of types in errors related to generics (#758) +* NullAwayInfer/Annotator data serialization support [experimental] + - Update path serialization for class files (#752) +* Build / CI tooling for NullAway itself: + - Update to Gradle 8.0.2 (#743) + - Fix CI on Windows (#759) + - Upgrade to Error Prone 2.19.1 (#763) + Version 0.10.10 --------------- * Add command line option to skip specific library models. (#741) diff --git a/README.md b/README.md index fa62c4e80b..4b184d93cf 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ plugins { } dependencies { - annotationProcessor "com.uber.nullaway:nullaway:0.10.10" + annotationProcessor "com.uber.nullaway:nullaway:0.10.11" // Optional, some source of nullability annotations. // Not required on Android if you use the support @@ -75,7 +75,7 @@ The configuration for an Android project is very similar to the Java case, with ```gradle dependencies { - annotationProcessor "com.uber.nullaway:nullaway:0.10.10" + annotationProcessor "com.uber.nullaway:nullaway:0.10.11" errorprone "com.google.errorprone:error_prone_core:2.4.0" errorproneJavac "com.google.errorprone:javac:9+181-r4173-1" } diff --git a/gradle.properties b/gradle.properties index f1774cbb2c..1f71ad1768 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.11-SNAPSHOT +VERSION_NAME=0.10.11 POM_DESCRIPTION=A fast annotation-based null checker for Java From 1012a0a78fc8de417ced45020a5d618eddc61ba6 Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Thu, 22 Jun 2023 17:53:51 -0700 Subject: [PATCH 11/57] Prepare next development version. --- CHANGELOG.md | 1 + gradle.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1168c77dd..0317627cd0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Version 0.10.11 - Update to Gradle 8.0.2 (#743) - Fix CI on Windows (#759) - Upgrade to Error Prone 2.19.1 (#763) + - Upgrade maven publish plugin to 0.21.0 (#773) Version 0.10.10 --------------- diff --git a/gradle.properties b/gradle.properties index 1f71ad1768..3cbe7b4a46 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.11 +VERSION_NAME=0.10.12-SNAPSHOT POM_DESCRIPTION=A fast annotation-based null checker for Java From 9cef8095080fc35db7f61dcf4f3ea3c51688b244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Clapp?= Date: Thu, 22 Jun 2023 18:05:15 -0700 Subject: [PATCH 12/57] Fix Publish Snapshot CI job (#774) The name of these two configuration options was changed in newer versions of `gradle-maven-publish-plugin`. Without this change, #773 breaks our snapshot publishing GitHub Actions CI workflow. --- .github/workflows/continuous-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 020ccfddf5..6f59100ea3 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -103,7 +103,7 @@ jobs: - name: 'Publish' uses: gradle/gradle-build-action@v2 env: - ORG_GRADLE_PROJECT_mavenCentralRepositoryUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralRepositoryPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} with: arguments: clean publish --no-daemon --no-parallel From cea42a5641b3e58df7cd0b6a70dee1e41f324812 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 23 Jun 2023 14:25:16 -0700 Subject: [PATCH 13/57] Add step to create release on GitHub (#775) Users look for GitHub Releases these days (and can be easily notified of such releases), so we should support them. I created a release for 0.10.11: https://github.com/uber/NullAway/releases/tag/v0.10.11 The GitHub Release page has a nice feature for generating release notes from commits, so eventually we may want to deprecate `CHANGELOG.md`. --- RELEASING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASING.md b/RELEASING.md index 3bcbbd1e98..198a83b0dd 100755 --- a/RELEASING.md +++ b/RELEASING.md @@ -31,3 +31,4 @@ Releasing 8. `git commit -am "Prepare next development version."` 9. `git push && git push --tags` 10. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. + 11. Go to [this page](https://github.com/uber/NullAway/releases/new) to create a new release on GitHub, using the release notes from `CHANGELOG.md`. From a81c4b318b741e29bcfe143a6e00d201bd2f013b Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 23 Jun 2023 16:05:29 -0700 Subject: [PATCH 14/57] Build the Android sample app on JDK 17 (#776) Seems that this works now, so might as well run it on CI. The JarInfer tasks still don't work on JDK 17 due to a WALA issue; I'll see if I can work on that. --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6f59100ea3..2a023bfe2e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -66,7 +66,7 @@ jobs: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} uses: gradle/gradle-build-action@v2 with: - arguments: build -x :sample-app:build -x :jar-infer:jar-infer-lib:build -x :jar-infer:nullaway-integration-test:build -x :jar-infer:test-java-lib-jarinfer:build + arguments: build -x :jar-infer:jar-infer-lib:build -x :jar-infer:nullaway-integration-test:build -x :jar-infer:test-java-lib-jarinfer:build if: matrix.java == '17' - name: Report jacoco coverage uses: gradle/gradle-build-action@v2 From 18b6ab30a6ee93dd79f2740542ad2ac8cc3a63c4 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 23 Jun 2023 16:13:35 -0700 Subject: [PATCH 15/57] Update to Error Prone 2.20.0 (#772) Pretty straightforward update --- .github/workflows/continuous-integration.yml | 10 +++++----- gradle/dependencies.gradle | 2 +- .../java/com/uber/nullaway/jarinfer/JarInferTest.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 2a023bfe2e..9e83f401e0 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -21,16 +21,16 @@ jobs: epVersion: 2.4.0 - os: macos-latest java: 11 - epVersion: 2.19.1 + epVersion: 2.20.0 - os: ubuntu-latest java: 11 - epVersion: 2.19.1 + epVersion: 2.20.0 - os: windows-latest java: 11 - epVersion: 2.19.1 + epVersion: 2.20.0 - os: ubuntu-latest java: 17 - epVersion: 2.19.1 + epVersion: 2.20.0 fail-fast: false runs-on: ${{ matrix.os }} steps: @@ -76,7 +76,7 @@ jobs: with: arguments: coveralls continue-on-error: true - if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.19.1' && github.repository == 'uber/NullAway' + if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.20.0' && github.repository == 'uber/NullAway' - name: Test publishToMavenLocal flow env: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 8c8d72a613..961df9b844 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -19,7 +19,7 @@ import org.gradle.util.VersionNumber // The oldest version of Error Prone that we support running on def oldestErrorProneVersion = "2.4.0" // Latest released Error Prone version that we've tested with -def latestErrorProneVersion = "2.19.1" +def latestErrorProneVersion = "2.20.0" // Default to using latest tested Error Prone version, except on Java 8, where 2.10.0 is the last version // that works def defaultErrorProneVersion = JavaVersion.current() >= JavaVersion.VERSION_11 ? latestErrorProneVersion : "2.10.0" diff --git a/jar-infer/jar-infer-lib/src/test/java/com/uber/nullaway/jarinfer/JarInferTest.java b/jar-infer/jar-infer-lib/src/test/java/com/uber/nullaway/jarinfer/JarInferTest.java index dda6aaf47b..897e96e2ed 100644 --- a/jar-infer/jar-infer-lib/src/test/java/com/uber/nullaway/jarinfer/JarInferTest.java +++ b/jar-infer/jar-infer-lib/src/test/java/com/uber/nullaway/jarinfer/JarInferTest.java @@ -170,7 +170,7 @@ private void testAnnotationInAarTemplate( * @param result Map of 'method signatures' to their 'inferred list of NonNull parameters'. * @param expected Map of 'method signatures' to their 'expected list of NonNull parameters'. */ - private boolean verify(Map> result, HashMap> expected) { + private boolean verify(Map> result, Map> expected) { for (Map.Entry> entry : result.entrySet()) { String mtd_sign = entry.getKey(); Set ddParams = entry.getValue(); From ec64e823f32e8739e47cd0e2ff1b6e1a3d74a275 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sat, 8 Jul 2023 11:04:11 -0700 Subject: [PATCH 16/57] Add tasks to run JDK 8 tests on JDK 11+ (#778) This PR adds tasks to additionally run certain test suites on JDK 8, even when we are building on a JDK 11+ JVM. The approach is described [here](https://jakewharton.com/build-on-latest-java-test-through-lowest-java/). This moves us towards only supporting building NullAway on JDK 11+, while still targeting Java 8 (so NullAway can still be used on JDK 8). This will help us with keeping build tooling and dependencies up to date, as more and more tools and plugins do not support running on JDK 8. Specifically, we run the core NullAway tests and the tests from `guava-recent-unit-tests` on JDK 8. We do _not_ run JarInfer tests, as we intend to remove the ability to run JarInfer on JDK 8 (see #777). Also, we only create the relevant test tasks when the Error Prone version being used is 2.4.0 (compatible with Java 8). We have one CI job, Java 11 + EP 2.4.0, that exercises this scenario. I confirmed locally that when running the `testJdk8` tasks, the tests are running on a JDK 8 JVM. Additionally, the PR contains a minor tweak that allows `publishToMavenLocal` to work when Gradle is run on JDK 11+. --- annotations/build.gradle | 2 - build.gradle | 17 ++--- guava-recent-unit-tests/build.gradle | 27 ++++++++ .../build.gradle | 2 - .../build.gradle | 2 - .../build.gradle | 2 - .../build.gradle | 2 - jar-infer/jar-infer-cli/build.gradle | 1 + jar-infer/jar-infer-lib/build.gradle | 2 - .../test-android-lib-jarinfer/build.gradle | 2 - nullaway/build.gradle | 62 ++++++++++++++----- sample/build.gradle | 3 - test-java-lib-lombok/build.gradle | 3 - test-java-lib/build.gradle | 3 - 14 files changed, 78 insertions(+), 52 deletions(-) diff --git a/annotations/build.gradle b/annotations/build.gradle index 5ffa3cec63..8c88c7bed2 100644 --- a/annotations/build.gradle +++ b/annotations/build.gradle @@ -20,8 +20,6 @@ plugins { id 'nullaway.jacoco-conventions' } -sourceCompatibility = 1.8 - dependencies { } diff --git a/build.gradle b/build.gradle index 2843a0bbf4..07198f9b0c 100644 --- a/build.gradle +++ b/build.gradle @@ -96,18 +96,11 @@ subprojects { project -> } } - // We target Java 11 when building on JDK 11+, but Java 8 when building on JDK 8, since - // EP 2.11.0+ requires Java 11 - if (JavaVersion.current() >= JavaVersion.VERSION_11) { - tasks.withType(JavaCompile) { - java.sourceCompatibility = "11" - java.targetCompatibility = "11" - } - } else { - tasks.withType(JavaCompile) { - java.sourceCompatibility = "1.8" - java.targetCompatibility = "1.8" - } + // Target JDK 8. We need to use the older sourceCompatibility / targetCompatibility settings to get + // the build to work on JDK 11+. Once we stop supporting JDK 8, switch to using the javac "release" option + tasks.withType(JavaCompile) { + java.sourceCompatibility = "1.8" + java.targetCompatibility = "1.8" } // Ensure we are running on Java 8 whenever publishing to remote repos diff --git a/guava-recent-unit-tests/build.gradle b/guava-recent-unit-tests/build.gradle index f831222651..24dbdfe76b 100644 --- a/guava-recent-unit-tests/build.gradle +++ b/guava-recent-unit-tests/build.gradle @@ -53,3 +53,30 @@ test { ] } } + +if (JavaVersion.current() >= JavaVersion.VERSION_11) { + // Create a task to test on JDK 8 + def jdk8Test = tasks.register("testJdk8", Test) { + onlyIf { + // Only if we are using a version of Error Prone compatible with JDK 8 + deps.versions.errorProneApi == "2.4.0" + } + + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } + + description = "Runs the test suite on JDK 8" + group = LifecycleBasePlugin.VERIFICATION_GROUP + + // Copy inputs from normal Test task. + def testTask = tasks.getByName("test") + classpath = testTask.classpath + testClassesDirs = testTask.testClassesDirs + jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" + } + + tasks.named('check').configure { + dependsOn(jdk8Test) + } +} diff --git a/jar-infer/android-jarinfer-models-sdk28/build.gradle b/jar-infer/android-jarinfer-models-sdk28/build.gradle index 9ff005f6ed..fe50fe8f62 100644 --- a/jar-infer/android-jarinfer-models-sdk28/build.gradle +++ b/jar-infer/android-jarinfer-models-sdk28/build.gradle @@ -2,8 +2,6 @@ plugins { id "java-library" } -sourceCompatibility = 1.8 - repositories { mavenCentral() } diff --git a/jar-infer/android-jarinfer-models-sdk29/build.gradle b/jar-infer/android-jarinfer-models-sdk29/build.gradle index 9ff005f6ed..fe50fe8f62 100644 --- a/jar-infer/android-jarinfer-models-sdk29/build.gradle +++ b/jar-infer/android-jarinfer-models-sdk29/build.gradle @@ -2,8 +2,6 @@ plugins { id "java-library" } -sourceCompatibility = 1.8 - repositories { mavenCentral() } diff --git a/jar-infer/android-jarinfer-models-sdk30/build.gradle b/jar-infer/android-jarinfer-models-sdk30/build.gradle index 9ff005f6ed..fe50fe8f62 100644 --- a/jar-infer/android-jarinfer-models-sdk30/build.gradle +++ b/jar-infer/android-jarinfer-models-sdk30/build.gradle @@ -2,8 +2,6 @@ plugins { id "java-library" } -sourceCompatibility = 1.8 - repositories { mavenCentral() } diff --git a/jar-infer/android-jarinfer-models-sdk31/build.gradle b/jar-infer/android-jarinfer-models-sdk31/build.gradle index 9ff005f6ed..fe50fe8f62 100644 --- a/jar-infer/android-jarinfer-models-sdk31/build.gradle +++ b/jar-infer/android-jarinfer-models-sdk31/build.gradle @@ -2,8 +2,6 @@ plugins { id "java-library" } -sourceCompatibility = 1.8 - repositories { mavenCentral() } diff --git a/jar-infer/jar-infer-cli/build.gradle b/jar-infer/jar-infer-cli/build.gradle index 8744bc3524..e0a5090876 100644 --- a/jar-infer/jar-infer-cli/build.gradle +++ b/jar-infer/jar-infer-cli/build.gradle @@ -125,6 +125,7 @@ publishing { project.tasks.named('publishShadowPublicationToMavenLocal').configure { dependsOn 'sourcesJar' dependsOn 'simpleJavadocJar' + dependsOn 'signMavenPublication' } } } diff --git a/jar-infer/jar-infer-lib/build.gradle b/jar-infer/jar-infer-lib/build.gradle index 638ae2de31..1bcf4ae9ff 100644 --- a/jar-infer/jar-infer-lib/build.gradle +++ b/jar-infer/jar-infer-lib/build.gradle @@ -18,8 +18,6 @@ plugins { id 'nullaway.jacoco-conventions' } -sourceCompatibility = 1.8 - repositories { mavenCentral() // uncomment if you want to use wala.dalvik or wala.scandroid diff --git a/jar-infer/test-android-lib-jarinfer/build.gradle b/jar-infer/test-android-lib-jarinfer/build.gradle index db29e5756a..b79ed03539 100644 --- a/jar-infer/test-android-lib-jarinfer/build.gradle +++ b/jar-infer/test-android-lib-jarinfer/build.gradle @@ -16,8 +16,6 @@ apply plugin: 'com.android.library' -sourceCompatibility = 1.8 - android { compileSdkVersion deps.build.compileSdkVersion diff --git a/nullaway/build.gradle b/nullaway/build.gradle index 3b4f10f379..a4df4148e7 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -101,25 +101,53 @@ test { apply plugin: 'com.vanniktech.maven.publish' if (JavaVersion.current() >= JavaVersion.VERSION_11) { - // Required on Java 11+ since Error Prone and NullAway access a bunch of - // JDK-internal APIs that are not exposed otherwise - tasks.withType(JavaCompile).configureEach { - options.compilerArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.source.tree=ALL-UNNAMED", - ] +// These --add-exports arguments are required when targeting JDK 11+ since Error Prone and NullAway access a bunch of +// JDK-internal APIs that are not exposed otherwise. Since we currently target JDK 8, we do not need to pass the +// arguments, as encapsulation of JDK internals is not enforced on JDK 8. In fact, the arguments cause a compiler error +// when targeting JDK 8. Leaving commented so we can easily add them back once we target JDK 11. +// tasks.withType(JavaCompile).configureEach { +// options.compilerArgs += [ +// "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", +// "--add-exports=jdk.compiler/com.sun.source.tree=ALL-UNNAMED", +// ] +// } + + // Create a task to test on JDK 8 + def jdk8Test = tasks.register("testJdk8", Test) { + onlyIf { + // Only if we are using a version of Error Prone compatible with JDK 8 + deps.versions.errorProneApi == "2.4.0" + } + + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } + + description = "Runs the test suite on JDK 8" + group = LifecycleBasePlugin.VERIFICATION_GROUP + + // Copy inputs from normal Test task. + def testTask = tasks.getByName("test") + classpath = testTask.classpath + testClassesDirs = testTask.testClassesDirs + jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" } + + tasks.named('check').configure { + dependsOn(jdk8Test) + } + // Create a task to build NullAway with NullAway checking enabled - // For some reason, this doesn't work on Java 8 + // (For some reason, this doesn't work on Java 8) tasks.register('buildWithNullAway', JavaCompile) { onlyIf { // We only do NullAway checks when compiling against the latest diff --git a/sample/build.gradle b/sample/build.gradle index 2586c8dee7..7c80543b4d 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -20,9 +20,6 @@ plugins { id "java-library" } -sourceCompatibility = "1.8" -targetCompatibility = "1.8" - dependencies { annotationProcessor project(":nullaway") annotationProcessor project(path: ":sample-library-model") diff --git a/test-java-lib-lombok/build.gradle b/test-java-lib-lombok/build.gradle index 8e8d7c5a94..2065f7cdcc 100644 --- a/test-java-lib-lombok/build.gradle +++ b/test-java-lib-lombok/build.gradle @@ -20,9 +20,6 @@ plugins { id "java-library" } -sourceCompatibility = "1.8" -targetCompatibility = "1.8" - dependencies { annotationProcessor project(":nullaway") annotationProcessor deps.test.lombok diff --git a/test-java-lib/build.gradle b/test-java-lib/build.gradle index 9fda6c3bef..f877afb08e 100644 --- a/test-java-lib/build.gradle +++ b/test-java-lib/build.gradle @@ -20,9 +20,6 @@ plugins { id "java-library" } -sourceCompatibility = "1.8" -targetCompatibility = "1.8" - dependencies { annotationProcessor project(":nullaway") implementation deps.build.jspecify From 188632b4542ad5acfc475ab2ae87e0b5a3e98b56 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sat, 8 Jul 2023 11:24:18 -0700 Subject: [PATCH 17/57] Update to WALA 1.6.1 and remove ability to build on JDK 8 (#777) WALA now runs on JDK 17. So, with this update, we can run our full build on JDK 17, including JarInfer. Since WALA has required JDK 11 since version 1.6.0, JarInfer now requires a JDK 11 VM to run, and will no longer run on JDK 8. In order to get everything to build with this new dependence, we have removed support for building on JDK 8. NullAway itself will still run as part of a JDK 8 build (and it still tested there, see #778), but running JarInfer now requires JDK 11. Much of this PR is cleanup enabled by removing support for building on JDK 8. --- .github/workflows/continuous-integration.yml | 18 +-- annotations/build.gradle | 34 +++-- build.gradle | 68 ++++------ gradle/dependencies.gradle | 7 +- guava-recent-unit-tests/build.gradle | 74 +++++------ jar-infer/jar-infer-cli/build.gradle | 4 + jar-infer/jar-infer-lib/build.gradle | 46 +++---- .../nullaway-integration-test/build.gradle | 30 ++--- jmh/build.gradle | 1 - nullaway/build.gradle | 117 ++++++++---------- settings.gradle | 10 +- test-java-lib-lombok/build.gradle | 13 +- 12 files changed, 184 insertions(+), 238 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 9e83f401e0..517ebe3c5e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -13,9 +13,6 @@ jobs: strategy: matrix: include: - - os: ubuntu-latest - java: 8 - epVersion: 2.4.0 - os: ubuntu-latest java: 11 epVersion: 2.4.0 @@ -47,13 +44,6 @@ jobs: with: java-version: ${{ matrix.java }} distribution: 'temurin' - - name: Build and test using Java 8 and Error Prone ${{ matrix.epVersion }} - env: - ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} - uses: gradle/gradle-build-action@v2 - with: - arguments: build - if: matrix.java == '8' - name: Build and test using Java 11 and Error Prone ${{ matrix.epVersion }} env: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} @@ -66,7 +56,7 @@ jobs: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} uses: gradle/gradle-build-action@v2 with: - arguments: build -x :jar-infer:jar-infer-lib:build -x :jar-infer:nullaway-integration-test:build -x :jar-infer:test-java-lib-jarinfer:build + arguments: build if: matrix.java == '17' - name: Report jacoco coverage uses: gradle/gradle-build-action@v2 @@ -84,7 +74,7 @@ jobs: uses: gradle/gradle-build-action@v2 with: arguments: publishToMavenLocal -x signMavenPublication -x signShadowPublication - if: matrix.java == '8' + if: matrix.java == '11' - name: Check that Git tree is clean after build and test run: ./.buildscript/check_git_clean.sh publish_snapshot: @@ -95,10 +85,10 @@ jobs: steps: - name: 'Check out repository' uses: actions/checkout@v3 - - name: 'Set up JDK 8' + - name: 'Set up JDK 11' uses: actions/setup-java@v3 with: - java-version: 8 + java-version: 11 distribution: 'temurin' - name: 'Publish' uses: gradle/gradle-build-action@v2 diff --git a/annotations/build.gradle b/annotations/build.gradle index 8c88c7bed2..55e960775b 100644 --- a/annotations/build.gradle +++ b/annotations/build.gradle @@ -25,25 +25,21 @@ dependencies { test { maxHeapSize = "1024m" - if (!JavaVersion.current().java9Compatible) { - jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" - } else { - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] - } + // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer + jvmArgs += [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + ] } apply plugin: 'com.vanniktech.maven.publish' diff --git a/build.gradle b/build.gradle index 07198f9b0c..e5edbcdb97 100644 --- a/build.gradle +++ b/build.gradle @@ -57,42 +57,33 @@ subprojects { project -> } project.tasks.withType(JavaCompile) { dependsOn(installGitHooks) - if (JavaVersion.current().isJava9Compatible()) { - options.compilerArgs += [ - "-Xlint:deprecation", - "-Xlint:rawtypes", - "-Xlint:unchecked", - "-Werror" - ] - options.errorprone { - // disable warnings in generated code; AutoValue code fails UnnecessaryParentheses check - disableWarningsInGeneratedCode = true - // this check is too noisy - check("StringSplitter", CheckSeverity.OFF) - // https://github.com/google/error-prone/issues/3366 - check("CanIgnoreReturnValueSuggester", CheckSeverity.OFF) - // turn up various checks - check("WildcardImport", CheckSeverity.ERROR) - check("MissingBraces", CheckSeverity.ERROR) - check("TypeToString", CheckSeverity.ERROR) - check("SymbolToString", CheckSeverity.ERROR) - check("MultipleTopLevelClasses", CheckSeverity.ERROR) - check("ClassName", CheckSeverity.ERROR) - check("PackageLocation", CheckSeverity.ERROR) - check("UnnecessaryAnonymousClass", CheckSeverity.ERROR) - check("UnusedException", CheckSeverity.ERROR) - // To enable auto-patching, uncomment the line below, replace [CheckerName] with - // the checker(s) you want to apply patches for (comma-separated), and above, disable - // "-Werror" + options.compilerArgs += [ + "-Xlint:deprecation", + "-Xlint:rawtypes", + "-Xlint:unchecked", + "-Werror" + ] + options.errorprone { + // disable warnings in generated code; AutoValue code fails UnnecessaryParentheses check + disableWarningsInGeneratedCode = true + // this check is too noisy + check("StringSplitter", CheckSeverity.OFF) + // https://github.com/google/error-prone/issues/3366 + check("CanIgnoreReturnValueSuggester", CheckSeverity.OFF) + // turn up various checks + check("WildcardImport", CheckSeverity.ERROR) + check("MissingBraces", CheckSeverity.ERROR) + check("TypeToString", CheckSeverity.ERROR) + check("SymbolToString", CheckSeverity.ERROR) + check("MultipleTopLevelClasses", CheckSeverity.ERROR) + check("ClassName", CheckSeverity.ERROR) + check("PackageLocation", CheckSeverity.ERROR) + check("UnnecessaryAnonymousClass", CheckSeverity.ERROR) + check("UnusedException", CheckSeverity.ERROR) + // To enable auto-patching, uncomment the line below, replace [CheckerName] with + // the checker(s) you want to apply patches for (comma-separated), and above, disable + // "-Werror" // errorproneArgs.addAll("-XepPatchChecks:[CheckerName]", "-XepPatchLocation:IN_PLACE") - } - } else { - // Disable Error Prone checks on JDK 8, as more recent Error Prone versions don't run on JDK 8 - // NOTE: we use disableAllChecks rather than the enabled flag because we still want to use the - // JDK 9 javac packaged with Error Prone, to work around the following bug with JDK 8 javac - // and use of the @NullMarked / @NullUnmarked annotations from jspecify: - // https://github.com/jspecify/jspecify/issues/302 - options.errorprone.disableAllChecks = true } } @@ -103,13 +94,6 @@ subprojects { project -> java.targetCompatibility = "1.8" } - // Ensure we are running on Java 8 whenever publishing to remote repos - tasks.withType(PublishToMavenRepository) { - doFirst { - assert JavaVersion.current() == JavaVersion.VERSION_1_8 : "Only publish to remote repos on JDK 1.8" - } - } - tasks.withType(Test).configureEach { maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 } diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 961df9b844..2194d2c9d1 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -20,9 +20,8 @@ import org.gradle.util.VersionNumber def oldestErrorProneVersion = "2.4.0" // Latest released Error Prone version that we've tested with def latestErrorProneVersion = "2.20.0" -// Default to using latest tested Error Prone version, except on Java 8, where 2.10.0 is the last version -// that works -def defaultErrorProneVersion = JavaVersion.current() >= JavaVersion.VERSION_11 ? latestErrorProneVersion : "2.10.0" +// Default to using latest tested Error Prone version +def defaultErrorProneVersion = latestErrorProneVersion def errorProneVersionToCompileAgainst = defaultErrorProneVersion // If the epApiVersion project property is set, compile and test against that version of Error Prone @@ -49,7 +48,7 @@ def versions = [ // The version of Error Prone that NullAway is compiled and tested against errorProneApi : errorProneVersionToCompileAgainst, support : "27.1.1", - wala : "1.5.8", + wala : "1.6.1", commonscli : "1.4", autoValue : "1.9", autoService : "1.0.1", diff --git a/guava-recent-unit-tests/build.gradle b/guava-recent-unit-tests/build.gradle index 24dbdfe76b..8efb6d0815 100644 --- a/guava-recent-unit-tests/build.gradle +++ b/guava-recent-unit-tests/build.gradle @@ -33,50 +33,44 @@ dependencies { test { maxHeapSize = "1024m" - if (!JavaVersion.current().java9Compatible) { - jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" - } else { - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] - } + // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer + jvmArgs += [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + ] } -if (JavaVersion.current() >= JavaVersion.VERSION_11) { - // Create a task to test on JDK 8 - def jdk8Test = tasks.register("testJdk8", Test) { - onlyIf { - // Only if we are using a version of Error Prone compatible with JDK 8 - deps.versions.errorProneApi == "2.4.0" - } +// Create a task to test on JDK 8 +def jdk8Test = tasks.register("testJdk8", Test) { + onlyIf { + // Only if we are using a version of Error Prone compatible with JDK 8 + deps.versions.errorProneApi == "2.4.0" + } - javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(8) - } + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } - description = "Runs the test suite on JDK 8" - group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Runs the test suite on JDK 8" + group = LifecycleBasePlugin.VERIFICATION_GROUP - // Copy inputs from normal Test task. - def testTask = tasks.getByName("test") - classpath = testTask.classpath - testClassesDirs = testTask.testClassesDirs - jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" - } + // Copy inputs from normal Test task. + def testTask = tasks.getByName("test") + classpath = testTask.classpath + testClassesDirs = testTask.testClassesDirs + jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" +} - tasks.named('check').configure { - dependsOn(jdk8Test) - } +tasks.named('check').configure { + dependsOn(jdk8Test) } diff --git a/jar-infer/jar-infer-cli/build.gradle b/jar-infer/jar-infer-cli/build.gradle index e0a5090876..ac05e287a2 100644 --- a/jar-infer/jar-infer-cli/build.gradle +++ b/jar-infer/jar-infer-cli/build.gradle @@ -4,6 +4,10 @@ plugins { id "com.github.johnrengelman.shadow" } +// JarInfer requires JDK 11+, due to its dependence on WALA +sourceCompatibility JavaVersion.VERSION_11 +targetCompatibility JavaVersion.VERSION_11 + repositories { mavenCentral() } diff --git a/jar-infer/jar-infer-lib/build.gradle b/jar-infer/jar-infer-lib/build.gradle index 1bcf4ae9ff..70b00ecced 100644 --- a/jar-infer/jar-infer-lib/build.gradle +++ b/jar-infer/jar-infer-lib/build.gradle @@ -18,6 +18,10 @@ plugins { id 'nullaway.jacoco-conventions' } +// JarInfer requires JDK 11+, due to its dependence on WALA +sourceCompatibility JavaVersion.VERSION_11 +targetCompatibility JavaVersion.VERSION_11 + repositories { mavenCentral() // uncomment if you want to use wala.dalvik or wala.scandroid @@ -43,28 +47,26 @@ dependencies { test { maxHeapSize = "1024m" - if (!JavaVersion.current().java9Compatible) { - jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" - } else { - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] - } - if (JavaVersion.current() >= JavaVersion.VERSION_11) { - dependsOn ':jar-infer:test-android-lib-jarinfer:bundleReleaseAar' - } + // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer + jvmArgs += [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + ] + dependsOn ':jar-infer:test-android-lib-jarinfer:bundleReleaseAar' +} + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED" } apply plugin: 'com.vanniktech.maven.publish' diff --git a/jar-infer/nullaway-integration-test/build.gradle b/jar-infer/nullaway-integration-test/build.gradle index 100e2d81ae..543b0c60bb 100644 --- a/jar-infer/nullaway-integration-test/build.gradle +++ b/jar-infer/nullaway-integration-test/build.gradle @@ -31,21 +31,17 @@ dependencies { test { maxHeapSize = "1024m" - if (!JavaVersion.current().java9Compatible) { - jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" - } else { - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - ] - } + // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer + jvmArgs += [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + ] } diff --git a/jmh/build.gradle b/jmh/build.gradle index edc2679494..2481e23461 100644 --- a/jmh/build.gradle +++ b/jmh/build.gradle @@ -130,7 +130,6 @@ jmh { // don't run test task on pre-JDK-11 VMs tasks.named('test') { - onlyIf { JavaVersion.current() >= JavaVersion.VERSION_11 } // pass the extra JVM args so we can compile benchmarks in unit tests jvmArgs extraJVMArgs // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer diff --git a/nullaway/build.gradle b/nullaway/build.gradle index a4df4148e7..8e3f3ce54e 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -76,10 +76,7 @@ javadoc { test { - maxHeapSize = "1024m" - if (!JavaVersion.current().java9Compatible) { - jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" - } else { + maxHeapSize = "1024m" // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", @@ -95,12 +92,10 @@ test { // Accessed by Lombok tests "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", ] - } } apply plugin: 'com.vanniktech.maven.publish' -if (JavaVersion.current() >= JavaVersion.VERSION_11) { // These --add-exports arguments are required when targeting JDK 11+ since Error Prone and NullAway access a bunch of // JDK-internal APIs that are not exposed otherwise. Since we currently target JDK 8, we do not need to pass the // arguments, as encapsulation of JDK internals is not enforced on JDK 8. In fact, the arguments cause a compiler error @@ -121,68 +116,66 @@ if (JavaVersion.current() >= JavaVersion.VERSION_11) { // ] // } - // Create a task to test on JDK 8 - def jdk8Test = tasks.register("testJdk8", Test) { - onlyIf { - // Only if we are using a version of Error Prone compatible with JDK 8 - deps.versions.errorProneApi == "2.4.0" - } +// Create a task to test on JDK 8 +def jdk8Test = tasks.register("testJdk8", Test) { + onlyIf { + // Only if we are using a version of Error Prone compatible with JDK 8 + deps.versions.errorProneApi == "2.4.0" + } - javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(8) - } + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } - description = "Runs the test suite on JDK 8" - group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Runs the test suite on JDK 8" + group = LifecycleBasePlugin.VERIFICATION_GROUP - // Copy inputs from normal Test task. - def testTask = tasks.getByName("test") - classpath = testTask.classpath - testClassesDirs = testTask.testClassesDirs - jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" - } + // Copy inputs from normal Test task. + def testTask = tasks.getByName("test") + classpath = testTask.classpath + testClassesDirs = testTask.testClassesDirs + jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" +} - tasks.named('check').configure { - dependsOn(jdk8Test) - } +tasks.named('check').configure { + dependsOn(jdk8Test) +} - // Create a task to build NullAway with NullAway checking enabled - // (For some reason, this doesn't work on Java 8) - tasks.register('buildWithNullAway', JavaCompile) { - onlyIf { - // We only do NullAway checks when compiling against the latest - // version of Error Prone (as nullability annotations on the APIs - // can change between versions) - deps.versions.errorProneApi == deps.versions.errorProneLatest - } - // Configure compilation to run with Error Prone and NullAway - source = sourceSets.main.java - classpath = sourceSets.main.compileClasspath - destinationDirectory = file("$buildDir/ignoredClasses") - def nullawayDeps = configurations.nullawayJar.asCollection() - options.annotationProcessorPath = files( - configurations.errorprone.asCollection(), - sourceSets.main.annotationProcessorPath, - nullawayDeps) - options.errorprone.enabled = true - options.errorprone { - option("NullAway:AnnotatedPackages", "com.uber,org.checkerframework.nullaway") - option("NullAway:CastToNonNullMethod", "com.uber.nullaway.NullabilityUtil.castToNonNull") - option("NullAway:CheckOptionalEmptiness") - option("NullAway:AcknowledgeRestrictiveAnnotations") - } - // Make sure the jar has already been built - dependsOn 'jar' - // Check that the NullAway jar actually exists (without this, - // Gradle will run the compilation even if the jar doesn't exist) - doFirst { - nullawayDeps.forEach { f -> - assert f.exists() - } +// Create a task to build NullAway with NullAway checking enabled +tasks.register('buildWithNullAway', JavaCompile) { + onlyIf { + // We only do NullAway checks when compiling against the latest + // version of Error Prone (as nullability annotations on the APIs + // can change between versions) + deps.versions.errorProneApi == deps.versions.errorProneLatest + } + // Configure compilation to run with Error Prone and NullAway + source = sourceSets.main.java + classpath = sourceSets.main.compileClasspath + destinationDirectory = file("$buildDir/ignoredClasses") + def nullawayDeps = configurations.nullawayJar.asCollection() + options.annotationProcessorPath = files( + configurations.errorprone.asCollection(), + sourceSets.main.annotationProcessorPath, + nullawayDeps) + options.errorprone.enabled = true + options.errorprone { + option("NullAway:AnnotatedPackages", "com.uber,org.checkerframework.nullaway") + option("NullAway:CastToNonNullMethod", "com.uber.nullaway.NullabilityUtil.castToNonNull") + option("NullAway:CheckOptionalEmptiness") + option("NullAway:AcknowledgeRestrictiveAnnotations") + } + // Make sure the jar has already been built + dependsOn 'jar' + // Check that the NullAway jar actually exists (without this, + // Gradle will run the compilation even if the jar doesn't exist) + doFirst { + nullawayDeps.forEach { f -> + assert f.exists() } } +} - project.tasks.named('check').configure { - dependsOn 'buildWithNullAway' - } +project.tasks.named('check').configure { + dependsOn 'buildWithNullAway' } diff --git a/settings.gradle b/settings.gradle index 4683c7b94c..3f7ee39b83 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,10 +25,6 @@ include ':jar-infer:nullaway-integration-test' include ':jmh' include ':guava-recent-unit-tests' include ':jdk17-unit-tests' - -// The following modules require JDK 11 and fail during Gradle configuration on JDK 8 -if (JavaVersion.current() >= JavaVersion.VERSION_11) { - include ':code-coverage-report' - include ':sample-app' - include ':jar-infer:test-android-lib-jarinfer' -} +include ':code-coverage-report' +include ':sample-app' +include ':jar-infer:test-android-lib-jarinfer' diff --git a/test-java-lib-lombok/build.gradle b/test-java-lib-lombok/build.gradle index 2065f7cdcc..011a1b9013 100644 --- a/test-java-lib-lombok/build.gradle +++ b/test-java-lib-lombok/build.gradle @@ -36,16 +36,9 @@ tasks.withType(JavaCompile) { check("NullAway", CheckSeverity.ERROR) option("NullAway:AnnotatedPackages", "com.uber") option("NullAway:UnannotatedSubPackages", "com.uber.lib.unannotated") - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - // false positive warnings, only on Java 8 - check("MissingSummary", CheckSeverity.OFF) - check("SameNameButDifferent", CheckSeverity.OFF) - } } } - if (JavaVersion.current().java9Compatible) { - // We need to fork on JDK 16+ since Lombok accesses internal compiler APIs - options.fork = true - options.forkOptions.jvmArgs += ["--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED"] - } + // We need to fork on JDK 16+ since Lombok accesses internal compiler APIs + options.fork = true + options.forkOptions.jvmArgs += ["--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED"] } From 31004d3163cd99b5fbd06a2f48a0afe8f8bd97dc Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sat, 15 Jul 2023 10:46:52 -0700 Subject: [PATCH 18/57] Switch to Spotless for formatting Java code (#780) --- .github/workflows/continuous-integration.yml | 10 +-------- build.gradle | 22 ++++++++++++++----- config/hooks/pre-commit | 2 +- .../DefinitelyDerefedParamsDriver.java | 1 + .../nullaway/ErrorProneCLIFlagsConfig.java | 1 + .../java/com/uber/nullaway/LibraryModels.java | 1 + .../uber/nullaway/dataflow/AccessPath.java | 1 + .../uber/nullaway/dataflow/NullnessStore.java | 2 ++ .../FixSerializationConfig.java | 1 + .../nullaway/fixserialization/Serializer.java | 4 ++++ .../location/AbstractSymbolLocation.java | 2 ++ .../location/MethodParameterLocation.java | 2 ++ .../fixserialization/out/ErrorInfo.java | 2 ++ .../out/FieldInitializationInfo.java | 1 + .../out/SuggestedNullableFixInfo.java | 1 + .../AbstractFieldContractHandler.java | 1 + .../StreamNullabilityPropagatorFactory.java | 1 + .../handlers/stream/MaplikeMethodRecord.java | 1 + .../stream/MaplikeToFilterInstanceRecord.java | 1 + .../handlers/stream/StreamModelBuilder.java | 1 + .../handlers/stream/StreamTypeRecord.java | 1 + sample-app/build.gradle | 6 +++++ settings.gradle | 1 - 23 files changed, 49 insertions(+), 17 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 517ebe3c5e..cfd9de052f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -44,20 +44,12 @@ jobs: with: java-version: ${{ matrix.java }} distribution: 'temurin' - - name: Build and test using Java 11 and Error Prone ${{ matrix.epVersion }} - env: - ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} - uses: gradle/gradle-build-action@v2 - with: - arguments: verGJF build - if: matrix.java == '11' - - name: Build and test using Java 17 and Error Prone ${{ matrix.epVersion }} + - name: Build and test using Java ${{ matrix.java }} and Error Prone ${{ matrix.epVersion }} env: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} uses: gradle/gradle-build-action@v2 with: arguments: build - if: matrix.java == '17' - name: Report jacoco coverage uses: gradle/gradle-build-action@v2 env: diff --git a/build.gradle b/build.gradle index e5edbcdb97..f1f0717b6c 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ buildscript { } } plugins { - id "com.github.sherter.google-java-format" version "0.9" + id "com.diffplug.spotless" version "6.19.0" id "net.ltgt.errorprone" version "3.0.1" apply false id "com.github.johnrengelman.shadow" version "8.1.0" apply false id "com.github.kt3k.coveralls" version "2.12.0" apply false @@ -103,14 +103,24 @@ subprojects { project -> google() } + // For some reason, spotless complains when applied to the jar-infer folder itself, even + // though there is no top-level :jar-infer project + if (project.name != "jar-infer") { + project.apply plugin: "com.diffplug.spotless" + spotless { + java { + googleJavaFormat() + } + } + } } -googleJavaFormat { - toolVersion = "1.14.0" - // don't enforce formatting for generated Java code under buildSrc - exclude 'buildSrc/build/**/*.java' +spotless { + predeclareDeps() +} +spotlessPredeclare { + java { googleJavaFormat('1.17.0') } } - //////////////////////////////////////////////////////////////////////// // // Google Java Format pre-commit hook installation diff --git a/config/hooks/pre-commit b/config/hooks/pre-commit index f31a3cf79c..ffdb168862 100755 --- a/config/hooks/pre-commit +++ b/config/hooks/pre-commit @@ -7,6 +7,6 @@ REPO_ROOT_DIR="$(git rev-parse --show-toplevel)" files=$((git diff --cached --name-only --diff-filter=ACMR | grep -Ei "\.java$") || true) if [ ! -z "${files}" ]; then comma_files=$(echo "$files" | paste -s -d "," -) - "${REPO_ROOT_DIR}/gradlew" goJF -DgoogleJavaFormat.include="$comma_files" &>/dev/null + "${REPO_ROOT_DIR}/gradlew" spotlessApply -Pspotless.ratchet.from=HEAD >/dev/null 2>&1 git add $(echo "$files" | paste -s -d " " -) fi diff --git a/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java b/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java index 5deb5cf229..6eb0c10c24 100644 --- a/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java +++ b/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java @@ -510,6 +510,7 @@ private static String getAstubxSignature(IMethod mtd) { + strArgTypes + ")"; } + /** * Get simple unqualified type name. * diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java index fe3cf8a3b7..6e30e4e43f 100644 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java @@ -83,6 +83,7 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig { static final String FL_JI_REGEX_MODEL_PATH = EP_FL_NAMESPACE + ":JarInferRegexStripModelJar"; static final String FL_JI_REGEX_CODE_PATH = EP_FL_NAMESPACE + ":JarInferRegexStripCodeJar"; static final String FL_ERROR_URL = EP_FL_NAMESPACE + ":ErrorURL"; + /** --- Serialization configs --- */ static final String FL_FIX_SERIALIZATION = EP_FL_NAMESPACE + ":SerializeFixMetadata"; diff --git a/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java b/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java index a69bbcd846..d801d40036 100644 --- a/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java +++ b/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java @@ -151,6 +151,7 @@ public interface LibraryModels { final class MethodRef { public final String enclosingClass; + /** * we store the method name separately to enable fast comparison with MethodSymbols. See {@link * com.uber.nullaway.handlers.LibraryModelsHandler.OptimizedLibraryModels} diff --git a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java index 4a90c1fbf5..056344a53f 100644 --- a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java +++ b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java @@ -191,6 +191,7 @@ private static AccessPath fromVanillaMethodCall( static AccessPath switchRoot(AccessPath origAP, Element newRoot) { return new AccessPath(newRoot, origAP.elements, origAP.mapGetArg); } + /** * Construct the access path given a {@code base.element} structure. * diff --git a/nullaway/src/main/java/com/uber/nullaway/dataflow/NullnessStore.java b/nullaway/src/main/java/com/uber/nullaway/dataflow/NullnessStore.java index 3d82d7763e..732ed01920 100644 --- a/nullaway/src/main/java/com/uber/nullaway/dataflow/NullnessStore.java +++ b/nullaway/src/main/java/com/uber/nullaway/dataflow/NullnessStore.java @@ -50,6 +50,7 @@ public class NullnessStore implements Store { private NullnessStore(Map contents) { this.contents = ImmutableMap.copyOf(contents); } + /** * Produce an empty store. * @@ -139,6 +140,7 @@ public AccessPath getMapGetIteratorContentsAccessPath(LocalVariableNode iterator } return null; } + /** * Gets the {@link Nullness} value of an access path. * diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java index 2e2df8faee..5ffca638ce 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java @@ -49,6 +49,7 @@ public class FixSerializationConfig { * untouched. */ public final boolean suggestEnabled; + /** * If enabled, serialized information of a fix suggest will also include the enclosing method and * class of the element involved in error. Finding enclosing elements is costly and will only be diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/Serializer.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/Serializer.java index db6d8c7250..3adb94bbb7 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/Serializer.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/Serializer.java @@ -46,10 +46,13 @@ public class Serializer { /** Path to write errors. */ private final Path errorOutputPath; + /** Path to write suggested fix metadata. */ private final Path suggestedFixesOutputPath; + /** Path to write suggested fix metadata. */ private final Path fieldInitializationOutputPath; + /** * Adapter used to serialize outputs. This adapter is capable of serializing outputs according to * the requested serilization version and maintaining backward compatibility with previous @@ -66,6 +69,7 @@ public Serializer(FixSerializationConfig config, SerializationAdapter serializat serializeVersion(outputDirectory); initializeOutputFiles(config); } + /** * Appends the string representation of the {@link SuggestedNullableFixInfo}. * diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/AbstractSymbolLocation.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/AbstractSymbolLocation.java index 75a9a455b9..319c636e48 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/AbstractSymbolLocation.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/AbstractSymbolLocation.java @@ -38,8 +38,10 @@ public abstract class AbstractSymbolLocation implements SymbolLocation { /** Element kind of the targeted symbol */ protected final ElementKind type; + /** Path of the file containing the symbol, if available. */ @Nullable protected final Path path; + /** Enclosing class of the symbol. */ protected final Symbol.ClassSymbol enclosingClass; diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/MethodParameterLocation.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/MethodParameterLocation.java index ff4f0a542e..7a1c22f9f4 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/MethodParameterLocation.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/location/MethodParameterLocation.java @@ -33,8 +33,10 @@ public class MethodParameterLocation extends AbstractSymbolLocation { /** Symbol of the targeted method. */ private final Symbol.MethodSymbol enclosingMethod; + /** Symbol of the targeted method parameter. */ private final Symbol.VarSymbol paramSymbol; + /** Index of the method parameter in the containing method's argument list. */ private final int index; diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/ErrorInfo.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/ErrorInfo.java index e6311c2d94..eb4b21fd57 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/ErrorInfo.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/ErrorInfo.java @@ -45,6 +45,7 @@ public class ErrorInfo { * target, and this field is the Symbol for that target. */ @Nullable private final Symbol nonnullTarget; + /** * In cases where {@link ErrorInfo#nonnullTarget} is {@code null}, we serialize this value at its * placeholder in the output tsv file. @@ -54,6 +55,7 @@ public class ErrorInfo { /** Offset of program point where this error is reported. */ private final int offset; + /** Path to the containing source file where this error is reported. */ @Nullable private final Path path; diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/FieldInitializationInfo.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/FieldInitializationInfo.java index dac0c6d117..1a23c81d36 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/FieldInitializationInfo.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/FieldInitializationInfo.java @@ -35,6 +35,7 @@ public class FieldInitializationInfo { /** Symbol of the initializer method. */ private final SymbolLocation initializerMethodLocation; + /** Symbol of the initialized class field. */ private final Symbol field; diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/SuggestedNullableFixInfo.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/SuggestedNullableFixInfo.java index fb271cd644..3c5c661239 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/SuggestedNullableFixInfo.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/out/SuggestedNullableFixInfo.java @@ -34,6 +34,7 @@ public class SuggestedNullableFixInfo { /** SymbolLocation of the target element in source code. */ private final SymbolLocation symbolLocation; + /** Error which will be resolved by this type change. */ private final ErrorMessage errorMessage; diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java index 20ad5cdc62..7b0c645e40 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java @@ -48,6 +48,7 @@ public abstract class AbstractFieldContractHandler extends BaseNoOpHandler { protected static final String THIS_NOTATION = "this."; + /** Simple name of the annotation in {@code String} */ protected final String annotName; diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java b/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java index 2fb0e5a9c2..0642296b8e 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java @@ -1,4 +1,5 @@ package com.uber.nullaway.handlers; + /* * Copyright (c) 2017 Uber Technologies, Inc. * diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeMethodRecord.java b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeMethodRecord.java index a2abfd0a1b..cb2a34e7e0 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeMethodRecord.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeMethodRecord.java @@ -1,4 +1,5 @@ package com.uber.nullaway.handlers.stream; + /* * Copyright (c) 2017 Uber Technologies, Inc. * diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeToFilterInstanceRecord.java b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeToFilterInstanceRecord.java index 908931cc56..74c932e33c 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeToFilterInstanceRecord.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/MaplikeToFilterInstanceRecord.java @@ -1,4 +1,5 @@ package com.uber.nullaway.handlers.stream; + /* * Copyright (c) 2017 Uber Technologies, Inc. * diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java index 2423966229..e0381832d3 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java @@ -1,4 +1,5 @@ package com.uber.nullaway.handlers.stream; + /* * Copyright (c) 2017 Uber Technologies, Inc. * diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamTypeRecord.java b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamTypeRecord.java index 4167ec9c8b..fc1caf696e 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamTypeRecord.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamTypeRecord.java @@ -1,4 +1,5 @@ package com.uber.nullaway.handlers.stream; + /* * Copyright (c) 2017 Uber Technologies, Inc. * diff --git a/sample-app/build.gradle b/sample-app/build.gradle index 7b64db306e..595a305694 100644 --- a/sample-app/build.gradle +++ b/sample-app/build.gradle @@ -72,3 +72,9 @@ dependencies { testImplementation deps.test.junit4 } + +spotless { + java { + target 'src/*/java/**/*.java' + } +} diff --git a/settings.gradle b/settings.gradle index 3f7ee39b83..b1fe598c56 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,7 +13,6 @@ include ':sample' include ':test-java-lib' include ':test-java-lib-lombok' include ':test-library-models' -include ':compile-bench' include ':jar-infer:android-jarinfer-models-sdk28' include ':jar-infer:android-jarinfer-models-sdk29' include ':jar-infer:android-jarinfer-models-sdk30' From 61abe56a86476492bb73e497400eec0d33626465 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sat, 15 Jul 2023 15:06:29 -0700 Subject: [PATCH 19/57] Fix compatibility issue when building on JDK 17 but running on JDK 8 (#779) Error Prone ran into a similar issue in https://github.com/google/error-prone/issues/3895. We re-use the same fix, and also add a CI job to test this case. We could avoid this problem by only building releases on JDK 11, but might as well fix to avoid that requirement. --- .github/workflows/continuous-integration.yml | 3 +++ nullaway/build.gradle | 7 +++++++ .../main/java/com/uber/nullaway/NullabilityUtil.java | 12 ++++++++++++ .../handlers/AbstractFieldContractHandler.java | 2 +- .../nullaway/handlers/ApacheThriftIsSetHandler.java | 3 ++- .../java/com/uber/nullaway/handlers/GrpcHandler.java | 3 ++- 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index cfd9de052f..aeb938f2ce 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,6 +16,9 @@ jobs: - os: ubuntu-latest java: 11 epVersion: 2.4.0 + - os: ubuntu-latest + java: 17 + epVersion: 2.4.0 - os: macos-latest java: 11 epVersion: 2.20.0 diff --git a/nullaway/build.gradle b/nullaway/build.gradle index 8e3f3ce54e..af27fcf69b 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -92,6 +92,13 @@ test { // Accessed by Lombok tests "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", ] + if (deps.versions.errorProneApi == "2.4.0" && JavaVersion.current() >= JavaVersion.VERSION_17) { + // This test does not pass on JDK 17 with Error Prone 2.4.0 due to a Mockito incompatibility. Skip it (the + // test passes with more recent Error Prone versions on JDK 17) + filter { + excludeTestsMatching "com.uber.nullaway.NullAwaySerializationTest.suggestNullableArgumentOnBytecodeNoFileInfo" + } + } } apply plugin: 'com.vanniktech.maven.publish' diff --git a/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java b/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java index 54a4c37710..880909213f 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java @@ -176,6 +176,18 @@ public static TreePath findEnclosingMethodOrLambdaOrInitializer(TreePath path) { return findEnclosingMethodOrLambdaOrInitializer(path, ImmutableSet.of()); } + /** + * A wrapper for {@link Symbol#getEnclosedElements} to avoid binary compatibility issues for + * covariant overrides in subtypes of {@link Symbol}. + * + *

Same as this ASTHelpers method in Error Prone: + * https://github.com/google/error-prone/blame/a1318e4b0da4347dff7508108835d77c470a7198/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java#L1148 + * TODO: delete this method and switch to ASTHelpers once we can require Error Prone 2.20.0 + */ + public static List getEnclosedElements(Symbol symbol) { + return symbol.getEnclosedElements(); + } + /** * NOTE: this method does not work for getting all annotations of parameters of methods from class * files. For that case, use {@link #getAllAnnotationsForParameter(Symbol.MethodSymbol, int)} diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java index 7b0c645e40..c458f3200a 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java @@ -222,7 +222,7 @@ protected boolean validateAnnotationSyntax( public static @Nullable VariableElement getInstanceFieldOfClass( Symbol.ClassSymbol classSymbol, String name) { Preconditions.checkNotNull(classSymbol); - for (Element member : classSymbol.getEnclosedElements()) { + for (Element member : NullabilityUtil.getEnclosedElements(classSymbol)) { if (member.getKind().isField() && !member.getModifiers().contains(Modifier.STATIC)) { if (member.getSimpleName().toString().equals(name)) { return (VariableElement) member; diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java index 2b36cdeb9a..25c4210591 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java @@ -30,6 +30,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.uber.nullaway.NullAway; +import com.uber.nullaway.NullabilityUtil; import com.uber.nullaway.Nullness; import com.uber.nullaway.dataflow.AccessPath; import com.uber.nullaway.dataflow.AccessPathNullnessPropagation; @@ -142,7 +143,7 @@ private FieldAndGetterElements getFieldAndGetterForProperty( Element getter = null; String fieldName = decapitalize(capPropName); String getterName = "get" + capPropName; - for (Symbol elem : symbol.owner.getEnclosedElements()) { + for (Symbol elem : NullabilityUtil.getEnclosedElements(symbol.owner)) { if (elem.getKind().isField() && elem.getSimpleName().toString().equals(fieldName)) { if (field != null) { throw new RuntimeException("already found field " + fieldName); diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java index 0e27408718..ed5e620db6 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java @@ -35,6 +35,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.uber.nullaway.NullAway; +import com.uber.nullaway.NullabilityUtil; import com.uber.nullaway.Nullness; import com.uber.nullaway.dataflow.AccessPath; import com.uber.nullaway.dataflow.AccessPathNullnessPropagation; @@ -122,7 +123,7 @@ public ImmutableSet onRegisterImmutableTypes() { private Symbol.MethodSymbol getGetterForMetadataSubtype( Symbol.ClassSymbol classSymbol, Types types) { // Is there a better way than iteration? - for (Symbol elem : classSymbol.getEnclosedElements()) { + for (Symbol elem : NullabilityUtil.getEnclosedElements(classSymbol)) { if (elem.getKind().equals(ElementKind.METHOD)) { Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) elem; if (grpcIsMetadataGetCall(methodSymbol, types)) { From e7ec4b966e6942c328235a41b8cdd4e90adb4e05 Mon Sep 17 00:00:00 2001 From: Md Armughanuddin <52311490+armughan11@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:37:15 -0500 Subject: [PATCH 20/57] Added GCP JMH Benchmark Workflow (#770) Adds a workflow that is triggered via a "/benchmark" comment on a pull request. Clones the main and PR branch on the GCP instance and runs the `./gradlew jmh` on each one of them, returning the results as a comment on the pull request. --------- Co-authored-by: Manu Sridharan --- .github/workflows/gcloud_ssh.sh | 11 ++++ .github/workflows/get_repo_details.sh | 20 +++++++ .github/workflows/jmh-benchmark.yml | 70 ++++++++++++++++++++++++ .github/workflows/run_gcp_benchmarks.sh | 20 +++++++ .github/workflows/run_main_benchmarks.sh | 9 +++ .github/workflows/run_pr_benchmarks.sh | 9 +++ 6 files changed, 139 insertions(+) create mode 100644 .github/workflows/gcloud_ssh.sh create mode 100644 .github/workflows/get_repo_details.sh create mode 100644 .github/workflows/jmh-benchmark.yml create mode 100644 .github/workflows/run_gcp_benchmarks.sh create mode 100644 .github/workflows/run_main_benchmarks.sh create mode 100644 .github/workflows/run_pr_benchmarks.sh diff --git a/.github/workflows/gcloud_ssh.sh b/.github/workflows/gcloud_ssh.sh new file mode 100644 index 0000000000..80d9e68795 --- /dev/null +++ b/.github/workflows/gcloud_ssh.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# This script is used to run commands on a Google Cloud instance via SSH + +# Define the variables for Google Cloud project, zone, username, and instance +PROJECT_ID="ucr-ursa-major-sridharan-lab" +ZONE="us-central1-a" +USER="root" +INSTANCE="nullway-jmh" + +gcloud compute ssh --project=$PROJECT_ID --zone=$ZONE $USER@$INSTANCE --command="$1" diff --git a/.github/workflows/get_repo_details.sh b/.github/workflows/get_repo_details.sh new file mode 100644 index 0000000000..55521665f4 --- /dev/null +++ b/.github/workflows/get_repo_details.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# This script retrieves the repository and branch details of a GitHub pull request + +# Assign command line arguments to variables +# GH_TOKEN is the GitHub authentication token +# PR_NUMBER is the number of the pull request +# REPO_NAME is the name of the repository +GH_TOKEN="$1" +PR_NUMBER="$2" +REPO_NAME="$3" + +PR_DETAILS=$(curl -s -H "Authorization: token $GH_TOKEN" "https://api.github.com/repos/$REPO_NAME/pulls/$PR_NUMBER") + +REPO_FULL_NAME=$(echo "$PR_DETAILS" | jq -r .head.repo.full_name) +BRANCH_NAME=$(echo "$PR_DETAILS" | jq -r .head.ref) + +# Export vars to GITHUB_ENV so they can be used by later scripts +echo "REPO_FULL_NAME=$REPO_FULL_NAME" >> $GITHUB_ENV +echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV diff --git a/.github/workflows/jmh-benchmark.yml b/.github/workflows/jmh-benchmark.yml new file mode 100644 index 0000000000..858a411306 --- /dev/null +++ b/.github/workflows/jmh-benchmark.yml @@ -0,0 +1,70 @@ +# This GitHub Actions workflow runs JMH benchmarks when a new comment is created on a pull request +name: Run JMH Benchmarks for Pull Request + +on: + issue_comment: # This workflow triggers when a comment is created + types: [created] + +jobs: + benchmarking: + # Only run this job if a comment on a pull request contains '/benchmark' and is a PR on the uber/NullAway repository + if: github.event.issue.pull_request && contains(github.event.comment.body, '/benchmark') && github.repository == 'uber/NullAway' + runs-on: ubuntu-latest + permissions: write-all + + steps: + - name: Add reaction + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ github.event.comment.id }} + reactions: '+1' + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set branch name + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + chmod +x ./.github/workflows/get_repo_details.sh + ./.github/workflows/get_repo_details.sh "${{ secrets.GITHUB_TOKEN }}" "${{ github.event.issue.number }}" "${{ github.repository }}" + + - id: 'auth' + name: Authenticating + uses: 'google-github-actions/auth@v1' + with: + credentials_json: '${{ secrets.GCP_SA_KEY_1 }}' + + - name: Set up Google Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Start VM + run: gcloud compute instances start nullway-jmh --zone=us-central1-a + + - name: Run benchmarks + run: | + chmod +x ./.github/workflows/run_gcp_benchmarks.sh + ./.github/workflows/run_gcp_benchmarks.sh + + - name: Cleanup + # Delete the branch directory on the Google Cloud instance + if: always() + run: | + ./.github/workflows/gcloud_ssh.sh " export BRANCH_NAME=${BRANCH_NAME} && rm -r -f $BRANCH_NAME" + + - name: Formatting Benchmark # Create a text file containing the benchmark results + run: | + (echo 'Main Branch:'; echo '```' ; cat main_text.txt; echo '```'; echo 'With This PR:'; echo '```' ; cat pr_text.txt; echo '```') > benchmark.txt + + - name: Comment Benchmark + uses: mshick/add-pr-comment@v2 + if: always() # This step is for adding the comment + with: + message-path: benchmark.txt # The path to the message file to leave as a comment + message-id: benchmark + - name: Stop VM + if: always() + run: gcloud compute instances stop nullway-jmh --zone=us-central1-a + + + diff --git a/.github/workflows/run_gcp_benchmarks.sh b/.github/workflows/run_gcp_benchmarks.sh new file mode 100644 index 0000000000..059e72c4f6 --- /dev/null +++ b/.github/workflows/run_gcp_benchmarks.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# This script is responsible for running benchmarks for a GitHub pull request and the main branch on Google Cloud Compute Engine (GCCE). + + +chmod +x ./.github/workflows/gcloud_ssh.sh +./.github/workflows/gcloud_ssh.sh "export BRANCH_NAME=${BRANCH_NAME} && mkdir $BRANCH_NAME" + +# Using gcloud compute scp to copy the bash scripts that will run the benchmarks onto the GCCE +gcloud compute scp ./.github/workflows/run_pr_benchmarks.sh root@nullway-jmh:$BRANCH_NAME/ --zone=us-central1-a +gcloud compute scp ./.github/workflows/run_main_benchmarks.sh root@nullway-jmh:$BRANCH_NAME/ --zone=us-central1-a + +# Running the benchmark script for the pull request branch and main branch on GCCE +./.github/workflows/gcloud_ssh.sh " export BRANCH_NAME=${BRANCH_NAME} && export REPO_NAME=${REPO_FULL_NAME} && chmod +x $BRANCH_NAME/run_pr_benchmarks.sh && $BRANCH_NAME/run_pr_benchmarks.sh && cd && chmod +x $BRANCH_NAME/run_main_benchmarks.sh && $BRANCH_NAME/run_main_benchmarks.sh" + +# Copying the benchmark results from GCCE back to the Github runner for the PR branch +gcloud compute scp root@nullway-jmh:$BRANCH_NAME/pr/NullAway/jmh/build/results/jmh/results.txt ./pr_text.txt --zone=us-central1-a + +# Copying the benchmark results from GCCE back to the Github runner for the main branch +gcloud compute scp root@nullway-jmh:$BRANCH_NAME/main/NullAway/jmh/build/results/jmh/results.txt ./main_text.txt --zone=us-central1-a diff --git a/.github/workflows/run_main_benchmarks.sh b/.github/workflows/run_main_benchmarks.sh new file mode 100644 index 0000000000..c28e9231aa --- /dev/null +++ b/.github/workflows/run_main_benchmarks.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd $BRANCH_NAME/ +mkdir main +cd main/ +git clone git@github.com:Uber/NullAway.git +cd NullAway/ + +./gradlew jmh diff --git a/.github/workflows/run_pr_benchmarks.sh b/.github/workflows/run_pr_benchmarks.sh new file mode 100644 index 0000000000..f711176899 --- /dev/null +++ b/.github/workflows/run_pr_benchmarks.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd $BRANCH_NAME/ +mkdir pr +cd pr/ +git clone --branch $BRANCH_NAME --single-branch git@github.com:$REPO_NAME.git NullAway +cd NullAway/ + +./gradlew jmh From 4eed5cd52de581ee083b4816fc93e12e58a7fa60 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 17 Jul 2023 20:06:11 -0700 Subject: [PATCH 21/57] Set concurrency for JMH benchmarking workflow (#784) We should only allow one benchmarking workflow to be running at any given time, since we only have one VM allocated for it. --- .github/workflows/jmh-benchmark.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/jmh-benchmark.yml b/.github/workflows/jmh-benchmark.yml index 858a411306..aed98872d7 100644 --- a/.github/workflows/jmh-benchmark.yml +++ b/.github/workflows/jmh-benchmark.yml @@ -5,6 +5,9 @@ on: issue_comment: # This workflow triggers when a comment is created types: [created] +# Only allow one instance of JMH benchmarking to be running at any given time +concurrency: all + jobs: benchmarking: # Only run this job if a comment on a pull request contains '/benchmark' and is a PR on the uber/NullAway repository From 45cc4c27835e1598d8d363f27dbafde1497a7dfc Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 18 Jul 2023 09:23:13 -0700 Subject: [PATCH 22/57] Disable daemon when running benchmarks (#786) This might reduce the noise a bit when running benchmarks (since old daemon processes won't be running). --- .github/workflows/run_main_benchmarks.sh | 2 +- .github/workflows/run_pr_benchmarks.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_main_benchmarks.sh b/.github/workflows/run_main_benchmarks.sh index c28e9231aa..27d58187aa 100644 --- a/.github/workflows/run_main_benchmarks.sh +++ b/.github/workflows/run_main_benchmarks.sh @@ -6,4 +6,4 @@ cd main/ git clone git@github.com:Uber/NullAway.git cd NullAway/ -./gradlew jmh +./gradlew jmh --no-daemon diff --git a/.github/workflows/run_pr_benchmarks.sh b/.github/workflows/run_pr_benchmarks.sh index f711176899..0627db598c 100644 --- a/.github/workflows/run_pr_benchmarks.sh +++ b/.github/workflows/run_pr_benchmarks.sh @@ -6,4 +6,4 @@ cd pr/ git clone --branch $BRANCH_NAME --single-branch git@github.com:$REPO_NAME.git NullAway cd NullAway/ -./gradlew jmh +./gradlew jmh --no-daemon From 9ece8b38298cb4d7419cf69806165e4e52529341 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 25 Jul 2023 13:37:36 -0700 Subject: [PATCH 23/57] Update to Gradle 8.2.1 (#781) We had to back out a previous Gradle upgrade, but I believe now things have been fixed up enough that we can upgrade again. One issue with the upgrade is that publishing a build will fail if signing is not functional and the version is not a `SNAPSHOT`. In particular, with our default config, `LOCAL` versions must be signed. (This was the documented behavior before this Gradle upgrade but for some reason the build wasn't failing.) So, publishing `LOCAL` builds without signing requires disabling a setting via a Gradle property or env var. We update the CI config and the docs to reflect this. I tested publishing a release to Sonatype (without pushing to Maven Central) from my laptop and it succeeded. I have not yet validated the actual artifacts. Will hold off on landing this until we're sure it's thoroughly tested. Fixes #768 --- .github/workflows/continuous-integration.yml | 3 ++- RELEASING.md | 14 ++++++++---- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 5 +++-- gradlew | 12 ++++++---- jar-infer/jar-infer-cli/build.gradle | 22 +++++++++++++------ jar-infer/jar-infer-lib/build.gradle | 6 +++-- 7 files changed, 42 insertions(+), 20 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index aeb938f2ce..a7d278d86e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -66,9 +66,10 @@ jobs: env: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} ORG_GRADLE_PROJECT_VERSION_NAME: '0.0.0.1-LOCAL' + ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: 'false' uses: gradle/gradle-build-action@v2 with: - arguments: publishToMavenLocal -x signMavenPublication -x signShadowPublication + arguments: publishToMavenLocal if: matrix.java == '11' - name: Check that Git tree is clean after build and test run: ./.buildscript/check_git_clean.sh diff --git a/RELEASING.md b/RELEASING.md index 198a83b0dd..5ed5d44719 100755 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,7 +1,13 @@ -IMPORTANT: Make sure you are using a JDK 8 JVM by checking `java -version` before any -of the steps below. If you run the steps below on a JDK 11+ JVM, that will break Java -8 support, as the released jars will only run on JDK 11. We do not target Java 8 when -building on JDK 11 since Error Prone has required Java 11 since version 2.11.0. +(For testing only) Publishing an unsigned LOCAL build +===================================================== +By default, we set `RELEASE_SIGNING_ENABLED=true` in `gradle.properties`, which means +published builds must be signed unless they are for a `SNAPSHOT` version. To publish +a non-`SNAPSHOT` build locally without signing (e.g., a `LOCAL` version), use the +following command: + +```bash +ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED=false ./gradlew publishToMavenLocal +``` (Recommended, but optional) Update JarInfer Android SDK Models ============================================================== diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 21856 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 delta 20086 zcmV)LK)Jte0)X>cxMY+-YAg;Yy- z(?%5jCbA>NR(J?f!lS7g9!V6CS|EiaGzCIvX-#;TgeIhmi8DAuWyzJsr0J%c{)zYx zbi<-_VRG72y6=zb=^fbw7tBG6nYoX--*+FQfByaBC4lqzu7Es(0*>Po6Q5c*Z{k7$ z7co_UfzN;R>5_%ZdY`s1qy1(rT+#C9T9~u&g@vnHzGmUNg?S4%EG!go6JHMCE8Nni zuPuCIqQuaDMMhH1F&N_$%M7{sctbGkFUd$OHfrl4dBE30(m18K!oy{rNG*4iIkhQM zhS5@-RD){5H$o9KlnetIDUn1x3|3{m9LoAE!-0R4KNch|L@L={z`x34CZ{91~QL*I2j0P*iz% zEvbJtY9dk#KUPG&9ei}`bUNnB)Qw{0a#wS9f0A`qXx6Sy*{8C-j&x`J2{ANr+r}d9 zXt=vJ?%^y03vArixNOaammOwol<~mEq_+3@6v^%iNTSzl& z%5;Ct9~vU6h-?Hb5p8ttbW`6>m8dZkt3usP z;<`vQLfaurJbMr{1bs{8z0vG45aFVVOYxRS=6TvR?cF{-^2EpVi1W~29!$>^ts{RV z1++7*^~NIu?Is-08Ej$S4hDaBv400G&Gb- zD2P^_CAm$uU3PJ^f&LAB@To6-(I=-bzH~-}>P-8lGyQA)^`bL&JogfQbP6~flgYU^ z_uO;7@4M&h-Dl5#d;;Jte4!(W_gc_~F=57q$?0UyhjBs2MZ6zD3YWynWgS;A5y2#; ze>CJZe4rzW4?~xhAU`?^m=@-$h8YdlG|V!DY`0qF9z$2r^{UybXI5;UUH8n@Rqkbr z(wSojSGdiqrq3D99<*`NU3YBPtX%ips>kc~qE+{~BSVj`dVI}t8};2i+$(d(XJ7@w zKW{mff05y8`UnEA1Uaz45S(x;#I?0(e>wbGV`Z6p^X9TmD~Y08Hthw|v&8@AV$iQy zb%vfve#>mw{$ZIJjDkbsgl*RAoB}Q!#q0EcWTT@=Rhqt%Z~Be;M9p-nyu#3yF5WiR z%&f!x?2WlXZhBg1(#%RlBw&cW=w^tPU5AqTMebDn8lCJkgT9koWQ-C*`Ob{Ff7(=> z5?q~~QkYd}-_iQsK`%gq*Mr|y>ds&BatvFM>z;48E@gOU~%>OAymp@(X_x(p-DMv`dMUS5fR+2Br@ z=LYsUF3a7bF>)`^EBT(~RH^aSOuNBnZ&Bsbh5dos8z?AwP%`3}o;Vbze`7zj`I_gh zONWpa+jGpAqA8RW^RDQjLP7M9vSLx$x{f$A^wMpZ1o4G}gyA$8Bu&lrYS0AnmD*=2|`q3yC8#~VmX zcHDZ*Wp@ zPN`eaiFUmIF(LshW>PAh;zP8Kt3M&S)TwSF_VkeiAH^7qri|AR23Qy|)`D)PV}wO8 z%34W66gd{d6pKqK=ZIX4$Y)8YiX6SyM)0OAOnz5hUW#IJCPXPxR2a(W6%9s&bkPmb z)HO${M07G~P$P%wS5Z%|r+?afYl@p^B(xv}&Q`57HeQDz)J=?s5> z83$0u5B>o^_=(}{ZQ4oFPKup$_nh75oIU3`XZJpO{?pF@PNQVtC`O|+oHo#hca?f3 ziudrol0Go-A1*+37j7)an_5s8pnvn{+oE+ZCm80-m!R%RkRBnCgg3FENA* zZb>rq6fH+i*S=hn-hx=OX(dr~O<^wy&r*70F|0zMy;;w_w>f`N^Vg;0Tc+?`kAYux z9O)HoQLReCxemk0RJwRqd@XX0^mF@z$V056{O)Y4 zBmx^-IOW{D?^#ZnKoJ7@uIbtg(Ez_#l@xz$-WTQk z7!J+O&D>aKIQ6oAo1!GD7w3w!)EpJ95Q;bJ4BhIY>^5=l4!*U`k9S~+>Mu-FR;$U; zJT++cfOlfsea*M*9NiD~!YG78ZbnjjkKP+iqxIC?^WB}MinTbUs!>uzT(iTOwY$>v zTO2l)hy#D$EvyS~!t=x?JxrY3z9{wdQOmi($NBBD&`UVzB zU3KWi%=ge9Ao38=6L;_zO#F=s{t1%FP{6C` z#oJ^RBJ?f1LlPsKaimC6gwz_Trs@9(wC6B&^lbnB1CN6^9BMEyNd_UsS7vAnY=8a+ zw-G`Or%D>8YOp{WK{lvDF0jnwO(^dns@xdaX%sH~#Um76AEqz%zW`860|XQR00;;G z002P%hp<%o-4Xx*fGGd~A(LTw9+TdJ41c-*zRYrOCO07&ATWq9DogeVB47eh5)uex z!At@MRJAZA(tJ;8+v7YT?1ck(nb6pNumVXucu zdAeB45W`GCpj72q;?!)FeG1R<&^$iL!*ls$4;|-uVwf+`{9-u8Lv4Jj!lx;`z(b39 zp@$anA`iX8i^X=C7|Koi>74QmP>7h6IES2j#T+ge-q16JiTHywT&-PFwJ5)YLeAkGfQROD_U8{1l_!fv8 zTNGX=*j+D%RxxZ4!$vW*iJ@H#9SV0Uyh-JAJ=DW3LVzxn10EXYZk2mf-hZs}76Cm^ zXtdWu{k&D>Z65k1pRe)-3STHd+f}|uWnH0{6yD+CKJHi95O)kHJgD%H%0ZQPs=Q0( zkjle?$FQItQF%n=i&c)Q98=j;Ij-_DbuOxus+K=H{+I zYi9@0+IH%@_1cgg9;^+R(O`J6o~f`Y5{{XA*xam#;)cqXFfCZy+_I*pt$TY%bN80c zu8rHfTicsEd%Cx`x3;yl2AZ2XJJtr6xYa?lxR|M+v8Sb_xodl%b$@F!KxM>Kw5h9e zT~~7;u)S$L_;3g-Gr2>N!9l_BNo|qnVC`U3?++QZd!qWth!L%Albc{k!MZ~qHV_S% zZ8oB@U?j{`JTgh}>Rk7HoStxVacm#v!`K0$85G)`W+^3Z21B*&`UsHn1Hr+tZpNcv z;LPmZRg*P18Pb3|ihsg}xhE3c)g2r*B5@OzKs;T=Y_Ti+2fgoz`4dT6sA4T`l5becm!NP6;A$^y?#|6k5p1#7eVBH`UoZeWt8 z7nfxyGrdt?KFI`u--wyPuzhz?_E`jT^WHvVL~P=4agt_h*fgSH zJ%j`Cm=RqwXxNnsalIoxFd(Wx-nK>%4#$VP48df988P)xn-Lx~hca$5`ja5ufYg~R zCQXsJAb~6iU4XCInQXVAIEE4DUEh@O((4n+EP8Gojo;Zw{5&ml8@X-dG(xH%Fx zp?O0e#&%vXoJwifZ4GB`DlvV)=!u3V=&!9_;VTrrQsGZ2e3imiD|}4~Imk3`f?>)h zD*xmxKww|k)c0ob92eJ=!g`H*EZ8^E6UuWMezKB;3wZicYdDXvT5EV9g0^Od9y69N zYYz9x;(xWMEJtn2)u9v*8m4t9+HROb5tw*R242({2!UFywFi97kjP0~#ac$Q`yV@h zL(KwqW@ofnkA{K*Yi0&kriLWqiVQ@z^eRkdWt@;*TbtZ~$*yH8LCNWhhji2ENonHL z+}6q#TWT&W+aPrWm!h43G~TT1WH>O*D9d?+M}NRJ8{vM@4i;DoyO%0SZ>3=FNX+WG z@*}ZK;F@UPkZ8sF`VcM)mYjY%Q%%nNmg*%YSM7A3->mU1e5=Nv;oF#| z=YJ^Y8sE;hY5ZCGP~$tq>`wk18qq1l(CAkBj7GQ7wHn<{FlGG|$O}H9Ne9FHd5u0x zcW87s-JsF4bO=5*WSS#2BO><-b837S-;LZPps_R<`kspNNpGc$DNmbhOSh`*@uqx_gg@6vl3 z{f0h-jRnKi(l0gs5+Bg`%ls9Mzbb~u`3a4m)Y15eY4q0!>W36gXgtb?H9o@6F_FS!8b8l)j2DD<=YK+~Mt|f^ zjGD%HFeGETBuN=S|3EY{?AQ23J}QQn5QY8$9Xjc+^PAS4{xY%i4dl_`Sh?Wso5(|% zF|P5qgy(%*3@-~6922uw#PBKxocIpBaf%#`q?cy43pERbGd8EUNQV4o#6O}(V}@VQ zq+ijm(M3yl()cz0F4O)kYq~mGJAc;sdtySe_1)c@{4(SFvs1o5f+pUthoSVC(T4`X zpU&!nNV2N>amc?;dZxc0z8($khzpz=|E7?Ed@qj0f>5bHo+22iLHG-Dk!bJ|yTG_D zCNz4Vey;KN`1=~a&Tk<33!!U7DAzQ~@}co@eiMF{E(!>k3Hev!xA<+_e}6O2E=Zjy zvWh}?#4@{0C$lst{Eo&y;2%PNJ>4zUD>eQR|2PXTd31V?dcouu(7iQ#heI_xg5my} zwYsUBcycsZ&m-w$Be=k{SZhRMB%V4KWcpnWy~g}|g67Z^b_>S*;fU#v#YaXWAk*&$ zY#;Kjf5YZ>=-q2&ks3RTSbyoIG#j|dt~SD@f1>fbBCcimsM&276U|=PMPiz@98N=& zNUX-HT^`J`z`86aCoPQCE{yrh7RKu3f8`ldjo;&+B0J?sdaY|yz39nP<-{fE)=+T@ zvkC#EPp?~H0_ztgGjcuBlHqG-za-X#ZVsx;-3VnSv~UJ`W=?3!a(|ufp=e}}^&Fj< zvz^HHJ^8wZfCz(3re~LB*50gVuqq$b0R#O)Nfs7B^;xQtIBGDZpwcY#q~b)gI;6)s zjJ>9ODQrax9hQsib|lqlxxwp313s>rsjyQ4v)g&ci0GPEmQ6l78Itq{_x~!0!R{YJ%P=84F!%Ms}a4j6bOuiH0 z)UCKXDqoeZvTcoGS)3KJa;p&aQRqCm&h~!`Dk3ZxYfX1B2=iPIAV^X9N4P3(Nmk+j z>PtMgp4E zx!J8${RI3RL4Pz6e=e*!#dgKiu98ZmQknTMYoKq)5T!`Le0?NlTC+_kkM>L#))uMg z+_)i%Z~>+*QGIY&zOF`2$Tf5PI`sc`OmOW+c<7kKk`P zCIw6{k=9XJeS`{`5>$lAG^SVZzC1vhwE*-86+39lf$>)=58_Uz#M-B_U^*oT?K2nEwfiu`L($7LE*CG{Nj^r7R5=dQP(of6GfbfFtDqmZJzSbV*4y8~;wo=d#&)TC(+w&DLvx zE8=hE6{F;>s60f|E2)aXgEzOee>-c$=4%tCq;IzOguXvW&d(sh_n+Go|k*kyC>KIKMqj|j*JWBHqlYc)g zR*_`kT$)P(Dx)6BzD2OyX8&>NlzDRtNO`f$7oUS z7%lDv;j&RG@9?=3R8i-wsPrKLRDZ&JRdtHb>8r<+rf3a0Z<SMI6Izi_gpzb?`_3A zK^uI^C~Z7iH!b&tin!1>?da3ce-Rz04`_|}@_cCf13pE5EqAyVeok~D^nMY1O$SyV z&7%Qik|C->jaf=Nk(G8KdVfRk_hIUWrMA%s?VyX{g;8L|Xg8U3CB;!|_QJFGAr3F4 zC+RYH>*e%q`V{Ezr=OrQy^qTDx4`-ZD$)mZHGN3efRp`PLDz9DUC+ztMqWub@hZBR z&qieu9&|T6Ma0#Mh!_Qw<8%}`Rt4n?=^Kccd~gz`Z(`;FKfCB#pnq5Zu7>Dk%nBjL zcMuD&z{ftMT6z^LMfg?HBQdi*5;Myq^SA-|I;~-V2P>Diq3LRJIO@-c4=xv4Gk(qOd*ojCVM<`5%#S-hZ(s3}a%m4;LrYe=QB2Kp%+ zBoC)o($8di7koomzoP3)HqfK^L`GdG!SC@b)=gp#B6I z9aY`|u^x0{-GA%w4g~QBeE8_8W%SRo`J9I&^+C#aP<20|2CM7B=5LU1d+>q$z`2Wt}jWgU2=VCYVG!3R;)@#!N!b0ufU%f=!Lrldfh%( zpjYv^1HEcxptsmvg+Zwf9Hz?;rkVgNnqG%=Z+~EZ9QJqTy2IAU zG)Vq0(!%qQ-2YW-q#6b2-=s!X(`Nd2*(|lA75fj&T)1aB{U>JbVwL`j{@XTeExsZm zr9^3l+!DAjqs?*AZzbV8`X4#|FDeuv1v%lu_=ihN@dmgGEmT-0#rpfr>?XULB_-zb$$RtO``y3q@AuxG`|m>!16YNB&Or+-9)D6i zyqE`@hcpk4j8kR^xOcU4BPP=Xk z%+IfEy+7mVmbu3^CYemliSFs{Ag0ThEM}R9^+d*2nNDC?r)4Li30sXDT7PudR#Pd> zj`n(UTOld&hfCG;45+KttnJAp1!_EHhH56FJw#w8h#}|<=;@^^1s0dn(qX0@)i9WD zqi*WXW`R2*GZ7XCq1-C(>m@ri-Rfm~1^jJRoT5IjQ#ZA)OtN3IcdPxJhnh}K%U;#i zd7azzvzG!&hiQ^{LrteOT7Ofc9F2{0GwTCp@Bd;lZhtHe$7bTK>T%TA z&<-`_vPk%pgi{h8AZsU0PfIv0aCttMZboj~!uo0US zZ06y4xIw`dY*o;Pc7FxiktJD$suYZ2m%zeOh;Gg3MqINMjN^=ghj5ni+GGtW_zbVN zo}+28~CP#M-+Suk1F^!CM0}E!FTaJ zfk0DAwH*cD#}CM;JOD^{JuCd7gdZvRF?0O{Kb7z^1wY3x6n~t@FBSX>k16;yexu;G ztp9gN5N6snLvxs&@JA81>|-S8ar|Dw9~Ar%Pbl~k{;c3H_$v{Y0VW};&B_?@ded_1 zGv=*&s-6m{-mz#BO#-2AO-);7D@pwuJN$S2L&1}n5U87qUU$ZH^rSW^lw(CM?BpOX zo~c+#dnslFu78{nGy>OMVu|LHo@-sywu{Swn$FZlhoe#Bvg~}6T#E{#RCjTugxZ*` zsg7H_0xe}bZP^|sj-rS!*ta#QZWk2uUek8HDEr<&Z)N4#v5mP%;;lQ9pHGNqkrBv30tQ zBgGGdWq+k_qr6+IpEaQ6bKGeq5-wPaz@58bW&(HkqQWQ>hLxByZ4H~YarUNlNbXr07Ay{3_Pa%Nj&AFK# zMD9#)B6p@|kvr47iGCyc=zlw&Puv~!wI3CbXuh^#`6xugicwTFG>*d85cZG4-w+Or zB7e{j4vr$&Fb3%XqO8CRXbciRNYe{xtff7YfKGG)?iXvK2L(R~=#2FEh7e#q6*XrO z>T7rq6v}z*HTOdDpMQYgw~5yNaV+XuJc6MaO;|i>LaRUQ&xsP_ zxH3mta}^OZW^pwUWl`@PK8MAJ{b#Vmhp^w1c1>^I;&Ci(bx)rw&>9E_&fr>s5p3eG z>jXV7L#@FQB@t2*4w9RmH91A$;5e4`o_J;=CpnNsLl(FVq-P-uuUY9WeYs_Gk-`GDRcsL{ynd1(9LU?@qj)T*gs^jNN|w0R(s%7RR#QIK zu#Ppi?x0Hb;TIgO+bi)A}Lbzqu%dg>uxNeaSI}Z-xfk|C*ik;Ftv|R+fQgp z(9SS+;5gyyc0$-m+=SEU#-l{_7=L!t6Sx;oVV_utKCvGA#a0{;+i;6Gh(jU?jWnsk zM!&cpF>wxY@d!Pw&tpJ54qZHnLGdpPiDzI`1Yo)NJ`9;OE+Q?v3C$-7^?Rt&{3L%l z_R?37q#Yog`v~KN@LU~4#1rVFQ-NV|AJy3b;yo_z!Oc{iK0GDra0?ESlz;PB?Me>d zMZ~2Ly?1tqqM_1GGvo`{Cm|%E`)SOrx&dnKlFP7000@2PpCG3T~8B16o%hvw_Up3LapEU zRi#K<3R?=A5XBfth$clZYMS8Punc8kX~}M@{v&U=&_trq#7i&yQN}Z~+Yd?&G)>RU z*`4<}@7bB%KYxGx0#LzY1922(m`_kB$*>^PMIB{1E*VImqGOrCXn1_b6)#(df?=|{ z&);*)X;~Y8jw_zfg^oXq6tWVlZDd{Wf>q^*v!9 zsI)1(b(j0T@EO+fXVryj%WGJH3G0K$kB)?Ag_PVNjp}IYtsN>dRdt%;b?0zfcyIZ( zt6a5g?$){I$UMBLA9hXae#f>cVOY(d?r@yy@uyCI(`i3rm>GhMXsS+T+j|aG9H)Ze z_;ukqyN=jlh~^9L7*e%1w+}Y?QP`BhRVTTna+#r&zc5^~A|K0rKt%#p#{di;jV@?e zg4V_cojyUyZdF0Bu(7L9UUtSa~E`E z;s$Pi%J7IG-S_rpdtcaXhUx!f)F#!Dr8g=;FJ1&0>40V#X@|xH<>U@@LMdB;Ml-

eqt8i5@4B6x;LXnLi6iyKWHxb!jNZ6t*O5+rjpD50s zKr5C`5dG4_WT*sK2^w2Kf-Pc_SqhmAWSGW(EkMmAoT-^?HbOso@?n`5G|GFa;#X+D zp=+O@MUUx1biiw6z-uZ5-=%*Hk>qohp1?h-8t|-;7bDxT+@*c1Q&e=Pt2Ad_Kv7LzWk4u4q*d{oudKPR)i$?(_$0fx;{ z31lVg%LJhz1PO+Kgr$MHyd*El$SiT@4J6Xqiq^fgt+g%I-L`bGt*9hms%W*iTWfc% z*jj5_>(*6mRlfhZZ)P${9s>S8NbY^_E@%CpbI-eZ{DXV%C!#t0uAg@DSZNBS87Iwn zX(r^O34cg4QC=tcse~tMJjF%)iwFGV=czJxk~Bd-pUkI7bE==Gagm>{=jqbS(74!7 zW4T1umug(*<8nXU%@rEY^wUJH^wT{&OGakP(;Ru4tMNQP1$nZc=ko$T)$l@jT_nw7 zX+p#3AXoW#iN>eN^in^4l9$Qg$9!BZ{c=Co@P7&)*UHRFk^Xd9xXMrCxz5L{r9VUC zdOt1XH5xbgX))MgaIG}!q&ZXQte35gKHeZB8#Ugf@n#=?+{aBau*FZyd8;&^@Y4%? zmd0mme2zSx>!&l=@Y8B;*0{w_YlZr1ZuPM#FKyC+6;n(DN$Zbf=txO5s;M>Rx@Hb;c=DNY;K<*vb|iGOBS zOC*&HZ#P$lBW86=iO}6O+3p z-fA+9tPjV`hE%lKOl&YPDISQo7?DjzA}sIeLATWrhUX?ba<+X9>HRGU)3D4H;iSd1 z*inPcb`OWPh1*jJ!wSb^RVB95!+-Hgv5B~%C3l{wtWMn7gF0!flnzONTtLndKltz9kA7$YJY1JSmteA z&gAMayD%PxZ`%~ZyeNWXicKJCwxkkatGjXy&wm_|T zvcVAG<+qvFlF zn%OMxQWCm_I5u36$(rMb3X)E*bDT*nDLb0z5}sey_&PwnCEnSclj)dd_FE=513D{Y zLltIsDKNSs;sU>+Tr+LftXWL$jz?wYT>bw{c6nX1q?s+voPH_hi4@KUc18+EF4;OO zLukGbT5{rqa)0Zv46OynrghA0m7%pzXjPmDtu6hV7s=)`k7M(RX6n4Ix&2%g3zw=B zMnY~l4oj1oJ?n(k#cqG2Iw8`rnqD_kmg)2qJ+0GU=~bN`pa*sO1HGivA$mlo&(ftj zT}+qg)Jywyx{AL7#wS$u%BHXCbOl|h(`9tI#$VHUH-BHR^Vj(Xjc=5fn{>XJZ_)Wy zz6CYthoF775W7w1+xZ(%Ay-2riN;mc6)2o^{w9A5nId;KCx}4-y7C_Ww$9(-@9O+L z{)*0b@b{6hoXZ%Ef1vXZ`A(hp@?ARL&G$&NPv;)KLFZn2k?GvimAt{+QX@E{C^;k8 z8aI={Sby9KqT;oTa4cxVy0I*Q<2f!laAvSJeO3ppc(4s8LD1~Nu^#CT&d#puo{1`@ zU%0i+=V4gq{d_>@d$~{NAMssGXB}0DH18x>#Dmd~CV|fP@%2w?2uJgnE6P*viq};Jd%73PCi^}4`c$?GKV4DHwww_!RD~dG% z+>UFaMt88o*lq@!O*0m>jA*A65y2f{t7B$SOnqG>qCu;}u+W&n8IIo$GlHFoI5t|{ zLFmyfyljY8q#-&pJr9EhrGg5ElTbS$)`QDiWlPXVK(uro1iBQv^!8v|A|8c(tVAj) zo`0~5xtXF^Ft)_nb7$Wz5(@7 zKfczdVpce6X6qOUx<^q}^k?7dM8fEn6s+@O^rFs>^AkEhDNj$qfpl16!rvT@rQ=u# zJdG;iXcgE1bZ5RQJS`p3I7%kh!uj)c{(mWA4OE`d`DgsB&OaBxk8`dJnF0=nw>_ux zFZh>2=vUJGdVo^d%2DUvh(+9DPl>YMW|j@C0Cj#&mQDTeB2eS!b^aZ{pz~qLo#Th* zn6wE>YvQR$tBM?y$%w&BogD1!FO-9A@kA&n`R7HAf3Nc%btIWvZeQRA0%{(mFC ztnr_8eue+6^I!O{nBHO6mpZ@7f7AIjejO#PV;QB>Tl|L3Z;B(|;=gAsJfwEg`5%(I z|Ecr8_+_2{E$*H?*codF6oV6T({b*U_~Ad|mbZ0!m;S5sJN#Xs@#u#D(~RS+hfscJ zrX{FRZ_YXKkNS-7mCLeTWt#LMAAi!hc*!j*b0}@-NW^#8pOGU5RyC;ghs4PMhviha zOkW({ zo%E6JW(o$0t-hh)jL_^~c~O>@adF4zWI?YNb5t|Yxi`b)3c@taNI0)MA0z=#h( ztFi{d3AhGa7};e56dVQxl?JgEXgJ&98lvPF*FI-TJv8_Wb>?nS=cAqV^`)Q719shTp zI~l$J)^)*?LoAl{vs2hFkH;;9fYG@I1zDqIBrNg3NGbd3jZ_T9L1MLmk*UtNJN;3T z)n`un+j7wI4E#*SQz6Y_hQ+0DP9!XiI#QUHvxqtCucG(#}UoO8^ z+E-em0l^_^gp(^WH7@LFv;tA59{c_gEJi_?8Y32up+=3^(u^y1xOh>~kQ7UcxVet% zzh%;P7-0+b2Mz-jn>m?$d`^NIcpE#x`I18Tjp?6C-k%uqyktP`} zXF*Mek!VDOm`K(t8r%_8Bot=BJdjSHIM-rT+vq!j2RIr4I3kJWj88mw3(Sg(ca~aZnJON1O2cZa)VZxYp4Mi zbw|U$X9vzElTN0cR6;}^3eczNGsJWe-tuS{70AT}o(tp_L-m)@<*L7eu0%fuYpJ~5Q)rozbCu04E9X-BGF=CWWc^p@tDxeCR$qgaT-aMmyXgjK zHVJy(NH<~CDKwdG#*>$B!EQHp-ikf=rnmLsyD{d&%yk75_ETU@A5Cm3>!nEzt_AMV zqv%JzkbkGpop&=$t_XN~Xz~Jgq5GyIZGTcyCX5pR z&2P~jn6eYk--e+z+5!-L2eab3>$0x7j=l>+BdkE>dvu4A{Zjfq{QxpI(CPF;P;rC% zQh&OW_JZDB*fWo`BOvS5Xs1TwHM(1)dsw4=gm12?D>drzeL$n}u2GQRqv;yeyi3Ce zxUv_Jv;*Y`XnI8cQh3nQP;r1}Fz_(8pxAvMl{C4^8~0P`ek!{YGv&xvH)E<|@RaKS z&CDY}RM5PiDj&h3S%VhI#Oynj4C0IFlz)%*TIl^{XnY+70n1WAvWmVAt#6>Ufa19T z;WbKMC1KWPZ3?EWo({mad7yGG-AjG2+3B$Sk1*?|`!KI7%gWsSm=oKLB-eWs)ac;E z_h=fdMC|Mkuc3!kVw5Kd(meI_6BW#Nsnp5;1HoK(AEHOn8XiF}k-NH& z79-X|l8mZ$(-L$`8ld&u3UT563xDhoEcDz)CrkLvu|w~)-K3WXv;(v>kL-{;Tt3*P z2SEQ`m>DSyj+g*9%!ct7(?Q5|AHwP3tV?IpXa#-^E9cRp^cWO?WLZU5r`_zsPH}Et z)}{8y{TLC2%gA*EsY}a*!$FN6#}C6qQw83x>;x7+p_1s6_ovM0Q&cU>PyO_oq+3Rnh9#psE{iHy=TF#gMX&b&jC&!IIRcm zUji*5I)i=%`W{gB(r>`Q4Z1p>o>O3dD2suIR4}6kM*4eRfxQr^@poCU*OJe71blrN zALFM+qvaaCpwVg#X@uURsTdO=xN{egEZT@An}-KM_Au13GvXO|lb9M)VC<*nyQrmy zTI2=dw{md5n{?ygrRm+665%$atVsq zeu_iAkXK$hkxwKh&IkG*@1q1lC)pJ6_L9|0sgPEx>WA$Ct(SI4Y;^6R;US-T3iuB0 zIr3F-?3OYjUw<9~`AE8M;Jl4ukShj6N}rDdei!;(sML04bJcp}9HReW_I>0koTf+? zMVv zaLQ}2&q9;2I6wB5=>&70$2D8vSbeM}PWAyPE!~aGsCi<6rb|xG9tM z?4AF?PH{~&X%HRprP15((;|(c!1WO@&kY5cv}ZJWhfpK*yhHi_r94$6WRHlAVD~Ql zH+@9hh%td_(P%eHxrQ>>c5>4K_t$Aed4($AF4%MAb%1dy5>HqEF{f%$E+j8cpwS$l z$vhe5$A1)>##3_+@uIB8MX>%4l*UB}(Kpp0ZWm218i8~9eN{-^rF74GPl}y=?@=N5 z3q8t5AHZ{VY}I0yoE}E1A-W*D2Ab+o_$((oF3CEk&#cS&VI{ARC10DAr zCrD=);BF&g9SkmT^})FK9C7V+q-1{_`LdLJpk&u6K3LbTQ+x;?N!=MI-U>b=cx0N- zKquxov-xl*3Y#2+@F>KlgE82d_EC&Md5CVT=P~#|5Tv{C!LSn_41SeG^bn^v~a4 zzX4zc_CgSbCjvYTz_TEX!iE5w0obC;=QLz#*cRXg%{w&g(vTA%FF=8SSko-q)T%9i zTQhV5qos4@vm)!NEgzMOn*<`A^4K%G!FR z)g&N%(Jovkx^jC*@x)8R{B)=_RikASAVmWHElt<#EP=sv=9qvlXVg#>E@`^D-)dG> z^H8Za93bOVX&fu2MxVV+pM9oT1TrOm!>r47Q(c^SIMm-B#|K#>>kOl@Z;hRdiXr?XSmvWM(xipsu|T}-ybFL!>Pdw=(K&-ah>`JVH7&ht6v zuXCRBKJQ`}>Tn1pD~mFFxV4m(i}|u9W0o8K^W0pc`*Kr-$O}>xH%HxQk!xd9Om#O{ z>L85MJa8(A`%UnNK7VhykK-*G!|$f}Tpp+v$=}zjUW7?1{Zo zcDhhJ3xTKhfmEx1Vk_d6ea(Pb!1a8ZOw6pYQ~I3<2AsUEZOTQ%2S4MTijA`xp6)!b zobF3ylF(1qI$ZqPJ`u^F@PspM(IB?bNh*#mBC(Y3S4gdnfrfPy7mEW*wQF!G4%uRN z0mj~KrxnGZqiZm<)OB)PE422(hqYvRt(YZfyNP*0U#1NZ(D@Zm7<9kmy`dbT8IDTHA1X;m^UD5>A{&9-fo`s<8v7v01x&i=PD14(7J?2)Oz!qDTnt@!scSlnz+8bAQg zu64qoLhuRV4RfgyZp8bXcUIsm!0(W73ZKUlc4<1jM;Kd_CUnRn0x)eFWZEJggZU}JukgeUej<&@&S@wr>nhFmocKl5OTSWt^iUkw_xlcP;O_<-xjYD6?Nz{snZ~ zQz*9x!TPMcW$UPQ>!_%9E9~M1=^h>OWb`oppmnBc=3oH9H#T(Av)bh+CKrM{@6>;r7WVMw;p7;NCfY@`JMlD^+MuWotk?)p#5MCAjPaqXlbOSQ74+2mQJV4Is#iHPDYs{zrNfAc zgsk&Wc}#DPlR>>;;{bcY*UkO*-=g%Q-k>snC`#;^DN0ao!OStQ&?BFE2vVcyFX-T& zABL}C09*F^+GI+PYwAf58zMJ7E0f7?;Os=Q<^7ZTdm&#%rd$cq+Z^+QClW|Uo8WMx zG4RxtrtilIFRWwIv2tGdZsP^nDeJ?dvLC7gFvw15j^%=*SN?Nm(+(d&u^RQ48jL%7 zq1zu#K&>S=!Xb6#OQK`&n8{_jNA3&NE1us{4p&PXflZbA4oP3tmr2}iD1pyR=|to5 zF?wi&w4vLzw;m+qz`46ugph9wHEe8?asmw)zB{88)%6YaExoM*-z0wI^ymX45C!#S zm{&WJ{Z6#82~~?2ZczG4xg+Bs(?gerNt=obds;wMGMZ!4{Fl)YHRhfvSvT}4@58>O z-&(`U;(PXd2!nbRUBqmnf~k1*?$Gg2?<>RiWp4VrEhsCgS49Ym7LmzrR4Dkao!i|) zl0Uw%a6Wy-d@zn5*s@}#(BA0IV?u^uLSIHW2z?Wh;c-SAd-|L5gNMTzKl=2a3HOoe?` z82K#d7doV`E~1_*3L<%u!XeBd!qv!`25D>AUZ}4i4{1`nkyd{uH05TgH~dL9dQ( z=^x$}QH0B-d^zL3VvqmgdD1<<`S}{~=cOv~w|K+qr6sA#lE_lz=L6+RVpC4!8;i?2{n@yJPsMzCBzD4L%h{Ou@Fp&oL5(B~z8b-Chmhpe z`?2Uup5eaU#_IV{1+}PkcS9{nwcH(ARL=;HnoV`xXNOq1lp)UQ;$HlH4i#g11OHFT zx?(U?>)Aunl9l*~PS}jt0K1WkQDA3>-^}=8ni6?p-bU>HW~hJ!(Q|vf<>4FnYVw=t z0Iyc>49my;dD|g9mtW#AtqmhL#*xlmVOh~pE468a3Ww}`rWf^TzxhApUVY4DRRw-o zGpHp~$7{3>4<-J$NJ*cv3dDme5JbYcOrgf0mnlx;K58$$EMZ5AZz1PS3^9@>D z$84GM2$D3dytG(8gr|TFv@gEmeHL9xU{BNZapi@5PI%5#@3k9%;Yqbu4ZFF_h8N56 zP(Yo5#7Mh79p%zGaA;?CkLf_nDhz;2;tQ{7ywsbbJQR{jHIZ@r@|mHNQ6%D4*>*3Y z-g$$q*;IgsiBvqQw8j`LOLf-=!t%P)H_Cy$;EKg5$86ieeW`csd!8u9 z6+x9^-GbFw%`D_GaVbz~|9#MvkaBzP*TkS)Vh}s}f?{;+%MW6tC9%lYAI6%#j5Qf; z$OP=?k*2HN5?tnEURUYYJ2xz3TyAncRJxTF8s+(HRtO@5<0E)#(u7qCo}E}>-9rq# zOJ4kSZoV;NzLhD}0rqVe#)XBYby{uos6aYvqkPqJ%X;2n>f?Vu^5!C8+56wQb`!~v zxivGMZa*po2YRG^cePc9f;A!}%+?e znN{YLUPt0JOetV9p8X)$1ee*#ujAS=7Z0`GJCkHl@4##Bo!wp{q+p{K=OKBqA{Diw zEdn%vF31Mn^to6uC}KAECQE8ZUAS5-)}_k%3B0OK6`$6j^2;WK2f_OMz3vSU!&_!1 z-=mYNb1Y?|%khp;(3hF6)&>qz5k!9W+zIU z&mkbp1rB{_wFp3QbmE{yDUqO_>(7o$y zE+XWOL#0xVw4nO5OB|PWM!M$9C8{ELdl^09rc|M&^z{`C*5+p_L}t0`+m38z>0ND269%(-HKYDlfZ5Su?c0UeSO*UX1_@>iVtaE$d;kSL=qd2|1LVx%y!m? z+i7)5=WP#XPFrgjjtYN3?=iUc zND%u6maT@{cNvV!=j{-~3YwBA|p*$mbR3->U%mL!|Q$_V+}$iYcHGHSOEe zd/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/jar-infer/jar-infer-cli/build.gradle b/jar-infer/jar-infer-cli/build.gradle index ac05e287a2..2d737e22b8 100644 --- a/jar-infer/jar-infer-cli/build.gradle +++ b/jar-infer/jar-infer-cli/build.gradle @@ -5,8 +5,10 @@ plugins { } // JarInfer requires JDK 11+, due to its dependence on WALA -sourceCompatibility JavaVersion.VERSION_11 -targetCompatibility JavaVersion.VERSION_11 +tasks.withType(JavaCompile) { + java.sourceCompatibility = JavaVersion.VERSION_11 + java.targetCompatibility = JavaVersion.VERSION_11 +} repositories { mavenCentral() @@ -119,17 +121,23 @@ publishing { dependsOn 'javaSourcesJar' dependsOn 'simpleJavadocJar' } - project.tasks.named('signShadowPublication').configure { - dependsOn 'sourcesJar' - dependsOn 'simpleJavadocJar' + if (project.tasks.findByName('signShadowPublication')) { + project.tasks.named('signShadowPublication').configure { + dependsOn 'sourcesJar' + dependsOn 'simpleJavadocJar' + } } project.tasks.named('publishShadowPublicationToMavenCentralRepository').configure { - dependsOn 'signMavenPublication' + if (project.tasks.findByName('signMavenPublication')) { + dependsOn 'signMavenPublication' + } } project.tasks.named('publishShadowPublicationToMavenLocal').configure { dependsOn 'sourcesJar' dependsOn 'simpleJavadocJar' - dependsOn 'signMavenPublication' + if (project.tasks.findByName('signMavenPublication')) { + dependsOn 'signMavenPublication' + } } } } diff --git a/jar-infer/jar-infer-lib/build.gradle b/jar-infer/jar-infer-lib/build.gradle index 70b00ecced..9327cc5152 100644 --- a/jar-infer/jar-infer-lib/build.gradle +++ b/jar-infer/jar-infer-lib/build.gradle @@ -19,8 +19,10 @@ plugins { } // JarInfer requires JDK 11+, due to its dependence on WALA -sourceCompatibility JavaVersion.VERSION_11 -targetCompatibility JavaVersion.VERSION_11 +tasks.withType(JavaCompile) { + java.sourceCompatibility = JavaVersion.VERSION_11 + java.targetCompatibility = JavaVersion.VERSION_11 +} repositories { mavenCentral() From 48772af91a0fa9d6d1dc0dd825beb5cbcedf0e0c Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Wed, 26 Jul 2023 10:20:06 -0700 Subject: [PATCH 24/57] JSpecify: avoid crashes when encountering raw types (#792) We check for the presence of a raw type and (for now) bail out of any checking. Adding further support for JSpecify checking of raw types is a low priority (not even sure anything needs to be done). This fixes the initial crash from #791, though we may want to leave that issue open for now until we split off issues for the other crashes as needed. --- .../com/uber/nullaway/GenericsChecks.java | 7 +++- .../NullAwayJSpecifyGenericsTests.java | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index e66430e114..4ca3d3ebd2 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -214,7 +214,12 @@ private Type getTreeType(Tree tree) { } return typeWithPreservedAnnotations(paramTypedTree); } else { - return ASTHelpers.getType(tree); + Type result = ASTHelpers.getType(tree); + if (result != null && result.isRaw()) { + // bail out of any checking involving raw types for now + return null; + } + return result; } } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index 2cac6729cc..5040606433 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -718,6 +718,41 @@ public void varargsParameter() { .doTest(); } + /** + * Currently this test is solely to ensure NullAway does not crash in the presence of raw types. + * Further study of the JSpecify documents is needed to determine whether any errors should be + * reported for these cases. + */ + @Test + public void rawTypes() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " static class NonNullTypeParam {}", + " static class NullableTypeParam {}", + " static void rawLocals() {", + " NonNullTypeParam t1 = new NonNullTypeParam();", + " NullableTypeParam<@Nullable String> t2 = new NullableTypeParam();", + " NonNullTypeParam t3 = new NonNullTypeParam();", + " NullableTypeParam t4 = new NullableTypeParam<@Nullable String>();", + " NonNullTypeParam t5 = new NonNullTypeParam();", + " NullableTypeParam t6 = new NullableTypeParam();", + " }", + " static void rawConditionalExpression(boolean b, NullableTypeParam<@Nullable String> p) {", + " NullableTypeParam<@Nullable String> t = b ? new NullableTypeParam() : p;", + " }", + " static void doNothing(NullableTypeParam<@Nullable String> p) { }", + " static void rawParameterPassing() { doNothing(new NullableTypeParam()); }", + " static NullableTypeParam<@Nullable String> rawReturn() {", + " return new NullableTypeParam();", + "}", + "}") + .doTest(); + } + private CompilationTestHelper makeHelper() { return makeTestHelperWithArgs( Arrays.asList( From 8e4a36a43bfb253ebe7e6109df4a5470e92d2129 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 31 Jul 2023 11:24:05 -0700 Subject: [PATCH 25/57] Fix JDK compatibility issue in LombokHandler and introduce AstHelpersBackports (#795) Fixes #794. Since the JDK interface change was made for JDK 17, I think even if NullAway was built on and targeted JDK 11, we would still have this problem. I added some hacky unit test coverage for this code so we'd have a way to test for regressions in the future. (The new unit test fails the `:nullaway:testJdk8` task without this change.) We missed this problem earlier since we suppressed a warning from `AstHelpersSuggestions`. Even though we cannot accept some of the suggestions from that check (since we may not require a recent enough version of Error Prone), the issues it flags can be serious. So, this PR removes all other suppressions of that checker and fixes the warnings. We introduce an `AstHelpersBackports` class to contain any backported logic from `AstHelpers` to enable the fixes. --- .../uber/nullaway/ASTHelpersBackports.java | 38 +++++++++++++++++++ .../java/com/uber/nullaway/ErrorBuilder.java | 5 +-- .../main/java/com/uber/nullaway/NullAway.java | 13 ++----- .../com/uber/nullaway/NullabilityUtil.java | 15 -------- .../AccessPathNullnessPropagation.java | 4 +- .../AbstractFieldContractHandler.java | 3 +- .../handlers/ApacheThriftIsSetHandler.java | 5 ++- .../uber/nullaway/handlers/GrpcHandler.java | 4 +- .../uber/nullaway/handlers/LombokHandler.java | 16 +++----- .../uber/nullaway/NullAwayFrameworkTests.java | 26 +++++++++++++ 10 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java diff --git a/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java b/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java new file mode 100644 index 0000000000..32baa12c9b --- /dev/null +++ b/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java @@ -0,0 +1,38 @@ +package com.uber.nullaway; + +import com.sun.tools.javac.code.Symbol; +import java.util.List; + +/** + * Methods backported from {@link com.google.errorprone.util.ASTHelpers} since we do not yet require + * a recent-enough Error Prone version. The methods should be removed once we bump our minimum Error + * Prone version accordingly. + */ +public class ASTHelpersBackports { + + private ASTHelpersBackports() {} + + /** + * Returns true if the symbol is static. Returns {@code false} for module symbols. Remove once we + * require Error Prone 2.16.0 or higher. + */ + @SuppressWarnings("ASTHelpersSuggestions") + public static boolean isStatic(Symbol symbol) { + if (symbol.getKind().name().equals("MODULE")) { + return false; + } + return symbol.isStatic(); + } + + /** + * A wrapper for {@link Symbol#getEnclosedElements} to avoid binary compatibility issues for + * covariant overrides in subtypes of {@link Symbol}. + * + *

Same as this ASTHelpers method in Error Prone: + * https://github.com/google/error-prone/blame/a1318e4b0da4347dff7508108835d77c470a7198/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java#L1148 + * TODO: delete this method and switch to ASTHelpers once we can require Error Prone 2.20.0 + */ + public static List getEnclosedElements(Symbol symbol) { + return symbol.getEnclosedElements(); + } +} diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java index d35424b9c6..eeff202842 100755 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java @@ -22,6 +22,7 @@ package com.uber.nullaway; +import static com.uber.nullaway.ASTHelpersBackports.isStatic; import static com.uber.nullaway.ErrorMessage.MessageTypes.FIELD_NO_INIT; import static com.uber.nullaway.ErrorMessage.MessageTypes.GET_ON_EMPTY_OPTIONAL; import static com.uber.nullaway.ErrorMessage.MessageTypes.METHOD_NO_INIT; @@ -479,9 +480,7 @@ void reportInitErrorOnField(Symbol symbol, VisitorState state, Description.Build fieldName = flatName.substring(index) + "." + fieldName; } - @SuppressWarnings("ASTHelpersSuggestions") // remove once we require EP 2.16 or greater - boolean isStatic = symbol.isStatic(); - if (isStatic) { + if (isStatic(symbol)) { state.reportMatch( createErrorDescription( new ErrorMessage( diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index 9222a8b5ba..b78c228657 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -28,6 +28,7 @@ import static com.sun.source.tree.Tree.Kind.OTHER; import static com.sun.source.tree.Tree.Kind.PARENTHESIZED; import static com.sun.source.tree.Tree.Kind.TYPE_CAST; +import static com.uber.nullaway.ASTHelpersBackports.isStatic; import static com.uber.nullaway.ErrorBuilder.errMsgForInitializer; import static com.uber.nullaway.NullabilityUtil.castToNonNull; @@ -1005,9 +1006,7 @@ private Description checkForReadBeforeInit(ExpressionTree tree, VisitorState sta } // for static fields, make sure the enclosing init is a static method or block - @SuppressWarnings("ASTHelpersSuggestions") // remove once we require EP 2.16 or greater - boolean isStatic = symbol.isStatic(); - if (isStatic) { + if (isStatic(symbol)) { Tree enclosing = enclosingBlockPath.getLeaf(); if (enclosing instanceof MethodTree && !ASTHelpers.getSymbol((MethodTree) enclosing).isStatic()) { @@ -1116,9 +1115,7 @@ private boolean fieldAlwaysInitializedBeforeRead( Symbol symbol, TreePath pathToRead, VisitorState state, TreePath enclosingBlockPath) { AccessPathNullnessAnalysis nullnessAnalysis = getNullnessAnalysis(state); Set nonnullFields; - @SuppressWarnings("ASTHelpersSuggestions") // remove once we require EP 2.16 or greater - boolean isStatic = symbol.isStatic(); - if (isStatic) { + if (isStatic(symbol)) { nonnullFields = nullnessAnalysis.getNonnullStaticFieldsBefore(pathToRead, state.context); } else { nonnullFields = new LinkedHashSet<>(); @@ -2102,9 +2099,7 @@ private FieldInitEntities collectEntities(ClassTree tree, VisitorState state) { // matchVariable() continue; } - @SuppressWarnings("ASTHelpersSuggestions") // remove once we require EP 2.16 or greater - boolean fieldIsStatic = fieldSymbol.isStatic(); - if (fieldIsStatic) { + if (isStatic(fieldSymbol)) { nonnullStaticFields.add(fieldSymbol); } else { nonnullInstanceFields.add(fieldSymbol); diff --git a/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java b/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java index 880909213f..ba4be9f0f3 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java @@ -22,9 +22,6 @@ package com.uber.nullaway; -import static com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind.ARRAY; -import static com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind.INNER_TYPE; - import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.errorprone.VisitorState; @@ -176,18 +173,6 @@ public static TreePath findEnclosingMethodOrLambdaOrInitializer(TreePath path) { return findEnclosingMethodOrLambdaOrInitializer(path, ImmutableSet.of()); } - /** - * A wrapper for {@link Symbol#getEnclosedElements} to avoid binary compatibility issues for - * covariant overrides in subtypes of {@link Symbol}. - * - *

Same as this ASTHelpers method in Error Prone: - * https://github.com/google/error-prone/blame/a1318e4b0da4347dff7508108835d77c470a7198/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java#L1148 - * TODO: delete this method and switch to ASTHelpers once we can require Error Prone 2.20.0 - */ - public static List getEnclosedElements(Symbol symbol) { - return symbol.getEnclosedElements(); - } - /** * NOTE: this method does not work for getting all annotations of parameters of methods from class * files. For that case, use {@link #getAllAnnotationsForParameter(Symbol.MethodSymbol, int)} diff --git a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java index 85fa9ce61c..ece8d4de27 100644 --- a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java +++ b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java @@ -16,6 +16,7 @@ package com.uber.nullaway.dataflow; import static com.google.common.base.Preconditions.checkNotNull; +import static com.uber.nullaway.ASTHelpersBackports.isStatic; import static com.uber.nullaway.NullabilityUtil.castToNonNull; import static com.uber.nullaway.Nullness.BOTTOM; import static com.uber.nullaway.Nullness.NONNULL; @@ -761,10 +762,9 @@ private CodeAnnotationInfo getCodeAnnotationInfo(VisitorState state) { return codeAnnotationInfo; } - @SuppressWarnings("ASTHelpersSuggestions") // remove once we require EP 2.16 or greater private void setReceiverNonnull( AccessPathNullnessPropagation.ReadableUpdates updates, Node receiver, Symbol symbol) { - if (symbol != null && !symbol.isStatic()) { + if ((symbol != null) && !isStatic(symbol)) { setNonnullIfAnalyzeable(updates, receiver); } } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java index c458f3200a..d90cc80e88 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java @@ -22,6 +22,7 @@ package com.uber.nullaway.handlers; +import static com.uber.nullaway.ASTHelpersBackports.getEnclosedElements; import static com.uber.nullaway.NullabilityUtil.castToNonNull; import com.google.common.base.Preconditions; @@ -222,7 +223,7 @@ protected boolean validateAnnotationSyntax( public static @Nullable VariableElement getInstanceFieldOfClass( Symbol.ClassSymbol classSymbol, String name) { Preconditions.checkNotNull(classSymbol); - for (Element member : NullabilityUtil.getEnclosedElements(classSymbol)) { + for (Element member : getEnclosedElements(classSymbol)) { if (member.getKind().isField() && !member.getModifiers().contains(Modifier.STATIC)) { if (member.getSimpleName().toString().equals(name)) { return (VariableElement) member; diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java index 25c4210591..f477c801d5 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java @@ -21,6 +21,8 @@ */ package com.uber.nullaway.handlers; +import static com.uber.nullaway.ASTHelpersBackports.getEnclosedElements; + import com.google.common.base.Preconditions; import com.google.errorprone.VisitorState; import com.google.errorprone.suppliers.Supplier; @@ -30,7 +32,6 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.uber.nullaway.NullAway; -import com.uber.nullaway.NullabilityUtil; import com.uber.nullaway.Nullness; import com.uber.nullaway.dataflow.AccessPath; import com.uber.nullaway.dataflow.AccessPathNullnessPropagation; @@ -143,7 +144,7 @@ private FieldAndGetterElements getFieldAndGetterForProperty( Element getter = null; String fieldName = decapitalize(capPropName); String getterName = "get" + capPropName; - for (Symbol elem : NullabilityUtil.getEnclosedElements(symbol.owner)) { + for (Symbol elem : getEnclosedElements(symbol.owner)) { if (elem.getKind().isField() && elem.getSimpleName().toString().equals(fieldName)) { if (field != null) { throw new RuntimeException("already found field " + fieldName); diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java index ed5e620db6..38e26c53f0 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java @@ -21,6 +21,7 @@ */ package com.uber.nullaway.handlers; +import static com.uber.nullaway.ASTHelpersBackports.getEnclosedElements; import static com.uber.nullaway.NullabilityUtil.castToNonNull; import com.google.common.base.Preconditions; @@ -35,7 +36,6 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.uber.nullaway.NullAway; -import com.uber.nullaway.NullabilityUtil; import com.uber.nullaway.Nullness; import com.uber.nullaway.dataflow.AccessPath; import com.uber.nullaway.dataflow.AccessPathNullnessPropagation; @@ -123,7 +123,7 @@ public ImmutableSet onRegisterImmutableTypes() { private Symbol.MethodSymbol getGetterForMetadataSubtype( Symbol.ClassSymbol classSymbol, Types types) { // Is there a better way than iteration? - for (Symbol elem : NullabilityUtil.getEnclosedElements(classSymbol)) { + for (Symbol elem : getEnclosedElements(classSymbol)) { if (elem.getKind().equals(ElementKind.METHOD)) { Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) elem; if (grpcIsMetadataGetCall(methodSymbol, types)) { diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java index 49e5aafaf7..7069497800 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java @@ -31,7 +31,6 @@ public LombokHandler(Config config) { this.config = config; } - @SuppressWarnings("ASTHelpersSuggestions") // Suggested API doesn't exist in EP 2.4.0 private boolean isLombokMethodWithMissingNullableAnnotation( Symbol.MethodSymbol methodSymbol, VisitorState state) { if (!ASTHelpers.hasAnnotation(methodSymbol, LOMBOK_GENERATED_ANNOTATION_NAME, state)) { @@ -44,16 +43,11 @@ private boolean isLombokMethodWithMissingNullableAnnotation( String originalFieldName = methodNameString.substring(LOMBOK_BUILDER_DEFAULT_METHOD_PREFIX.length()); ImmutableList matchingMembers = - StreamSupport.stream( - methodSymbol - .enclClass() - .members() - .getSymbols( - sym -> - sym.name.contentEquals(originalFieldName) - && sym.getKind().equals(ElementKind.FIELD)) - .spliterator(), - false) + StreamSupport.stream(methodSymbol.enclClass().members().getSymbols().spliterator(), false) + .filter( + sym -> + sym.name.contentEquals(originalFieldName) + && sym.getKind().equals(ElementKind.FIELD)) .collect(ImmutableList.toImmutableList()); Preconditions.checkArgument( matchingMembers.size() == 1, diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java index 8a41d2fd18..33777eb641 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayFrameworkTests.java @@ -372,6 +372,32 @@ public void testLombokBuilderWithoutGeneratedAsUnannotated() { .doTest(); } + /** + * This test is solely to check if we can run through some of the {@link + * com.uber.nullaway.handlers.LombokHandler} logic without crashing. It does not check that the + * logic is correct. + */ + @Test + public void lombokHandlerRunsWithoutCrashing() { + makeTestHelperWithArgs( + Arrays.asList( + "-d", + temporaryFolder.getRoot().getAbsolutePath(), + "-XepOpt:NullAway:AnnotatedPackages=com.uber")) + .addSourceLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " @Nullable Object test;", + " @lombok.Generated", + " Object $default$test() {", + " return new Object();", + " }", + "}") + .doTest(); + } + @Test public void systemConsoleNullable() { defaultCompilationHelper From e7118874ee135d1796e51ab28526abde84c0be51 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 31 Jul 2023 11:34:52 -0700 Subject: [PATCH 26/57] Fix off-by-one error in JSpecify checking of parameter passing (#793) Not sure how we missed this before... --- .../src/main/java/com/uber/nullaway/GenericsChecks.java | 2 +- .../com/uber/nullaway/NullAwayJSpecifyGenericsTests.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index 4ca3d3ebd2..d12a112f03 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -478,7 +478,7 @@ public void compareGenericTypeParameterNullabilityForCall( // all remaining actual arguments in the next loop. n = n - 1; } - for (int i = 0; i < n - 1; i++) { + for (int i = 0; i < n; i++) { Type formalParameter = formalParams.get(i).type; if (!formalParameter.getTypeArguments().isEmpty()) { Type actualParameter = getTreeType(actualParams.get(i)); diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index 5040606433..cafb4541c3 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -45,6 +45,7 @@ public void constructorTypeParamInstantiation() { " // BUG: Diagnostic contains: Generic type parameter", " testBadNonNull(new NonNullTypeParam<@Nullable String>());", " testBadNonNull(", + " // BUG: Diagnostic contains: Cannot pass parameter of type NonNullTypeParam<@Nullable String>", " new NonNullTypeParam<", " // BUG: Diagnostic contains: Generic type parameter", " @Nullable String>());", @@ -677,6 +678,8 @@ public void parameterPassing() { " static A sampleMethod2(A> a1, A a2) {", " return a2;", " }", + " static void sampleMethod3(A<@Nullable String> a1) {", + " }", " static void testPositive1(A> a1, A a2) {", " // BUG: Diagnostic contains: Cannot pass parameter of type A>", " A a = sampleMethod1(a1, a2);", @@ -685,6 +688,10 @@ public void parameterPassing() { " // BUG: Diagnostic contains: Cannot pass parameter of type", " A a = sampleMethod2(a1, a2);", " }", + " static void testPositive3(A a1) {", + " // BUG: Diagnostic contains: Cannot pass parameter of type", + " sampleMethod3(a1);", + " }", " static void testNegative(A> a1, A a2) {", " A a = sampleMethod1(a1, a2);", " }", From b061288c0f2b2131e0e19c8ca6fecc0cec98a524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Clapp?= Date: Tue, 1 Aug 2023 16:51:37 -0400 Subject: [PATCH 27/57] Improve auto-fixing of unnecessary castToNonNull calls (#796) In particular, this allows removing unnecessary casts where the castToNonNull method is specified by a library model, rather than a CLI argument. --- .../java/com/uber/nullaway/ErrorBuilder.java | 37 +++++++++++-------- .../main/java/com/uber/nullaway/NullAway.java | 4 +- .../nullaway/NullAwayAutoSuggestTest.java | 29 +++++++++++++++ 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java index eeff202842..629e477e6c 100755 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java @@ -33,6 +33,7 @@ import static com.uber.nullaway.NullAway.getTreesInstance; import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -133,7 +134,7 @@ public Description createErrorDescription( } if (config.suggestSuppressions() && suggestTree != null) { - builder = addSuggestedSuppression(errorMessage, suggestTree, builder); + builder = addSuggestedSuppression(errorMessage, suggestTree, builder, state); } if (config.serializationIsActive()) { @@ -187,7 +188,10 @@ private boolean hasPathSuppression(TreePath treePath, String subcheckerName) { } private Description.Builder addSuggestedSuppression( - ErrorMessage errorMessage, Tree suggestTree, Description.Builder builder) { + ErrorMessage errorMessage, + Tree suggestTree, + Description.Builder builder, + VisitorState state) { switch (errorMessage.messageType) { case DEREFERENCE_NULLABLE: case RETURN_NULLABLE: @@ -201,7 +205,7 @@ private Description.Builder addSuggestedSuppression( } break; case CAST_TO_NONNULL_ARG_NONNULL: - builder = removeCastToNonNullFix(suggestTree, builder); + builder = removeCastToNonNullFix(suggestTree, builder, state); break; case WRONG_OVERRIDE_RETURN: builder = addSuppressWarningsFix(suggestTree, builder, suppressionName); @@ -334,20 +338,23 @@ private Description.Builder addCastToNonNullFix(Tree suggestTree, Description.Bu } private Description.Builder removeCastToNonNullFix( - Tree suggestTree, Description.Builder builder) { - assert suggestTree.getKind() == Tree.Kind.METHOD_INVOCATION; - final MethodInvocationTree invTree = (MethodInvocationTree) suggestTree; - final Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(invTree); - final String qualifiedName = - ASTHelpers.enclosingClass(methodSymbol) + "." + methodSymbol.getSimpleName().toString(); - if (!qualifiedName.equals(config.getCastToNonNullMethod())) { - throw new RuntimeException("suggestTree should point to the castToNonNull invocation."); - } + Tree suggestTree, Description.Builder builder, VisitorState state) { + // Note: Here suggestTree refers to the argument being cast. We need to find the + // castToNonNull(...) invocation to be replaced by it. Fortunately, state.getPath() + // should be currently pointing at said call. + Tree currTree = state.getPath().getLeaf(); + Preconditions.checkArgument( + currTree.getKind() == Tree.Kind.METHOD_INVOCATION, + String.format("Expected castToNonNull invocation expression, found:\n%s", currTree)); + final MethodInvocationTree invTree = (MethodInvocationTree) currTree; + Preconditions.checkArgument( + invTree.getArguments().contains(suggestTree), + String.format( + "Method invocation tree %s does not contain the expression %s as an argument being cast", + invTree, suggestTree)); // Remove the call to castToNonNull: final SuggestedFix fix = - SuggestedFix.builder() - .replace(suggestTree, invTree.getArguments().get(0).toString()) - .build(); + SuggestedFix.builder().replace(invTree, suggestTree.toString()).build(); return builder.addFix(fix); } diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index b78c228657..d7c0afb42c 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -1712,7 +1712,9 @@ private Description checkCastToNonNullTakesNullable( + "at the invocation site, but which are known not to be null at runtime."; return errorBuilder.createErrorDescription( new ErrorMessage(MessageTypes.CAST_TO_NONNULL_ARG_NONNULL, message), - tree, + // The Tree passed as suggestTree is the expression being cast + // to avoid recomputing the arg index: + actual, buildDescription(tree), state, null); diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java index 5aec8370a2..92138b2f79 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java @@ -24,6 +24,7 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper; import com.google.errorprone.ErrorProneFlags; +import com.uber.nullaway.testlibrarymodels.TestLibraryModels; import java.io.IOException; import org.junit.Before; import org.junit.Rule; @@ -57,6 +58,8 @@ private BugCheckerRefactoringTestHelper makeTestHelper() { .setArgs( "-d", temporaryFolder.getRoot().getAbsolutePath(), + "-processorpath", + TestLibraryModels.class.getProtectionDomain().getCodeSource().getLocation().getPath(), // the remaining args are not needed right now, but they will be necessary when we // switch to the more modern newInstance() API "-XepOpt:NullAway:AnnotatedPackages=com.uber,com.ubercab,io.reactivex", @@ -132,6 +135,32 @@ public void removeUnnecessaryCastToNonNull() throws IOException { .doTest(); } + @Test + public void removeUnnecessaryCastToNonNullFromLibraryModel() throws IOException { + makeTestHelper() + .addInputLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "import static com.uber.nullaway.testdata.Util.castToNonNull;", + "class Test {", + " Object test1(Object o) {", + " return castToNonNull(\"CAST_REASON\",o,42);", + " }", + "}") + .addOutputLines( + "out/Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "import static com.uber.nullaway.testdata.Util.castToNonNull;", + "class Test {", + " Object test1(Object o) {", + " return o;", + " }", + "}") + .doTest(); + } + @Test public void suggestSuppressionOnMethodRef() throws IOException { makeTestHelper() From 6a1e8bb9436a570ea0ae400ba1da08319af12ce6 Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Wed, 2 Aug 2023 13:37:19 -0400 Subject: [PATCH 28/57] Prepare for release 0.10.12. --- CHANGELOG.md | 25 +++++++++++++++++++++++++ README.md | 4 ++-- gradle.properties | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0317627cd0..d212b7999e 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ Changelog ========= +Version 0.10.12 +--------------- +Note: This is the first release built with Java 11. In particular, running + JarInfer now requires a JDK 11 JVM. NullAway is still capable of analyzing JDK 8 + source/target projects, and should be compatible with the Error Prone JDK 9 javac + just as the release before, but a JDK 11 javac is recommended. +* Update to WALA 1.6.1 and remove ability to build on JDK 8 (#777) +* Fix compatibility issue when building on JDK 17 but running on JDK 8 (#779) +* Fix JDK compatibility issue in LombokHandler (#795) +* Improve auto-fixing of unnecessary castToNonNull calls (#796) +* Support for JSpecify's 0.3.0 annotation [experimental] + - JSpecify: avoid crashes when encountering raw types (#792) + - Fix off-by-one error in JSpecify checking of parameter passing (#793) +* Build / CI tooling for NullAway itself: + - Fix Publish Snapshot CI job (#774) + - Add step to create release on GitHub (#775) + - Build the Android sample app on JDK 17 (#776) + - Update to Error Prone 2.20.0 (#772) + - Add tasks to run JDK 8 tests on JDK 11+ (#778) + - Switch to Spotless for formatting Java code (#780) + - Added GCP JMH Benchmark Workflow (#770) + - Set concurrency for JMH benchmarking workflow (#784) + - Disable daemon when running benchmarks (#786) + - Update to Gradle 8.2.1 (#781) + Version 0.10.11 --------------- * NULL_LITERAL expressions may always be null (#749) diff --git a/README.md b/README.md index 4b184d93cf..c6a3f1d54c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ plugins { } dependencies { - annotationProcessor "com.uber.nullaway:nullaway:0.10.11" + annotationProcessor "com.uber.nullaway:nullaway:0.10.12" // Optional, some source of nullability annotations. // Not required on Android if you use the support @@ -75,7 +75,7 @@ The configuration for an Android project is very similar to the Java case, with ```gradle dependencies { - annotationProcessor "com.uber.nullaway:nullaway:0.10.11" + annotationProcessor "com.uber.nullaway:nullaway:0.10.12" errorprone "com.google.errorprone:error_prone_core:2.4.0" errorproneJavac "com.google.errorprone:javac:9+181-r4173-1" } diff --git a/gradle.properties b/gradle.properties index 3cbe7b4a46..cad64be6d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.12-SNAPSHOT +VERSION_NAME=0.10.12 POM_DESCRIPTION=A fast annotation-based null checker for Java From 38f3081692d76429aed94800173c441340a46741 Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Wed, 2 Aug 2023 13:43:29 -0400 Subject: [PATCH 29/57] Prepare next development version. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cad64be6d7..f797e050a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.12 +VERSION_NAME=0.10.13-SNAPSHOT POM_DESCRIPTION=A fast annotation-based null checker for Java From f14b43dc1a65618418bb178d8cb5ec0609ed1fb6 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 11 Aug 2023 11:54:58 -0700 Subject: [PATCH 30/57] Bump versions for some dependencies (#800) To keep up to date and avoid surprises later. --- build.gradle | 8 ++++---- gradle/dependencies.gradle | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index f1f0717b6c..a16feec7cf 100644 --- a/build.gradle +++ b/build.gradle @@ -35,12 +35,12 @@ buildscript { } } plugins { - id "com.diffplug.spotless" version "6.19.0" + id "com.diffplug.spotless" version "6.20.0" id "net.ltgt.errorprone" version "3.0.1" apply false - id "com.github.johnrengelman.shadow" version "8.1.0" apply false + id "com.github.johnrengelman.shadow" version "8.1.1" apply false id "com.github.kt3k.coveralls" version "2.12.0" apply false - id "me.champeau.jmh" version "0.6.8" apply false - id "com.github.ben-manes.versions" version "0.42.0" + id "me.champeau.jmh" version "0.7.1" apply false + id "com.github.ben-manes.versions" version "0.47.0" } repositories { diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 2194d2c9d1..9b0f2679b9 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -50,8 +50,8 @@ def versions = [ support : "27.1.1", wala : "1.6.1", commonscli : "1.4", - autoValue : "1.9", - autoService : "1.0.1", + autoValue : "1.10.2", + autoService : "1.1.1", ] def apt = [ From bbc696c2d3db01a4d5494a3ce2ac6114bb70f222 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 11 Aug 2023 12:01:26 -0700 Subject: [PATCH 31/57] Update to WALA 1.6.2 (#798) We use a [new WALA feature](https://github.com/wala/WALA/pull/1286) to only include the `java.base` module in the `AnalysisScope` used for JarInfer. JarInfer does not require all JDK standard library modules to be part of the analysis scope, just enough to make a valid class hierarchy, and `java.base` suffices. This should speed up runs of JarInfer, and in particular it speeds up our JarInfer regression tests. --- gradle/dependencies.gradle | 2 +- .../uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 9b0f2679b9..a28c03123a 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -48,7 +48,7 @@ def versions = [ // The version of Error Prone that NullAway is compiled and tested against errorProneApi : errorProneVersionToCompileAgainst, support : "27.1.1", - wala : "1.6.1", + wala : "1.6.2", commonscli : "1.4", autoValue : "1.10.2", autoService : "1.1.1", diff --git a/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java b/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java index 6eb0c10c24..fc852414fb 100644 --- a/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java +++ b/jar-infer/jar-infer-lib/src/main/java/com/uber/nullaway/jarinfer/DefinitelyDerefedParamsDriver.java @@ -228,7 +228,7 @@ private void analyzeFile(String pkgName, String inPath, boolean includeNonPublic } else if (!new File(inPath).exists()) { return; } - AnalysisScope scope = AnalysisScopeReader.instance.makePrimordialScope(null); + AnalysisScope scope = AnalysisScopeReader.instance.makeBasePrimordialScope(null); scope.setExclusions( new FileOfClasses( new ByteArrayInputStream(DEFAULT_EXCLUSIONS.getBytes(StandardCharsets.UTF_8)))); From 9ee04c2fcee08db1ff715c1b6230ac54aadde44a Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 11 Aug 2023 12:22:34 -0700 Subject: [PATCH 32/57] Update to Error Prone 2.21.1 (#797) Mostly straightforward. Error Prone has a new [NullableOptional](https://errorprone.info/bugpattern/NullableOptional) check that warned on a couple instances in our code base. I changed these cases to use an `@Initializer` method which I think better captures what is going on. Will update the required CI checks before landing. --- .github/workflows/continuous-integration.yml | 10 +++++----- gradle/dependencies.gradle | 2 +- .../com/uber/nullaway/ASTHelpersBackports.java | 1 + .../handlers/ApacheThriftIsSetHandler.java | 10 +++++++--- .../com/uber/nullaway/handlers/GrpcHandler.java | 15 ++++++++------- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index a7d278d86e..185735733e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -21,16 +21,16 @@ jobs: epVersion: 2.4.0 - os: macos-latest java: 11 - epVersion: 2.20.0 + epVersion: 2.21.1 - os: ubuntu-latest java: 11 - epVersion: 2.20.0 + epVersion: 2.21.1 - os: windows-latest java: 11 - epVersion: 2.20.0 + epVersion: 2.21.1 - os: ubuntu-latest java: 17 - epVersion: 2.20.0 + epVersion: 2.21.1 fail-fast: false runs-on: ${{ matrix.os }} steps: @@ -61,7 +61,7 @@ jobs: with: arguments: coveralls continue-on-error: true - if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.20.0' && github.repository == 'uber/NullAway' + if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.21.1' && github.repository == 'uber/NullAway' - name: Test publishToMavenLocal flow env: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index a28c03123a..481be0dc74 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -19,7 +19,7 @@ import org.gradle.util.VersionNumber // The oldest version of Error Prone that we support running on def oldestErrorProneVersion = "2.4.0" // Latest released Error Prone version that we've tested with -def latestErrorProneVersion = "2.20.0" +def latestErrorProneVersion = "2.21.1" // Default to using latest tested Error Prone version def defaultErrorProneVersion = latestErrorProneVersion def errorProneVersionToCompileAgainst = defaultErrorProneVersion diff --git a/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java b/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java index 32baa12c9b..06a91f99c1 100644 --- a/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java +++ b/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java @@ -32,6 +32,7 @@ public static boolean isStatic(Symbol symbol) { * https://github.com/google/error-prone/blame/a1318e4b0da4347dff7508108835d77c470a7198/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java#L1148 * TODO: delete this method and switch to ASTHelpers once we can require Error Prone 2.20.0 */ + @SuppressWarnings("ASTHelpersSuggestions") public static List getEnclosedElements(Symbol symbol) { return symbol.getEnclosedElements(); } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java index f477c801d5..802cedd447 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/ApacheThriftIsSetHandler.java @@ -23,7 +23,6 @@ import static com.uber.nullaway.ASTHelpersBackports.getEnclosedElements; -import com.google.common.base.Preconditions; import com.google.errorprone.VisitorState; import com.google.errorprone.suppliers.Supplier; import com.google.errorprone.suppliers.Suppliers; @@ -33,6 +32,7 @@ import com.sun.tools.javac.code.Types; import com.uber.nullaway.NullAway; import com.uber.nullaway.Nullness; +import com.uber.nullaway.annotations.Initializer; import com.uber.nullaway.dataflow.AccessPath; import com.uber.nullaway.dataflow.AccessPathNullnessPropagation; import java.util.Objects; @@ -54,8 +54,13 @@ public class ApacheThriftIsSetHandler extends BaseNoOpHandler { private static final Supplier TBASE_TYPE_SUPPLIER = Suppliers.typeFromString(TBASE_NAME); - @Nullable private Optional tbaseType; + private Optional tbaseType; + /** + * This method is annotated {@code @Initializer} since it will be invoked when the first class is + * processed, before any other handler methods + */ + @Initializer @Override public void onMatchTopLevelClass( NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol) { @@ -173,7 +178,6 @@ private static String decapitalize(String str) { } private boolean thriftIsSetCall(Symbol.MethodSymbol symbol, Types types) { - Preconditions.checkNotNull(tbaseType); // noinspection ConstantConditions return tbaseType.isPresent() && symbol.getSimpleName().toString().startsWith("isSet") diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java index 38e26c53f0..82b436fc46 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/GrpcHandler.java @@ -24,7 +24,6 @@ import static com.uber.nullaway.ASTHelpersBackports.getEnclosedElements; import static com.uber.nullaway.NullabilityUtil.castToNonNull; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.errorprone.VisitorState; import com.google.errorprone.suppliers.Supplier; @@ -37,6 +36,7 @@ import com.sun.tools.javac.code.Types; import com.uber.nullaway.NullAway; import com.uber.nullaway.Nullness; +import com.uber.nullaway.annotations.Initializer; import com.uber.nullaway.dataflow.AccessPath; import com.uber.nullaway.dataflow.AccessPathNullnessPropagation; import java.util.ArrayList; @@ -61,9 +61,14 @@ public class GrpcHandler extends BaseNoOpHandler { private static final Supplier GRPC_METADATA_KEY_TYPE_SUPPLIER = Suppliers.typeFromString(GRPC_METADATA_KEY_TNAME); - @Nullable private Optional grpcMetadataType; - @Nullable private Optional grpcKeyType; + private Optional grpcMetadataType; + private Optional grpcKeyType; + /** + * This method is annotated {@code @Initializer} since it will be invoked when the first class is + * processed, before any other handler methods + */ + @Initializer @Override public void onMatchTopLevelClass( NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol) { @@ -135,8 +140,6 @@ private Symbol.MethodSymbol getGetterForMetadataSubtype( } private boolean grpcIsMetadataContainsKeyCall(Symbol.MethodSymbol symbol, Types types) { - Preconditions.checkNotNull(grpcMetadataType); - Preconditions.checkNotNull(grpcKeyType); // noinspection ConstantConditions return grpcMetadataType.isPresent() && grpcKeyType.isPresent() @@ -149,8 +152,6 @@ private boolean grpcIsMetadataContainsKeyCall(Symbol.MethodSymbol symbol, Types } private boolean grpcIsMetadataGetCall(Symbol.MethodSymbol symbol, Types types) { - Preconditions.checkNotNull(grpcMetadataType); - Preconditions.checkNotNull(grpcKeyType); // noinspection ConstantConditions return grpcMetadataType.isPresent() && grpcKeyType.isPresent() From 01ee4d7667870d604cd226755aa24f82fe8593ec Mon Sep 17 00:00:00 2001 From: Nikita Dinkar Aware Date: Sat, 12 Aug 2023 17:15:52 -0500 Subject: [PATCH 33/57] Generics checks for method overriding (#755) This PR implements JSpecify-related generics checks for method overriding, including: * checks that overrides of generic methods respect subtyping of nullability qualifiers * proper handling of calls to generic methods, taking into account (qualified) types passed as type parameters See the new tests and docs for further examples. **Note:** This PR does not handle overriding in explicitly-typed anonymous inner classes. Those will be handled in a follow-up PR, to keep the PR size manageable. --------- Co-authored-by: Manu Sridharan --- .../java/com/uber/nullaway/ErrorMessage.java | 2 + .../com/uber/nullaway/GenericsChecks.java | 290 +++++++++++++++++- .../main/java/com/uber/nullaway/NullAway.java | 57 +++- .../AccessPathNullnessPropagation.java | 26 +- .../NullAwayJSpecifyGenericsTests.java | 281 +++++++++++++++++ 5 files changed, 647 insertions(+), 9 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java b/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java index 22ebf13b4b..186b386cde 100644 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java @@ -56,6 +56,8 @@ public enum MessageTypes { ASSIGN_GENERIC_NULLABLE, RETURN_NULLABLE_GENERIC, PASS_NULLABLE_GENERIC, + WRONG_OVERRIDE_RETURN_GENERIC, + WRONG_OVERRIDE_PARAM_GENERIC, } public String getMessage() { diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index d12a112f03..c4ba2f1504 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -13,6 +13,9 @@ import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; @@ -190,6 +193,38 @@ private void reportInvalidParametersNullabilityError( errorMessage, analysis.buildDescription(paramExpression), state, null)); } + private void reportInvalidOverridingMethodReturnTypeError( + Tree methodTree, Type overriddenMethodReturnType, Type overridingMethodReturnType) { + ErrorBuilder errorBuilder = analysis.getErrorBuilder(); + ErrorMessage errorMessage = + new ErrorMessage( + ErrorMessage.MessageTypes.WRONG_OVERRIDE_RETURN_GENERIC, + "Method returns " + + prettyTypeForError(overridingMethodReturnType) + + ", but overridden method returns " + + prettyTypeForError(overriddenMethodReturnType) + + ", which has mismatched type parameter nullability"); + state.reportMatch( + errorBuilder.createErrorDescription( + errorMessage, analysis.buildDescription(methodTree), state, null)); + } + + private void reportInvalidOverridingMethodParamTypeError( + Tree formalParameterTree, Type typeParameterType, Type methodParamType) { + ErrorBuilder errorBuilder = analysis.getErrorBuilder(); + ErrorMessage errorMessage = + new ErrorMessage( + ErrorMessage.MessageTypes.WRONG_OVERRIDE_PARAM_GENERIC, + "Parameter has type " + + prettyTypeForError(methodParamType) + + ", but overridden method has parameter type " + + prettyTypeForError(typeParameterType) + + ", which has mismatched type parameter nullability"); + state.reportMatch( + errorBuilder.createErrorDescription( + errorMessage, analysis.buildDescription(formalParameterTree), state, null)); + } + /** * This method returns the type of the given tree, including any type use annotations. * @@ -366,7 +401,6 @@ private Type.ClassType typeWithPreservedAnnotations(ParameterizedTypeTree tree) Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); List typeArguments = tree.getTypeArguments(); List newTypeArgs = new ArrayList<>(); - boolean hasNullableAnnotation = false; for (int i = 0; i < typeArguments.size(); i++) { AnnotatedTypeTree annotatedType = null; Tree curTypeArg = typeArguments.get(i); @@ -380,6 +414,7 @@ private Type.ClassType typeWithPreservedAnnotations(ParameterizedTypeTree tree) } List annotations = annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); + boolean hasNullableAnnotation = false; for (AnnotationTree annotation : annotations) { if (ASTHelpers.isSameType( nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { @@ -512,6 +547,259 @@ public void compareGenericTypeParameterNullabilityForCall( } } + /** + * Checks that type parameter nullability is consistent between an overriding method and the + * corresponding overridden method. + * + * @param tree A method tree to check + * @param overridingMethod A symbol of the overriding method + * @param overriddenMethod A symbol of the overridden method + */ + public void checkTypeParameterNullnessForMethodOverriding( + MethodTree tree, Symbol.MethodSymbol overridingMethod, Symbol.MethodSymbol overriddenMethod) { + if (!config.isJSpecifyMode()) { + return; + } + // Obtain type parameters for the overridden method within the context of the overriding + // method's class + Type methodWithTypeParams = + state.getTypes().memberType(overridingMethod.owner.type, overriddenMethod); + + checkTypeParameterNullnessForOverridingMethodReturnType(tree, methodWithTypeParams); + checkTypeParameterNullnessForOverridingMethodParameterType(tree, methodWithTypeParams); + } + + /** + * Computes the nullability of the return type of some generic method when seen as a member of + * some class {@code C}, based on type parameter nullability within {@code C}. + * + *

Consider the following example: + * + *

+   *     interface Fn

{ + * R apply(P p); + * } + * class C implements Fn { + * public @Nullable String apply(String p) { + * return null; + * } + * } + *

+ * + * Within the context of class {@code C}, the method {@code Fn.apply} has a return type of + * {@code @Nullable String}, since {@code @Nullable String} is passed as the type parameter for + * {@code R}. Hence, it is valid for overriding method {@code C.apply} to return {@code @Nullable + * String}. + * + * @param method the generic method + * @param enclosingType the enclosing type in which we want to know {@code method}'s return type + * nullability + * @param state Visitor state + * @param config The analysis config + * @return nullability of the return type of {@code method} in the context of {@code + * enclosingType} + */ + public static Nullness getGenericMethodReturnTypeNullness( + Symbol.MethodSymbol method, Type enclosingType, VisitorState state, Config config) { + Type overriddenMethodType = state.getTypes().memberType(enclosingType, method); + if (!(overriddenMethodType instanceof Type.MethodType)) { + throw new RuntimeException("expected method type but instead got " + overriddenMethodType); + } + return getTypeNullness(overriddenMethodType.getReturnType(), config); + } + + /** + * Computes the nullness of the return of a generic method at an invocation, in the context of the + * declared type of its receiver argument. If the return type is a type variable, its nullness + * depends on the nullability of the corresponding type parameter in the receiver's type. + * + *

Consider the following example: + * + *

+   *     interface Fn

{ + * R apply(P p); + * } + * class C implements Fn { + * public @Nullable String apply(String p) { + * return null; + * } + * } + * static void m() { + * Fn f = new C(); + * f.apply("hello").hashCode(); // NPE + * } + *

+ * + * The declared type of {@code f} passes {@code Nullable String} as the type parameter for type + * variable {@code R}. So, the call {@code f.apply("hello")} returns {@code @Nullable} and an + * error should be reported. + * + * @param invokedMethodSymbol symbol for the invoked method + * @param tree the tree for the invocation + * @return Nullness of invocation's return type, or {@code NONNULL} if the call does not invoke an + * instance method + */ + public static Nullness getGenericReturnNullnessAtInvocation( + Symbol.MethodSymbol invokedMethodSymbol, + MethodInvocationTree tree, + VisitorState state, + Config config) { + if (!(tree.getMethodSelect() instanceof MemberSelectTree)) { + return Nullness.NONNULL; + } + Type methodReceiverType = + castToNonNull( + ASTHelpers.getType(((MemberSelectTree) tree.getMethodSelect()).getExpression())); + return getGenericMethodReturnTypeNullness( + invokedMethodSymbol, methodReceiverType, state, config); + } + + /** + * Computes the nullness of a formal parameter of a generic method at an invocation, in the + * context of the declared type of its receiver argument. If the formal parameter's type is a type + * variable, its nullness depends on the nullability of the corresponding type parameter in the + * receiver's type. + * + *

Consider the following example: + * + *

+   *     interface Fn

{ + * R apply(P p); + * } + * class C implements Fn<@Nullable String, String> { + * public String apply(@Nullable String p) { + * return ""; + * } + * } + * static void m() { + * Fn<@Nullable String, String> f = new C(); + * f.apply(null); + * } + *

+ * + * The declared type of {@code f} passes {@code Nullable String} as the type parameter for type + * variable {@code P}. So, it is legal to pass {@code null} as a parameter to {@code f.apply}. + * + * @param paramIndex parameter index + * @param invokedMethodSymbol symbol for the invoked method + * @param tree the tree for the invocation + * @return Nullness of parameter at {@code paramIndex}, or {@code NONNULL} if the call does not + * invoke an instance method + */ + public Nullness getGenericParameterNullnessAtInvocation( + int paramIndex, Symbol.MethodSymbol invokedMethodSymbol, MethodInvocationTree tree) { + if (!(tree.getMethodSelect() instanceof MemberSelectTree)) { + return Nullness.NONNULL; + } + Type methodReceiverType = + castToNonNull( + ASTHelpers.getType(((MemberSelectTree) tree.getMethodSelect()).getExpression())); + return getGenericMethodParameterNullness(paramIndex, invokedMethodSymbol, methodReceiverType); + } + + /** + * Computes the nullability of a parameter type of some generic method when seen as a member of + * some class {@code C}, based on type parameter nullability within {@code C}. + * + *

Consider the following example: + * + *

+   *     interface Fn

{ + * R apply(P p); + * } + * class C implements Fn<@Nullable String, String> { + * public String apply(@Nullable String p) { + * return ""; + * } + * } + *

+ * + * Within the context of class {@code C}, the method {@code Fn.apply} has a parameter type of + * {@code @Nullable String}, since {@code @Nullable String} is passed as the type parameter for + * {@code P}. Hence, overriding method {@code C.apply} must take a {@code @Nullable String} as a + * parameter. + * + * @param parameterIndex index of the parameter + * @param method the generic method + * @param enclosingType the enclosing type in which we want to know {@code method}'s parameter + * type nullability + * @return nullability of the relevant parameter type of {@code method} in the context of {@code + * enclosingType} + */ + public Nullness getGenericMethodParameterNullness( + int parameterIndex, Symbol.MethodSymbol method, Type enclosingType) { + Type methodType = state.getTypes().memberType(enclosingType, method); + Type paramType = methodType.getParameterTypes().get(parameterIndex); + return getTypeNullness(paramType, config); + } + + /** + * This method compares the type parameter annotations for overriding method parameters with + * corresponding type parameters for the overridden method and reports an error if they don't + * match + * + * @param tree tree for overriding method + * @param overriddenMethodType type of the overridden method + */ + private void checkTypeParameterNullnessForOverridingMethodParameterType( + MethodTree tree, Type overriddenMethodType) { + List methodParameters = tree.getParameters(); + List overriddenMethodParameterTypes = overriddenMethodType.getParameterTypes(); + // TODO handle varargs; they are not handled for now + for (int i = 0; i < methodParameters.size(); i++) { + Type overridingMethodParameterType = ASTHelpers.getType(methodParameters.get(i)); + Type overriddenMethodParameterType = overriddenMethodParameterTypes.get(i); + if (overriddenMethodParameterType instanceof Type.ClassType + && overridingMethodParameterType instanceof Type.ClassType) { + if (!compareNullabilityAnnotations( + (Type.ClassType) overriddenMethodParameterType, + (Type.ClassType) overridingMethodParameterType)) { + reportInvalidOverridingMethodParamTypeError( + methodParameters.get(i), + overriddenMethodParameterType, + overridingMethodParameterType); + } + } + } + } + + /** + * This method compares the type parameter annotations for an overriding method's return type with + * corresponding type parameters for the overridden method and reports an error if they don't + * match + * + * @param tree tree for overriding method + * @param overriddenMethodType type of the overridden method + */ + private void checkTypeParameterNullnessForOverridingMethodReturnType( + MethodTree tree, Type overriddenMethodType) { + Type overriddenMethodReturnType = overriddenMethodType.getReturnType(); + Type overridingMethodReturnType = ASTHelpers.getType(tree.getReturnType()); + if (!(overriddenMethodReturnType instanceof Type.ClassType)) { + return; + } + Preconditions.checkArgument(overridingMethodReturnType instanceof Type.ClassType); + if (!compareNullabilityAnnotations( + (Type.ClassType) overriddenMethodReturnType, (Type.ClassType) overridingMethodReturnType)) { + reportInvalidOverridingMethodReturnTypeError( + tree, overriddenMethodReturnType, overridingMethodReturnType); + } + } + + /** + * @param type A type for which we need the Nullness. + * @param config The analysis config + * @return Returns the Nullness of the type based on the Nullability annotation. + */ + private static Nullness getTypeNullness(Type type, Config config) { + boolean hasNullableAnnotation = + Nullness.hasNullableAnnotation(type.getAnnotationMirrors().stream(), config); + if (hasNullableAnnotation) { + return Nullness.NULLABLE; + } + return Nullness.NONNULL; + } + /** * Returns a pretty-printed representation of type suitable for error messages. The representation * uses simple names rather than fully-qualified names, and retains all type-use annotations. diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index d7c0afb42c..5353b57161 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -628,6 +628,13 @@ public Description matchMethod(MethodTree tree, VisitorState state) { Symbol.MethodSymbol closestOverriddenMethod = NullabilityUtil.getClosestOverriddenMethod(methodSymbol, state.getTypes()); if (closestOverriddenMethod != null) { + if (config.isJSpecifyMode()) { + // Check that any generic type parameters in the return type and parameter types are + // identical (invariant) across the overriding and overridden methods + new GenericsChecks(state, config, this) + .checkTypeParameterNullnessForMethodOverriding( + tree, methodSymbol, closestOverriddenMethod); + } return checkOverriding(closestOverriddenMethod, methodSymbol, null, state); } } @@ -723,7 +730,11 @@ private Description checkParamOverriding( overriddenMethodArgNullnessMap[i] = Nullness.paramHasNullableAnnotation(overriddenMethod, i, config) ? Nullness.NULLABLE - : Nullness.NONNULL; + : (config.isJSpecifyMode() + ? new GenericsChecks(state, config, this) + .getGenericMethodParameterNullness( + i, overriddenMethod, overridingParamSymbols.get(i).owner.owner.type) + : Nullness.NONNULL); } } @@ -926,7 +937,7 @@ private Description checkOverriding( // if the super method returns nonnull, overriding method better not return nullable // Note that, for the overriding method, the permissive default is non-null, // but it's nullable for the overridden one. - if (getMethodReturnNullness(overriddenMethod, state, Nullness.NULLABLE).equals(Nullness.NONNULL) + if (overriddenMethodReturnsNonNull(overriddenMethod, overridingMethod.owner.type, state) && getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL) .equals(Nullness.NULLABLE) && (memberReferenceTree == null @@ -939,7 +950,6 @@ && getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL) + "." + overriddenMethod.toString() + " returns @NonNull"; - } else { message = "method returns @Nullable, but superclass method " @@ -965,6 +975,23 @@ && getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL) overridingMethod.getParameters(), overriddenMethod, null, memberReferenceTree, state); } + private boolean overriddenMethodReturnsNonNull( + Symbol.MethodSymbol overriddenMethod, Type overridingMethodType, VisitorState state) { + Nullness methodReturnNullness = + getMethodReturnNullness(overriddenMethod, state, Nullness.NULLABLE); + if (!methodReturnNullness.equals(Nullness.NONNULL)) { + return false; + } + // In JSpecify mode, for generic methods, we additionally need to check the return nullness + // using the type parameters from the type enclosing the overriding method + if (config.isJSpecifyMode()) { + return GenericsChecks.getGenericMethodReturnTypeNullness( + overriddenMethod, overridingMethodType, state, config) + .equals(Nullness.NONNULL); + } + return true; + } + @Override public Description matchIdentifier(IdentifierTree tree, VisitorState state) { if (!withinAnnotatedCode(state)) { @@ -1610,7 +1637,11 @@ private Description handleInvocation( argumentPositionNullness[i] = Nullness.paramHasNullableAnnotation(methodSymbol, i, config) ? Nullness.NULLABLE - : Nullness.NONNULL; + : ((config.isJSpecifyMode() && tree instanceof MethodInvocationTree) + ? new GenericsChecks(state, config, this) + .getGenericParameterNullnessAtInvocation( + i, methodSymbol, (MethodInvocationTree) tree) + : Nullness.NONNULL); } } new GenericsChecks(state, config, this) @@ -2279,7 +2310,9 @@ private boolean mayBeNullExpr(VisitorState state, ExpressionTree expr) { + " for method invocation " + state.getSourceForNode(expr)); } - exprMayBeNull = mayBeNullMethodCall((Symbol.MethodSymbol) exprSymbol); + exprMayBeNull = + mayBeNullMethodCall( + (Symbol.MethodSymbol) exprSymbol, (MethodInvocationTree) expr, state); break; case CONDITIONAL_EXPRESSION: case ASSIGNMENT: @@ -2298,11 +2331,21 @@ private boolean mayBeNullExpr(VisitorState state, ExpressionTree expr) { return exprMayBeNull && nullnessFromDataflow(state, expr); } - private boolean mayBeNullMethodCall(Symbol.MethodSymbol exprSymbol) { + private boolean mayBeNullMethodCall( + Symbol.MethodSymbol exprSymbol, MethodInvocationTree invocationTree, VisitorState state) { if (codeAnnotationInfo.isSymbolUnannotated(exprSymbol, config)) { return false; } - return Nullness.hasNullableAnnotation(exprSymbol, config); + if (Nullness.hasNullableAnnotation(exprSymbol, config)) { + return true; + } + if (config.isJSpecifyMode() + && GenericsChecks.getGenericReturnNullnessAtInvocation( + exprSymbol, invocationTree, state, config) + .equals(Nullness.NULLABLE)) { + return true; + } + return false; } public boolean nullnessFromDataflow(VisitorState state, ExpressionTree expr) { diff --git a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java index ece8d4de27..60b80399fc 100644 --- a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java +++ b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java @@ -29,11 +29,13 @@ import com.google.errorprone.suppliers.Supplier; import com.google.errorprone.suppliers.Suppliers; import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeTag; import com.uber.nullaway.CodeAnnotationInfo; import com.uber.nullaway.Config; +import com.uber.nullaway.GenericsChecks; import com.uber.nullaway.NullabilityUtil; import com.uber.nullaway.Nullness; import com.uber.nullaway.handlers.Handler; @@ -1008,7 +1010,8 @@ Nullness returnValueNullness( nullness = input.getRegularStore().valueOfMethodCall(node, state, NULLABLE, apContext); } else if (node == null || methodReturnsNonNull.test(node) - || !Nullness.hasNullableAnnotation((Symbol) node.getTarget().getMethod(), config)) { + || (!Nullness.hasNullableAnnotation((Symbol) node.getTarget().getMethod(), config) + && !genericReturnIsNullable(node))) { // definite non-null return nullness = NONNULL; } else { @@ -1018,6 +1021,27 @@ Nullness returnValueNullness( return nullness; } + /** + * Computes the nullability of a generic return type in the context of some receiver type at an + * invocation. + * + * @param node the invocation node + * @return nullability of the return type in the context of the type of the receiver argument at + * {@code node} + */ + private boolean genericReturnIsNullable(MethodInvocationNode node) { + if (node != null && config.isJSpecifyMode()) { + MethodInvocationTree tree = node.getTree(); + if (tree != null) { + Nullness nullness = + GenericsChecks.getGenericReturnNullnessAtInvocation( + ASTHelpers.getSymbol(tree), tree, state, config); + return nullness.equals(NULLABLE); + } + } + return false; + } + @Override public TransferResult visitObjectCreation( ObjectCreationNode objectCreationNode, TransferInput input) { diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index cafb4541c3..260b3cc7ff 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -760,6 +760,287 @@ public void rawTypes() { .doTest(); } + @Test + public void overrideReturnTypes() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " interface Fn

{", + " R apply(P p);", + " }", + " static class TestFunc1 implements Fn {", + " @Override", + " public @Nullable String apply(String s) {", + " return s;", + " }", + " }", + " static class TestFunc2 implements Fn {", + " @Override", + " public String apply(String s) {", + " return s;", + " }", + " }", + " static class TestFunc3 implements Fn {", + " @Override", + " // BUG: Diagnostic contains: method returns @Nullable, but superclass", + " public @Nullable String apply(String s) {", + " return s;", + " }", + " }", + " static class TestFunc4 implements Fn<@Nullable String, String> {", + " @Override", + " // BUG: Diagnostic contains: method returns @Nullable, but superclass", + " public @Nullable String apply(String s) {", + " return s;", + " }", + " }", + " static void useTestFunc(String s) {", + " Fn f1 = new TestFunc1();", + " String t1 = f1.apply(s);", + " // BUG: Diagnostic contains: dereferenced expression", + " t1.hashCode();", + " TestFunc2 f2 = new TestFunc2();", + " String t2 = f2.apply(s);", + " // There should not be an error here", + " t2.hashCode();", + " Fn f3 = new TestFunc2();", + " String t3 = f3.apply(s);", + " // BUG: Diagnostic contains: dereferenced expression", + " t3.hashCode();", + " // BUG: Diagnostic contains: dereferenced expression", + " f3.apply(s).hashCode();", + " }", + "}") + .doTest(); + } + + @Test + public void overrideWithNullCheck() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " interface Fn

{", + " R apply(P p);", + " }", + " static class TestFunc1 implements Fn {", + " @Override", + " public @Nullable String apply(String s) {", + " return s;", + " }", + " }", + " static void useTestFuncWithCast() {", + " Fn f1 = new TestFunc1();", + " if (f1.apply(\"hello\") != null) {", + " String t1 = f1.apply(\"hello\");", + " // no error here due to null check", + " t1.hashCode();", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void overrideParameterType() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " interface Fn

{", + " R apply(P p);", + " }", + " static class TestFunc1 implements Fn<@Nullable String, String> {", + " @Override", + " // BUG: Diagnostic contains: parameter s is", + " public String apply(String s) {", + " return s;", + " }", + " }", + " static class TestFunc2 implements Fn<@Nullable String, String> {", + " @Override", + " public String apply(@Nullable String s) {", + " return \"hi\";", + " }", + " }", + " static class TestFunc3 implements Fn {", + " @Override", + " public String apply(String s) {", + " return \"hi\";", + " }", + " }", + " static class TestFunc4 implements Fn {", + " // this override is legal, we should get no error", + " @Override", + " public String apply(@Nullable String s) {", + " return \"hi\";", + " }", + " }", + " static void useTestFunc(String s) {", + " Fn<@Nullable String, String> f1 = new TestFunc2();", + " // should get no error here", + " f1.apply(null);", + " Fn f2 = new TestFunc3();", + " // BUG: Diagnostic contains: passing @Nullable parameter", + " f2.apply(null);", + " }", + "}") + .doTest(); + } + + @Test + public void nullableGenericTypeVariableReturnType() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " interface Fn

{", + " @Nullable R apply(P p);", + " }", + " static class TestFunc implements Fn {", + " @Override", + " //This override is fine and is handled by the current code", + " public @Nullable String apply(String s) {", + " return s;", + " }", + " }", + " static void useTestFunc(String s) {", + " Fn f = new TestFunc();", + " String t = f.apply(s);", + " // BUG: Diagnostic contains: dereferenced expression", + " t.hashCode();", + " }", + "}") + .doTest(); + } + + @Test + public void overrideWithNestedTypeParametersInReturnType() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " class P{}", + " interface Fn, T4 extends @Nullable Object> {", + " T3 apply();", + " }", + " class TestFunc1 implements Fn, @Nullable String> {", + " @Override", + " // BUG: Diagnostic contains: Method returns P<@Nullable String, @Nullable String>, but overridden method", + " public P<@Nullable String, @Nullable String> apply() {", + " return new P<@Nullable String, @Nullable String>();", + " }", + " }", + " class TestFunc2 implements Fn, @Nullable String> {", + " @Override", + " // BUG: Diagnostic contains: Method returns P<@Nullable String, String>, but overridden method returns", + " public P<@Nullable String, String> apply() {", + " return new P<@Nullable String, String>();", + " }", + " }", + " class TestFunc3 implements Fn, @Nullable String> {", + " @Override", + " public P<@Nullable String, @Nullable String> apply() {", + " return new P<@Nullable String, @Nullable String>();", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void overrideWithNestedTypeParametersInParameterType() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " class P{}", + " interface Fn, R extends @Nullable Object> {", + " String apply(T t, String s);", + " }", + " class TestFunc implements Fn, String> {", + " @Override", + " // BUG: Diagnostic contains: Parameter has type P<@Nullable String, String>, but overridden method has parameter type P", + " public String apply(P<@Nullable String, String> p, String s) {", + " return s;", + " }", + " }", + " class TestFunc2 implements Fn, String> {", + " @Override", + " public String apply(P p, String s) {", + " return s;", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void interactionWithContracts() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "import org.jetbrains.annotations.Contract;", + "class Test {", + " interface Fn1

{", + " R apply(P p);", + " }", + " static class TestFunc1 implements Fn1<@Nullable String, @Nullable String> {", + " @Override", + " @Contract(\"!null -> !null\")", + " public @Nullable String apply(@Nullable String s) {", + " return s;", + " }", + " }", + " interface Fn2

{", + " @Contract(\"!null -> !null\")", + " R apply(P p);", + " }", + " static class TestFunc2 implements Fn2<@Nullable String, @Nullable String> {", + " @Override", + " public @Nullable String apply(@Nullable String s) {", + " return s;", + " }", + " }", + " static class TestFunc2_Bad implements Fn2<@Nullable String, @Nullable String> {", + " @Override", + " public @Nullable String apply(@Nullable String s) {", + " // False negative: with contract checking enabled, this should be rejected", + " // See https://github.com/uber/NullAway/issues/803", + " return null;", + " }", + " }", + " static void testMethod() {", + " // No error due to @Contract", + " (new TestFunc1()).apply(\"hello\").hashCode();", + " Fn1<@Nullable String, @Nullable String> fn1 = new TestFunc1();", + " // BUG: Diagnostic contains: dereferenced expression fn1.apply(\"hello\")", + " fn1.apply(\"hello\").hashCode();", + " // BUG: Diagnostic contains: dereferenced expression (new TestFunc2())", + " (new TestFunc2()).apply(\"hello\").hashCode();", + " Fn2<@Nullable String, @Nullable String> fn2 = new TestFunc2();", + " // No error due to @Contract", + " fn2.apply(\"hello\").hashCode();", + " }", + "}") + .doTest(); + } + private CompilationTestHelper makeHelper() { return makeTestHelperWithArgs( Arrays.asList( From 91186bd0453a8592285e9cf4ed161dbce70ccfbc Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 14 Aug 2023 12:21:20 -0700 Subject: [PATCH 34/57] Enable contract checking when building NullAway (#802) I think we only have one `@Contract` annotation in the NullAway code base right now, but anyway this will give us a bit more test coverage of contract checking. --- nullaway/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/nullaway/build.gradle b/nullaway/build.gradle index af27fcf69b..b9811c1267 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -171,6 +171,7 @@ tasks.register('buildWithNullAway', JavaCompile) { option("NullAway:CastToNonNullMethod", "com.uber.nullaway.NullabilityUtil.castToNonNull") option("NullAway:CheckOptionalEmptiness") option("NullAway:AcknowledgeRestrictiveAnnotations") + option("NullAway:CheckContracts") } // Make sure the jar has already been built dependsOn 'jar' From 9a427678f0e75d9a136b03e0ae94aa2ebed68086 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 14 Aug 2023 13:07:54 -0700 Subject: [PATCH 35/57] Bump Error Prone Gradle Plugin version (#804) Just to stay up to date. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a16feec7cf..07f6250836 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ buildscript { } plugins { id "com.diffplug.spotless" version "6.20.0" - id "net.ltgt.errorprone" version "3.0.1" apply false + id "net.ltgt.errorprone" version "3.1.0" apply false id "com.github.johnrengelman.shadow" version "8.1.1" apply false id "com.github.kt3k.coveralls" version "2.12.0" apply false id "me.champeau.jmh" version "0.7.1" apply false From 540eaa93d023719231bb3b14694d984b17faf7b9 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Wed, 16 Aug 2023 14:48:20 -0400 Subject: [PATCH 36/57] Make GenericsChecks methods static (#805) Previously, some methods in `GenericsChecks` were static methods and some were instance methods. We did not get much of any benefit from the instance methods, as it would be painful to create a single `GenericsChecks` object and thread it everywhere it is needed. So, this PR just converts all `GenericsChecks` methods to be static and passes parameters as needed (which isn't too painful). --- .../com/uber/nullaway/GenericsChecks.java | 184 ++++++++++++------ .../main/java/com/uber/nullaway/NullAway.java | 40 ++-- 2 files changed, 144 insertions(+), 80 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index c4ba2f1504..bd1be2a876 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -40,15 +40,9 @@ public final class GenericsChecks { private static final Supplier NULLABLE_TYPE_SUPPLIER = Suppliers.typeFromString(NULLABLE_NAME); - private VisitorState state; - private Config config; - private NullAway analysis; - - public GenericsChecks(VisitorState state, Config config, NullAway analysis) { - this.state = state; - this.config = config; - this.analysis = analysis; - } + + /** Do not instantiate; all methods should be static */ + private GenericsChecks() {} /** * Checks that for an instantiated generic type, {@code @Nullable} types are only used for type @@ -173,7 +167,7 @@ private static void reportMismatchedTypeForTernaryOperator( errorMessage, analysis.buildDescription(tree), state, null)); } - private void reportInvalidParametersNullabilityError( + private static void reportInvalidParametersNullabilityError( Type formalParameterType, Type actualParameterType, ExpressionTree paramExpression, @@ -193,8 +187,12 @@ private void reportInvalidParametersNullabilityError( errorMessage, analysis.buildDescription(paramExpression), state, null)); } - private void reportInvalidOverridingMethodReturnTypeError( - Tree methodTree, Type overriddenMethodReturnType, Type overridingMethodReturnType) { + private static void reportInvalidOverridingMethodReturnTypeError( + Tree methodTree, + Type overriddenMethodReturnType, + Type overridingMethodReturnType, + NullAway analysis, + VisitorState state) { ErrorBuilder errorBuilder = analysis.getErrorBuilder(); ErrorMessage errorMessage = new ErrorMessage( @@ -209,8 +207,12 @@ private void reportInvalidOverridingMethodReturnTypeError( errorMessage, analysis.buildDescription(methodTree), state, null)); } - private void reportInvalidOverridingMethodParamTypeError( - Tree formalParameterTree, Type typeParameterType, Type methodParamType) { + private static void reportInvalidOverridingMethodParamTypeError( + Tree formalParameterTree, + Type typeParameterType, + Type methodParamType, + NullAway analysis, + VisitorState state) { ErrorBuilder errorBuilder = analysis.getErrorBuilder(); ErrorMessage errorMessage = new ErrorMessage( @@ -234,10 +236,11 @@ private void reportInvalidOverridingMethodParamTypeError( * Foo<@Nullable A>}). * * @param tree A tree for which we need the type with preserved annotations. + * @param state the visitor state * @return Type of the tree with preserved annotations. */ @Nullable - private Type getTreeType(Tree tree) { + private static Type getTreeType(Tree tree, VisitorState state) { if (tree instanceof NewClassTree && ((NewClassTree) tree).getIdentifier() instanceof ParameterizedTypeTree) { ParameterizedTypeTree paramTypedTree = @@ -247,7 +250,7 @@ private Type getTreeType(Tree tree) { // TODO: support diamond operators return null; } - return typeWithPreservedAnnotations(paramTypedTree); + return typeWithPreservedAnnotations(paramTypedTree, state); } else { Type result = ASTHelpers.getType(tree); if (result != null && result.isRaw()) { @@ -266,9 +269,12 @@ private Type getTreeType(Tree tree) { * * @param tree the tree to check, which must be either an {@link AssignmentTree} or a {@link * VariableTree} + * @param analysis the analysis object + * @param state the visitor state */ - public void checkTypeParameterNullnessForAssignability(Tree tree) { - if (!config.isJSpecifyMode()) { + public static void checkTypeParameterNullnessForAssignability( + Tree tree, NullAway analysis, VisitorState state) { + if (!analysis.getConfig().isJSpecifyMode()) { return; } Tree lhsTree; @@ -287,21 +293,33 @@ public void checkTypeParameterNullnessForAssignability(Tree tree) { if (rhsTree == null || rhsTree.getKind().equals(Tree.Kind.NULL_LITERAL)) { return; } - Type lhsType = getTreeType(lhsTree); - Type rhsType = getTreeType(rhsTree); + Type lhsType = getTreeType(lhsTree, state); + Type rhsType = getTreeType(rhsTree, state); if (lhsType instanceof Type.ClassType && rhsType instanceof Type.ClassType) { boolean isAssignmentValid = - compareNullabilityAnnotations((Type.ClassType) lhsType, (Type.ClassType) rhsType); + compareNullabilityAnnotations((Type.ClassType) lhsType, (Type.ClassType) rhsType, state); if (!isAssignmentValid) { reportInvalidAssignmentInstantiationError(tree, lhsType, rhsType, state, analysis); } } } - public void checkTypeParameterNullnessForFunctionReturnType( - ExpressionTree retExpr, Symbol.MethodSymbol methodSymbol) { - if (!config.isJSpecifyMode()) { + /** + * Checks that the nullability of type parameters for a returned expression matches that of the + * type parameters of the enclosing method's return type. + * + * @param retExpr the returned expression + * @param methodSymbol symbol for enclosing method + * @param analysis the analysis object + * @param state the visitor state + */ + public static void checkTypeParameterNullnessForFunctionReturnType( + ExpressionTree retExpr, + Symbol.MethodSymbol methodSymbol, + NullAway analysis, + VisitorState state) { + if (!analysis.getConfig().isJSpecifyMode()) { return; } @@ -310,12 +328,12 @@ public void checkTypeParameterNullnessForFunctionReturnType( if (formalReturnType.getTypeArguments().isEmpty()) { return; } - Type returnExpressionType = getTreeType(retExpr); + Type returnExpressionType = getTreeType(retExpr, state); if (formalReturnType instanceof Type.ClassType && returnExpressionType instanceof Type.ClassType) { boolean isReturnTypeValid = compareNullabilityAnnotations( - (Type.ClassType) formalReturnType, (Type.ClassType) returnExpressionType); + (Type.ClassType) formalReturnType, (Type.ClassType) returnExpressionType, state); if (!isReturnTypeValid) { reportInvalidReturnTypeError( retExpr, formalReturnType, returnExpressionType, state, analysis); @@ -333,8 +351,10 @@ public void checkTypeParameterNullnessForFunctionReturnType( * * @param lhsType type for the lhs of the assignment * @param rhsType type for the rhs of the assignment + * @param state the visitor state */ - private boolean compareNullabilityAnnotations(Type.ClassType lhsType, Type.ClassType rhsType) { + private static boolean compareNullabilityAnnotations( + Type.ClassType lhsType, Type.ClassType rhsType, VisitorState state) { Types types = state.getTypes(); // The base type of rhsType may be a subtype of lhsType's base type. In such cases, we must // compare lhsType against the supertype of rhsType with a matching base type. @@ -379,7 +399,7 @@ private boolean compareNullabilityAnnotations(Type.ClassType lhsType, Type.Class // nested generics if (lhsTypeArgument.getTypeArguments().length() > 0) { if (!compareNullabilityAnnotations( - (Type.ClassType) lhsTypeArgument, (Type.ClassType) rhsTypeArgument)) { + (Type.ClassType) lhsTypeArgument, (Type.ClassType) rhsTypeArgument, state)) { return false; } } @@ -393,9 +413,11 @@ private boolean compareNullabilityAnnotations(Type.ClassType lhsType, Type.Class * Type of the tree with the annotations. * * @param tree A parameterized typed tree for which we need class type with preserved annotations. + * @param state the visitor state * @return A Type with preserved annotations. */ - private Type.ClassType typeWithPreservedAnnotations(ParameterizedTypeTree tree) { + private static Type.ClassType typeWithPreservedAnnotations( + ParameterizedTypeTree tree, VisitorState state) { Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); Preconditions.checkNotNull(type); Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); @@ -435,7 +457,8 @@ private Type.ClassType typeWithPreservedAnnotations(ParameterizedTypeTree tree) Type currentTypeArgType = castToNonNull(ASTHelpers.getType(curTypeArg)); if (currentTypeArgType.getTypeArguments().size() > 0) { // nested generic type; recursively preserve its nullability type argument annotations - currentTypeArgType = typeWithPreservedAnnotations((ParameterizedTypeTree) curTypeArg); + currentTypeArgType = + typeWithPreservedAnnotations((ParameterizedTypeTree) curTypeArg, state); } Type.ClassType newTypeArgType = (Type.ClassType) currentTypeArgType.cloneWithMetadata(typeMetadata); @@ -459,32 +482,35 @@ private Type.ClassType typeWithPreservedAnnotations(ParameterizedTypeTree tree) * somewhat confusing; we may want to improve this in the future. * * @param tree A conditional expression tree to check + * @param analysis the analysis object + * @param state the visitor state */ - public void checkTypeParameterNullnessForConditionalExpression(ConditionalExpressionTree tree) { - if (!config.isJSpecifyMode()) { + public static void checkTypeParameterNullnessForConditionalExpression( + ConditionalExpressionTree tree, NullAway analysis, VisitorState state) { + if (!analysis.getConfig().isJSpecifyMode()) { return; } Tree truePartTree = tree.getTrueExpression(); Tree falsePartTree = tree.getFalseExpression(); - Type condExprType = getTreeType(tree); - Type truePartType = getTreeType(truePartTree); - Type falsePartType = getTreeType(falsePartTree); + Type condExprType = getTreeType(tree, state); + Type truePartType = getTreeType(truePartTree, state); + Type falsePartType = getTreeType(falsePartTree, state); // The condExpr type should be the least-upper bound of the true and false part types. To check // the nullability annotations, we check that the true and false parts are assignable to the // type of the whole expression if (condExprType instanceof Type.ClassType) { if (truePartType instanceof Type.ClassType) { if (!compareNullabilityAnnotations( - (Type.ClassType) condExprType, (Type.ClassType) truePartType)) { + (Type.ClassType) condExprType, (Type.ClassType) truePartType, state)) { reportMismatchedTypeForTernaryOperator( truePartTree, condExprType, truePartType, state, analysis); } } if (falsePartType instanceof Type.ClassType) { if (!compareNullabilityAnnotations( - (Type.ClassType) condExprType, (Type.ClassType) falsePartType)) { + (Type.ClassType) condExprType, (Type.ClassType) falsePartType, state)) { reportMismatchedTypeForTernaryOperator( falsePartTree, condExprType, falsePartType, state, analysis); } @@ -499,12 +525,16 @@ public void checkTypeParameterNullnessForConditionalExpression(ConditionalExpres * @param formalParams the formal parameters * @param actualParams the actual parameters * @param isVarArgs true if the call is to a varargs method + * @param analysis the analysis object + * @param state the visitor state */ - public void compareGenericTypeParameterNullabilityForCall( + public static void compareGenericTypeParameterNullabilityForCall( List formalParams, List actualParams, - boolean isVarArgs) { - if (!config.isJSpecifyMode()) { + boolean isVarArgs, + NullAway analysis, + VisitorState state) { + if (!analysis.getConfig().isJSpecifyMode()) { return; } int n = formalParams.size(); @@ -516,11 +546,11 @@ public void compareGenericTypeParameterNullabilityForCall( for (int i = 0; i < n; i++) { Type formalParameter = formalParams.get(i).type; if (!formalParameter.getTypeArguments().isEmpty()) { - Type actualParameter = getTreeType(actualParams.get(i)); + Type actualParameter = getTreeType(actualParams.get(i), state); if (formalParameter instanceof Type.ClassType && actualParameter instanceof Type.ClassType) { if (!compareNullabilityAnnotations( - (Type.ClassType) formalParameter, (Type.ClassType) actualParameter)) { + (Type.ClassType) formalParameter, (Type.ClassType) actualParameter, state)) { reportInvalidParametersNullabilityError( formalParameter, actualParameter, actualParams.get(i), state, analysis); } @@ -533,11 +563,11 @@ public void compareGenericTypeParameterNullabilityForCall( Type varargsElementType = varargsArrayType.elemtype; if (varargsElementType.getTypeArguments().size() > 0) { for (int i = formalParams.size() - 1; i < actualParams.size(); i++) { - Type actualParameter = getTreeType(actualParams.get(i)); + Type actualParameter = getTreeType(actualParams.get(i), state); if (varargsElementType instanceof Type.ClassType && actualParameter instanceof Type.ClassType) { if (!compareNullabilityAnnotations( - (Type.ClassType) varargsElementType, (Type.ClassType) actualParameter)) { + (Type.ClassType) varargsElementType, (Type.ClassType) actualParameter, state)) { reportInvalidParametersNullabilityError( varargsElementType, actualParameter, actualParams.get(i), state, analysis); } @@ -554,10 +584,16 @@ public void compareGenericTypeParameterNullabilityForCall( * @param tree A method tree to check * @param overridingMethod A symbol of the overriding method * @param overriddenMethod A symbol of the overridden method + * @param analysis the analysis object + * @param state the visitor state */ - public void checkTypeParameterNullnessForMethodOverriding( - MethodTree tree, Symbol.MethodSymbol overridingMethod, Symbol.MethodSymbol overriddenMethod) { - if (!config.isJSpecifyMode()) { + public static void checkTypeParameterNullnessForMethodOverriding( + MethodTree tree, + Symbol.MethodSymbol overridingMethod, + Symbol.MethodSymbol overriddenMethod, + NullAway analysis, + VisitorState state) { + if (!analysis.getConfig().isJSpecifyMode()) { return; } // Obtain type parameters for the overridden method within the context of the overriding @@ -565,8 +601,10 @@ public void checkTypeParameterNullnessForMethodOverriding( Type methodWithTypeParams = state.getTypes().memberType(overridingMethod.owner.type, overriddenMethod); - checkTypeParameterNullnessForOverridingMethodReturnType(tree, methodWithTypeParams); - checkTypeParameterNullnessForOverridingMethodParameterType(tree, methodWithTypeParams); + checkTypeParameterNullnessForOverridingMethodReturnType( + tree, methodWithTypeParams, analysis, state); + checkTypeParameterNullnessForOverridingMethodParameterType( + tree, methodWithTypeParams, analysis, state); } /** @@ -683,18 +721,25 @@ public static Nullness getGenericReturnNullnessAtInvocation( * @param paramIndex parameter index * @param invokedMethodSymbol symbol for the invoked method * @param tree the tree for the invocation + * @param state the visitor state + * @param config the analysis config * @return Nullness of parameter at {@code paramIndex}, or {@code NONNULL} if the call does not * invoke an instance method */ - public Nullness getGenericParameterNullnessAtInvocation( - int paramIndex, Symbol.MethodSymbol invokedMethodSymbol, MethodInvocationTree tree) { + public static Nullness getGenericParameterNullnessAtInvocation( + int paramIndex, + Symbol.MethodSymbol invokedMethodSymbol, + MethodInvocationTree tree, + VisitorState state, + Config config) { if (!(tree.getMethodSelect() instanceof MemberSelectTree)) { return Nullness.NONNULL; } Type methodReceiverType = castToNonNull( ASTHelpers.getType(((MemberSelectTree) tree.getMethodSelect()).getExpression())); - return getGenericMethodParameterNullness(paramIndex, invokedMethodSymbol, methodReceiverType); + return getGenericMethodParameterNullness( + paramIndex, invokedMethodSymbol, methodReceiverType, state, config); } /** @@ -723,11 +768,17 @@ public Nullness getGenericParameterNullnessAtInvocation( * @param method the generic method * @param enclosingType the enclosing type in which we want to know {@code method}'s parameter * type nullability + * @param state the visitor state + * @param config the analysis config * @return nullability of the relevant parameter type of {@code method} in the context of {@code * enclosingType} */ - public Nullness getGenericMethodParameterNullness( - int parameterIndex, Symbol.MethodSymbol method, Type enclosingType) { + public static Nullness getGenericMethodParameterNullness( + int parameterIndex, + Symbol.MethodSymbol method, + Type enclosingType, + VisitorState state, + Config config) { Type methodType = state.getTypes().memberType(enclosingType, method); Type paramType = methodType.getParameterTypes().get(parameterIndex); return getTypeNullness(paramType, config); @@ -740,9 +791,11 @@ public Nullness getGenericMethodParameterNullness( * * @param tree tree for overriding method * @param overriddenMethodType type of the overridden method + * @param analysis the analysis object + * @param state the visitor state */ - private void checkTypeParameterNullnessForOverridingMethodParameterType( - MethodTree tree, Type overriddenMethodType) { + private static void checkTypeParameterNullnessForOverridingMethodParameterType( + MethodTree tree, Type overriddenMethodType, NullAway analysis, VisitorState state) { List methodParameters = tree.getParameters(); List overriddenMethodParameterTypes = overriddenMethodType.getParameterTypes(); // TODO handle varargs; they are not handled for now @@ -753,11 +806,14 @@ private void checkTypeParameterNullnessForOverridingMethodParameterType( && overridingMethodParameterType instanceof Type.ClassType) { if (!compareNullabilityAnnotations( (Type.ClassType) overriddenMethodParameterType, - (Type.ClassType) overridingMethodParameterType)) { + (Type.ClassType) overridingMethodParameterType, + state)) { reportInvalidOverridingMethodParamTypeError( methodParameters.get(i), overriddenMethodParameterType, - overridingMethodParameterType); + overridingMethodParameterType, + analysis, + state); } } } @@ -770,9 +826,11 @@ private void checkTypeParameterNullnessForOverridingMethodParameterType( * * @param tree tree for overriding method * @param overriddenMethodType type of the overridden method + * @param analysis the analysis object + * @param state the visitor state */ - private void checkTypeParameterNullnessForOverridingMethodReturnType( - MethodTree tree, Type overriddenMethodType) { + private static void checkTypeParameterNullnessForOverridingMethodReturnType( + MethodTree tree, Type overriddenMethodType, NullAway analysis, VisitorState state) { Type overriddenMethodReturnType = overriddenMethodType.getReturnType(); Type overridingMethodReturnType = ASTHelpers.getType(tree.getReturnType()); if (!(overriddenMethodReturnType instanceof Type.ClassType)) { @@ -780,9 +838,11 @@ private void checkTypeParameterNullnessForOverridingMethodReturnType( } Preconditions.checkArgument(overridingMethodReturnType instanceof Type.ClassType); if (!compareNullabilityAnnotations( - (Type.ClassType) overriddenMethodReturnType, (Type.ClassType) overridingMethodReturnType)) { + (Type.ClassType) overriddenMethodReturnType, + (Type.ClassType) overridingMethodReturnType, + state)) { reportInvalidOverridingMethodReturnTypeError( - tree, overriddenMethodReturnType, overridingMethodReturnType); + tree, overriddenMethodReturnType, overridingMethodReturnType, analysis, state); } } diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index 5353b57161..bfed0af259 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -221,6 +221,11 @@ private enum NullMarking { private final Config config; + /** Returns the configuration being used for this analysis. */ + public Config getConfig() { + return config; + } + private final ErrorBuilder errorBuilder; /** @@ -471,7 +476,7 @@ public Description matchAssignment(AssignmentTree tree, VisitorState state) { } // generics check if (lhsType != null && lhsType.getTypeArguments().length() > 0) { - new GenericsChecks(state, config, this).checkTypeParameterNullnessForAssignability(tree); + GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state); } Symbol assigned = ASTHelpers.getSymbol(tree.getVariable()); @@ -631,9 +636,8 @@ public Description matchMethod(MethodTree tree, VisitorState state) { if (config.isJSpecifyMode()) { // Check that any generic type parameters in the return type and parameter types are // identical (invariant) across the overriding and overridden methods - new GenericsChecks(state, config, this) - .checkTypeParameterNullnessForMethodOverriding( - tree, methodSymbol, closestOverriddenMethod); + GenericsChecks.checkTypeParameterNullnessForMethodOverriding( + tree, methodSymbol, closestOverriddenMethod, this, state); } return checkOverriding(closestOverriddenMethod, methodSymbol, null, state); } @@ -731,9 +735,12 @@ private Description checkParamOverriding( Nullness.paramHasNullableAnnotation(overriddenMethod, i, config) ? Nullness.NULLABLE : (config.isJSpecifyMode() - ? new GenericsChecks(state, config, this) - .getGenericMethodParameterNullness( - i, overriddenMethod, overridingParamSymbols.get(i).owner.owner.type) + ? GenericsChecks.getGenericMethodParameterNullness( + i, + overriddenMethod, + overridingParamSymbols.get(i).owner.owner.type, + state, + config) : Nullness.NONNULL); } } @@ -862,8 +869,8 @@ private Description checkReturnExpression( state, methodSymbol); } - new GenericsChecks(state, config, this) - .checkTypeParameterNullnessForFunctionReturnType(retExpr, methodSymbol); + GenericsChecks.checkTypeParameterNullnessForFunctionReturnType( + retExpr, methodSymbol, this, state); return Description.NO_MATCH; } @@ -1359,7 +1366,7 @@ public Description matchVariable(VariableTree tree, VisitorState state) { } VarSymbol symbol = ASTHelpers.getSymbol(tree); if (tree.getInitializer() != null) { - new GenericsChecks(state, config, this).checkTypeParameterNullnessForAssignability(tree); + GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state); } if (symbol.type.isPrimitive() && tree.getInitializer() != null) { @@ -1511,8 +1518,7 @@ public Description matchUnary(UnaryTree tree, VisitorState state) { public Description matchConditionalExpression( ConditionalExpressionTree tree, VisitorState state) { if (withinAnnotatedCode(state)) { - new GenericsChecks(state, config, this) - .checkTypeParameterNullnessForConditionalExpression(tree); + GenericsChecks.checkTypeParameterNullnessForConditionalExpression(tree, this, state); doUnboxingCheck(state, tree.getCondition()); } return Description.NO_MATCH; @@ -1638,15 +1644,13 @@ private Description handleInvocation( Nullness.paramHasNullableAnnotation(methodSymbol, i, config) ? Nullness.NULLABLE : ((config.isJSpecifyMode() && tree instanceof MethodInvocationTree) - ? new GenericsChecks(state, config, this) - .getGenericParameterNullnessAtInvocation( - i, methodSymbol, (MethodInvocationTree) tree) + ? GenericsChecks.getGenericParameterNullnessAtInvocation( + i, methodSymbol, (MethodInvocationTree) tree, state, config) : Nullness.NONNULL); } } - new GenericsChecks(state, config, this) - .compareGenericTypeParameterNullabilityForCall( - formalParams, actualParams, methodSymbol.isVarArgs()); + GenericsChecks.compareGenericTypeParameterNullabilityForCall( + formalParams, actualParams, methodSymbol.isVarArgs(), this, state); } // Allow handlers to override the list of non-null argument positions From 7cf3e3bdfd4518fb77a80ea496362b3137caadb6 Mon Sep 17 00:00:00 2001 From: Md Armughanuddin <52311490+armughan11@users.noreply.github.com> Date: Mon, 21 Aug 2023 22:43:09 +0530 Subject: [PATCH 37/57] Modify JMH Benchmark Workflow For Shellcheck (#813) Enable shellcheck checking as part of our build, and fix Shellcheck errors for JMH workflow files. --------- Co-authored-by: Manu Sridharan --- .buildscript/check_git_clean.sh | 2 ++ .github/workflows/continuous-integration.yml | 5 +++++ .github/workflows/get_repo_details.sh | 4 ++-- .github/workflows/run_gcp_benchmarks.sh | 8 ++++---- .github/workflows/run_main_benchmarks.sh | 8 ++++---- .github/workflows/run_pr_benchmarks.sh | 10 +++++----- build.gradle | 11 +++++++++++ 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.buildscript/check_git_clean.sh b/.buildscript/check_git_clean.sh index 8668c07c9d..1ffa522f07 100755 --- a/.buildscript/check_git_clean.sh +++ b/.buildscript/check_git_clean.sh @@ -1,3 +1,5 @@ +#!/bin/sh -eux + if [ -n "$(git status --porcelain)" ]; then echo 'warning: source tree contains uncommitted changes; .gitignore patterns may need to be fixed' git status diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 185735733e..11bcff71e7 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -53,6 +53,11 @@ jobs: uses: gradle/gradle-build-action@v2 with: arguments: build + - name: Run shellcheck + uses: gradle/gradle-build-action@v2 + with: + arguments: shellcheck + if: runner.os == 'Linux' - name: Report jacoco coverage uses: gradle/gradle-build-action@v2 env: diff --git a/.github/workflows/get_repo_details.sh b/.github/workflows/get_repo_details.sh index 55521665f4..fc448af858 100644 --- a/.github/workflows/get_repo_details.sh +++ b/.github/workflows/get_repo_details.sh @@ -16,5 +16,5 @@ REPO_FULL_NAME=$(echo "$PR_DETAILS" | jq -r .head.repo.full_name) BRANCH_NAME=$(echo "$PR_DETAILS" | jq -r .head.ref) # Export vars to GITHUB_ENV so they can be used by later scripts -echo "REPO_FULL_NAME=$REPO_FULL_NAME" >> $GITHUB_ENV -echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV +echo "REPO_FULL_NAME=$REPO_FULL_NAME" >> "$GITHUB_ENV" +echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_ENV" diff --git a/.github/workflows/run_gcp_benchmarks.sh b/.github/workflows/run_gcp_benchmarks.sh index 059e72c4f6..9dc8604f87 100644 --- a/.github/workflows/run_gcp_benchmarks.sh +++ b/.github/workflows/run_gcp_benchmarks.sh @@ -7,14 +7,14 @@ chmod +x ./.github/workflows/gcloud_ssh.sh ./.github/workflows/gcloud_ssh.sh "export BRANCH_NAME=${BRANCH_NAME} && mkdir $BRANCH_NAME" # Using gcloud compute scp to copy the bash scripts that will run the benchmarks onto the GCCE -gcloud compute scp ./.github/workflows/run_pr_benchmarks.sh root@nullway-jmh:$BRANCH_NAME/ --zone=us-central1-a -gcloud compute scp ./.github/workflows/run_main_benchmarks.sh root@nullway-jmh:$BRANCH_NAME/ --zone=us-central1-a +gcloud compute scp ./.github/workflows/run_pr_benchmarks.sh root@nullway-jmh:"$BRANCH_NAME/" --zone=us-central1-a +gcloud compute scp ./.github/workflows/run_main_benchmarks.sh root@nullway-jmh:"$BRANCH_NAME/" --zone=us-central1-a # Running the benchmark script for the pull request branch and main branch on GCCE ./.github/workflows/gcloud_ssh.sh " export BRANCH_NAME=${BRANCH_NAME} && export REPO_NAME=${REPO_FULL_NAME} && chmod +x $BRANCH_NAME/run_pr_benchmarks.sh && $BRANCH_NAME/run_pr_benchmarks.sh && cd && chmod +x $BRANCH_NAME/run_main_benchmarks.sh && $BRANCH_NAME/run_main_benchmarks.sh" # Copying the benchmark results from GCCE back to the Github runner for the PR branch -gcloud compute scp root@nullway-jmh:$BRANCH_NAME/pr/NullAway/jmh/build/results/jmh/results.txt ./pr_text.txt --zone=us-central1-a +gcloud compute scp root@nullway-jmh:"$BRANCH_NAME/pr/NullAway/jmh/build/results/jmh/results.txt" ./pr_text.txt --zone=us-central1-a # Copying the benchmark results from GCCE back to the Github runner for the main branch -gcloud compute scp root@nullway-jmh:$BRANCH_NAME/main/NullAway/jmh/build/results/jmh/results.txt ./main_text.txt --zone=us-central1-a +gcloud compute scp root@nullway-jmh:"$BRANCH_NAME/main/NullAway/jmh/build/results/jmh/results.txt" ./main_text.txt --zone=us-central1-a diff --git a/.github/workflows/run_main_benchmarks.sh b/.github/workflows/run_main_benchmarks.sh index 27d58187aa..fad59dcd0d 100644 --- a/.github/workflows/run_main_benchmarks.sh +++ b/.github/workflows/run_main_benchmarks.sh @@ -1,9 +1,9 @@ -#!/bin/bash +#!/bin/bash -eux -cd $BRANCH_NAME/ +cd "$BRANCH_NAME/" mkdir main -cd main/ +cd main/ git clone git@github.com:Uber/NullAway.git -cd NullAway/ +cd NullAway/ ./gradlew jmh --no-daemon diff --git a/.github/workflows/run_pr_benchmarks.sh b/.github/workflows/run_pr_benchmarks.sh index 0627db598c..83c4a3b20d 100644 --- a/.github/workflows/run_pr_benchmarks.sh +++ b/.github/workflows/run_pr_benchmarks.sh @@ -1,9 +1,9 @@ -#!/bin/bash +#!/bin/bash -eux -cd $BRANCH_NAME/ +cd "$BRANCH_NAME/" mkdir pr -cd pr/ -git clone --branch $BRANCH_NAME --single-branch git@github.com:$REPO_NAME.git NullAway -cd NullAway/ +cd pr/ +git clone --branch "$BRANCH_NAME" --single-branch git@github.com:"$REPO_NAME".git NullAway +cd NullAway/ ./gradlew jmh --no-daemon diff --git a/build.gradle b/build.gradle index 07f6250836..fd309c61fb 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ plugins { id "com.github.kt3k.coveralls" version "2.12.0" apply false id "me.champeau.jmh" version "0.7.1" apply false id "com.github.ben-manes.versions" version "0.47.0" + id "com.felipefzdz.gradle.shellcheck" version "1.4.6" } repositories { @@ -121,6 +122,16 @@ spotless { spotlessPredeclare { java { googleJavaFormat('1.17.0') } } + +shellcheck { + useDocker = false + shellcheckBinary = "shellcheck" + sourceFiles = + fileTree(".") { + include("**/*.sh") + } +} + //////////////////////////////////////////////////////////////////////// // // Google Java Format pre-commit hook installation From 5c836045a41baedb0601aac5c62084a57aed5c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Clapp?= Date: Tue, 22 Aug 2023 00:07:33 -0400 Subject: [PATCH 38/57] Allow library models to define custom stream classes. (#807) This is needed to support both fully custom stream libraries, as well as backports/shadowed versions of existing stream libraries. --- .../java/com/uber/nullaway/LibraryModels.java | 20 ++++++++- .../com/uber/nullaway/handlers/Handlers.java | 6 ++- .../handlers/LibraryModelsHandler.java | 34 +++++++++++++++ .../StreamNullabilityPropagatorFactory.java | 18 ++++++++ .../handlers/stream/StreamModelBuilder.java | 12 ++++++ .../NullAwayStreamSupportNegativeCases.java | 21 +++++++++ .../NullAwayStreamSupportPositiveCases.java | 23 ++++++++++ .../testdata/unannotated/CustomStream.java | 43 +++++++++++++++++++ .../unannotated/CustomStreamWithoutModel.java | 43 +++++++++++++++++++ .../testlibrarymodels/TestLibraryModels.java | 35 +++++++++++++++ 10 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStream.java create mode 100644 nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStreamWithoutModel.java diff --git a/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java b/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java index d801d40036..a0816b3712 100644 --- a/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java +++ b/nullaway/src/main/java/com/uber/nullaway/LibraryModels.java @@ -22,9 +22,11 @@ package com.uber.nullaway; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.sun.tools.javac.code.Symbol; +import com.uber.nullaway.handlers.stream.StreamTypeRecord; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -130,7 +132,23 @@ public interface LibraryModels { ImmutableSetMultimap castToNonNullMethods(); /** - * representation of a method as a qualified class name + a signature for the method + * Get a list of custom stream library specifications. + * + *

This allows users to define filter/map/other methods for APIs which behave similarly to Java + * 8 streams or ReactiveX streams, so that NullAway is able to understand nullability invariants + * across stream API calls. See {@link com.uber.nullaway.handlers.stream.StreamModelBuilder} for + * details on how to construct these {@link com.uber.nullaway.handlers.stream.StreamTypeRecord} + * specs. A full example is available at {@link + * com.uber.nullaway.testlibrarymodels.TestLibraryModels}. + * + * @return A list of StreamTypeRecord specs (usually generated using StreamModelBuilder). + */ + default ImmutableList customStreamNullabilitySpecs() { + return ImmutableList.of(); + } + + /** + * Representation of a method as a qualified class name + a signature for the method * *

The formatting of a method signature should match the result of calling {@link * Symbol.MethodSymbol#toString()} on the corresponding symbol. See {@link diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java index 606a7bb847..22792ddd5f 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java @@ -57,9 +57,13 @@ public static Handler buildDefault(Config config) { handlerListBuilder.add(new AssertionHandler(methodNameUtil)); } handlerListBuilder.add(new GuavaAssertionsHandler()); - handlerListBuilder.add(new LibraryModelsHandler(config)); + LibraryModelsHandler libraryModelsHandler = new LibraryModelsHandler(config); + handlerListBuilder.add(libraryModelsHandler); handlerListBuilder.add(StreamNullabilityPropagatorFactory.getRxStreamNullabilityPropagator()); handlerListBuilder.add(StreamNullabilityPropagatorFactory.getJavaStreamNullabilityPropagator()); + handlerListBuilder.add( + StreamNullabilityPropagatorFactory.fromSpecs( + libraryModelsHandler.getStreamNullabilitySpecs())); handlerListBuilder.add(new ContractHandler(config)); handlerListBuilder.add(new ApacheThriftIsSetHandler()); handlerListBuilder.add(new GrpcHandler()); diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java index 1ed32ebac6..e000c4fdcf 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java @@ -27,6 +27,7 @@ import static com.uber.nullaway.Nullness.NULLABLE; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.errorprone.VisitorState; @@ -46,6 +47,7 @@ import com.uber.nullaway.Nullness; import com.uber.nullaway.dataflow.AccessPath; import com.uber.nullaway.dataflow.AccessPathNullnessPropagation; +import com.uber.nullaway.handlers.stream.StreamTypeRecord; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -291,6 +293,25 @@ private void setUnconditionalArgumentNullness( } } + /** + * Get all the stream specifications loaded from any of our library models. + * + *

This is used in Handlers.java to create a StreamNullabilityPropagator handler, which gets + * registered independently of this LibraryModelsHandler itself. + * + *

LibraryModelsHandler is responsible from reading the library models for stream specs, but + * beyond that, checking of the specs falls under the responsibility of the generated + * StreamNullabilityPropagator handler. + * + * @return The list of all stream specifications loaded from any of our library models. + */ + public ImmutableList getStreamNullabilitySpecs() { + // Note: Currently, OptimizedLibraryModels doesn't carry the information about stream type + // records, it is not clear what it means to "optimize" lookup for those and they get accessed + // only once by calling this method during handler setup in Handlers.java. + return libraryModels.customStreamNullabilitySpecs(); + } + private static LibraryModels loadLibraryModels(Config config) { Iterable externalLibraryModels = ServiceLoader.load(LibraryModels.class, LibraryModels.class.getClassLoader()); @@ -827,6 +848,8 @@ private static class CombinedLibraryModels implements LibraryModels { private final ImmutableSetMultimap castToNonNullMethods; + private final ImmutableList customStreamNullabilitySpecs; + public CombinedLibraryModels(Iterable models, Config config) { this.config = config; ImmutableSetMultimap.Builder failIfNullParametersBuilder = @@ -845,6 +868,8 @@ public CombinedLibraryModels(Iterable models, Config config) { ImmutableSet.Builder nonNullReturnsBuilder = new ImmutableSet.Builder<>(); ImmutableSetMultimap.Builder castToNonNullMethodsBuilder = new ImmutableSetMultimap.Builder<>(); + ImmutableList.Builder customStreamNullabilitySpecsBuilder = + new ImmutableList.Builder<>(); for (LibraryModels libraryModels : models) { for (Map.Entry entry : libraryModels.failIfNullParameters().entries()) { if (shouldSkipModel(entry.getKey())) { @@ -904,6 +929,9 @@ public CombinedLibraryModels(Iterable models, Config config) { } castToNonNullMethodsBuilder.put(entry); } + for (StreamTypeRecord streamTypeRecord : libraryModels.customStreamNullabilitySpecs()) { + customStreamNullabilitySpecsBuilder.add(streamTypeRecord); + } } failIfNullParameters = failIfNullParametersBuilder.build(); explicitlyNullableParameters = explicitlyNullableParametersBuilder.build(); @@ -914,6 +942,7 @@ public CombinedLibraryModels(Iterable models, Config config) { nullableReturns = nullableReturnsBuilder.build(); nonNullReturns = nonNullReturnsBuilder.build(); castToNonNullMethods = castToNonNullMethodsBuilder.build(); + customStreamNullabilitySpecs = customStreamNullabilitySpecsBuilder.build(); } private boolean shouldSkipModel(MethodRef key) { @@ -964,6 +993,11 @@ public ImmutableSet nonNullReturns() { public ImmutableSetMultimap castToNonNullMethods() { return castToNonNullMethods; } + + @Override + public ImmutableList customStreamNullabilitySpecs() { + return customStreamNullabilitySpecs; + } } /** diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java b/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java index 0642296b8e..a3695c67ee 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagatorFactory.java @@ -29,6 +29,8 @@ import com.uber.nullaway.handlers.stream.StreamTypeRecord; public class StreamNullabilityPropagatorFactory { + + /** Returns a handler for the standard Java 8 stream APIs. */ public static StreamNullabilityPropagator getJavaStreamNullabilityPropagator() { ImmutableList streamModels = StreamModelBuilder.start() @@ -78,6 +80,7 @@ public static StreamNullabilityPropagator getJavaStreamNullabilityPropagator() { return new StreamNullabilityPropagator(streamModels); } + /** Returns a handler for io.reactivex.* stream APIs */ public static StreamNullabilityPropagator getRxStreamNullabilityPropagator() { ImmutableList rxModels = StreamModelBuilder.start() @@ -137,4 +140,19 @@ public static StreamNullabilityPropagator getRxStreamNullabilityPropagator() { return new StreamNullabilityPropagator(rxModels); } + + /** + * Create a new StreamNullabilityPropagator from a list of StreamTypeRecord specs. + * + *

This is used to create a new StreamNullabilityPropagator based on stream API specs provided + * by library models. + * + * @param streamNullabilitySpecs the list of StreamTypeRecord objects defining one or more stream + * APIs (from {@link StreamModelBuilder}). + * @return A handler corresponding to the stream APIs defined by the given specs. + */ + public static StreamNullabilityPropagator fromSpecs( + ImmutableList streamNullabilitySpecs) { + return new StreamNullabilityPropagator(streamNullabilitySpecs); + } } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java index e0381832d3..94e0b1a6e8 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/stream/StreamModelBuilder.java @@ -25,6 +25,8 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.errorprone.predicates.TypePredicate; +import com.google.errorprone.predicates.type.DescendantOf; +import com.google.errorprone.suppliers.Suppliers; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; @@ -90,6 +92,16 @@ public StreamModelBuilder addStreamType(TypePredicate tp) { return this; } + /** + * Add a stream type to our models based on the type's fully qualified name. + * + * @param fullyQualifiedName the FQN of the class/interface in our stream-based API. + * @return This builder (for chaining). + */ + public StreamModelBuilder addStreamTypeFromName(String fullyQualifiedName) { + return this.addStreamType(new DescendantOf(Suppliers.typeFromString(fullyQualifiedName))); + } + private void initializeBuilders() { this.filterMethodSigs = ImmutableSet.builder(); this.filterMethodSimpleNames = ImmutableSet.builder(); diff --git a/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportNegativeCases.java b/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportNegativeCases.java index d67a94d02e..742a638b7e 100644 --- a/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportNegativeCases.java +++ b/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportNegativeCases.java @@ -24,6 +24,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import com.uber.nullaway.testdata.unannotated.CustomStream; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.DoubleStream; @@ -288,6 +289,26 @@ private void filterThenForEachOrdered(Stream> stream) stream.filter(s -> s.get() != null).forEachOrdered(s -> System.out.println(s.get().length())); } + // CustomStream is modeled in TestLibraryModels + private CustomStream filterThenMapLambdasCustomStream(CustomStream stream) { + return stream.filter(s -> s != null).map(s -> s.length()); + } + + private CustomStream filterThenMapNullableContainerLambdasCustomStream( + CustomStream> stream) { + return stream + .filter(c -> c.get() != null) + .map(c -> c.get().length()); + } + + private CustomStream filterThenMapMethodRefsCustomStream( + CustomStream> stream) { + return stream + .filter(c -> c.get() != null && perhaps()) + .map(NullableContainer::get) + .map(String::length); + } + private static class CheckFinalBeforeStream { @Nullable private final T ref; diff --git a/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportPositiveCases.java b/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportPositiveCases.java index e6c306e98c..82a12a00b9 100644 --- a/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportPositiveCases.java +++ b/nullaway/src/test/resources/com/uber/nullaway/testdata/NullAwayStreamSupportPositiveCases.java @@ -23,6 +23,7 @@ package com.uber.nullaway.testdata; import com.google.common.base.Preconditions; +import com.uber.nullaway.testdata.unannotated.CustomStreamWithoutModel; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.DoubleStream; @@ -153,6 +154,28 @@ private void forEachOrdered(Stream> stream) { stream.forEachOrdered(s -> System.out.println(s.get().length())); } + // CustomStreamWithoutModel is NOT modeled in TestLibraryModels + private CustomStreamWithoutModel filterThenMapLambdasCustomStream(CustomStreamWithoutModel stream) { + // Safe because generic is String, not @Nullable String + return stream.filter(s -> s != null).map(s -> s.length()); + } + + private CustomStreamWithoutModel filterThenMapNullableContainerLambdasCustomStream( + CustomStreamWithoutModel> stream) { + return stream + .filter(c -> c.get() != null) + // BUG: Diagnostic contains: dereferenced expression + .map(c -> c.get().length()); + } + + private CustomStreamWithoutModel filterThenMapMethodRefsCustomStream( + CustomStreamWithoutModel> stream) { + return stream + .filter(c -> c.get() != null && perhaps()) + .map(NullableContainer::get) // CSWoM> -> CSWoM<@Nullable String> + .map(String::length); // Should be an error with proper generics support! + } + private static class CheckNonfinalBeforeStream { @Nullable private T ref; diff --git a/nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStream.java b/nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStream.java new file mode 100644 index 0000000000..22ff34ab8c --- /dev/null +++ b/nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStream.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Uber Technologies, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.nullaway.testdata.unannotated; + +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * A class representing a custom implementation of java.util.stream.Stream to test the ability to + * define stream handler specs through library models. + */ +public class CustomStream { + + private CustomStream() {} + + public CustomStream filter(Predicate predicate) { + throw new UnsupportedOperationException(); + } + + public CustomStream map(Function mapper) { + throw new UnsupportedOperationException(); + } +} diff --git a/nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStreamWithoutModel.java b/nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStreamWithoutModel.java new file mode 100644 index 0000000000..1fd7037980 --- /dev/null +++ b/nullaway/src/test/resources/com/uber/nullaway/testdata/unannotated/CustomStreamWithoutModel.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Uber Technologies, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.nullaway.testdata.unannotated; + +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * A copy of {@link CustomStream} but for which our test library models have no spec/model, to test that errors still + * get reported in the absence of a model. + */ +public class CustomStreamWithoutModel { + + private CustomStreamWithoutModel() {} + + public CustomStreamWithoutModel filter(Predicate predicate) { + throw new UnsupportedOperationException(); + } + + public CustomStreamWithoutModel map(Function mapper) { + throw new UnsupportedOperationException(); + } +} diff --git a/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java b/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java index 3b84b8f8d5..c94dfef501 100644 --- a/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java +++ b/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java @@ -18,9 +18,12 @@ import static com.uber.nullaway.LibraryModels.MethodRef.methodRef; import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.uber.nullaway.LibraryModels; +import com.uber.nullaway.handlers.stream.StreamModelBuilder; +import com.uber.nullaway.handlers.stream.StreamTypeRecord; @AutoService(LibraryModels.class) public class TestLibraryModels implements LibraryModels { @@ -87,4 +90,36 @@ public ImmutableSetMultimap castToNonNullMethods() { 1) .build(); } + + @Override + public ImmutableList customStreamNullabilitySpecs() { + // Identical to the default model for java.util.stream.Stream, but with the original type + // renamed + return StreamModelBuilder.start() + .addStreamTypeFromName("com.uber.nullaway.testdata.unannotated.CustomStream") + .withFilterMethodFromSignature("filter(java.util.function.Predicate)") + .withMapMethodFromSignature( + "map(java.util.function.Function)", + "apply", + ImmutableSet.of(0)) + .withMapMethodFromSignature( + "mapToInt(java.util.function.ToIntFunction)", + "applyAsInt", + ImmutableSet.of(0)) + .withMapMethodFromSignature( + "mapToLong(java.util.function.ToLongFunction)", + "applyAsLong", + ImmutableSet.of(0)) + .withMapMethodFromSignature( + "mapToDouble(java.util.function.ToDoubleFunction)", + "applyAsDouble", + ImmutableSet.of(0)) + .withMapMethodFromSignature( + "forEach(java.util.function.Consumer)", "accept", ImmutableSet.of(0)) + .withMapMethodFromSignature( + "forEachOrdered(java.util.function.Consumer)", "accept", ImmutableSet.of(0)) + .withMapMethodAllFromName("flatMap", "apply", ImmutableSet.of(0)) + .withPassthroughMethodFromSignature("distinct()") + .end(); + } } From e05fd9a9d19b88cba5392ab3c0d661cf6a448971 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 21 Aug 2023 21:19:15 -0700 Subject: [PATCH 39/57] Bump gradle maven publish plugin from 0.21.0 to 0.25.3 (#810) [Changelog for plugin](https://github.com/vanniktech/gradle-maven-publish-plugin/blob/cf02bca4185083bfc5d258e81e82939b5b2451a8/CHANGELOG.md) We have to do some renaming as the `javaSourcesJar` configuration is now just `sourcesJar`. Also, the `:nullaway:buildWithNullAway` previously used a hacky way to refer to the current NullAway jar that broke with this upgrade. Here we clean things up to refer to the current jar more directly. The plugin has better integration with Sonatype and will close the staging repository after all artifacts are uploaded. There is an option to automatically release as well, but I haven't enabled that for now (it makes testing of release uploads difficult), so you still need to go to https://oss.sonatype.org to finalize the release. Relatedly, apparently we don't need `--no-daemon` and `--no-parallel` flags anymore when cutting a release; updated the release docs accordingly. I tested locally and confirmed that the exact same number of files get included in the jar-infer-cli shadow jar, and that cutting a real release successfully uploads to Maven Central. --- RELEASING.md | 2 +- build.gradle | 2 +- jar-infer/jar-infer-cli/build.gradle | 4 ++-- jar-infer/test-java-lib-jarinfer/build.gradle | 2 +- nullaway/build.gradle | 12 ++++-------- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 5ed5d44719..e9635dbf34 100755 --- a/RELEASING.md +++ b/RELEASING.md @@ -32,7 +32,7 @@ Releasing 3. Update the `README.md` with the new version. 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 5. `git tag -a vX.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version) - 6. `./gradlew clean publish --no-daemon --no-parallel` + 6. `./gradlew clean publish` 7. Update the `gradle.properties` to the next SNAPSHOT version. 8. `git commit -am "Prepare next development version."` 9. `git push && git push --tags` diff --git a/build.gradle b/build.gradle index fd309c61fb..d790aee856 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.0' - classpath 'com.vanniktech:gradle-maven-publish-plugin:0.21.0' + classpath 'com.vanniktech:gradle-maven-publish-plugin:0.25.3' // This restriction is needed due to our mix of Android and Java modules; // without it, the build fails with a weird error. // See https://stackoverflow.com/questions/70217853/how-to-include-android-project-in-a-gradle-multi-project-build diff --git a/jar-infer/jar-infer-cli/build.gradle b/jar-infer/jar-infer-cli/build.gradle index 2d737e22b8..9766c76a12 100644 --- a/jar-infer/jar-infer-cli/build.gradle +++ b/jar-infer/jar-infer-cli/build.gradle @@ -79,7 +79,7 @@ publishing { // Since we are skipping the default maven publication, we append the `:sources` and // `:javadoc` artifacts here. They are also required for Maven Central validation. afterEvaluate { - artifact project.javaSourcesJar + artifact project.sourcesJar artifact project.javadocJar } // The shadow publication does not auto-configure the pom.xml file for us, so we need to @@ -118,7 +118,7 @@ publishing { // Not sure why e.g. publishShadowPublicationToMavenCentralRepository must depend on signMavenPublication // (rather than just signShadowPublication) project.tasks.named('generateMetadataFileForMavenPublication').configure { - dependsOn 'javaSourcesJar' + dependsOn 'sourcesJar' dependsOn 'simpleJavadocJar' } if (project.tasks.findByName('signShadowPublication')) { diff --git a/jar-infer/test-java-lib-jarinfer/build.gradle b/jar-infer/test-java-lib-jarinfer/build.gradle index 7e60265438..d3906baed6 100644 --- a/jar-infer/test-java-lib-jarinfer/build.gradle +++ b/jar-infer/test-java-lib-jarinfer/build.gradle @@ -34,7 +34,7 @@ jar { jar.doLast { javaexec { - classpath = files("${rootProject.projectDir}/jar-infer/jar-infer-cli/build/libs/jar-infer-cli-${VERSION_NAME}.jar") + classpath = files("${rootProject.projectDir}/jar-infer/jar-infer-cli/build/libs/jar-infer-cli.jar") main = "com.uber.nullaway.jarinfer.JarInfer" args = ["-i", jar.archiveFile.get(), "-o", "${jar.destinationDirectory.get()}/${astubxPath}"] } diff --git a/nullaway/build.gradle b/nullaway/build.gradle index b9811c1267..f658e8f4de 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -65,9 +65,6 @@ dependencies { testImplementation deps.test.mockitoInline testImplementation deps.test.javaxAnnotationApi testImplementation deps.test.assertJ - - // This ends up being resolved to the NullAway jar under nullaway/build/libs - nullawayJar "com.uber.nullaway:nullaway:$VERSION_NAME" } javadoc { @@ -160,11 +157,12 @@ tasks.register('buildWithNullAway', JavaCompile) { source = sourceSets.main.java classpath = sourceSets.main.compileClasspath destinationDirectory = file("$buildDir/ignoredClasses") - def nullawayDeps = configurations.nullawayJar.asCollection() options.annotationProcessorPath = files( configurations.errorprone.asCollection(), sourceSets.main.annotationProcessorPath, - nullawayDeps) + // This refers to the NullAway jar built from the current source + jar.archiveFile.get(), + sourceSets.main.compileClasspath) options.errorprone.enabled = true options.errorprone { option("NullAway:AnnotatedPackages", "com.uber,org.checkerframework.nullaway") @@ -178,9 +176,7 @@ tasks.register('buildWithNullAway', JavaCompile) { // Check that the NullAway jar actually exists (without this, // Gradle will run the compilation even if the jar doesn't exist) doFirst { - nullawayDeps.forEach { f -> - assert f.exists() - } + assert jar.archiveFile.get().getAsFile().exists() } } From 72e6d6f1da5c425ebd3cb9f678a17ce59b6c5943 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 21 Aug 2023 21:26:15 -0700 Subject: [PATCH 40/57] Use Spotless to enforce consistent formatting for Gradle build scripts (#809) I used 4-space indentation to be consistent with what most of our scripts were already doing. The only changes here are enabling the Gradle script formatting checks and the corresponding needed reformatting. --- annotations/build.gradle | 36 +++++++++---------- build.gradle | 10 ++++++ guava-recent-unit-tests/build.gradle | 24 ++++++------- jar-infer/jar-infer-cli/build.gradle | 10 +++--- jar-infer/jar-infer-lib/build.gradle | 24 ++++++------- .../nullaway-integration-test/build.gradle | 33 +++++++++-------- jar-infer/test-java-lib-jarinfer/build.gradle | 11 ++++-- jdk17-unit-tests/build.gradle | 27 +++++++------- jmh/build.gradle | 35 +++++++++--------- nullaway/build.gradle | 28 +++++++-------- sample-app/build.gradle | 23 ++++++------ sample-library-model/build.gradle | 14 ++++---- sample/build.gradle | 14 ++++---- test-java-lib-lombok/build.gradle | 22 ++++++------ test-java-lib/build.gradle | 14 ++++---- test-library-models/build.gradle | 14 ++++---- 16 files changed, 176 insertions(+), 163 deletions(-) diff --git a/annotations/build.gradle b/annotations/build.gradle index 55e960775b..5e06f02c25 100644 --- a/annotations/build.gradle +++ b/annotations/build.gradle @@ -16,30 +16,30 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { - id 'java-library' - id 'nullaway.jacoco-conventions' + id 'java-library' + id 'nullaway.jacoco-conventions' } dependencies { } test { - maxHeapSize = "1024m" - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] + maxHeapSize = "1024m" + // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer + jvmArgs += [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + ] } apply plugin: 'com.vanniktech.maven.publish' diff --git a/build.gradle b/build.gradle index d790aee856..df01f2361c 100644 --- a/build.gradle +++ b/build.gradle @@ -112,6 +112,13 @@ subprojects { project -> java { googleJavaFormat() } + groovyGradle { + target '**/*.gradle' + greclipse() + indentWithSpaces(4) + trimTrailingWhitespace() + endWithNewline() + } } } } @@ -121,6 +128,9 @@ spotless { } spotlessPredeclare { java { googleJavaFormat('1.17.0') } + groovyGradle { + greclipse() + } } shellcheck { diff --git a/guava-recent-unit-tests/build.gradle b/guava-recent-unit-tests/build.gradle index 8efb6d0815..b81bd0f8df 100644 --- a/guava-recent-unit-tests/build.gradle +++ b/guava-recent-unit-tests/build.gradle @@ -35,18 +35,18 @@ test { maxHeapSize = "1024m" // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", ] } diff --git a/jar-infer/jar-infer-cli/build.gradle b/jar-infer/jar-infer-cli/build.gradle index 9766c76a12..5333a5d74e 100644 --- a/jar-infer/jar-infer-cli/build.gradle +++ b/jar-infer/jar-infer-cli/build.gradle @@ -34,9 +34,7 @@ java { jar { manifest { - attributes( - 'Main-Class': 'com.uber.nullaway.jarinfer.JarInfer' - ) + attributes('Main-Class': 'com.uber.nullaway.jarinfer.JarInfer') } // add this classifier so that the output file for the jar task differs from // the output file for the shadowJar task (otherwise they overwrite each other's @@ -46,7 +44,9 @@ jar { shadowJar { mergeServiceFiles() - configurations = [project.configurations.runtimeClasspath] + configurations = [ + project.configurations.runtimeClasspath + ] archiveClassifier = "" } shadowJar.dependsOn jar @@ -110,7 +110,7 @@ publishing { } } } - } + } afterEvaluate { // Below is a series of hacks needed to get publication to work with diff --git a/jar-infer/jar-infer-lib/build.gradle b/jar-infer/jar-infer-lib/build.gradle index 9327cc5152..ec221573c2 100644 --- a/jar-infer/jar-infer-lib/build.gradle +++ b/jar-infer/jar-infer-lib/build.gradle @@ -51,18 +51,18 @@ test { maxHeapSize = "1024m" // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", ] dependsOn ':jar-infer:test-android-lib-jarinfer:bundleReleaseAar' } diff --git a/jar-infer/nullaway-integration-test/build.gradle b/jar-infer/nullaway-integration-test/build.gradle index 543b0c60bb..03da29711a 100644 --- a/jar-infer/nullaway-integration-test/build.gradle +++ b/jar-infer/nullaway-integration-test/build.gradle @@ -14,18 +14,17 @@ * limitations under the License. */ plugins { - id "java-library" - id "nullaway.jacoco-conventions" + id "java-library" + id "nullaway.jacoco-conventions" } dependencies { - testImplementation deps.test.junit4 - testImplementation(deps.build.errorProneTestHelpers) { + testImplementation deps.test.junit4 + testImplementation(deps.build.errorProneTestHelpers) { exclude group: "junit", module: "junit" } - testImplementation project(":nullaway") - testImplementation project(":jar-infer:test-java-lib-jarinfer") - + testImplementation project(":nullaway") + testImplementation project(":jar-infer:test-java-lib-jarinfer") } @@ -33,15 +32,15 @@ test { maxHeapSize = "1024m" // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", ] } diff --git a/jar-infer/test-java-lib-jarinfer/build.gradle b/jar-infer/test-java-lib-jarinfer/build.gradle index d3906baed6..df1a7a0683 100644 --- a/jar-infer/test-java-lib-jarinfer/build.gradle +++ b/jar-infer/test-java-lib-jarinfer/build.gradle @@ -15,7 +15,7 @@ */ plugins { - id "java-library" + id "java-library" } evaluationDependsOn(":jar-infer:jar-infer-cli") @@ -28,7 +28,7 @@ jar { 'Created-By' : "Gradle ${gradle.gradleVersion}", 'Build-Jdk' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})", 'Build-OS' : "${System.properties['os.name']} ${System.properties['os.arch']} ${System.properties['os.version']}" - ) + ) } } @@ -36,7 +36,12 @@ jar.doLast { javaexec { classpath = files("${rootProject.projectDir}/jar-infer/jar-infer-cli/build/libs/jar-infer-cli.jar") main = "com.uber.nullaway.jarinfer.JarInfer" - args = ["-i", jar.archiveFile.get(), "-o", "${jar.destinationDirectory.get()}/${astubxPath}"] + args = [ + "-i", + jar.archiveFile.get(), + "-o", + "${jar.destinationDirectory.get()}/${astubxPath}" + ] } exec { workingDir "./build/libs" diff --git a/jdk17-unit-tests/build.gradle b/jdk17-unit-tests/build.gradle index 506e81dd48..8f37181bff 100644 --- a/jdk17-unit-tests/build.gradle +++ b/jdk17-unit-tests/build.gradle @@ -47,19 +47,18 @@ test { maxHeapSize = "1024m" // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Expose a module path for tests as a JVM property. - // Used by com.uber.nullaway.jdk17.NullAwayModuleInfoTests - "-Dtest.module.path=${configurations.testModulePath.asPath}" + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Expose a module path for tests as a JVM property. + // Used by com.uber.nullaway.jdk17.NullAwayModuleInfoTests + "-Dtest.module.path=${configurations.testModulePath.asPath}" ] } - diff --git a/jmh/build.gradle b/jmh/build.gradle index 2481e23461..03752ffbbc 100644 --- a/jmh/build.gradle +++ b/jmh/build.gradle @@ -105,13 +105,13 @@ def nullawayReleaseProcessorpath = configurations.nullawayReleaseProcessors.asPa // Extra JVM arguments to expose relevant paths for compiling benchmarks def extraJVMArgs = [ - "-Dnullaway.caffeine.sources=${caffeineSourceDir.get()}", - "-Dnullaway.caffeine.classpath=$caffeineClasspath", - "-Dnullaway.autodispose.sources=${autodisposeSourceDir.get()}", - "-Dnullaway.autodispose.classpath=$autodisposeClasspath", - "-Dnullaway.nullawayRelease.sources=${nullawayReleaseSourceDir.get()}", - "-Dnullaway.nullawayRelease.classpath=$nullawayReleaseClasspath", - "-Dnullaway.nullawayRelease.processorpath=$nullawayReleaseProcessorpath", + "-Dnullaway.caffeine.sources=${caffeineSourceDir.get()}", + "-Dnullaway.caffeine.classpath=$caffeineClasspath", + "-Dnullaway.autodispose.sources=${autodisposeSourceDir.get()}", + "-Dnullaway.autodispose.classpath=$autodisposeClasspath", + "-Dnullaway.nullawayRelease.sources=${nullawayReleaseSourceDir.get()}", + "-Dnullaway.nullawayRelease.classpath=$nullawayReleaseClasspath", + "-Dnullaway.nullawayRelease.processorpath=$nullawayReleaseProcessorpath", ] jmh { @@ -134,16 +134,15 @@ tasks.named('test') { jvmArgs extraJVMArgs // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", ] } - diff --git a/nullaway/build.gradle b/nullaway/build.gradle index f658e8f4de..5b6346f4a9 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -16,8 +16,8 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { - id 'java-library' - id 'nullaway.jacoco-conventions' + id 'java-library' + id 'nullaway.jacoco-conventions' } configurations { @@ -76,18 +76,18 @@ test { maxHeapSize = "1024m" // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", ] if (deps.versions.errorProneApi == "2.4.0" && JavaVersion.current() >= JavaVersion.VERSION_17) { // This test does not pass on JDK 17 with Error Prone 2.4.0 due to a Mockito incompatibility. Skip it (the diff --git a/sample-app/build.gradle b/sample-app/build.gradle index 595a305694..6c4deb2f8b 100644 --- a/sample-app/build.gradle +++ b/sample-app/build.gradle @@ -39,7 +39,7 @@ android { lintOptions { abortOnError false } - + DomainObjectSet variants = getApplicationVariants() // or getLibraryVariants() in libraries variants.addAll(getTestVariants()) variants.addAll(getUnitTestVariants()) @@ -51,17 +51,17 @@ android { } } } - + // If you want to disable NullAway in just tests, you can do the below -// DomainObjectSet testVariants = getTestVariants() -// testVariants.addAll(getUnitTestVariants()) -// testVariants.configureEach { variant -> -// variant.getJavaCompileProvider().configure { -// options.errorprone { -// check("NullAway", CheckSeverity.OFF) -// } -// } -// } + // DomainObjectSet testVariants = getTestVariants() + // testVariants.addAll(getUnitTestVariants()) + // testVariants.configureEach { variant -> + // variant.getJavaCompileProvider().configure { + // options.errorprone { + // check("NullAway", CheckSeverity.OFF) + // } + // } + // } } dependencies { @@ -70,7 +70,6 @@ dependencies { annotationProcessor project(path: ":sample-library-model") testImplementation deps.test.junit4 - } spotless { diff --git a/sample-library-model/build.gradle b/sample-library-model/build.gradle index 01ca784f99..8feee697c9 100644 --- a/sample-library-model/build.gradle +++ b/sample-library-model/build.gradle @@ -17,7 +17,7 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { - id "java-library" + id "java-library" } dependencies { @@ -29,10 +29,10 @@ dependencies { } tasks.withType(JavaCompile) { - if (!name.toLowerCase().contains("test")) { - options.errorprone { - check("NullAway", CheckSeverity.ERROR) - option("NullAway:AnnotatedPackages", "com.uber") + if (!name.toLowerCase().contains("test")) { + options.errorprone { + check("NullAway", CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "com.uber") + } } - } -} \ No newline at end of file +} diff --git a/sample/build.gradle b/sample/build.gradle index 7c80543b4d..58ddd90185 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -17,7 +17,7 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { - id "java-library" + id "java-library" } dependencies { @@ -29,10 +29,10 @@ dependencies { } tasks.withType(JavaCompile) { - if (!name.toLowerCase().contains("test")) { - options.errorprone { - check("NullAway", CheckSeverity.ERROR) - option("NullAway:AnnotatedPackages", "com.uber") + if (!name.toLowerCase().contains("test")) { + options.errorprone { + check("NullAway", CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "com.uber") + } } - } -} +} diff --git a/test-java-lib-lombok/build.gradle b/test-java-lib-lombok/build.gradle index 011a1b9013..c70f54828d 100644 --- a/test-java-lib-lombok/build.gradle +++ b/test-java-lib-lombok/build.gradle @@ -17,7 +17,7 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { - id "java-library" + id "java-library" } dependencies { @@ -31,14 +31,16 @@ dependencies { } tasks.withType(JavaCompile) { - if (!name.toLowerCase().contains("test")) { - options.errorprone { - check("NullAway", CheckSeverity.ERROR) - option("NullAway:AnnotatedPackages", "com.uber") - option("NullAway:UnannotatedSubPackages", "com.uber.lib.unannotated") + if (!name.toLowerCase().contains("test")) { + options.errorprone { + check("NullAway", CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "com.uber") + option("NullAway:UnannotatedSubPackages", "com.uber.lib.unannotated") + } } - } - // We need to fork on JDK 16+ since Lombok accesses internal compiler APIs - options.fork = true - options.forkOptions.jvmArgs += ["--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED"] + // We need to fork on JDK 16+ since Lombok accesses internal compiler APIs + options.fork = true + options.forkOptions.jvmArgs += [ + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED" + ] } diff --git a/test-java-lib/build.gradle b/test-java-lib/build.gradle index f877afb08e..79323d6ebf 100644 --- a/test-java-lib/build.gradle +++ b/test-java-lib/build.gradle @@ -17,7 +17,7 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { - id "java-library" + id "java-library" } dependencies { @@ -30,11 +30,11 @@ dependencies { } tasks.withType(JavaCompile) { - if (!name.toLowerCase().contains("test")) { - options.errorprone { - check("NullAway", CheckSeverity.ERROR) - option("NullAway:AnnotatedPackages", "com.uber") - option("NullAway:UnannotatedSubPackages", "com.uber.lib.unannotated") + if (!name.toLowerCase().contains("test")) { + options.errorprone { + check("NullAway", CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "com.uber") + option("NullAway:UnannotatedSubPackages", "com.uber.lib.unannotated") + } } - } } diff --git a/test-library-models/build.gradle b/test-library-models/build.gradle index 01ca784f99..8feee697c9 100644 --- a/test-library-models/build.gradle +++ b/test-library-models/build.gradle @@ -17,7 +17,7 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { - id "java-library" + id "java-library" } dependencies { @@ -29,10 +29,10 @@ dependencies { } tasks.withType(JavaCompile) { - if (!name.toLowerCase().contains("test")) { - options.errorprone { - check("NullAway", CheckSeverity.ERROR) - option("NullAway:AnnotatedPackages", "com.uber") + if (!name.toLowerCase().contains("test")) { + options.errorprone { + check("NullAway", CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "com.uber") + } } - } -} \ No newline at end of file +} From 56d5ff142a3cacc5ee4b9be7137d099befe0407d Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 22 Aug 2023 08:30:50 -0700 Subject: [PATCH 41/57] More gradle script formatting (#814) Follow-up to #809. I had put the configuration for Gradle formatting in the wrong place previously (only applying to sub-projects), so a couple Gradle scripts were missed. --- build.gradle | 52 +++++++++++++++++++------------------- gradle/dependencies.gradle | 17 ++++++++----- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/build.gradle b/build.gradle index df01f2361c..db827fa246 100644 --- a/build.gradle +++ b/build.gradle @@ -35,18 +35,18 @@ buildscript { } } plugins { - id "com.diffplug.spotless" version "6.20.0" - id "net.ltgt.errorprone" version "3.1.0" apply false - id "com.github.johnrengelman.shadow" version "8.1.1" apply false - id "com.github.kt3k.coveralls" version "2.12.0" apply false - id "me.champeau.jmh" version "0.7.1" apply false - id "com.github.ben-manes.versions" version "0.47.0" - id "com.felipefzdz.gradle.shellcheck" version "1.4.6" + id "com.diffplug.spotless" version "6.20.0" + id "net.ltgt.errorprone" version "3.1.0" apply false + id "com.github.johnrengelman.shadow" version "8.1.1" apply false + id "com.github.kt3k.coveralls" version "2.12.0" apply false + id "me.champeau.jmh" version "0.7.1" apply false + id "com.github.ben-manes.versions" version "0.47.0" + id "com.felipefzdz.gradle.shellcheck" version "1.4.6" } repositories { - // to get the google-java-format jar and dependencies - mavenCentral() + // to get the google-java-format jar and dependencies + mavenCentral() } apply from: "gradle/dependencies.gradle" @@ -59,10 +59,10 @@ subprojects { project -> project.tasks.withType(JavaCompile) { dependsOn(installGitHooks) options.compilerArgs += [ - "-Xlint:deprecation", - "-Xlint:rawtypes", - "-Xlint:unchecked", - "-Werror" + "-Xlint:deprecation", + "-Xlint:rawtypes", + "-Xlint:unchecked", + "-Werror" ] options.errorprone { // disable warnings in generated code; AutoValue code fails UnnecessaryParentheses check @@ -84,7 +84,7 @@ subprojects { project -> // To enable auto-patching, uncomment the line below, replace [CheckerName] with // the checker(s) you want to apply patches for (comma-separated), and above, disable // "-Werror" -// errorproneArgs.addAll("-XepPatchChecks:[CheckerName]", "-XepPatchLocation:IN_PLACE") + // errorproneArgs.addAll("-XepPatchChecks:[CheckerName]", "-XepPatchLocation:IN_PLACE") } } @@ -112,19 +112,19 @@ subprojects { project -> java { googleJavaFormat() } - groovyGradle { - target '**/*.gradle' - greclipse() - indentWithSpaces(4) - trimTrailingWhitespace() - endWithNewline() - } } } } spotless { predeclareDeps() + groovyGradle { + target '**/*.gradle' + greclipse() + indentWithSpaces(4) + trimTrailingWhitespace() + endWithNewline() + } } spotlessPredeclare { java { googleJavaFormat('1.17.0') } @@ -148,9 +148,9 @@ shellcheck { // tasks.register('installGitHooks', Copy) { - from(file('config/hooks/pre-commit-stub')) { - rename 'pre-commit-stub', 'pre-commit' - } - into file('.git/hooks') - fileMode 0777 + from(file('config/hooks/pre-commit-stub')) { + rename 'pre-commit-stub', 'pre-commit' + } + into file('.git/hooks') + fileMode 0777 } diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 481be0dc74..85b515f945 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -32,8 +32,8 @@ if (project.hasProperty("epApiVersion")) { } if (epApiVNum.compareTo(VersionNumber.parse(oldestErrorProneVersion)) < 0) { throw new IllegalArgumentException( - "Error Prone API version " + epApiVersion + " is too old; " - + oldestErrorProneVersion + " is the oldest supported version") + "Error Prone API version " + epApiVersion + " is too old; " + + oldestErrorProneVersion + " is the oldest supported version") } errorProneVersionToCompileAgainst = epApiVersion } @@ -77,9 +77,11 @@ def build = [ jspecify : "org.jspecify:jspecify:0.3.0", jsr305Annotations : "com.google.code.findbugs:jsr305:3.0.2", commonsIO : "commons-io:commons-io:2.11.0", - wala : ["com.ibm.wala:com.ibm.wala.util:${versions.wala}", - "com.ibm.wala:com.ibm.wala.shrike:${versions.wala}", - "com.ibm.wala:com.ibm.wala.core:${versions.wala}"], + wala : [ + "com.ibm.wala:com.ibm.wala.util:${versions.wala}", + "com.ibm.wala:com.ibm.wala.shrike:${versions.wala}", + "com.ibm.wala:com.ibm.wala.core:${versions.wala}" + ], commonscli : "commons-cli:commons-cli:${versions.commonscli}", // android stuff @@ -96,7 +98,10 @@ def support = [ def test = [ junit4 : "junit:junit:4.13.2", - junit5Jupiter : ["org.junit.jupiter:junit-jupiter-api:5.0.2","org.apiguardian:apiguardian-api:1.0.0"], + junit5Jupiter : [ + "org.junit.jupiter:junit-jupiter-api:5.0.2", + "org.apiguardian:apiguardian-api:1.0.0" + ], jetbrainsAnnotations : "org.jetbrains:annotations:13.0", cfQual : "org.checkerframework:checker-qual:${versions.checkerFramework}", // 2.5.5 is the last release to contain this artifact From 154b7583781a4b63fce57412692baffc516ec804 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 25 Aug 2023 10:56:50 -0700 Subject: [PATCH 42/57] Avoid suggesting castToNonNull fixes in certain cases (#799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We avoid suggesting a `castToNonNull` fix if either (1) the expression is the `null` literal (i.e., don't suggest `castToNonNull(null)`), or (2) the expression is a `@Nullable` parameter (see code comments for justification). In these cases, we suggest a `@SuppressWarnings` annotation instead. Here we also add some tests to improve coverage of the `ErrorBuilder` class. --------- Co-authored-by: Lázaro Clapp --- .../java/com/uber/nullaway/ErrorBuilder.java | 44 ++++++- .../nullaway/NullAwayAutoSuggestTest.java | 122 +++++++++++++++++- 2 files changed, 160 insertions(+), 6 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java index 629e477e6c..5188f823b1 100755 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java @@ -31,6 +31,7 @@ import static com.uber.nullaway.NullAway.INITIALIZATION_CHECK_NAME; import static com.uber.nullaway.NullAway.OPTIONAL_CHECK_NAME; import static com.uber.nullaway.NullAway.getTreesInstance; +import static com.uber.nullaway.Nullness.hasNullableAnnotation; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; @@ -61,6 +62,7 @@ import java.util.stream.StreamSupport; import javax.annotation.Nullable; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.tools.JavaFileObject; /** A class to construct error message to be displayed after the analysis finds error. */ @@ -198,10 +200,16 @@ private Description.Builder addSuggestedSuppression( case PASS_NULLABLE: case ASSIGN_FIELD_NULLABLE: case SWITCH_EXPRESSION_NULLABLE: - if (config.getCastToNonNullMethod() != null) { + if (config.getCastToNonNullMethod() != null && canBeCastToNonNull(suggestTree)) { builder = addCastToNonNullFix(suggestTree, builder); } else { - builder = addSuppressWarningsFix(suggestTree, builder, suppressionName); + // When there is a castToNonNull method, suggestTree is set to the expression to be + // casted, which is not suppressible. For simplicity, we just always recompute the + // suppressible node here. + Tree suppressibleNode = suppressibleNode(state.getPath()); + if (suppressibleNode != null) { + builder = addSuppressWarningsFix(suppressibleNode, builder, suppressionName); + } } break; case CAST_TO_NONNULL_ARG_NONNULL: @@ -320,6 +328,34 @@ private Tree suppressibleNode(@Nullable TreePath path) { .orElse(null); } + /** + * Checks if it would be appropriate to wrap {@code tree} in a {@code castToNonNull} call. There + * are two cases where this method returns {@code false}: + * + *

    + *
  1. {@code tree} represents the {@code null} literal + *
  2. {@code tree} represents a {@code @Nullable} formal parameter of the enclosing method + *
+ */ + private boolean canBeCastToNonNull(Tree tree) { + switch (tree.getKind()) { + case NULL_LITERAL: + // never do castToNonNull(null) + return false; + case IDENTIFIER: + // Don't wrap a @Nullable parameter in castToNonNull, as this misleads callers into thinking + // they can pass in null without causing an NPE. A more appropriate fix would likely be to + // make the parameter @NonNull and add casts at call sites, but that is beyond the scope of + // our suggested fixes + Symbol symbol = ASTHelpers.getSymbol(tree); + return !(symbol != null + && symbol.getKind().equals(ElementKind.PARAMETER) + && hasNullableAnnotation(symbol, config)); + default: + return true; + } + } + private Description.Builder addCastToNonNullFix(Tree suggestTree, Description.Builder builder) { final String fullMethodName = config.getCastToNonNullMethod(); if (fullMethodName == null) { @@ -359,8 +395,8 @@ private Description.Builder removeCastToNonNullFix( } /** - * Reports initialization errors where a constructor fails to guarantee non-fields are initialized - * along all paths at exit points. + * Reports initialization errors where a constructor fails to guarantee non-null fields are + * initialized along all paths at exit points. * * @param methodSymbol Constructor symbol. * @param message Error message. diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java index 92138b2f79..d025fa9a7c 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java @@ -24,6 +24,7 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper; import com.google.errorprone.ErrorProneFlags; +import com.sun.source.tree.Tree; import com.uber.nullaway.testlibrarymodels.TestLibraryModels; import java.io.IOException; import org.junit.Before; @@ -92,7 +93,8 @@ public void suggestCastToNonNull() throws IOException { "package com.uber;", "import javax.annotation.Nullable;", "class Test {", - " Object test1(@Nullable Object o) {", + " @Nullable Object o;", + " Object test1() {", " return o;", " }", "}") @@ -102,13 +104,65 @@ public void suggestCastToNonNull() throws IOException { "import static com.uber.nullaway.testdata.Util.castToNonNull;", "import javax.annotation.Nullable;", "class Test {", - " Object test1(@Nullable Object o) {", + " @Nullable Object o;", + " Object test1() {", " return castToNonNull(o);", " }", "}") .doTest(); } + /** + * Test for cases where we heuristically decide not to wrap an expression in castToNonNull; see + * {@link ErrorBuilder#canBeCastToNonNull(Tree)} + */ + @Test + public void suppressInsteadOfCastToNonNull() throws IOException { + makeTestHelper() + .addInputLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " Object f = new Object();", + " Object test1(@Nullable Object o) {", + " return o;", + " }", + " Object test2() {", + " return null;", + " }", + " void test3() {", + " f = null;", + " }", + " @Nullable Object m() { return null; }", + " Object shouldAddCast() {", + " return m();", + " }", + "}") + .addOutputLines( + "out/Test.java", + "package com.uber;", + "import static com.uber.nullaway.testdata.Util.castToNonNull;", + "import javax.annotation.Nullable;", + "class Test {", + " Object f = new Object();", + " @SuppressWarnings(\"NullAway\") Object test1(@Nullable Object o) {", + " return o;", + " }", + " @SuppressWarnings(\"NullAway\") Object test2() {", + " return null;", + " }", + " @SuppressWarnings(\"NullAway\") void test3() {", + " f = null;", + " }", + " @Nullable Object m() { return null; }", + " Object shouldAddCast() {", + " return castToNonNull(m());", + " }", + "}") + .doTest(); + } + @Test public void removeUnnecessaryCastToNonNull() throws IOException { makeTestHelper() @@ -202,4 +256,68 @@ public void suggestSuppressionOnMethodRef() throws IOException { "}") .doTest(); } + + @Test + public void suggestInitSuppressionOnConstructor() throws IOException { + makeTestHelper() + .addInputLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " Object f;", + " Object g;", + " Test() {}", + "}") + .addOutputLines( + "out/Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " Object f;", + " Object g;", + " @SuppressWarnings(\"NullAway.Init\") Test() {}", + "}") + .doTest(); + } + + @Test + public void suggestInitSuppressionOnField() throws IOException { + makeTestHelper() + .addInputLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " Object f;", + "}") + .addOutputLines( + "out/Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " @SuppressWarnings(\"NullAway.Init\") Object f;", + "}") + .doTest(); + } + + @Test + public void updateExtantSuppressWarnings() throws IOException { + makeTestHelper() + .addInputLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " @SuppressWarnings(\"unused\") Object f;", + "}") + .addOutputLines( + "out/Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " @SuppressWarnings({\"unused\",\"NullAway.Init\"}) Object f;", + "}") + .doTest(); + } } From c42b0f789bbea95c0174c404fc2f5f9360f82cb2 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 25 Aug 2023 13:33:44 -0700 Subject: [PATCH 43/57] Ensure castToNonNull insertion/removal suggested fixes do not remove comments (#815) We now preserve the original source code of the AST node for which we are adding or removing a cast, ensuring that comments within the node are preserved. --- .../java/com/uber/nullaway/ErrorBuilder.java | 9 ++- .../nullaway/NullAwayAutoSuggestTest.java | 79 +++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java index 5188f823b1..fb7aee6f70 100755 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java @@ -201,7 +201,7 @@ private Description.Builder addSuggestedSuppression( case ASSIGN_FIELD_NULLABLE: case SWITCH_EXPRESSION_NULLABLE: if (config.getCastToNonNullMethod() != null && canBeCastToNonNull(suggestTree)) { - builder = addCastToNonNullFix(suggestTree, builder); + builder = addCastToNonNullFix(suggestTree, builder, state); } else { // When there is a castToNonNull method, suggestTree is set to the expression to be // casted, which is not suppressible. For simplicity, we just always recompute the @@ -356,7 +356,8 @@ private boolean canBeCastToNonNull(Tree tree) { } } - private Description.Builder addCastToNonNullFix(Tree suggestTree, Description.Builder builder) { + private Description.Builder addCastToNonNullFix( + Tree suggestTree, Description.Builder builder, VisitorState state) { final String fullMethodName = config.getCastToNonNullMethod(); if (fullMethodName == null) { throw new IllegalStateException("cast-to-non-null method not set"); @@ -364,7 +365,7 @@ private Description.Builder addCastToNonNullFix(Tree suggestTree, Description.Bu // Add a call to castToNonNull around suggestTree: final String[] parts = fullMethodName.split("\\."); final String shortMethodName = parts[parts.length - 1]; - final String replacement = shortMethodName + "(" + suggestTree.toString() + ")"; + final String replacement = shortMethodName + "(" + state.getSourceForNode(suggestTree) + ")"; final SuggestedFix fix = SuggestedFix.builder() .replace(suggestTree, replacement) @@ -390,7 +391,7 @@ private Description.Builder removeCastToNonNullFix( invTree, suggestTree)); // Remove the call to castToNonNull: final SuggestedFix fix = - SuggestedFix.builder().replace(invTree, suggestTree.toString()).build(); + SuggestedFix.builder().replace(invTree, state.getSourceForNode(suggestTree)).build(); return builder.addFix(fix); } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java index d025fa9a7c..03ae7e35a3 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayAutoSuggestTest.java @@ -22,6 +22,8 @@ package com.uber.nullaway; +import static com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH; + import com.google.errorprone.BugCheckerRefactoringTestHelper; import com.google.errorprone.ErrorProneFlags; import com.sun.source.tree.Tree; @@ -215,6 +217,38 @@ public void removeUnnecessaryCastToNonNullFromLibraryModel() throws IOException .doTest(); } + @Test + public void removeUnnecessaryCastToNonNullMultiLine() throws IOException { + makeTestHelper() + .addInputLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "import static com.uber.nullaway.testdata.Util.castToNonNull;", + "class Test {", + " static class Foo { Object getObj() { return new Object(); } }", + " Object test1(Foo f) {", + " return castToNonNull(f", + " // comment that should not be deleted", + " .getObj());", + " }", + "}") + .addOutputLines( + "out/Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "import static com.uber.nullaway.testdata.Util.castToNonNull;", + "class Test {", + " static class Foo { Object getObj() { return new Object(); } }", + " Object test1(Foo f) {", + " return f", + " // comment that should not be deleted", + " .getObj();", + " }", + "}") + .doTest(TEXT_MATCH); + } + @Test public void suggestSuppressionOnMethodRef() throws IOException { makeTestHelper() @@ -258,6 +292,51 @@ public void suggestSuppressionOnMethodRef() throws IOException { } @Test + public void suggestCastToNonNullPreserveComments() throws IOException { + makeTestHelper() + .addInputLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " Object x = new Object();", + " static class Foo { @Nullable Object getObj() { return null; } }", + " Object test1(Foo f) {", + " return f", + " // comment that should not be deleted", + " .getObj();", + " }", + " void test2(Foo f) {", + " x = f.getObj(); // comment that should not be deleted", + " }", + " Object test3(Foo f) {", + " return f./* keep this comment */getObj();", + " }", + "}") + .addOutputLines( + "out/Test.java", + "package com.uber;", + "import static com.uber.nullaway.testdata.Util.castToNonNull;", + "", + "import javax.annotation.Nullable;", + "class Test {", + " Object x = new Object();", + " static class Foo { @Nullable Object getObj() { return null; } }", + " Object test1(Foo f) {", + " return castToNonNull(f", + " // comment that should not be deleted", + " .getObj());", + " }", + " void test2(Foo f) {", + " x = castToNonNull(f.getObj()); // comment that should not be deleted", + " }", + " Object test3(Foo f) {", + " return castToNonNull(f./* keep this comment */getObj());", + " }", + "}") + .doTest(TEXT_MATCH); + } + public void suggestInitSuppressionOnConstructor() throws IOException { makeTestHelper() .addInputLines( From ade9ed175c33b915904c04ab4406a4468b6f86e0 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Wed, 30 Aug 2023 13:00:06 -0700 Subject: [PATCH 44/57] Remove unnecessary compile dependence for jar-infer-cli (#816) --- jar-infer/jar-infer-cli/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/jar-infer/jar-infer-cli/build.gradle b/jar-infer/jar-infer-cli/build.gradle index 5333a5d74e..3245d662b3 100644 --- a/jar-infer/jar-infer-cli/build.gradle +++ b/jar-infer/jar-infer-cli/build.gradle @@ -17,8 +17,6 @@ repositories { dependencies { implementation deps.build.commonscli implementation deps.build.guava - compileOnly deps.build.errorProneCheckApi - implementation project(":jar-infer:jar-infer-lib") testImplementation deps.test.junit4 From 0431a906127d1973733ceb247f3a724a0fcef85e Mon Sep 17 00:00:00 2001 From: akulk022 <116849480+akulk022@users.noreply.github.com> Date: Thu, 31 Aug 2023 02:15:35 +0530 Subject: [PATCH 45/57] Issue #740: Adding visitors for handling different types along with ClassType in Generic Type invariance check (#806) - Description: Only ClassTypes are supported for checking the invariance of the nullability annotations for Generic types and hence TypeVisitors and a TreeVisitor are added to handle different scenarios like for example: ` Foo[]> x = new Foo[]>(); ` - Issue Number: 740 - All the tests in NullAwayJSpecifyGenericTests.java have passed for these changes. Fixes #740 --------- Co-authored-by: Manu Sridharan --- .../com/uber/nullaway/GenericsChecks.java | 259 +++++++++++------- .../NullAwayJSpecifyGenericsTests.java | 79 ++++++ 2 files changed, 237 insertions(+), 101 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index bd1be2a876..a9f3a6d717 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -10,6 +10,7 @@ import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.AnnotatedTypeTree; import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.ExpressionTree; @@ -20,6 +21,7 @@ import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; +import com.sun.source.util.SimpleTreeVisitor; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Symbol; @@ -354,57 +356,10 @@ public static void checkTypeParameterNullnessForFunctionReturnType( * @param state the visitor state */ private static boolean compareNullabilityAnnotations( - Type.ClassType lhsType, Type.ClassType rhsType, VisitorState state) { - Types types = state.getTypes(); - // The base type of rhsType may be a subtype of lhsType's base type. In such cases, we must - // compare lhsType against the supertype of rhsType with a matching base type. - rhsType = (Type.ClassType) types.asSuper(rhsType, lhsType.tsym); - // This is impossible, considering the fact that standard Java subtyping succeeds before running - // NullAway - if (rhsType == null) { - throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType); - } - List lhsTypeArguments = lhsType.getTypeArguments(); - List rhsTypeArguments = rhsType.getTypeArguments(); - // This is impossible, considering the fact that standard Java subtyping succeeds before running - // NullAway - if (lhsTypeArguments.size() != rhsTypeArguments.size()) { - throw new RuntimeException( - "Number of types arguments in " + rhsType + " does not match " + lhsType); - } - for (int i = 0; i < lhsTypeArguments.size(); i++) { - Type lhsTypeArgument = lhsTypeArguments.get(i); - Type rhsTypeArgument = rhsTypeArguments.get(i); - boolean isLHSNullableAnnotated = false; - List lhsAnnotations = lhsTypeArgument.getAnnotationMirrors(); - // To ensure that we are checking only jspecify nullable annotations - for (Attribute.TypeCompound annotation : lhsAnnotations) { - if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { - isLHSNullableAnnotated = true; - break; - } - } - boolean isRHSNullableAnnotated = false; - List rhsAnnotations = rhsTypeArgument.getAnnotationMirrors(); - // To ensure that we are checking only jspecify nullable annotations - for (Attribute.TypeCompound annotation : rhsAnnotations) { - if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { - isRHSNullableAnnotated = true; - break; - } - } - if (isLHSNullableAnnotated != isRHSNullableAnnotated) { - return false; - } - // nested generics - if (lhsTypeArgument.getTypeArguments().length() > 0) { - if (!compareNullabilityAnnotations( - (Type.ClassType) lhsTypeArgument, (Type.ClassType) rhsTypeArgument, state)) { - return false; - } - } - } - return true; + Type lhsType, Type rhsType, VisitorState state) { + // it is fair to assume rhyType should be the same as lhsType as the Java compiler has passed + // before NullAway. + return lhsType.accept(new CompareNullabilityVisitor(state), rhsType); } /** @@ -418,56 +373,7 @@ private static boolean compareNullabilityAnnotations( */ private static Type.ClassType typeWithPreservedAnnotations( ParameterizedTypeTree tree, VisitorState state) { - Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); - Preconditions.checkNotNull(type); - Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); - List typeArguments = tree.getTypeArguments(); - List newTypeArgs = new ArrayList<>(); - for (int i = 0; i < typeArguments.size(); i++) { - AnnotatedTypeTree annotatedType = null; - Tree curTypeArg = typeArguments.get(i); - // If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a - // ParameterizedTypeTree in the case of a nested generic type - if (curTypeArg instanceof AnnotatedTypeTree) { - annotatedType = (AnnotatedTypeTree) curTypeArg; - } else if (curTypeArg instanceof ParameterizedTypeTree - && ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { - annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); - } - List annotations = - annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); - boolean hasNullableAnnotation = false; - for (AnnotationTree annotation : annotations) { - if (ASTHelpers.isSameType( - nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { - hasNullableAnnotation = true; - break; - } - } - // construct a TypeMetadata object containing a nullability annotation if needed - com.sun.tools.javac.util.List nullableAnnotationCompound = - hasNullableAnnotation - ? com.sun.tools.javac.util.List.from( - Collections.singletonList( - new Attribute.TypeCompound( - nullableType, com.sun.tools.javac.util.List.nil(), null))) - : com.sun.tools.javac.util.List.nil(); - TypeMetadata typeMetadata = - new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); - Type currentTypeArgType = castToNonNull(ASTHelpers.getType(curTypeArg)); - if (currentTypeArgType.getTypeArguments().size() > 0) { - // nested generic type; recursively preserve its nullability type argument annotations - currentTypeArgType = - typeWithPreservedAnnotations((ParameterizedTypeTree) curTypeArg, state); - } - Type.ClassType newTypeArgType = - (Type.ClassType) currentTypeArgType.cloneWithMetadata(typeMetadata); - newTypeArgs.add(newTypeArgType); - } - Type.ClassType finalType = - new Type.ClassType( - type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); - return finalType; + return (Type.ClassType) tree.accept(new PreservedAnnotationTreeVisitor(state), null); } /** @@ -577,6 +483,156 @@ public static void compareGenericTypeParameterNullabilityForCall( } } + /** + * Visitor that is called from compareNullabilityAnnotations which recursively compares the + * Nullability annotations for the nested generic type arguments. Compares the Type it is called + * upon, i.e. the LHS type and the Type passed as an argument, i.e. The RHS type. + */ + public static class CompareNullabilityVisitor extends Types.DefaultTypeVisitor { + private final VisitorState state; + + CompareNullabilityVisitor(VisitorState state) { + this.state = state; + } + + @Override + public Boolean visitClassType(Type.ClassType lhsType, Type rhsType) { + Types types = state.getTypes(); + // The base type of rhsType may be a subtype of lhsType's base type. In such cases, we must + // compare lhsType against the supertype of rhsType with a matching base type. + rhsType = (Type.ClassType) types.asSuper(rhsType, lhsType.tsym); + // This is impossible, considering the fact that standard Java subtyping succeeds before + // running NullAway + if (rhsType == null) { + throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType); + } + List lhsTypeArguments = lhsType.getTypeArguments(); + List rhsTypeArguments = rhsType.getTypeArguments(); + // This is impossible, considering the fact that standard Java subtyping succeeds before + // running NullAway + if (lhsTypeArguments.size() != rhsTypeArguments.size()) { + throw new RuntimeException( + "Number of types arguments in " + rhsType + " does not match " + lhsType); + } + for (int i = 0; i < lhsTypeArguments.size(); i++) { + Type lhsTypeArgument = lhsTypeArguments.get(i); + Type rhsTypeArgument = rhsTypeArguments.get(i); + boolean isLHSNullableAnnotated = false; + List lhsAnnotations = lhsTypeArgument.getAnnotationMirrors(); + // To ensure that we are checking only jspecify nullable annotations + for (Attribute.TypeCompound annotation : lhsAnnotations) { + if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { + isLHSNullableAnnotated = true; + break; + } + } + boolean isRHSNullableAnnotated = false; + List rhsAnnotations = rhsTypeArgument.getAnnotationMirrors(); + // To ensure that we are checking only jspecify nullable annotations + for (Attribute.TypeCompound annotation : rhsAnnotations) { + if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { + isRHSNullableAnnotated = true; + break; + } + } + if (isLHSNullableAnnotated != isRHSNullableAnnotated) { + return false; + } + // nested generics + if (!lhsTypeArgument.accept(this, rhsTypeArgument)) { + return false; + } + } + return true; + } + + @Override + public Boolean visitArrayType(Type.ArrayType lhsType, Type rhsType) { + Type.ArrayType arrRhsType = (Type.ArrayType) rhsType; + return lhsType.getComponentType().accept(this, arrRhsType.getComponentType()); + } + + @Override + public Boolean visitType(Type t, Type type) { + return true; + } + } + + /** + * Visitor For getting the preserved Annotation Type for the nested generic type arguments within + * the ParameterizedTypeTree originally passed to TypeWithPreservedAnnotations method, since these + * nested arguments may not always be ParameterizedTypeTrees and may be of different types for + * e.g. ArrayTypeTree. + */ + public static class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor { + + private final VisitorState state; + + PreservedAnnotationTreeVisitor(VisitorState state) { + this.state = state; + } + + @Override + public Type visitArrayType(ArrayTypeTree tree, Void p) { + Type elemType = tree.getType().accept(this, null); + return new Type.ArrayType(elemType, castToNonNull(ASTHelpers.getType(tree)).tsym); + } + + @Override + public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) { + Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); + Preconditions.checkNotNull(type); + Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); + List typeArguments = tree.getTypeArguments(); + List newTypeArgs = new ArrayList<>(); + for (int i = 0; i < typeArguments.size(); i++) { + AnnotatedTypeTree annotatedType = null; + Tree curTypeArg = typeArguments.get(i); + // If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a + // ParameterizedTypeTree in the case of a nested generic type + if (curTypeArg instanceof AnnotatedTypeTree) { + annotatedType = (AnnotatedTypeTree) curTypeArg; + } else if (curTypeArg instanceof ParameterizedTypeTree + && ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { + annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); + } + List annotations = + annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); + boolean hasNullableAnnotation = false; + for (AnnotationTree annotation : annotations) { + if (ASTHelpers.isSameType( + nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { + hasNullableAnnotation = true; + break; + } + } + // construct a TypeMetadata object containing a nullability annotation if needed + com.sun.tools.javac.util.List nullableAnnotationCompound = + hasNullableAnnotation + ? com.sun.tools.javac.util.List.from( + Collections.singletonList( + new Attribute.TypeCompound( + nullableType, com.sun.tools.javac.util.List.nil(), null))) + : com.sun.tools.javac.util.List.nil(); + TypeMetadata typeMetadata = + new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); + Type currentTypeArgType = curTypeArg.accept(this, null); + Type newTypeArgType = currentTypeArgType.cloneWithMetadata(typeMetadata); + newTypeArgs.add(newTypeArgType); + } + Type.ClassType finalType = + new Type.ClassType( + type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); + return finalType; + } + + /** By default, just use the type computed by javac */ + @Override + protected Type defaultAction(Tree node, Void unused) { + return castToNonNull(ASTHelpers.getType(node)); + } + } + /** * Checks that type parameter nullability is consistent between an overriding method and the * corresponding overridden method. @@ -908,6 +964,7 @@ public String visitCapturedType(Type.CapturedType t, Void s) { @Override public String visitArrayType(Type.ArrayType t, Void unused) { + // TODO properly print cases like int @Nullable[] return t.elemtype.accept(this, null) + "[]"; } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index 260b3cc7ff..14eeb16cbc 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -760,6 +760,85 @@ public void rawTypes() { .doTest(); } + @Test + public void nestedGenericTypeAssignment() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " static class A { }", + " static void testPositive() {", + " // BUG: Diagnostic contains: Cannot assign from type", + " A[]> var1 = new A[]>();", + " // BUG: Diagnostic contains: Cannot assign from type", + " A[]> var2 = new A[]>();", + " }", + " static void testNegative() {", + " A[]> var1 = new A[]>();", + " A[]> var2 = new A[]>();", + " }", + "}") + .doTest(); + } + + @Test + public void genericPrimitiveArrayTypeAssignment() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " static class A { }", + " static void testPositive() {", + " // BUG: Diagnostic contains: Cannot assign from type A", + " A x = new A();", + " }", + " static void testNegative() {", + " A x = new A();", + " }", + "}") + .doTest(); + } + + @Test + public void nestedGenericTypeVariables() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " static class A { }", + " static class B {", + " void foo() {", + " A[]> x = new A[]>();", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void nestedGenericWildcardTypeVariables() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " static class A { }", + " static class B {", + " void foo() {", + " A[]> x = new A[]>();", + " }", + " }", + "}") + .doTest(); + } + @Test public void overrideReturnTypes() { makeHelper() From 0a83f422061368f8e3e4cdf5172ab59d712c7666 Mon Sep 17 00:00:00 2001 From: Md Armughanuddin <52311490+armughan11@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:10:05 +0530 Subject: [PATCH 46/57] Added Codecov to CI Pipeline (#820) --- .github/workflows/continuous-integration.yml | 11 ++++++++--- build.gradle | 1 - code-coverage-report/build.gradle | 14 -------------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 11bcff71e7..c642b22c03 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -58,15 +58,20 @@ jobs: with: arguments: shellcheck if: runner.os == 'Linux' - - name: Report jacoco coverage + - name: Aggregate jacoco coverage + id: jacoco_report uses: gradle/gradle-build-action@v2 env: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} with: - arguments: coveralls + arguments: codeCoverageReport continue-on-error: true if: runner.os == 'Linux' && matrix.java == '11' && matrix.epVersion == '2.21.1' && github.repository == 'uber/NullAway' + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./code-coverage-report/build/reports/jacoco/codeCoverageReport/codeCoverageReport.xml + if: steps.jacoco_report.outcome == 'success' - name: Test publishToMavenLocal flow env: ORG_GRADLE_PROJECT_epApiVersion: ${{ matrix.epVersion }} diff --git a/build.gradle b/build.gradle index db827fa246..704951e51c 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,6 @@ plugins { id "com.diffplug.spotless" version "6.20.0" id "net.ltgt.errorprone" version "3.1.0" apply false id "com.github.johnrengelman.shadow" version "8.1.1" apply false - id "com.github.kt3k.coveralls" version "2.12.0" apply false id "me.champeau.jmh" version "0.7.1" apply false id "com.github.ben-manes.versions" version "0.47.0" id "com.felipefzdz.gradle.shellcheck" version "1.4.6" diff --git a/code-coverage-report/build.gradle b/code-coverage-report/build.gradle index 9b4f755322..e9d3da6bcd 100644 --- a/code-coverage-report/build.gradle +++ b/code-coverage-report/build.gradle @@ -18,7 +18,6 @@ plugins { id 'java' id 'jacoco' - id 'com.github.kt3k.coveralls' } // Use JDK 17 for this module, via a toolchain. We need JDK 17 since this module @@ -71,19 +70,6 @@ def codeCoverageReport = tasks.register('codeCoverageReport', JacocoReport) { } } -coveralls { - jacocoReportPath = "build/reports/jacoco/codeCoverageReport/codeCoverageReport.xml" - afterEvaluate { - sourceDirs = sourcesPath.incoming.artifactView { lenient(true) }.files as List - } -} - -def coverallsTask = tasks.named('coveralls') - -coverallsTask.configure { - dependsOn 'codeCoverageReport' -} - // These dependencies indicate which projects have tests or tested code we want to include // when computing overall coverage. We aim to measure coverage for all code that actually ships // in a Maven artifact (so, e.g., we do not measure coverage for the jmh module) From 75db7db0fbedadeeffc84adab465b6889f85883b Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Fri, 1 Sep 2023 15:53:19 -0400 Subject: [PATCH 47/57] Prepare for release 0.10.13. --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 4 ++-- gradle.properties | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d212b7999e..4ae9128a84 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ Changelog ========= +Version 0.10.13 +--------------- +* Allow library models to define custom stream classes (#807) +* Avoid suggesting castToNonNull fixes in certain cases (#799) +* Ensure castToNonNull insertion/removal suggested fixes do not remove comments (#815) +* Support for JSpecify's 0.3.0 annotation [experimental] + - Generics checks for method overriding (#755) + - Make GenericsChecks methods static (#805) + - Add visitors for handling different types in generic type invariance check (#806) +* Build / CI tooling for NullAway itself: + - Bump versions for some dependencies (#800) + - Update to WALA 1.6.2 (#798) + - Update to Error Prone 2.21.1 (#797) + - Enable contract checking when building NullAway (#802) + - Bump Error Prone Gradle Plugin version (#804) + - Modify JMH Benchmark Workflow For Shellcheck (#813) + - Bump gradle maven publish plugin from 0.21.0 to 0.25.3 (#810) + - Use Spotless to enforce consistent formatting for Gradle build scripts (#809) + - Remove unnecessary compile dependence for jar-infer-cli (#816) + - Added Codecov to CI Pipeline (#820) + Version 0.10.12 --------------- Note: This is the first release built with Java 11. In particular, running diff --git a/README.md b/README.md index c6a3f1d54c..fddfe7a4f6 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ plugins { } dependencies { - annotationProcessor "com.uber.nullaway:nullaway:0.10.12" + annotationProcessor "com.uber.nullaway:nullaway:0.10.13" // Optional, some source of nullability annotations. // Not required on Android if you use the support @@ -75,7 +75,7 @@ The configuration for an Android project is very similar to the Java case, with ```gradle dependencies { - annotationProcessor "com.uber.nullaway:nullaway:0.10.12" + annotationProcessor "com.uber.nullaway:nullaway:0.10.13" errorprone "com.google.errorprone:error_prone_core:2.4.0" errorproneJavac "com.google.errorprone:javac:9+181-r4173-1" } diff --git a/gradle.properties b/gradle.properties index f797e050a0..e71fc1059b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.13-SNAPSHOT +VERSION_NAME=0.10.13 POM_DESCRIPTION=A fast annotation-based null checker for Java From b7f1326ac70cec267c59ef91827903d8800b10df Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Fri, 1 Sep 2023 16:18:27 -0400 Subject: [PATCH 48/57] Prepare next development version. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e71fc1059b..ee5f44742d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.13 +VERSION_NAME=0.10.14-SNAPSHOT POM_DESCRIPTION=A fast annotation-based null checker for Java From 7a78bc7841f1d47a2b54d326b14633776a5348c8 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 1 Sep 2023 13:53:39 -0700 Subject: [PATCH 49/57] Bump Checker Framework dependence to 3.38.0 (#819) Beyond getting up to date, this is a pre-requisite for addressing #811 --- gradle/dependencies.gradle | 2 +- .../dataflow/AccessPathNullnessPropagation.java | 16 ++++++++-------- .../dataflow/cfg/NullAwayCFGBuilder.java | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 85b515f945..c1c264e6b0 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -40,7 +40,7 @@ if (project.hasProperty("epApiVersion")) { def versions = [ asm : "9.3", - checkerFramework : "3.26.0", + checkerFramework : "3.38.0", // for comparisons in other parts of the build errorProneLatest : latestErrorProneVersion, // The version of Error Prone used to check NullAway's code. diff --git a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java index 60b80399fc..6681d69797 100644 --- a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java +++ b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java @@ -75,6 +75,7 @@ import org.checkerframework.nullaway.dataflow.cfg.node.DoubleLiteralNode; import org.checkerframework.nullaway.dataflow.cfg.node.EqualToNode; import org.checkerframework.nullaway.dataflow.cfg.node.ExplicitThisNode; +import org.checkerframework.nullaway.dataflow.cfg.node.ExpressionStatementNode; import org.checkerframework.nullaway.dataflow.cfg.node.FieldAccessNode; import org.checkerframework.nullaway.dataflow.cfg.node.FloatLiteralNode; import org.checkerframework.nullaway.dataflow.cfg.node.FloatingDivisionNode; @@ -113,7 +114,6 @@ import org.checkerframework.nullaway.dataflow.cfg.node.ReturnNode; import org.checkerframework.nullaway.dataflow.cfg.node.ShortLiteralNode; import org.checkerframework.nullaway.dataflow.cfg.node.SignedRightShiftNode; -import org.checkerframework.nullaway.dataflow.cfg.node.StringConcatenateAssignmentNode; import org.checkerframework.nullaway.dataflow.cfg.node.StringConcatenateNode; import org.checkerframework.nullaway.dataflow.cfg.node.StringConversionNode; import org.checkerframework.nullaway.dataflow.cfg.node.StringLiteralNode; @@ -376,13 +376,6 @@ public TransferResult visitBitwiseXor( return noStoreChanges(NONNULL, input); } - @Override - public TransferResult visitStringConcatenateAssignment( - StringConcatenateAssignmentNode stringConcatenateAssignmentNode, - TransferInput input) { - return noStoreChanges(NULLABLE, input); - } - @Override public TransferResult visitLessThan( LessThanNode lessThanNode, TransferInput input) { @@ -1085,6 +1078,13 @@ public TransferResult visitClassDeclaration( return noStoreChanges(NULLABLE, input); } + @Override + public TransferResult visitExpressionStatement( + ExpressionStatementNode expressionStatementNode, + TransferInput input) { + return noStoreChanges(NULLABLE, input); + } + @Override public TransferResult visitPackageName( PackageNameNode packageNameNode, TransferInput input) { diff --git a/nullaway/src/main/java/com/uber/nullaway/dataflow/cfg/NullAwayCFGBuilder.java b/nullaway/src/main/java/com/uber/nullaway/dataflow/cfg/NullAwayCFGBuilder.java index bf5070866d..2f955b7b51 100644 --- a/nullaway/src/main/java/com/uber/nullaway/dataflow/cfg/NullAwayCFGBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/dataflow/cfg/NullAwayCFGBuilder.java @@ -187,7 +187,7 @@ public R accept(TreeVisitor visitor, D data) { } }, booleanExpressionNode, - this.getProcessingEnvironment().getTypeUtils()), + this.env.getTypeUtils()), errorType); exNode.setTerminatesExecution(true); this.addLabelForNextNode(endPrecondition); From bab9452c535fd79f9c354cdb32c43925d9596500 Mon Sep 17 00:00:00 2001 From: Md Armughanuddin <52311490+armughan11@users.noreply.github.com> Date: Sat, 2 Sep 2023 02:32:10 +0530 Subject: [PATCH 50/57] Fixes Codecov Report Expired error (#821) Ignores coverage report timestamps to ensure build cache is used and reports are still uploaded to Codecov --------- Co-authored-by: Manu Sridharan --- .github/codecov.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000000..0f6ecbeab1 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,4 @@ +codecov: + # Disable report age checking since hits in the build cache can lead to an old timestamp + max_report_age: off + From ed4d0aebeea6c20fbe392b899d333a75c5acbd13 Mon Sep 17 00:00:00 2001 From: Md Armughanuddin <52311490+armughan11@users.noreply.github.com> Date: Sat, 2 Sep 2023 03:25:29 +0530 Subject: [PATCH 51/57] Updated Readme.md with Codecov link (#823) Replaced Coveralls with Codecov in Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fddfe7a4f6..41d6e21f64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## NullAway: Fast Annotation-Based Null Checking for Java [![Build Status](https://github.com/uber/nullaway/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/uber/nullaway/actions/workflows/continuous-integration.yml) [![Coverage Status](https://coveralls.io/repos/github/uber/NullAway/badge.svg?branch=master)](https://coveralls.io/github/uber/NullAway?branch=master) +## NullAway: Fast Annotation-Based Null Checking for Java [![Build Status](https://github.com/uber/nullaway/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/uber/nullaway/actions/workflows/continuous-integration.yml) [![Coverage Status](https://codecov.io/github/uber/NullAway/coverage.svg?branch=master)](https://codecov.io/github/uber/NullAway?branch=master) NullAway is a tool to help eliminate `NullPointerException`s (NPEs) in your Java code. To use NullAway, first add `@Nullable` annotations in your code wherever a field, method parameter, or return value may be `null`. Given these annotations, NullAway performs a series of type-based, local checks to ensure that any pointer that gets dereferenced in your code cannot be `null`. NullAway is similar to the type-based nullability checking in the Kotlin and Swift languages, and the [Checker Framework](https://checkerframework.org/) and [Eradicate](https://fbinfer.com/docs/checker-eradicate/) null checkers for Java. From a7b097a337d66e34437c08d9c81ac2ffb696e4cb Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 5 Sep 2023 11:46:49 -0700 Subject: [PATCH 52/57] Remove ASM-related hack in build config (#824) I'm guessing this was necessary for building on Java 8, but now everything works without it. --- build.gradle | 8 -------- 1 file changed, 8 deletions(-) diff --git a/build.gradle b/build.gradle index 704951e51c..af60cc9bee 100644 --- a/build.gradle +++ b/build.gradle @@ -24,14 +24,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.0' classpath 'com.vanniktech:gradle-maven-publish-plugin:0.25.3' - // This restriction is needed due to our mix of Android and Java modules; - // without it, the build fails with a weird error. - // See https://stackoverflow.com/questions/70217853/how-to-include-android-project-in-a-gradle-multi-project-build - classpath('org.ow2.asm:asm') { - version { - strictly '9.2' - } - } } } plugins { From fcaf39e0ad0e8bc386d33afdf2c773d1c9015086 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 5 Sep 2023 12:17:05 -0700 Subject: [PATCH 53/57] Treat parameter of generated Record.equals() methods as @Nullable (#825) Fixes #619 --- .../nullaway/jdk17/NullAwayRecordTests.java | 32 +++++++++++++++++++ .../main/java/com/uber/nullaway/Nullness.java | 28 ++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayRecordTests.java b/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayRecordTests.java index 8135deb27b..d60e264a77 100644 --- a/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayRecordTests.java +++ b/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayRecordTests.java @@ -178,4 +178,36 @@ public void testLocalRecord() { "}") .doTest(); } + + @Test + public void recordEqualsNull() { + defaultCompilationHelper + .addSourceLines( + "Records.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Records {", + " public void recordEqualsNull() {", + " record Rec() {", + " void print(Object o) { System.out.println(o.toString()); }", + " void equals(Integer i1, Integer i2) { }", + " boolean equals(String i1, String i2) { return false; }", + " boolean equals(Long l1) { return false; }", + " }", + " Object o = null;", + " // null can be passed to the generated equals() method taking an Object parameter", + " new Rec().equals(o);", + " // BUG: Diagnostic contains: passing @Nullable parameter 'null'", + " new Rec().print(null);", + " // BUG: Diagnostic contains: passing @Nullable parameter 'null'", + " new Rec().equals(null, Integer.valueOf(100));", + " // BUG: Diagnostic contains: passing @Nullable parameter 'null'", + " new Rec().equals(\"hello\", null);", + " Long l = null;", + " // BUG: Diagnostic contains: passing @Nullable parameter 'l'", + " new Rec().equals(l);", + " }", + "}") + .doTest(); + } } diff --git a/nullaway/src/main/java/com/uber/nullaway/Nullness.java b/nullaway/src/main/java/com/uber/nullaway/Nullness.java index 1e96a7abd6..c1b8198de0 100644 --- a/nullaway/src/main/java/com/uber/nullaway/Nullness.java +++ b/nullaway/src/main/java/com/uber/nullaway/Nullness.java @@ -19,6 +19,8 @@ package com.uber.nullaway; import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.util.List; import java.util.stream.Stream; import javax.lang.model.element.AnnotationMirror; import org.checkerframework.nullaway.dataflow.analysis.AbstractValue; @@ -210,10 +212,36 @@ public static boolean hasNullableAnnotation(Symbol symbol, Config config) { */ public static boolean paramHasNullableAnnotation( Symbol.MethodSymbol symbol, int paramInd, Config config) { + // We treat the (generated) equals() method of record types to have a @Nullable parameter, as + // the generated implementation handles null (as required by the contract of Object.equals()) + if (isRecordEqualsParam(symbol, paramInd)) { + return true; + } return hasNullableAnnotation( NullabilityUtil.getAllAnnotationsForParameter(symbol, paramInd), config); } + private static boolean isRecordEqualsParam(Symbol.MethodSymbol symbol, int paramInd) { + // Here we compare with toString() to preserve compatibility with JDK 11 (records only + // introduced in JDK 16) + if (!symbol.owner.getKind().toString().equals("RECORD")) { + return false; + } + if (!symbol.getSimpleName().contentEquals("equals")) { + return false; + } + // Check for a boolean return type and a single parameter of type java.lang.Object + Type type = symbol.type; + List parameterTypes = type.getParameterTypes(); + if (!(type.getReturnType().toString().equals("boolean") + && parameterTypes != null + && parameterTypes.size() == 1 + && parameterTypes.get(0).toString().equals("java.lang.Object"))) { + return false; + } + return paramInd == 0; + } + /** * Does the parameter of {@code symbol} at {@code paramInd} have a {@code @NonNull} declaration or * type-use annotation? This method works for methods defined in either source or class files. From f98bc4be7db50187237b55fbd93e003bf6b85245 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 5 Sep 2023 12:31:19 -0700 Subject: [PATCH 54/57] Run tests on JDK 21 (#826) It looks like most of our tests pass on JDK 21. Exceptions are the JSpecify generics tests, which rely on an API that changed in JDK 21 (see #827), and the JarInfer tests, as it looks like WALA does not yet support running on JDK 21 (see #829). Core NullAway support (ignoring the experimental/WIP JSpecify mode) should be working. This PR adds test configs so that we run tests on JDK 21 wherever possible. We update our Jacoco and Mockito versions to make this work. We also take the opportunity to move some common test configuration code to the `nullaway.java-test-conventions.gradle` file, which previously just held configuration for Jacoco. --- .github/workflows/continuous-integration.yml | 5 + annotations/build.gradle | 21 +-- .../groovy/nullaway.jacoco-conventions.gradle | 62 --------- .../nullaway.java-test-conventions.gradle | 120 ++++++++++++++++++ gradle/dependencies.gradle | 3 +- guava-recent-unit-tests/build.gradle | 21 +-- jar-infer/jar-infer-lib/build.gradle | 24 +--- .../nullaway-integration-test/build.gradle | 20 +-- jdk17-unit-tests/build.gradle | 22 ++-- jmh/build.gradle | 22 +--- nullaway/build.gradle | 32 ++--- 11 files changed, 165 insertions(+), 187 deletions(-) delete mode 100644 buildSrc/src/main/groovy/nullaway.jacoco-conventions.gradle create mode 100644 buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c642b22c03..acf0d55842 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -36,6 +36,11 @@ jobs: steps: - name: Check out NullAway sources uses: actions/checkout@v3 + - name: 'Set up JDK 21 so it is available' + uses: actions/setup-java@v3 + with: + java-version: '21-ea' + distribution: 'temurin' - name: 'Set up JDK 17 on Windows' uses: actions/setup-java@v3 with: diff --git a/annotations/build.gradle b/annotations/build.gradle index 5e06f02c25..ebb8ff5ca6 100644 --- a/annotations/build.gradle +++ b/annotations/build.gradle @@ -17,29 +17,10 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { id 'java-library' - id 'nullaway.jacoco-conventions' + id 'nullaway.java-test-conventions' } dependencies { } -test { - maxHeapSize = "1024m" - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] -} - apply plugin: 'com.vanniktech.maven.publish' diff --git a/buildSrc/src/main/groovy/nullaway.jacoco-conventions.gradle b/buildSrc/src/main/groovy/nullaway.jacoco-conventions.gradle deleted file mode 100644 index 1c7c97c620..0000000000 --- a/buildSrc/src/main/groovy/nullaway.jacoco-conventions.gradle +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2021. Uber Technologies - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Mostly taken from official Gradle sample: https://docs.gradle.org/current/samples/sample_jvm_multi_project_with_code_coverage.html -plugins { - id 'jacoco' -} - -jacoco { - toolVersion = "0.8.7" -} - -// Do not generate reports for individual projects -tasks.named("jacocoTestReport") { - enabled = false -} - -// Share sources folder with other projects for aggregated JaCoCo reports -configurations.create('transitiveSourcesElements') { - visible = false - canBeResolved = false - canBeConsumed = true - extendsFrom(configurations.implementation) - attributes { - attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)) - attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'source-folders')) - } - sourceSets.main.java.srcDirs.forEach { - outgoing.artifact(it) - } -} - -// Share the coverage data to be aggregated for the whole product -configurations.create('coverageDataElements') { - visible = false - canBeResolved = false - canBeConsumed = true - extendsFrom(configurations.implementation) - attributes { - attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)) - attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'jacoco-coverage-data')) - } - // This will cause the test task to run if the coverage data is requested by the aggregation task - outgoing.artifact(tasks.named("test").map { task -> - task.extensions.getByType(JacocoTaskExtension).destinationFile - }) -} diff --git a/buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle b/buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle new file mode 100644 index 0000000000..07f8f2690e --- /dev/null +++ b/buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Mostly taken from official Gradle sample: https://docs.gradle.org/current/samples/sample_jvm_multi_project_with_code_coverage.html +plugins { + id 'jacoco' +} + +jacoco { + toolVersion = "0.8.10" +} + +// Do not generate reports for individual projects +tasks.named("jacocoTestReport") { + enabled = false +} + +// Share sources folder with other projects for aggregated JaCoCo reports +configurations.create('transitiveSourcesElements') { + visible = false + canBeResolved = false + canBeConsumed = true + extendsFrom(configurations.implementation) + attributes { + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)) + attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'source-folders')) + } + sourceSets.main.java.srcDirs.forEach { + outgoing.artifact(it) + } +} + +// Share the coverage data to be aggregated for the whole product +configurations.create('coverageDataElements') { + visible = false + canBeResolved = false + canBeConsumed = true + extendsFrom(configurations.implementation) + attributes { + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)) + attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'jacoco-coverage-data')) + } + // This will cause the test task to run if the coverage data is requested by the aggregation task + outgoing.artifact(tasks.named("test").map { task -> + task.extensions.getByType(JacocoTaskExtension).destinationFile + }) +} + +test { + maxHeapSize = "1024m" + // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer + jvmArgs += [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + ] +} + +// Create a task to test on JDK 21 +def testJdk21 = tasks.register("testJdk21", Test) { + onlyIf { + // Only test on JDK 21 when using the latest Error Prone version + deps.versions.errorProneApi == deps.versions.errorProneLatest + } + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + } + + description = "Runs the test suite on JDK 21" + group = LifecycleBasePlugin.VERIFICATION_GROUP + + // Copy inputs from normal Test task. + def testTask = tasks.getByName("test") + classpath = testTask.classpath + testClassesDirs = testTask.testClassesDirs + maxHeapSize = "1024m" + // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer + jvmArgs += [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Accessed by Lombok tests + "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + ] +} + +tasks.named('check').configure { + dependsOn testJdk21 +} diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index c1c264e6b0..1e9bb1c99a 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -113,8 +113,7 @@ def test = [ springBeans : "org.springframework:spring-beans:5.3.7", springContext : "org.springframework:spring-context:5.3.7", grpcCore : "io.grpc:grpc-core:1.15.1", // Should upgrade, but this matches our guava version - mockito : "org.mockito:mockito-core:4.6.1", - mockitoInline : "org.mockito:mockito-inline:4.6.1", + mockito : "org.mockito:mockito-core:5.5.0", javaxAnnotationApi : "javax.annotation:javax.annotation-api:1.3.2", assertJ : "org.assertj:assertj-core:3.23.1", ] diff --git a/guava-recent-unit-tests/build.gradle b/guava-recent-unit-tests/build.gradle index b81bd0f8df..4d78ac52a3 100644 --- a/guava-recent-unit-tests/build.gradle +++ b/guava-recent-unit-tests/build.gradle @@ -15,7 +15,7 @@ */ plugins { id 'java-library' - id 'nullaway.jacoco-conventions' + id 'nullaway.java-test-conventions' } // We need this separate build target to test newer versions of Guava @@ -31,25 +31,6 @@ dependencies { testImplementation "com.google.guava:guava:31.1-jre" } -test { - maxHeapSize = "1024m" - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] -} - // Create a task to test on JDK 8 def jdk8Test = tasks.register("testJdk8", Test) { onlyIf { diff --git a/jar-infer/jar-infer-lib/build.gradle b/jar-infer/jar-infer-lib/build.gradle index ec221573c2..2ae8bea7a8 100644 --- a/jar-infer/jar-infer-lib/build.gradle +++ b/jar-infer/jar-infer-lib/build.gradle @@ -15,7 +15,7 @@ */ plugins { id "java-library" - id 'nullaway.jacoco-conventions' + id 'nullaway.java-test-conventions' } // JarInfer requires JDK 11+, due to its dependence on WALA @@ -48,25 +48,15 @@ dependencies { } test { - maxHeapSize = "1024m" - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] dependsOn ':jar-infer:test-android-lib-jarinfer:bundleReleaseAar' } +tasks.named('testJdk21', Test).configure { + // Tests fail since WALA does not yet support JDK 21; see https://github.com/uber/NullAway/issues/829 + // So, disable them + onlyIf { false } +} + tasks.withType(JavaCompile).configureEach { options.compilerArgs += "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED" } diff --git a/jar-infer/nullaway-integration-test/build.gradle b/jar-infer/nullaway-integration-test/build.gradle index 03da29711a..94a880861a 100644 --- a/jar-infer/nullaway-integration-test/build.gradle +++ b/jar-infer/nullaway-integration-test/build.gradle @@ -15,7 +15,7 @@ */ plugins { id "java-library" - id "nullaway.jacoco-conventions" + id "nullaway.java-test-conventions" } dependencies { @@ -26,21 +26,3 @@ dependencies { testImplementation project(":nullaway") testImplementation project(":jar-infer:test-java-lib-jarinfer") } - - -test { - maxHeapSize = "1024m" - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - ] -} diff --git a/jdk17-unit-tests/build.gradle b/jdk17-unit-tests/build.gradle index 8f37181bff..1c9f7f908d 100644 --- a/jdk17-unit-tests/build.gradle +++ b/jdk17-unit-tests/build.gradle @@ -15,7 +15,7 @@ */ plugins { id 'java-library' - id 'nullaway.jacoco-conventions' + id 'nullaway.java-test-conventions' } // Use JDK 17 for this module, via a toolchain @@ -44,19 +44,15 @@ dependencies { } test { - maxHeapSize = "1024m" - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + // Expose a module path for tests as a JVM property. + // Used by com.uber.nullaway.jdk17.NullAwayModuleInfoTests + "-Dtest.module.path=${configurations.testModulePath.asPath}" + ] +} + +tasks.getByName('testJdk21').configure { + jvmArgs += [ // Expose a module path for tests as a JVM property. // Used by com.uber.nullaway.jdk17.NullAwayModuleInfoTests "-Dtest.module.path=${configurations.testModulePath.asPath}" diff --git a/jmh/build.gradle b/jmh/build.gradle index 03752ffbbc..68e3c58446 100644 --- a/jmh/build.gradle +++ b/jmh/build.gradle @@ -15,7 +15,7 @@ */ plugins { id 'java-library' - id 'nullaway.jacoco-conventions' + id 'nullaway.java-test-conventions' id 'me.champeau.jmh' } @@ -128,21 +128,11 @@ jmh { // includes = ['DFlowMicro'] } -// don't run test task on pre-JDK-11 VMs tasks.named('test') { // pass the extra JVM args so we can compile benchmarks in unit tests - jvmArgs extraJVMArgs - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - ] + jvmArgs += extraJVMArgs +} + +tasks.getByName('testJdk21').configure { + jvmArgs += extraJVMArgs } diff --git a/nullaway/build.gradle b/nullaway/build.gradle index 5b6346f4a9..026a89094a 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -17,7 +17,7 @@ import net.ltgt.gradle.errorprone.CheckSeverity plugins { id 'java-library' - id 'nullaway.jacoco-conventions' + id 'nullaway.java-test-conventions' } configurations { @@ -62,7 +62,6 @@ dependencies { testImplementation deps.test.grpcCore testImplementation project(":test-java-lib-lombok") testImplementation deps.test.mockito - testImplementation deps.test.mockitoInline testImplementation deps.test.javaxAnnotationApi testImplementation deps.test.assertJ } @@ -73,22 +72,6 @@ javadoc { test { - maxHeapSize = "1024m" - // to expose necessary JDK types on JDK 16+; see https://errorprone.info/docs/installation#java-9-and-newer - jvmArgs += [ - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - // Accessed by Lombok tests - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - ] if (deps.versions.errorProneApi == "2.4.0" && JavaVersion.current() >= JavaVersion.VERSION_17) { // This test does not pass on JDK 17 with Error Prone 2.4.0 due to a Mockito incompatibility. Skip it (the // test passes with more recent Error Prone versions on JDK 17) @@ -139,12 +122,25 @@ def jdk8Test = tasks.register("testJdk8", Test) { classpath = testTask.classpath testClassesDirs = testTask.testClassesDirs jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" + filter { + // tests cannot run on JDK 8 since Mockito version no longer supports it + excludeTestsMatching "com.uber.nullaway.NullAwaySerializationTest.initializationError" + excludeTestsMatching "com.uber.nullaway.handlers.contract.ContractUtilsTest.getEmptyAntecedent" + } } tasks.named('check').configure { dependsOn(jdk8Test) } +tasks.named('testJdk21', Test).configure { + filter { + // JSpecify Generics tests do not yet pass on JDK 21 + // See https://github.com/uber/NullAway/issues/827 + excludeTestsMatching "com.uber.nullaway.NullAwayJSpecifyGenericsTests" + } +} + // Create a task to build NullAway with NullAway checking enabled tasks.register('buildWithNullAway', JavaCompile) { onlyIf { From 3a47fee24a81f7f9f7877f7a1b5356ce16b62825 Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Thu, 7 Sep 2023 14:44:14 -0400 Subject: [PATCH 55/57] Prepare for release 0.10.14. --- CHANGELOG.md | 12 ++++++++++++ gradle.properties | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ae9128a84..26c65e8f08 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ Changelog ========= +Version 0.10.14 +--------------- +IMPORTANT: This version introduces EXPERIMENTAL JDK21 support. +* Bump Checker Framework dependency to 3.38.0 (#819) + - Note: Not just an internal implementation change. Needed to support JDK 21! +* Treat parameter of generated Record.equals() methods as @Nullable (#825) +* Build / CI tooling for NullAway itself: + - Fixes Codecov Report Expired error (#821) + - Updated Readme.md with Codecov link (#823) + - Remove ASM-related hack in build config (#824) + - Run tests on JDK 21 (#826) + Version 0.10.13 --------------- * Allow library models to define custom stream classes (#807) diff --git a/gradle.properties b/gradle.properties index ee5f44742d..cbe9e56715 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.14-SNAPSHOT +VERSION_NAME=0.10.14 POM_DESCRIPTION=A fast annotation-based null checker for Java From 207acbd6651ef919cb8fa18b6a41ff4312affc64 Mon Sep 17 00:00:00 2001 From: Lazaro Clapp Date: Thu, 7 Sep 2023 14:54:22 -0400 Subject: [PATCH 56/57] Prepare next development version. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cbe9e56715..452472526a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.14 +VERSION_NAME=0.10.15-SNAPSHOT POM_DESCRIPTION=A fast annotation-based null checker for Java From 70af259fa1bfa7cad64e4b1d681e47423dc38960 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sun, 17 Sep 2023 10:39:17 -0700 Subject: [PATCH 57/57] Update Gradle and a couple of plugin versions (#832) Just to stay up to date --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 63375 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index af60cc9bee..729ffc2539 100644 --- a/build.gradle +++ b/build.gradle @@ -27,11 +27,11 @@ buildscript { } } plugins { - id "com.diffplug.spotless" version "6.20.0" + id "com.diffplug.spotless" version "6.21.0" id "net.ltgt.errorprone" version "3.1.0" apply false id "com.github.johnrengelman.shadow" version "8.1.1" apply false id "me.champeau.jmh" version "0.7.1" apply false - id "com.github.ben-manes.versions" version "0.47.0" + id "com.github.ben-manes.versions" version "0.48.0" id "com.felipefzdz.gradle.shellcheck" version "1.4.6" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c4cdf41af1ab109bc7f253b2b887023340..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 28216 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c2447881d4..864d6c4751 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionSha256Sum=591855b517fc635b9e04de1d05d5e76ada3f89f5fc76f87978d1b245b4f69225 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca147..0adc8e1a53 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum