Skip to content

Commit bbdd7c9

Browse files
authored
Merge pull request #4963 from joefarebrother/guava-collections
Java: Add flow steps for Guava collection utilities
2 parents 69ce24d + d69ecde commit bbdd7c9

File tree

21 files changed

+1972
-19
lines changed

21 files changed

+1972
-19
lines changed
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
/** Definitions of flow steps through the collection types in the Guava framework */
2+
3+
import java
4+
private import semmle.code.java.dataflow.DataFlow
5+
private import semmle.code.java.dataflow.FlowSteps
6+
private import semmle.code.java.Collections
7+
8+
private string guavaCollectPackage() { result = "com.google.common.collect" }
9+
10+
/** A reference type that extends a parameterization of one of the various immutable container types. */
11+
private class ImmutableContainerType extends RefType {
12+
string kind;
13+
14+
ImmutableContainerType() {
15+
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName(guavaCollectPackage(), kind) and
16+
kind = ["ImmutableCollection", "ImmutableMap", "ImmutableMultimap", "ImmutableTable"]
17+
}
18+
19+
/**
20+
* Gets the name of the most general superclass of this type
21+
* from among `ImmutableCollection`, `ImmutableMap`, `ImmutableMultimap`, and `ImmutableTable`.
22+
*/
23+
string getKind() { result = kind }
24+
}
25+
26+
/** A nested `Builder` class of one of the various immutable container classes */
27+
private class ContainerBuilder extends NestedType {
28+
ContainerBuilder() {
29+
this.hasName("Builder") and
30+
this.getEnclosingType() instanceof ImmutableContainerType
31+
}
32+
}
33+
34+
private class BuilderBuildMethod extends TaintPreservingCallable {
35+
BuilderBuildMethod() {
36+
this.getDeclaringType().getASourceSupertype*() instanceof ContainerBuilder and
37+
// abstract ImmutableCollection<E> build()
38+
// similar for other builder types
39+
this.hasName("build")
40+
}
41+
42+
override predicate returnsTaintFrom(int arg) { arg = -1 }
43+
}
44+
45+
/** A method on a `Builder` class that adds elements to the container being built */
46+
private class BuilderAddMethod extends TaintPreservingCallable {
47+
int argument;
48+
49+
BuilderAddMethod() {
50+
this.getDeclaringType().getASourceSupertype*() instanceof ContainerBuilder and
51+
(
52+
// abstract ImmutableCollection.Builder<E> add(E element)
53+
// ImmutableCollection.Builder<E> add(E... elements)
54+
// ImmutableCollection.Builder<E> addAll(Iterable<? extends E> elements)
55+
// ImmutableCollection.Builder<E> addAll(Iterator<? extends E> elements)
56+
// ImmutableMultiset.Builder<E> addCopies(E element, int occurrences)
57+
// ImmutableMultiset.Builder<E> setCount(E element, int count)
58+
this.hasName(["add", "addAll", "addCopies", "setCount"]) and
59+
argument = 0
60+
or
61+
// ImmutableMap.Builder<K,V> put(K key, V value)
62+
// ImmutableMap.Builder<K,V> put(Map.Entry<? extends K,? extends V> entry)
63+
// ImmutableMap.Builder<K,V> putAll(Map<? extends K,? extends V> map)
64+
// ImmutableMap.Builder<K,V> putAll(Iterable<? extends Map.Entry<? extends K,? extends V>> entries)
65+
// ImmutableMultimap.Builder<K,V> put(K key, V value)
66+
// ImmutableMultimap.Builder<K,V> put(Map.Entry<? extends K,? extends V> entry)
67+
// ImmutableMultimap.Builder<K,V> putAll(Iterable<? extends Map.Entry<? extends K,? extends V>> entries)
68+
// ImmutableMultimap.Builder<K,V> putAll(K key, Iterable<? extends V> values)
69+
// ImmutableMultimap.Builder<K,V> putAll(K key, V... values)
70+
// ImmutableMultimap.Builder<K,V> putAll(Multimap<? extends K,? extends V> multimap)
71+
// ImmutableTable.Builder<R,C,V> put(R rowKey, C columnKey, V value)
72+
// ImmutableTable.Builder<R,C,V> put(Table.Cell<? extends R,? extends C,? extends V> cell)
73+
// ImmutableTable.Builder<R,C,V> putAll(Table<? extends R,? extends C,? extends V> table)
74+
this.hasName(["put", "putAll"]) and
75+
argument = getNumberOfParameters() - 1
76+
)
77+
}
78+
79+
override predicate returnsTaintFrom(int arg) { arg = [-1, argument] }
80+
81+
override predicate transfersTaint(int src, int sink) { src = argument and sink = -1 }
82+
}
83+
84+
/**
85+
* In a chained call `b.add(x).add(y).add(z)`, represents a flow step from the return value of
86+
* this expression to the post update node of `b` (valid because the builder add methods return their qualifier).
87+
* This is sufficient to express flow from `y` and `z` to `b`.
88+
*/
89+
private class ChainedBuilderAddStep extends AdditionalTaintStep {
90+
override predicate step(DataFlow::Node src, DataFlow::Node sink) {
91+
exists(MethodAccess ma |
92+
ma.getMethod() instanceof BuilderAddMethod and
93+
src.asExpr() = ma and
94+
chainedBuilderMethod+(sink.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()) = ma
95+
)
96+
}
97+
}
98+
99+
private MethodAccess chainedBuilderMethod(Expr e) {
100+
result.getQualifier() = e and
101+
result.getMethod() instanceof BuilderAddMethod
102+
}
103+
104+
/**
105+
* A reference type that extends a parameterization of `com.google.common.collect.Multimap`.
106+
*/
107+
class MultimapType extends RefType {
108+
MultimapType() {
109+
this.getSourceDeclaration()
110+
.getASourceSupertype*()
111+
.hasQualifiedName(guavaCollectPackage(), "Multimap")
112+
}
113+
114+
/** Gets the type of keys stored in this map. */
115+
RefType getKeyType() {
116+
exists(GenericInterface map | map.hasQualifiedName(guavaCollectPackage(), "Multimap") |
117+
indirectlyInstantiates(this, map, 0, result)
118+
)
119+
}
120+
121+
/** Gets the type of values stored in this map. */
122+
RefType getValueType() {
123+
exists(GenericInterface map | map.hasQualifiedName(guavaCollectPackage(), "Multimap") |
124+
indirectlyInstantiates(this, map, 1, result)
125+
)
126+
}
127+
}
128+
129+
private class MultimapWriteMethod extends TaintPreservingCallable {
130+
MultimapWriteMethod() {
131+
this.getDeclaringType() instanceof MultimapType and
132+
// boolean put(K key, V value)
133+
// boolean putAll(K key, Iterable<? extends V> values)
134+
// boolean putAll(Multimap<? extends K,? extends V> multimap)
135+
// Collection<V> replaceValues(K key, Iterable<? extends V> values)
136+
this.hasName(["put", "putAll", "replaceValues"])
137+
}
138+
139+
override predicate transfersTaint(int src, int sink) {
140+
src = getNumberOfParameters() - 1 and
141+
sink = -1
142+
}
143+
}
144+
145+
private class MultimapReadMethod extends TaintPreservingCallable {
146+
MultimapReadMethod() {
147+
this.getDeclaringType() instanceof MultimapType and
148+
// Collection<V> replaceValues(K key, Iterable<? extends V> values)
149+
// Collection<V> removeAll(@CompatibleWith("K") Object key)
150+
// Collection<V> get(K key)
151+
// Collection<V> values()
152+
// Collection<Map.Entry<K,V>> entries()
153+
// Map<K,Collection<V>> asMap()
154+
this.hasName(["replaceValues", "removeAll", "get", "values", "entries", "asMap"])
155+
}
156+
157+
override predicate returnsTaintFrom(int arg) { arg = -1 }
158+
// Not implemented: Some of these methods return "views", which when modified will modify the map itself.
159+
// However, taint flow from these views to the map is not implemented.
160+
}
161+
162+
/**
163+
* A reference type that extends a parameterization of `com.google.common.collect.Table`.
164+
*/
165+
class TableType extends RefType {
166+
TableType() {
167+
this.getSourceDeclaration()
168+
.getASourceSupertype*()
169+
.hasQualifiedName(guavaCollectPackage(), "Table")
170+
}
171+
172+
/** Gets the type of row keys stored in this table. */
173+
RefType getRowType() {
174+
exists(GenericInterface table | table.hasQualifiedName(guavaCollectPackage(), "Table") |
175+
indirectlyInstantiates(this, table, 0, result)
176+
)
177+
}
178+
179+
/** Gets the type of column keys stored in this table. */
180+
RefType getColumnType() {
181+
exists(GenericInterface table | table.hasQualifiedName(guavaCollectPackage(), "Table") |
182+
indirectlyInstantiates(this, table, 1, result)
183+
)
184+
}
185+
186+
/** Gets the type of values stored in this table. */
187+
RefType getValueType() {
188+
exists(GenericInterface table | table.hasQualifiedName(guavaCollectPackage(), "Table") |
189+
indirectlyInstantiates(this, table, 2, result)
190+
)
191+
}
192+
}
193+
194+
private class TableWriteMethod extends TaintPreservingCallable {
195+
TableWriteMethod() {
196+
this.getDeclaringType() instanceof TableType and
197+
// V put(R rowKey, C columnKey, V value)
198+
// void putAll(Table<? extends R,? extends C,? extends V> table)
199+
this.hasName(["put", "putAll"])
200+
}
201+
202+
override predicate transfersTaint(int src, int sink) {
203+
src = getNumberOfParameters() - 1 and
204+
sink = -1
205+
}
206+
}
207+
208+
private class TableReadMethod extends TaintPreservingCallable {
209+
TableReadMethod() {
210+
this.getDeclaringType() instanceof TableType and
211+
// V put(R rowKey, C columnKey, V value)
212+
// V remove(@CompatibleWith("R") Object rowKey, @CompatibleWith("C") Object columnKey)
213+
// V get(@CompatibleWith("R") Object rowKey, @CompatibleWith("C") Object columnKey)
214+
// Map<C,V> row(R rowKey)
215+
// Map<R,V> column(C columnKey)
216+
// Set<Table.Cell<R,C,V>> cellSet()
217+
// Collection<V> values()
218+
// Map<R,Map<C,V>> rowMap()
219+
// Map<C,Map<R,V>> columnMap()
220+
this.hasName([
221+
"put", "remove", "get", "row", "column", "cellSet", "values", "rowMap", "columnMap"
222+
])
223+
}
224+
225+
override predicate returnsTaintFrom(int arg) { arg = -1 }
226+
// Not implemented: Some of these methods return "views", which when modified will modify the table itself.
227+
// However, taint flow from these views to the table is not implemented.
228+
}
229+
230+
private class TableCellReadMethod extends TaintPreservingCallable {
231+
TableCellReadMethod() {
232+
exists(NestedType cell |
233+
cell.getEnclosingType() instanceof TableType and
234+
cell.hasName("Cell") and
235+
this.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = cell and
236+
// V getValue()
237+
this.hasName("getValue")
238+
)
239+
}
240+
241+
override predicate returnsTaintFrom(int arg) { arg = -1 }
242+
}
243+
244+
/**
245+
* An `of` static method on the various immutable container types.
246+
*/
247+
private class OfMethod extends TaintPreservingCallable {
248+
string kind;
249+
250+
OfMethod() {
251+
this.getDeclaringType().(ImmutableContainerType).getKind() = kind and
252+
// static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6)
253+
// static <K,V> ImmutableMap<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4)
254+
// static <K,V> ImmutableMultimap<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4)
255+
// static <R,C,V> ImmutableTable<R,C,V> of(R rowKey, C columnKey, V value)
256+
// etc for other types and numbers of parameters
257+
this.hasName("of") and
258+
this.isStatic()
259+
}
260+
261+
override predicate returnsTaintFrom(int arg) {
262+
arg = [0 .. getNumberOfParameters()] and
263+
(kind.regexpMatch(".*[Mm]ap") implies arg % 2 = 1) and
264+
(kind = "ImmutableTable" implies arg % 3 = 2)
265+
}
266+
}
267+
268+
private class ComparatorType extends RefType {
269+
ComparatorType() { this.getASourceSupertype*().hasQualifiedName("java.util", "Comparator") }
270+
}
271+
272+
/**
273+
* A `copyOf`, `sortedCopyOf`, or `copyOfSorted` static method on the various immutable container types.
274+
*/
275+
private class CopyOfMethod extends TaintPreservingCallable {
276+
CopyOfMethod() {
277+
this.getDeclaringType() instanceof ImmutableContainerType and
278+
// static <E> ImmutableList<E> copyOf(E[] elements)
279+
// static <E> ImmutableList<E> copyOf(Iterable<? extends E> elements)
280+
// static <E> ImmutableList<E> copyOf(Collection<? extends E> elements)
281+
// static <E> ImmutableList<E> copyOf(Iterator<? extends E> elements)
282+
// static <E extends Comparable<? super E>> ImmutableList<E> sortedCopyOf(Iterable<? extends E> elements)
283+
// static <E> ImmutableList<E> sortedCopyOf(Comparator<? super E> comparator, Iterable<? extends E> elements)
284+
// static <K,V> ImmutableMap<K,V> copyOf(Map<? extends K,? extends V> map)
285+
// static <K,V> ImmutableMap<K,V> copyOf(Iterable<? extends Map.Entry<? extends K,? extends V>> entries)
286+
// static <K,V> ImmutableMultimap<K,V> copyOf(Multimap<? extends K,? extends V> multimap)
287+
// static <K,V> ImmutableMultimap<K,V> copyOf(Iterable<? extends Map.Entry<? extends K,? extends V>> entries)
288+
// static <R,C,V> ImmutableTable<R,C,V> copyOf(Table<? extends R,? extends C,? extends V> table)
289+
// static <K, V> ImmutableSortedMap<K, V> copyOf(Map<? extends K, ? extends V> map)
290+
// static <K, V> ImmutableSortedMap<K, V> copyOf(Map<? extends K, ? extends V> map, Comparator<? super K> comparator)
291+
// static <K, V> ImmutableSortedMap<K, V> copyOfSorted(SortedMap<K, ? extends V> map)
292+
// static <E> ImmutableSortedSet<E> copyOf(Iterator<? extends E> elements)
293+
// static <E> ImmutableSortedSet<E> copyOf(Comparator<? super E> comparator, Iterator<? extends E> elements)
294+
// static <E> ImmutableSortedSet<E> copyOfSorted(SortedSet<E> sortedSet)
295+
// etc
296+
this.hasName(["copyOf", "sortedCopyOf", "copyOfSorted"]) and
297+
this.isStatic()
298+
}
299+
300+
override predicate returnsTaintFrom(int arg) {
301+
arg = [0 .. getNumberOfParameters()] and
302+
not getParameterType(arg) instanceof ComparatorType
303+
}
304+
}
305+
306+
private class CollectionAsListMethod extends TaintPreservingCallable {
307+
CollectionAsListMethod() {
308+
this.getDeclaringType()
309+
.getASourceSupertype*()
310+
.hasQualifiedName(guavaCollectPackage(), "ImmutableCollection") and
311+
// public ImmutableList<E> asList()
312+
this.hasName("asList")
313+
}
314+
315+
override predicate returnsTaintFrom(int arg) { arg = -1 }
316+
}
317+
318+
/**
319+
* A taint-preserving static method of `com.google.common.collect.Sets`.
320+
*/
321+
private class SetsMethod extends TaintPreservingCallable {
322+
int arg;
323+
324+
SetsMethod() {
325+
this.getDeclaringType().hasQualifiedName(guavaCollectPackage(), "Sets") and
326+
this.isStatic() and
327+
(
328+
// static <E> HashSet<E> newHashSet(E... elements)
329+
// static <E> Set<E> newConcurrentHashSet(Iterable<? extends E> elements)
330+
// static <E> CopyOnWriteArraySet<E> newCopyOnWriteArraySet(Iterable<? extends E> elements)
331+
// etc
332+
this.getName().matches("new%Set") and
333+
arg = 0
334+
or
335+
// static <B> Set<List<B>> cartesianProduct(List<? extends Set<? extends B>> sets)
336+
// static <B> Set<List<B>> cartesianProduct(Set<? extends B>... sets)
337+
// static <E> Set<Set<E>> combinations(Set<E> set, int size)
338+
// static <E> Sets.SetView<E> difference(Set<E> set1, Set<?> set2)
339+
// static <E> NavigableSet<E> filter(NavigableSet<E> unfiltered, Predicate<? super E> predicate)
340+
// static <E> Set<E> filter(Set<E> unfiltered, Predicate<? super E> predicate)
341+
// static <E> SortedSet<E> filter(SortedSet<E> unfiltered, Predicate<? super E> predicate)
342+
// static <E> Set<Set<E>> powerSet(Set<E> set)
343+
// static <K extends Comparable<? super K>> NavigableSet<K> subSet(NavigableSet<K> set, Range<K> range)
344+
// static <E> NavigableSet<E> synchronizedNavigableSet(NavigableSet<E> navigableSet)
345+
// static <E> NavigableSet<E> unmodifiableNavigableSet(NavigableSet<E> set)
346+
this.hasName([
347+
"cartesianProduct", "combinations", "difference", "filter", "powerSet", "subSet",
348+
"synchronizedNavigableSet", "unmodifyableNavigableSet"
349+
]) and
350+
arg = 0
351+
or
352+
// static <E> Sets.SetView<E> symmetricDifference(Set<? extends E> set1, Set<? extends E> set2)
353+
// static <E> Sets.SetView<E> union(Set<? extends E> set1, Set<? extends E> set2)
354+
this.hasName(["symmetricDifference", "union"]) and
355+
arg = [0, 1]
356+
)
357+
}
358+
359+
override predicate returnsTaintFrom(int arg_) { arg_ = arg }
360+
}

java/ql/src/semmle/code/java/frameworks/guava/Guava.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
import java
66
import StringUtils
7+
import Collections

0 commit comments

Comments
 (0)