@@ -63,6 +63,7 @@ predicate methodCallHasConstantArguments(MethodCall mc) {
6363 )
6464}
6565
66+ /** The name of a class implementing `java.util.Collection`. */
6667class CollectionClass extends string {
6768 CollectionClass ( ) { this = [ "List" , "Set" ] }
6869}
@@ -87,64 +88,127 @@ module Collection {
8788 }
8889 }
8990
90- private class NonConstantElementAddition extends Expr {
91+ /** A call where a collection of constants of class `collectionClass` can be in */
92+ abstract class SafeCall extends Call {
93+ int arg ;
9194 CollectionClass collectionClass ;
9295
96+ SafeCall ( ) {
97+ arg = - 1 and exists ( this .getQualifier ( ) )
98+ or
99+ exists ( this .getArgument ( arg ) )
100+ }
101+
93102 /** Gets whether the collection is a "List" or a "Set". */
94103 CollectionClass getCollectionClass ( ) { result = collectionClass }
95104
96- NonConstantElementAddition ( ) {
97- exists ( Method m , RefType t , MethodCall mc |
98- this = mc .getQualifier ( ) and
99- mc .getMethod ( ) = m and
105+ /** Gets the argument index, or -1 for the qualifier. */
106+ int getArg ( ) { result = arg }
107+ }
108+
109+ private class AddConstantElement extends SafeCall , MethodCall {
110+ AddConstantElement ( ) {
111+ arg = - 1 and
112+ exists ( Method m , RefType t |
113+ this .getMethod ( ) = m and
100114 t = m .getDeclaringType ( ) .getSourceDeclaration ( ) .getASourceSupertype * ( )
101115 |
102116 collectionClass = "List" and
103117 t .hasQualifiedName ( "java.util" , "List" ) and
104118 m .getName ( ) = [ "add" , "addFirst" , "addLast" ] and
105- not mc .getArgument ( m .getNumberOfParameters ( ) - 1 ) .isCompileTimeConstant ( )
119+ this .getArgument ( m .getNumberOfParameters ( ) - 1 ) .isCompileTimeConstant ( )
106120 or
107121 collectionClass = "Set" and
108122 t .hasQualifiedName ( "java.util" , "Set" ) and
109123 m .getName ( ) = [ "add" ] and
110- not mc .getArgument ( 0 ) .isCompileTimeConstant ( )
111- or
112- // If a whole collection is added then we don't try to track if it contains
113- // only compile-time constants, and conservatively assume that it does.
114- t .hasQualifiedName ( "java.util" , "Collection" ) and
115- m .getName ( ) = "addAll"
124+ this .getArgument ( 0 ) .isCompileTimeConstant ( )
116125 )
117126 }
118127 }
119128
120- private predicate collectionOfConstantsLocalFlowTo ( CollectionClass collectionClass , Expr e ) {
121- exists ( CollectionOfConstants loc |
122- loc .getCollectionClass ( ) = collectionClass and DataFlow:: localExprFlow ( loc , e )
123- |
124- loc .isImmutable ( )
125- or
126- not DataFlow:: localExprFlow ( any ( NonConstantElementAddition ncea ) , e )
127- )
129+ private class UnmodifiableCollection extends SafeCall , MethodCall {
130+ UnmodifiableCollection ( ) {
131+ arg = 0 and
132+ exists ( Method m |
133+ this .getMethod ( ) = m and
134+ m .hasName ( "unmodifiable" + collectionClass ) and
135+ m .getDeclaringType ( )
136+ .getSourceDeclaration ( )
137+ .getASourceSupertype * ( )
138+ .hasQualifiedName ( "java.util" , "Collections" )
139+ )
140+ }
141+ }
142+
143+ // Expr foo(Expr q, string s) {
144+ // q = any(CollectionContainsCall ccc).getQualifier() and
145+ // result = getALocalExprFlowRoot(q) and
146+ // (
147+ // if exists(Field f | DataFlow::localExprFlow(f.getAnAccess(), result))
148+ // then
149+ // exists(Field f | DataFlow::localExprFlow(f.getAnAccess(), result) |
150+ // f.isStatic() and
151+ // f.isFinal() and
152+ // s = "static final field"
153+ // or
154+ // not (
155+ // f.isStatic() and
156+ // f.isFinal()
157+ // ) and
158+ // s = "field read"
159+ // )
160+ // else
161+ // if result = any(MethodCall mc)
162+ // then s = "method call"
163+ // else
164+ // if result = any(ConstructorCall cc)
165+ // then s = "constructor call"
166+ // else
167+ // if result = any(Call cc)
168+ // then s = "other call"
169+ // else s = "something else"
170+ // )
171+ // }
172+ Expr getALocalExprFlowRoot ( Expr e ) {
173+ DataFlow:: localExprFlow ( result , e ) and
174+ not exists ( Expr e2 | e2 != result | DataFlow:: localExprFlow ( e2 , result ) )
128175 }
129176
130- private predicate collectionOfConstantsFlowsTo ( CollectionClass collectionClass , Expr e ) {
131- collectionOfConstantsLocalFlowTo ( collectionClass , e )
132- or
133- // Access a static final field to get an immutable list of constants.
134- exists ( Field f |
135- f .isStatic ( ) and
136- f .isFinal ( ) and
137- forall ( Expr v | v = f .getInitializer ( ) or v = f .getAnAccess ( ) .( FieldWrite ) .getASource ( ) |
138- v =
139- any ( CollectionOfConstants loc |
140- loc .getCollectionClass ( ) = collectionClass and loc .isImmutable ( )
141- )
177+ private predicate noUnsafeCalls ( Expr e ) {
178+ forall ( MethodCall mc , int arg , Expr x |
179+ DataFlow:: localExprFlow ( x , e ) and
180+ (
181+ arg = - 1 and x = mc .getQualifier ( )
182+ or
183+ x = mc .getArgument ( arg )
142184 )
143185 |
144- DataFlow :: localExprFlow ( f . getAnAccess ( ) , e )
186+ x = e or arg = mc . ( SafeCall ) . getArg ( )
145187 )
146188 }
147189
190+ private predicate collectionOfConstantsFlowsTo ( Expr e ) {
191+ forex ( Expr r | r = getALocalExprFlowRoot ( e ) |
192+ r instanceof CollectionOfConstants
193+ or
194+ // Access a static final field to get an immutable list of constants.
195+ exists ( Field f | r = f .getAnAccess ( ) |
196+ f .isStatic ( ) and
197+ f .isFinal ( ) and
198+ forall ( Expr v | v = f .getInitializer ( ) |
199+ v = any ( CollectionOfConstants loc | loc .isImmutable ( ) )
200+ ) and
201+ forall ( Expr fieldSource | fieldSource = f .getAnAccess ( ) .( FieldWrite ) .getASource ( ) |
202+ forall ( Expr root | root = getALocalExprFlowRoot ( fieldSource ) |
203+ root .( CollectionOfConstants ) .isImmutable ( )
204+ ) and
205+ noUnsafeCalls ( fieldSource )
206+ )
207+ )
208+ ) and
209+ noUnsafeCalls ( e )
210+ }
211+
148212 /**
149213 * An invocation of `java.util.List.contains` where the qualifier contains only
150214 * compile-time constants.
@@ -155,7 +219,7 @@ module Collection {
155219 this = mc and
156220 e = mc .getArgument ( 0 ) and
157221 outcome = true and
158- collectionOfConstantsFlowsTo ( mc .getCollectionClass ( ) , mc . getQualifier ( ) )
222+ collectionOfConstantsFlowsTo ( mc .getQualifier ( ) )
159223 )
160224 }
161225 }
@@ -220,7 +284,8 @@ module Collection {
220284 .getASourceSupertype * ( )
221285 .hasQualifiedName ( "java.util" , "Collection" )
222286 ) and
223- collectionOfConstantsFlowsTo ( _, this .getArgument ( 0 ) )
287+ // Any collection can be used in the non-empty constructor.
288+ collectionOfConstantsFlowsTo ( this .getArgument ( 0 ) )
224289 }
225290
226291 override predicate isImmutable ( ) { none ( ) }
@@ -281,7 +346,7 @@ module Collection {
281346 .hasQualifiedName ( "java.util" , "Collections" ) and
282347 this .getMethod ( ) = m
283348 |
284- collectionOfConstantsFlowsTo ( collectionClass , this .getArgument ( 0 ) )
349+ collectionOfConstantsFlowsTo ( this .getArgument ( 0 ) )
285350 )
286351 }
287352
0 commit comments