mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #5 from hvitved/csharp/bad-practices-implementation-hiding
C#: Update queries in `Bad Practices/Implementation Hiding`
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
|
||||
| **Query** | **Tags** | **Purpose** |
|
||||
|-----------------------------|-----------|--------------------------------------------------------------------|
|
||||
| [Exposing internal representation (cs/expose-implementation)] | Different results | The query has been rewritten, based on the equivalent Java query. |
|
||||
| Local scope variable shadows member (cs/local-shadows-member) | maintainability, readability | Replaces the existing queries [Local variable shadows class member (cs/local-shadows-class-member)](https://help.semmle.com/wiki/display/CSHARP/Local+variable+shadows+class+member), [Local variable shadows struct member (cs/local-shadows-struct-member)](https://help.semmle.com/wiki/display/CSHARP/Local+variable+shadows+struct+member), [Parameter shadows class member (cs/parameter-shadows-class-member)](https://help.semmle.com/wiki/display/CSHARP/Parameter+shadows+class+member), and [Parameter shadows struct member (cs/parameter-shadows-struct-member)](https://help.semmle.com/wiki/display/CSHARP/Parameter+shadows+struct+member). |
|
||||
|
||||
## Changes to existing queries
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
class AbstractToConcreteCollection
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ICollection<String> foo = new List<String>();
|
||||
foo.Add("hello");
|
||||
foo.Add("world");
|
||||
List<String> bar = (List<String>)foo; // BAD
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ more difficult to change which implementation you are using at a later date.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The example shows casting from an ICollection to a List. This should be avoided where possible.</p>
|
||||
<sample src="AbstractToConcreteCollection.cs" />
|
||||
<p>The example shows casting from an <code>IEnumerable<string></code> to a <code>List<string></code>. This should be avoided where possible.</p>
|
||||
<sample src="AbstractToConcreteCollectionBad.cs" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* @name Cast from abstract to concrete collection
|
||||
* @description Finds casts from an abstract collection to a concrete implementation
|
||||
* type. This makes the code brittle; it is best to program against the
|
||||
* abstract collection interfaces only.
|
||||
* @description A cast from an abstract collection to a concrete implementation type
|
||||
* makes the code brittle; it is best to program against the abstract
|
||||
* collection interface only.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
@@ -13,20 +13,25 @@
|
||||
* external/cwe/cwe-485
|
||||
*/
|
||||
import csharp
|
||||
import semmle.code.csharp.frameworks.system.Collections
|
||||
import semmle.code.csharp.frameworks.system.collections.Generic
|
||||
|
||||
/** A sub-interface of Collection */
|
||||
/** A collection interface. */
|
||||
class CollectionInterface extends Interface {
|
||||
CollectionInterface() {
|
||||
exists(string name |
|
||||
this.getSourceDeclaration().getABaseType*().getName() = name and
|
||||
( name.matches("ICollection<%>")
|
||||
or name="ICollection")
|
||||
exists(Interface i |
|
||||
i = this.getABaseInterface*() |
|
||||
i instanceof SystemCollectionsICollectionInterface or
|
||||
i.getSourceDeclaration() instanceof SystemCollectionsGenericICollectionInterface or
|
||||
i instanceof SystemCollectionsIEnumerableInterface or
|
||||
i.getSourceDeclaration() instanceof SystemCollectionsGenericIEnumerableTInterface
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from CastExpr e, Class c, CollectionInterface i
|
||||
where e.getType() = c and
|
||||
e.getExpr().getType().(RefType).getSourceDeclaration() = i
|
||||
select e, "Questionable cast from abstract " + i.getName()
|
||||
+ " to concrete implementation " + c.getName() + "."
|
||||
where e.getType() = c
|
||||
and e.getExpr().getType() = i
|
||||
and c.isImplicitlyConvertibleTo(i)
|
||||
select e, "Questionable cast from abstract '" + i.getName()
|
||||
+ "' to concrete implementation '" + c.getName() + "'."
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
class Bad
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var names = GetNames();
|
||||
var list = (List<string>) names;
|
||||
list.Add("Eve");
|
||||
}
|
||||
|
||||
static IEnumerable<string> GetNames()
|
||||
{
|
||||
var ret = new List<string>()
|
||||
{
|
||||
"Alice",
|
||||
"Bob"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
class Range
|
||||
{
|
||||
private int[] rarray = new int[2];
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
}
|
||||
}
|
||||
public int[] get()
|
||||
{
|
||||
return rarray;
|
||||
}
|
||||
}
|
||||
class ExposeRepresentation
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Range a = new Range(1, 10);
|
||||
int[] a_range = a.get();
|
||||
a_range[0] = 500;
|
||||
Console.WriteLine("Min: " + a.get()[0] + " Max: " + a.get()[1]);
|
||||
// prints "Min: 500 Max: 10"
|
||||
}
|
||||
}
|
||||
@@ -19,25 +19,25 @@ fields will not be affected.</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>This example clearly demonstrates the problem with passing references to mutable objects outside a class. In this case
|
||||
it was possible to modify the values in the array despite the Range class not offering any method to do so.</p>
|
||||
<sample src="ExposeRepresentation.cs" />
|
||||
it was possible to modify the values in the array despite the <code>Range</code> class not offering any method to do so.</p>
|
||||
<sample src="ExposeRepresentationBad.cs" />
|
||||
|
||||
</example>
|
||||
<section title="Fixing With an Immutable Object">
|
||||
<section title="Fixing with an immutable object">
|
||||
<p>Here the example has been modified to prevent changes to the private field by using a <code>ReadOnlyCollection</code>
|
||||
object.</p>
|
||||
<sample src="ExposeRepresentationFix1.cs" />
|
||||
<sample src="ExposeRepresentationGood1.cs" />
|
||||
|
||||
</section>
|
||||
<section title="Fixing With Defensive Copying">
|
||||
<section title="Fixing with defensive copying">
|
||||
<p>This is an example of the same class but this time it returns a defensive copy of the private field. There is also
|
||||
a short program showing what happens when an attempt is made to modify the data held by the field.</p>
|
||||
<sample src="ExposeRepresentationFix2.cs" />
|
||||
<sample src="ExposeRepresentationGood2.cs" />
|
||||
|
||||
</section>
|
||||
<references>
|
||||
|
||||
<li>MSDN, C# Programming Guide, <a href="http://msdn.microsoft.com/en-us/library/vstudio/2z4khca9.aspx">Arrays as Objects</a>.</li>
|
||||
<li>MSDN, C# Programming Guide, <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/arrays/arrays-as-objects">Arrays as Objects</a>.</li>
|
||||
<li>MSDN, <a href="http://msdn.microsoft.com/en-us/library/ms132474.aspx">ReadOnlyCollection<T></a>.</li>
|
||||
|
||||
</references>
|
||||
|
||||
@@ -1,40 +1,82 @@
|
||||
/**
|
||||
* @name Exposes internal representation
|
||||
* @description Finds code that may expose an object's internal representation by
|
||||
* incorporating reference to mutable object.
|
||||
* @name Exposing internal representation
|
||||
* @description An object that accidentally exposes its internal representation may allow the
|
||||
* object's fields to be modified in ways that the object is not prepared to handle.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @precision medium
|
||||
* @precision high
|
||||
* @id cs/expose-implementation
|
||||
* @tags reliability
|
||||
* external/cwe/cwe-485
|
||||
*/
|
||||
import csharp
|
||||
import semmle.code.csharp.commons.Collections
|
||||
import DataFlow
|
||||
|
||||
/* This code stores a reference to an externally mutable object into the internal
|
||||
representation of the object.
|
||||
If instances are accessed by untrusted code, and unchecked changes to the mutable
|
||||
object would compromise security or other important properties,
|
||||
you will need to do something different. Storing a copy of the object is better
|
||||
approach in many situations.
|
||||
predicate storesCollection(Callable c, Parameter p, Field f) {
|
||||
f.getDeclaringType() = c.getDeclaringType().getABaseType*().getSourceDeclaration() and
|
||||
f.getType() instanceof CollectionType and
|
||||
p = c.getAParameter() and
|
||||
f.getAnAssignedValue() = p.getAnAccess() and
|
||||
not c.(Modifiable).isStatic()
|
||||
}
|
||||
|
||||
In this analysis an object is considered mutable if it is an array, a
|
||||
java.util.Hashtable, or a java.util.Date.
|
||||
The analysis detects stores to fields of these types where the value is given as a
|
||||
parameter. */
|
||||
predicate returnsCollection(Callable c, Field f) {
|
||||
f.getDeclaringType() = c.getDeclaringType().getABaseType*().getSourceDeclaration() and
|
||||
f.getType() instanceof CollectionType and
|
||||
c.canReturn(f.getAnAccess()) and
|
||||
not c.(Modifiable).isStatic()
|
||||
}
|
||||
|
||||
from Assignment a, Field f, VariableAccess va, RefType t
|
||||
where a.getLValue() = va and
|
||||
va.getTarget() = f and
|
||||
f.getType().(RefType).getSourceDeclaration() = t and
|
||||
( (va.(MemberAccess).hasQualifier() and
|
||||
va.(MemberAccess).getQualifier() instanceof ThisAccess)
|
||||
or not va.(MemberAccess).hasQualifier()) and
|
||||
a.getRValue().(VariableAccess).getTarget() instanceof Parameter and
|
||||
( t instanceof ArrayType
|
||||
//Add mutable types here as necessary. Kept the java types as a reference
|
||||
/*or t.hasQualifiedName("java.util", "Hashtable")
|
||||
or t.hasQualifiedName("java.util", "Date")*/)
|
||||
select a,
|
||||
"May expose internal representation by storing an externally mutable object in "
|
||||
+ f.getName() + "."
|
||||
predicate mayWriteToCollection(Expr modified) {
|
||||
modified instanceof CollectionModificationAccess
|
||||
or
|
||||
exists(Expr mid |
|
||||
mayWriteToCollection(mid) |
|
||||
localFlow(exprNode(modified), exprNode(mid))
|
||||
)
|
||||
or
|
||||
exists(MethodCall mid, Callable c |
|
||||
mayWriteToCollection(mid) |
|
||||
mid.getTarget() = c and
|
||||
c.canReturn(modified)
|
||||
)
|
||||
}
|
||||
|
||||
predicate modificationAfter(Expr before, Expr after) {
|
||||
mayWriteToCollection(after) and
|
||||
localFlowStep+(exprNode(before), exprNode(after))
|
||||
}
|
||||
|
||||
VariableAccess varPassedInto(Callable c, Parameter p) {
|
||||
exists(Call call |
|
||||
call.getTarget() = c |
|
||||
call.getArgumentForParameter(p) = result
|
||||
)
|
||||
}
|
||||
|
||||
predicate exposesByReturn(Callable c, Field f, Expr why, string whyText) {
|
||||
returnsCollection(c, f) and
|
||||
exists(MethodCall ma |
|
||||
ma.getTarget() = c |
|
||||
mayWriteToCollection(ma) and
|
||||
why = ma and
|
||||
whyText = "after this call to " + c.getName()
|
||||
)
|
||||
}
|
||||
|
||||
predicate exposesByStore(Callable c, Field f, Expr why, string whyText) {
|
||||
exists(VariableAccess v, Parameter p |
|
||||
storesCollection(c, p, f) and
|
||||
v = varPassedInto(c, p) and
|
||||
modificationAfter(v, why) and
|
||||
whyText = "through the variable " + v.getTarget().getName()
|
||||
)
|
||||
}
|
||||
|
||||
from Callable c, Field f, Expr why, string whyText
|
||||
where exposesByReturn(c, f, why, whyText) or
|
||||
exposesByStore(c, f, why, whyText)
|
||||
select c, "'" + c.getName() + "' exposes the internal representation stored in field '" + f.getName() +
|
||||
"'. The value may be modified $@.",
|
||||
why.getLocation(), whyText
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
class Bad
|
||||
{
|
||||
class Range
|
||||
{
|
||||
private int[] rarray = new int[2];
|
||||
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] Get() => rarray;
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var r = new Range(1, 10);
|
||||
var r_range = r.Get();
|
||||
r_range[0] = 500;
|
||||
Console.WriteLine("Min: " + r.Get()[0] + " Max: " + r.Get()[1]);
|
||||
// prints "Min: 500 Max: 10"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
class Range
|
||||
{
|
||||
private ReadOnlyCollection<int> rarray = new ReadOnlyCollection<int>(new int[2]);
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
int[] rarray = new int[2];
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
this.rarray = new ReadOnlyCollection<int>(rarray);
|
||||
}
|
||||
}
|
||||
public ReadOnlyCollection<int> get()
|
||||
{
|
||||
return rarray;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
class Range
|
||||
{
|
||||
private int[] rarray = new int[2];
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
}
|
||||
}
|
||||
public int[] get()
|
||||
{
|
||||
return (int[])rarray.Clone();
|
||||
}
|
||||
}
|
||||
class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Range a = new Range(1, 10);
|
||||
int[] a_range = a.get();
|
||||
a_range[0] = 500;
|
||||
Console.WriteLine("Min: " + a.get()[0] + " Max: " + a.get()[1]);
|
||||
// prints "Min: 1 Max: 10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
class Good1
|
||||
{
|
||||
class Range
|
||||
{
|
||||
private ReadOnlyCollection<int> rarray = new ReadOnlyCollection<int>(new int[2]);
|
||||
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
int[] rarray = new int[2];
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
this.rarray = new ReadOnlyCollection<int>(rarray);
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<int> Get() => rarray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
class Good2
|
||||
{
|
||||
class Range
|
||||
{
|
||||
private int[] rarray = new int[2];
|
||||
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] Get() => (int[])rarray.Clone();
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Range a = new Range(1, 10);
|
||||
int[] a_range = a.Get();
|
||||
a_range[0] = 500;
|
||||
Console.WriteLine("Min: " + a.Get()[0] + " Max: " + a.Get()[1]);
|
||||
// prints "Min: 1 Max: 10"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
class StaticArray
|
||||
{
|
||||
public static readonly string[] Foo = { "hello", "world" }; // BAD
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Foo[0] = "goodbye";
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,28 @@
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Arrays might be made static readonly in order to prevent their contents being changed.
|
||||
This is not the case because arrays are mutable. The readonly option prevents the array from being replaced
|
||||
by a new array but it does not prevent the internal contents of the array from being changed.</p>
|
||||
<p>Arrays might be made <code>static readonly</code> to prevent their contents from being changed.
|
||||
This doesn't have the desired effect because arrays are mutable. The <code>readonly</code> modifier prevents the array from being replaced
|
||||
by a new array but it does not prevent the contents of the array from being changed.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Consider whether the array could be split up into separate constants. If the array cannot be split then you may
|
||||
wish to use a ReadOnlyCollection instead of an array.</p>
|
||||
wish to use a <code>ReadOnlyCollection</code> instead of an array.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In this example the "Foo" array is readonly but it is still modified by the Main method.</p>
|
||||
<sample src="StaticArray.cs" />
|
||||
<p>In this example the <code>Foo</code> array is <code>readonly</code> but it is still modified by the <code>Main</code> method.</p>
|
||||
<sample src="StaticArrayBad.cs" />
|
||||
|
||||
<p>This example uses a <code>ReadOnlyCollection</code>. Any attempt to modify <code>Foo</code> will
|
||||
cause the program not to compile.</p>
|
||||
<sample src="StaticArrayFix.cs" />
|
||||
<sample src="StaticArrayGood.cs" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>MSDN, C# Programming Guide, <a href="http://msdn.microsoft.com/en-us/library/vstudio/2z4khca9.aspx">Arrays as Objects</a>.</li>
|
||||
<li>MSDN, C# Programming Guide, <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/arrays/arrays-as-objects">Arrays as Objects</a>.</li>
|
||||
<li>MSDN, <a href="http://msdn.microsoft.com/en-us/library/ms132474.aspx">ReadOnlyCollection<T></a>.</li>
|
||||
|
||||
</references>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/**
|
||||
* @name Static array and non-empty array literal
|
||||
* @description Finds public constants that are assigned an array.
|
||||
* Arrays are mutable and can be changed by malicious code or by accident.
|
||||
* @name Array constant vulnerable to change
|
||||
* @description Array constants are mutable and can be changed by malicious code or by accident.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cs/static-array
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
@@ -14,25 +13,28 @@
|
||||
import csharp
|
||||
|
||||
predicate nonEmptyArrayLiteralOrNull(Expr e) {
|
||||
exists(ArrayCreation arr | arr = e |
|
||||
e = any(ArrayCreation arr |
|
||||
exists(arr.getInitializer().getAnElement())
|
||||
or
|
||||
not arr.getALengthArgument().getValue() = "0"
|
||||
)
|
||||
or e instanceof NullLiteral
|
||||
or
|
||||
exists(ConditionalExpr cond | cond = e |
|
||||
e instanceof NullLiteral
|
||||
or
|
||||
e = any(ConditionalExpr cond |
|
||||
nonEmptyArrayLiteralOrNull(cond.getThen()) and
|
||||
nonEmptyArrayLiteralOrNull(cond.getElse())
|
||||
)
|
||||
}
|
||||
|
||||
from Field f
|
||||
where f.isPublic() and
|
||||
f.isStatic() and
|
||||
f.isReadOnly() and
|
||||
f.getType() instanceof ArrayType and
|
||||
f.fromSource() and
|
||||
forall(AssignExpr a | a.getLValue() = f.getAnAccess() | nonEmptyArrayLiteralOrNull(a.getRValue())) and
|
||||
forall(Expr e | e = f.getInitializer() | nonEmptyArrayLiteralOrNull(e))
|
||||
select f, f.getName() + " is a static array vulnerable to mutation."
|
||||
where f.isPublic()
|
||||
and f.isStatic()
|
||||
and f.isReadOnly()
|
||||
and f.getType() instanceof ArrayType
|
||||
and f.fromSource()
|
||||
and forall(AssignableDefinition def |
|
||||
def.getTarget() = f |
|
||||
nonEmptyArrayLiteralOrNull(def.getSource())
|
||||
)
|
||||
select f, "The array constant '" + f.getName() + "' is vulnerable to mutation."
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
class Bad
|
||||
{
|
||||
public static readonly string[] Foo = { "hello", "world" };
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Foo[0] = "goodbye";
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
class StaticArrayFix
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
class Good
|
||||
{
|
||||
public static readonly ReadOnlyCollection<string> Foo
|
||||
= new ReadOnlyCollection<string>(new string[] { "hello", "world" });
|
||||
@@ -1,159 +0,0 @@
|
||||
import csharp
|
||||
|
||||
string modifyMethodName()
|
||||
{
|
||||
result="Add" or
|
||||
result="AddFirst" or
|
||||
result="AddLast" or
|
||||
result="Clear" or
|
||||
result="Enqueue" or
|
||||
result="ExceptWith" or
|
||||
result="Insert" or
|
||||
result="IntersectWith" or
|
||||
result="Push" or
|
||||
result="Remove" or
|
||||
result="RemoveAt" or
|
||||
result="RemoveFirst" or
|
||||
result="RemoveLast" or
|
||||
result="Set" or
|
||||
result="SetAll" or
|
||||
result="SymmetricExceptWith" or
|
||||
result="UnionWith"
|
||||
}
|
||||
|
||||
class ModifierMethodCall extends MethodCall
|
||||
{
|
||||
ModifierMethodCall()
|
||||
{
|
||||
getParent() instanceof ExprStmt and
|
||||
this.getTarget().hasName(modifyMethodName())
|
||||
}
|
||||
}
|
||||
|
||||
string collectionTypeName()
|
||||
{
|
||||
result="ArrayList" or
|
||||
result="BitArray" or
|
||||
result="Hashtable" or
|
||||
result="ICollection" or
|
||||
result="IDictionary" or
|
||||
result="IList" or
|
||||
result="Queue" or
|
||||
result="ReadOnlyCollectionBase" or
|
||||
result="SortedList" or
|
||||
result="Stack"
|
||||
}
|
||||
|
||||
string collectionNamespaceName()
|
||||
{
|
||||
result="Mono.Collections" or
|
||||
result="System.Collections"
|
||||
}
|
||||
|
||||
string genericCollectionNamespaceName()
|
||||
{
|
||||
result="Mono.Collections.Generic" or
|
||||
result="System.Collections.Generic"
|
||||
}
|
||||
|
||||
string genericCollectionTypeName()
|
||||
{
|
||||
result="Dictionary<,>" or
|
||||
result="HashSet<>" or
|
||||
result="ICollection<>" or
|
||||
result="IDictionary<,>" or
|
||||
result="IList<>" or
|
||||
result="ISet<>" or
|
||||
result="LinkedList<>" or
|
||||
result="List<>" or
|
||||
result="Queue<>" or
|
||||
result="SortedDictionary<,>" or
|
||||
result="SortedList<,>" or
|
||||
result="SortedSet<>" or
|
||||
result="Stack<>" or
|
||||
result="SynchronizedCollection<>" or
|
||||
result="SynchronizedKeyedCollection<>" or
|
||||
result="SynchronizedReadOnlyCollection<>"
|
||||
}
|
||||
|
||||
predicate isCollectionType(RefType r)
|
||||
{
|
||||
r.hasQualifiedName( collectionNamespaceName(), collectionTypeName() ) or
|
||||
r.(ConstructedType).getUnboundGeneric().hasQualifiedName( genericCollectionNamespaceName(), genericCollectionTypeName() )
|
||||
}
|
||||
|
||||
class EmptyCollectionCreation extends ObjectCreation
|
||||
{
|
||||
EmptyCollectionCreation()
|
||||
{
|
||||
isCollectionType(getType())
|
||||
and not hasInitializer()
|
||||
and getNumberOfArguments()=0
|
||||
}
|
||||
}
|
||||
|
||||
// Methods that do not mutate a collection.
|
||||
// These methods are useless on empty collections.
|
||||
string readonlyMethodName()
|
||||
{
|
||||
result="BinarySearch" or
|
||||
result="Clone" or
|
||||
result="Contains" or
|
||||
result="ContainsKey" or
|
||||
result="ContainsValue" or
|
||||
result="CopyTo" or
|
||||
result="Equals" or
|
||||
result="FixedArray" or
|
||||
result="FixedSize" or
|
||||
result="Get" or
|
||||
result="GetEnumerator" or
|
||||
result="GetHashCode" or
|
||||
result="GetRange" or
|
||||
result="IndexOf" or
|
||||
result="IsProperSubsetOf" or
|
||||
result="IsProperSupersetOf" or
|
||||
result="IsSubsetOf" or
|
||||
result="IsSupersetOf" or
|
||||
result="LastIndexOf" or
|
||||
result="MemberwiseClone" or
|
||||
result="Peek" or
|
||||
result="ToArray" or
|
||||
result="ToString" or
|
||||
result="TryGetValue"
|
||||
}
|
||||
|
||||
// Methods that do not modify an empty collection.
|
||||
// These methods are useless on empty collections.
|
||||
string noAddMethodName()
|
||||
{
|
||||
result = readonlyMethodName() or
|
||||
result="Dequeue" or
|
||||
result="Pop"
|
||||
}
|
||||
|
||||
// An access that does not modify the collection
|
||||
predicate readonlyAccess(Access a)
|
||||
{
|
||||
// A read-only method call
|
||||
exists( MethodCall mc | mc.getQualifier()=a | mc.getTarget().hasName(readonlyMethodName()) )
|
||||
|
||||
// Any property access
|
||||
or exists( PropertyAccess pa | pa.getQualifier()=a )
|
||||
|
||||
// An indexed read
|
||||
or exists( IndexerAccess ia | ia.getQualifier()=a | not ia.getParent().(Assignment).getLValue()=ia )
|
||||
}
|
||||
|
||||
// An access that does not add items to a collection
|
||||
predicate noAddAccess(Access a)
|
||||
{
|
||||
readonlyAccess(a)
|
||||
or exists( MethodCall mc | mc.getQualifier()=a | mc.getTarget().hasName(noAddMethodName()) )
|
||||
}
|
||||
|
||||
// An empty collection is assigned
|
||||
predicate emptyCollectionAssignment(Access a)
|
||||
{
|
||||
exists( Assignment ass | ass.getLValue()=a | ass.getRValue() instanceof EmptyCollectionCreation )
|
||||
or a.(LocalVariableDeclAndInitExpr).getRValue() instanceof EmptyCollectionCreation
|
||||
}
|
||||
@@ -12,12 +12,12 @@
|
||||
*/
|
||||
|
||||
import csharp
|
||||
import Collection
|
||||
import semmle.code.csharp.commons.Collections
|
||||
|
||||
from Variable v
|
||||
where
|
||||
v.fromSource()
|
||||
and isCollectionType(v.getType())
|
||||
and v.getType() instanceof CollectionType
|
||||
|
||||
// Publics might get assigned elsewhere
|
||||
and (v instanceof LocalVariable or v.(Field).isPrivate())
|
||||
@@ -26,12 +26,12 @@ where
|
||||
and forall( AssignableDefinition d | v = d.getTarget() | d.getSource() instanceof EmptyCollectionCreation )
|
||||
|
||||
// All accesses do not add data.
|
||||
and forex( Access a | v.getAnAccess()=a | noAddAccess(a) or emptyCollectionAssignment(a) )
|
||||
and forex( Access a | v.getAnAccess()=a | a instanceof NoAddAccess or a instanceof EmptyInitializationAccess)
|
||||
|
||||
// Attributes indicate some kind of reflection
|
||||
and (not exists( Attribute a | v=a.getTarget() ))
|
||||
|
||||
// There is at least one non-assignment access
|
||||
and noAddAccess(v.getAnAccess())
|
||||
and v.getAnAccess() instanceof NoAddAccess
|
||||
select v, "The contents of this container are never initialized."
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
*/
|
||||
|
||||
import csharp
|
||||
import Collection
|
||||
import semmle.code.csharp.commons.Collections
|
||||
|
||||
from Variable v
|
||||
where isCollectionType(v.getType())
|
||||
where v.getType() instanceof CollectionType
|
||||
and (v instanceof LocalVariable or v = any(Field f | f.isEffectivelyPrivate() or f.isEffectivelyInternal()))
|
||||
and forex(Access a |
|
||||
a = v.getAnAccess() |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Provides classed for assertions. */
|
||||
/** Provides classes for assertions. */
|
||||
private import semmle.code.csharp.frameworks.system.Diagnostics
|
||||
private import semmle.code.csharp.frameworks.test.VisualStudio
|
||||
|
||||
|
||||
174
csharp/ql/src/semmle/code/csharp/commons/Collections.qll
Normal file
174
csharp/ql/src/semmle/code/csharp/commons/Collections.qll
Normal file
@@ -0,0 +1,174 @@
|
||||
/** Provides classes for collections. */
|
||||
import csharp
|
||||
|
||||
private string modifyMethodName() {
|
||||
result = "Add" or
|
||||
result = "AddFirst" or
|
||||
result = "AddLast" or
|
||||
result = "Clear" or
|
||||
result = "Enqueue" or
|
||||
result = "ExceptWith" or
|
||||
result = "Insert" or
|
||||
result = "IntersectWith" or
|
||||
result = "Push" or
|
||||
result = "Remove" or
|
||||
result = "RemoveAt" or
|
||||
result = "RemoveFirst" or
|
||||
result = "RemoveLast" or
|
||||
result = "Set" or
|
||||
result = "SetAll" or
|
||||
result = "SymmetricExceptWith" or
|
||||
result = "UnionWith"
|
||||
}
|
||||
|
||||
/** A method call that modifies a collection. */
|
||||
class ModifierMethodCall extends MethodCall {
|
||||
ModifierMethodCall() {
|
||||
this.getParent() instanceof ExprStmt and
|
||||
this.getTarget().hasName(modifyMethodName())
|
||||
}
|
||||
}
|
||||
|
||||
/** An access that modifies a collection. */
|
||||
class CollectionModificationAccess extends Access {
|
||||
CollectionModificationAccess() {
|
||||
this = any(ModifierMethodCall mc).getQualifier() or
|
||||
this = any(ElementWrite ew).getQualifier()
|
||||
}
|
||||
}
|
||||
|
||||
private string collectionTypeName() {
|
||||
result = "ArrayList" or
|
||||
result = "BitArray" or
|
||||
result = "Hashtable" or
|
||||
result = "ICollection" or
|
||||
result = "IDictionary" or
|
||||
result = "IList" or
|
||||
result = "Queue" or
|
||||
result = "ReadOnlyCollectionBase" or
|
||||
result = "SortedList" or
|
||||
result = "Stack"
|
||||
}
|
||||
|
||||
private string collectionNamespaceName() {
|
||||
result = "Mono.Collections" or
|
||||
result = "System.Collections"
|
||||
}
|
||||
|
||||
private string genericCollectionNamespaceName() {
|
||||
result = "Mono.Collections.Generic" or
|
||||
result = "System.Collections.Generic"
|
||||
}
|
||||
|
||||
private string genericCollectionTypeName() {
|
||||
result = "Dictionary<,>" or
|
||||
result = "HashSet<>" or
|
||||
result = "ICollection<>" or
|
||||
result = "IDictionary<,>" or
|
||||
result = "IList<>" or
|
||||
result = "ISet<>" or
|
||||
result = "LinkedList<>" or
|
||||
result = "List<>" or
|
||||
result = "Queue<>" or
|
||||
result = "SortedDictionary<,>" or
|
||||
result = "SortedList<,>" or
|
||||
result = "SortedSet<>" or
|
||||
result = "Stack<>" or
|
||||
result = "SynchronizedCollection<>" or
|
||||
result = "SynchronizedKeyedCollection<>" or
|
||||
result = "SynchronizedReadOnlyCollection<>"
|
||||
}
|
||||
|
||||
/** A collection type. */
|
||||
class CollectionType extends RefType {
|
||||
CollectionType() {
|
||||
exists(RefType base |
|
||||
base = this.getABaseType*() |
|
||||
base.hasQualifiedName(collectionNamespaceName(), collectionTypeName())
|
||||
or
|
||||
base.(ConstructedType).getUnboundGeneric().hasQualifiedName(genericCollectionNamespaceName(), genericCollectionTypeName())
|
||||
)
|
||||
or
|
||||
this instanceof ArrayType
|
||||
}
|
||||
}
|
||||
|
||||
/** An object creation that creates an empty collection. */
|
||||
class EmptyCollectionCreation extends ObjectCreation {
|
||||
EmptyCollectionCreation() {
|
||||
this.getType() instanceof CollectionType and
|
||||
not this.hasInitializer() and
|
||||
this.getNumberOfArguments() = 0
|
||||
}
|
||||
}
|
||||
|
||||
private string readonlyMethodName() {
|
||||
result = "BinarySearch" or
|
||||
result = "Clone" or
|
||||
result = "Contains" or
|
||||
result = "ContainsKey" or
|
||||
result = "ContainsValue" or
|
||||
result = "CopyTo" or
|
||||
result = "Equals" or
|
||||
result = "FixedArray" or
|
||||
result = "FixedSize" or
|
||||
result = "Get" or
|
||||
result = "GetEnumerator" or
|
||||
result = "GetHashCode" or
|
||||
result = "GetRange" or
|
||||
result = "IndexOf" or
|
||||
result = "IsProperSubsetOf" or
|
||||
result = "IsProperSupersetOf" or
|
||||
result = "IsSubsetOf" or
|
||||
result = "IsSupersetOf" or
|
||||
result = "LastIndexOf" or
|
||||
result = "MemberwiseClone" or
|
||||
result = "Peek" or
|
||||
result = "ToArray" or
|
||||
result = "ToString" or
|
||||
result = "TryGetValue"
|
||||
}
|
||||
|
||||
private string noAddMethodName() {
|
||||
result = readonlyMethodName() or
|
||||
result = "Dequeue" or
|
||||
result = "Pop"
|
||||
}
|
||||
|
||||
/** Holds if `a` is an access that does not modify a collection. */
|
||||
private predicate readonlyAccess(Access a) {
|
||||
// A read-only method call
|
||||
exists(MethodCall mc |
|
||||
mc.getQualifier() = a |
|
||||
mc.getTarget().hasName(readonlyMethodName())
|
||||
)
|
||||
or
|
||||
// Any property access
|
||||
a = any(PropertyAccess pa).getQualifier()
|
||||
or
|
||||
// An element read
|
||||
a = any(ElementRead er).getQualifier()
|
||||
}
|
||||
|
||||
/** An access that does not add items to a collection. */
|
||||
class NoAddAccess extends Access {
|
||||
NoAddAccess() {
|
||||
readonlyAccess(this)
|
||||
or
|
||||
exists(MethodCall mc |
|
||||
mc.getQualifier() = this |
|
||||
mc.getTarget().hasName(noAddMethodName())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** An access that initializes an empty collection. */
|
||||
class EmptyInitializationAccess extends Access {
|
||||
EmptyInitializationAccess() {
|
||||
exists(AssignableDefinition def |
|
||||
def.getTargetAccess() = this |
|
||||
def.getSource() instanceof EmptyCollectionCreation
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -77,3 +77,10 @@ class SystemCollectionsIEnumeratorInterface extends SystemCollectionsInterface {
|
||||
/** DEPRECATED. Gets the `System.Collections.IEnumerator` interface. */
|
||||
deprecated
|
||||
SystemCollectionsIEnumeratorInterface getSystemCollectionsIEnumeratorInterface() { any() }
|
||||
|
||||
/** The `System.Collections.ICollection` interface. */
|
||||
class SystemCollectionsICollectionInterface extends SystemCollectionsInterface {
|
||||
SystemCollectionsICollectionInterface() {
|
||||
this.hasName("ICollection")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,3 +158,10 @@ class SystemCollectionsGenericKeyValuePairStruct extends SystemCollectionsGeneri
|
||||
/** DEPRECATED. Gets the `System.Collections.Generic.KeyValuePair<TKey, TValue>` structure. */
|
||||
deprecated
|
||||
SystemCollectionsGenericKeyValuePairStruct getSystemCollectionsGenericKeyValuePairStruct() { any() }
|
||||
|
||||
/** The `System.Collections.Generic.ICollection<>` interface. */
|
||||
class SystemCollectionsGenericICollectionInterface extends SystemCollectionsGenericUnboundGenericInterface {
|
||||
SystemCollectionsGenericICollectionInterface() {
|
||||
this.hasName("ICollection<>")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
class AbstractToConcreteCollection
|
||||
{
|
||||
void M(IEnumerable<string> strings)
|
||||
{
|
||||
var list = (List<string>) strings; // BAD
|
||||
var o = (object) strings; // GOOD
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
| AbstractToConcreteCollection.cs:7:20:7:41 | (...) ... | Questionable cast from abstract 'IEnumerable<String>' to concrete implementation 'List<String>'. |
|
||||
| AbstractToConcreteCollectionBad.cs:8:20:8:39 | (...) ... | Questionable cast from abstract 'IEnumerable<String>' to concrete implementation 'List<String>'. |
|
||||
@@ -0,0 +1 @@
|
||||
Bad Practices/Implementation Hiding/AbstractToConcreteCollection.ql
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
class Bad
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var names = GetNames();
|
||||
var list = (List<string>) names;
|
||||
list.Add("Eve");
|
||||
}
|
||||
|
||||
static IEnumerable<string> GetNames()
|
||||
{
|
||||
var ret = new List<string>()
|
||||
{
|
||||
"Alice",
|
||||
"Bob"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
class ExposeRepresentation
|
||||
{
|
||||
class Range
|
||||
{
|
||||
private int[] rarray = new int[2];
|
||||
public void Set(int[] a) { rarray = a; }
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var a = new int[] { 0, 1 };
|
||||
var r = new Range();
|
||||
r.Set(a); // BAD
|
||||
a[0] = 500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
| ExposeRepresentation.cs:8:21:8:23 | Set | 'Set' exposes the internal representation stored in field 'rarray'. The value may be modified $@. | ExposeRepresentation.cs:16:9:16:9 | ExposeRepresentation.cs:16:9:16:9 | through the variable a |
|
||||
| ExposeRepresentationBad.cs:18:22:18:24 | Get | 'Get' exposes the internal representation stored in field 'rarray'. The value may be modified $@. | ExposeRepresentationBad.cs:24:23:24:29 | ExposeRepresentationBad.cs:24:23:24:29 | after this call to Get |
|
||||
@@ -0,0 +1 @@
|
||||
Bad Practices/Implementation Hiding/ExposeRepresentation.ql
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
class Bad
|
||||
{
|
||||
class Range
|
||||
{
|
||||
private int[] rarray = new int[2];
|
||||
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] Get() => rarray;
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var r = new Range(1, 10);
|
||||
var r_range = r.Get();
|
||||
r_range[0] = 500;
|
||||
Console.WriteLine("Min: " + r.Get()[0] + " Max: " + r.Get()[1]);
|
||||
// prints "Min: 500 Max: 10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
class Good1
|
||||
{
|
||||
class Range
|
||||
{
|
||||
private ReadOnlyCollection<int> rarray = new ReadOnlyCollection<int>(new int[2]);
|
||||
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
int[] rarray = new int[2];
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
this.rarray = new ReadOnlyCollection<int>(rarray);
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<int> Get() => rarray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
class Good2
|
||||
{
|
||||
class Range
|
||||
{
|
||||
private int[] rarray = new int[2];
|
||||
|
||||
public Range(int min, int max)
|
||||
{
|
||||
if (min <= max)
|
||||
{
|
||||
rarray[0] = min;
|
||||
rarray[1] = max;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] Get() => (int[])rarray.Clone();
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Range a = new Range(1, 10);
|
||||
int[] a_range = a.Get();
|
||||
a_range[0] = 500;
|
||||
Console.WriteLine("Min: " + a.Get()[0] + " Max: " + a.Get()[1]);
|
||||
// prints "Min: 1 Max: 10"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
| StaticArray.cs:13:34:13:47 | NonEmptyArray1 | NonEmptyArray1 is a static array vulnerable to mutation. |
|
||||
| StaticArray.cs:17:34:17:47 | NonEmptyArray3 | NonEmptyArray3 is a static array vulnerable to mutation. |
|
||||
| StaticArray.cs:19:34:19:38 | Array | Array is a static array vulnerable to mutation. |
|
||||
@@ -0,0 +1,4 @@
|
||||
| StaticArray.cs:13:34:13:47 | NonEmptyArray1 | The array constant 'NonEmptyArray1' is vulnerable to mutation. |
|
||||
| StaticArray.cs:17:34:17:47 | NonEmptyArray3 | The array constant 'NonEmptyArray3' is vulnerable to mutation. |
|
||||
| StaticArray.cs:19:34:19:38 | Array | The array constant 'Array' is vulnerable to mutation. |
|
||||
| StaticArrayBad.cs:3:37:3:39 | Foo | The array constant 'Foo' is vulnerable to mutation. |
|
||||
@@ -0,0 +1,8 @@
|
||||
class Bad
|
||||
{
|
||||
public static readonly string[] Foo = { "hello", "world" };
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Foo[0] = "goodbye";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
class Good
|
||||
{
|
||||
public static readonly ReadOnlyCollection<string> Foo
|
||||
= new ReadOnlyCollection<string>(new string[] { "hello", "world" });
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user