Files
codeql/csharp/ql/lib/semmle/code/cil/ConsistencyChecks.qll

778 lines
23 KiB
Plaintext

/**
* Provides checks for the consistency of the data model and database.
*/
private import CIL
private import csharp as CS
private newtype ConsistencyCheck =
MissingEntityCheck() or
TypeCheck(Type t) or
CfgCheck(ControlFlowNode n) or
DeclarationCheck(Declaration d) or
MissingCSharpCheck(CS::Declaration d)
/**
* A consistency violation in the database or data model.
*/
abstract class ConsistencyViolation extends ConsistencyCheck {
abstract string toString();
abstract string getMessage();
}
/**
* A check that is deliberately disabled.
*/
abstract class DisabledCheck extends ConsistencyViolation {
DisabledCheck() { none() }
}
/**
* A consistency violation on a control flow node.
*/
abstract class CfgViolation extends ConsistencyViolation, CfgCheck {
ControlFlowNode node;
CfgViolation() { this = CfgCheck(node) }
override string toString() { result = node.toString() }
}
/**
* A consistency violation in a specific instruction.
*/
abstract class InstructionViolation extends CfgViolation, CfgCheck {
Instruction instruction;
InstructionViolation() { this = CfgCheck(instruction) }
private string getInstructionsUpTo() {
result =
concat(Instruction i |
i.getIndex() <= instruction.getIndex() and
i.getImplementation() = instruction.getImplementation()
|
i.toString() + " [push: " + i.getPushCount() + ", pop: " + i.getPopCount() + "]", "; "
order by
i.getIndex()
)
}
override string toString() {
result =
instruction.getImplementation().getMethod().toStringWithTypes() + ": " +
instruction.toString() + ", " + this.getInstructionsUpTo()
}
}
/**
* A literal that does not have exactly one `getValue()`.
*/
class MissingValue extends InstructionViolation {
MissingValue() { exists(Literal l | l = instruction | count(l.getValue()) != 1) }
override string getMessage() { result = "Literal has invalid getValue()" }
}
/**
* A call that does not have exactly one `getTarget()`.
*/
class MissingCallTarget extends InstructionViolation {
MissingCallTarget() {
exists(Call c | c = instruction |
count(c.getTarget()) != 1 and not c instanceof Opcodes::Calli
or
count(c.(Opcodes::Calli).getTargetType()) != 1 and c instanceof Opcodes::Calli
)
}
override string getMessage() { result = "Call has invalid target" }
}
/**
* An instruction that has not been assigned a specific QL class.
*/
class MissingOpCode extends InstructionViolation {
MissingOpCode() { not exists(instruction.getOpcodeName()) }
override string getMessage() {
result = "Opcode " + instruction.getOpcode() + " is missing a QL class"
}
override string toString() {
result = "Unknown instruction in " + instruction.getImplementation().getMethod().toString()
}
}
/**
* An instruction that is missing an operand. It means that there is no instruction which pushes
* a value onto the stack for this instruction to pop.
*
* If this fails, it means that the `getPopCount`/`getPushCount`/control flow graph has failed.
* It could also mean that the target of a call has failed and has not determined the
* correct number of arguments.
*/
class MissingOperand extends InstructionViolation {
MissingOperand() {
exists(int op | op in [0 .. instruction.getPopCount() - 1] |
not exists(instruction.getOperand(op)) and not instruction instanceof DeadInstruction
)
}
int getMissingOperand() {
result in [0 .. instruction.getPopCount() - 1] and
not exists(instruction.getOperand(result))
}
override string getMessage() {
result = "This instruction is missing operand " + this.getMissingOperand()
}
}
/**
* A dead instruction, not reachable from any entry point.
* These should not exist, however it turns out that the Mono compiler sometimes
* emits them.
*/
class DeadInstruction extends Instruction {
DeadInstruction() { not exists(EntryPoint e | e.getASuccessor+() = this) }
}
/**
* An instruction that is not reachable from any entry point.
*
* If this fails, it means that the calculation of the call graph is incorrect.
* Disabled, because Mono compiler sometimes emits dead instructions.
*/
class DeadInstructionViolation extends InstructionViolation, DisabledCheck {
DeadInstructionViolation() { instruction instanceof DeadInstruction }
override string getMessage() { result = "This instruction is not reachable" }
}
class YesNoBranch extends ConditionalBranch {
YesNoBranch() { not this instanceof Opcodes::Switch }
}
/**
* A branch instruction that does not have exactly 2 successors.
*/
class InvalidBranchSuccessors extends InstructionViolation {
InvalidBranchSuccessors() {
// Mono compiler sometimes generates branches to the next instruction, which is just wrong.
// However it is valid CIL.
exists(YesNoBranch i | i = instruction | not count(i.getASuccessor()) in [1 .. 2])
}
override string getMessage() {
result = "Conditional branch has " + count(instruction.getASuccessor()) + " successors"
}
}
/**
* An instruction that has a true/false successor but is not a branch.
*/
class OnlyYesNoBranchHasTrueFalseSuccessors extends InstructionViolation {
OnlyYesNoBranchHasTrueFalseSuccessors() {
(exists(instruction.getTrueSuccessor()) or exists(instruction.getFalseSuccessor())) and
not instruction instanceof YesNoBranch
}
override string getMessage() { result = "This instruction has getTrue/FalseSuccessor()" }
}
/**
* An unconditional branch instruction that has more than one successor.
*/
class UnconditionalBranchSuccessors extends InstructionViolation {
UnconditionalBranchSuccessors() {
exists(UnconditionalBranch i | i = instruction | count(i.getASuccessor()) != 1)
}
override string getMessage() {
result = "Unconditional branch has " + count(instruction.getASuccessor()) + " successors"
}
}
/**
* A branch instruction that does not have a true successor.
*/
class NoTrueSuccessor extends InstructionViolation {
NoTrueSuccessor() { exists(YesNoBranch i | i = instruction | not exists(i.getTrueSuccessor())) }
override string getMessage() { result = "Missing a true successor" }
}
/**
* A branch instruction that does not have a false successor.
*/
class NoFalseSuccessor extends InstructionViolation {
NoFalseSuccessor() { exists(YesNoBranch i | i = instruction | not exists(i.getFalseSuccessor())) }
override string getMessage() { result = "Missing a false successor" }
}
/**
* An instruction whose true successor is not a successor.
*/
class TrueSuccessorIsSuccessor extends InstructionViolation {
TrueSuccessorIsSuccessor() {
exists(instruction.getTrueSuccessor()) and
not instruction.getTrueSuccessor() = instruction.getASuccessor()
}
override string getMessage() { result = "True successor isn't a successor" }
}
/**
* An instruction whose false successor is not a successor.
*/
class FalseSuccessorIsSuccessor extends InstructionViolation {
FalseSuccessorIsSuccessor() {
exists(instruction.getFalseSuccessor()) and
not instruction.getFalseSuccessor() = instruction.getASuccessor()
}
override string getMessage() { result = "True successor isn't a successor" }
}
/**
* An access that does not have exactly one target.
*/
class AccessMissingTarget extends InstructionViolation {
AccessMissingTarget() { exists(Access i | i = instruction | count(i.getTarget()) != 1) }
override string getMessage() { result = "Access has invalid getTarget()" }
}
/**
* A catch handler that doesn't have a caught exception type.
*/
class CatchHandlerMissingType extends CfgViolation {
CatchHandlerMissingType() { exists(CatchHandler h | h = node | not exists(h.getCaughtType())) }
override string getMessage() { result = "Catch handler missing caught type" }
}
/**
* A CFG node that does not have a stack size.
*/
class MissingStackSize extends CfgViolation {
MissingStackSize() {
(
not exists(node.getStackSizeAfter()) or
not exists(node.getStackSizeBefore())
) and
not node instanceof DeadInstruction
}
override string getMessage() { result = "Inconsistent stack size" }
}
/**
* A CFG node that does not have exactly one stack size.
* Disabled because inconsistent stack sizes have been observed.
*/
class InvalidStackSize extends CfgViolation, DisabledCheck {
InvalidStackSize() {
(
count(node.getStackSizeAfter()) != 1 or
count(node.getStackSizeBefore()) != 1
) and
not node instanceof DeadInstruction
}
override string getMessage() {
result =
"Inconsistent stack sizes " + count(node.getStackSizeBefore()) + " before and " +
count(node.getStackSizeAfter()) + " after"
}
}
/**
* A CFG node that does not have exactly 1 `getPopCount()`.
*/
class InconsistentPopCount extends CfgViolation {
InconsistentPopCount() { count(node.getPopCount()) != 1 }
override string getMessage() {
result = "Cfg node has " + count(node.getPopCount()) + " pop counts"
}
}
/**
* A CFG node that does not have exactly one `getPushCount()`.
*/
class InconsistentPushCount extends CfgViolation {
InconsistentPushCount() { count(node.getPushCount()) != 1 }
override string getMessage() {
result = "Cfg node has " + count(node.getPushCount()) + " push counts"
}
}
/**
* A return instruction that does not have a stack size of 0 after it.
*/
class InvalidReturn extends InstructionViolation {
InvalidReturn() { instruction instanceof Return and instruction.getStackSizeAfter() != 0 }
override string getMessage() { result = "Return has invalid stack size" }
}
/**
* A throw instruction that does not have a stack size of 0 after it.
*/
class InvalidThrow extends InstructionViolation, DisabledCheck {
InvalidThrow() { instruction instanceof Throw and instruction.getStackSizeAfter() != 0 }
override string getMessage() {
result = "Throw has invalid stack size: " + instruction.getStackSizeAfter()
}
}
/**
* A field access where the field is "static" but the instruction is "instance".
*/
class StaticFieldTarget extends InstructionViolation {
StaticFieldTarget() {
exists(FieldAccess i | i = instruction |
(i instanceof Opcodes::Stfld or i instanceof Opcodes::Stfld) and
i.getTarget().isStatic()
)
}
override string getMessage() { result = "Inconsistent static field" }
}
/**
* A branch without a target.
*/
class BranchWithoutTarget extends InstructionViolation {
BranchWithoutTarget() {
instruction = any(Branch b | not exists(b.getTarget()) and not b instanceof Opcodes::Switch)
}
override string getMessage() { result = "Branch without target" }
}
/**
* A consistency violation in a type.
*/
class TypeViolation extends ConsistencyViolation, TypeCheck {
/** Gets the type containing the violation. */
Type getType() { this = TypeCheck(result) }
override string toString() { result = this.getType().toString() }
abstract override string getMessage();
}
/**
* A type that has both type arguments and type parameters.
*/
class TypeIsBothConstructedAndUnbound extends TypeViolation {
TypeIsBothConstructedAndUnbound() {
this.getType() instanceof ConstructedGeneric and this.getType() instanceof UnboundGeneric
}
override string getMessage() { result = "Type is both constructed and unbound" }
}
/**
* The location of a constructed generic type should be the same
* as the location of its unbound generic type.
*/
class InconsistentTypeLocation extends TypeViolation {
InconsistentTypeLocation() {
this.getType().getLocation() != this.getType().getUnboundDeclaration().getLocation()
}
override string getMessage() { result = "Inconsistent constructed type location" }
}
/**
* A constructed type that does not match its unbound generic type.
*/
class TypeParameterMismatch extends TypeViolation {
TypeParameterMismatch() {
this.getType().(ConstructedGeneric).getNumberOfTypeArguments() !=
this.getType().getUnboundType().(UnboundGeneric).getNumberOfTypeParameters()
}
override string getMessage() {
result =
"Constructed type (" + this.getType().toStringWithTypes() + ") has " +
this.getType().(ConstructedGeneric).getNumberOfTypeArguments() +
" type arguments and unbound type (" + this.getType().getUnboundType().toStringWithTypes() +
") has " + this.getType().getUnboundType().(UnboundGeneric).getNumberOfTypeParameters() +
" type parameters"
}
}
/**
* A consistency violation in a method.
*/
class MethodViolation extends ConsistencyViolation, DeclarationCheck {
/** Gets the method containing the violation. */
Method getMethod() { this = DeclarationCheck(result) }
override string toString() { result = this.getMethod().toString() }
override string getMessage() { none() }
}
/**
* The location of a constructed method should be equal to the
* location of its unbound generic.
*/
class InconsistentMethodLocation extends MethodViolation {
InconsistentMethodLocation() {
this.getMethod().getLocation() != this.getMethod().getUnboundDeclaration().getLocation()
}
override string getMessage() { result = "Inconsistent constructed method location" }
}
/**
* A constructed method that does not match its unbound method.
*/
class ConstructedMethodTypeParams extends MethodViolation {
ConstructedMethodTypeParams() {
this.getMethod().(ConstructedGeneric).getNumberOfTypeArguments() !=
this.getMethod().getUnboundDeclaration().(UnboundGeneric).getNumberOfTypeParameters()
}
override string getMessage() {
result =
"The constructed method " + this.getMethod().toStringWithTypes() +
" does not match unbound method " +
this.getMethod().getUnboundDeclaration().toStringWithTypes()
}
}
/**
* A violation marking an entity that should be present but is not.
*/
abstract class MissingEntityViolation extends ConsistencyViolation, MissingEntityCheck {
override string toString() { result = "Missing entity" }
}
/**
* The type `object` is missing from the database.
*/
class MissingObjectViolation extends MissingEntityViolation {
MissingObjectViolation() {
exists(this) and
not exists(ObjectType o)
}
override string getMessage() { result = "Object missing" }
}
/**
* An override that is invalid because the overridden method is not in a base class.
*/
class InvalidOverride extends MethodViolation {
private Method base;
InvalidOverride() {
base = this.getMethod().getOverriddenMethod() and
not this.getMethod().getDeclaringType().getABaseType+() = base.getDeclaringType() and
base.getDeclaringType().isUnboundDeclaration() // Bases classes of constructed types aren't extracted properly.
}
override string getMessage() {
result =
"Overridden method from " + base.getDeclaringType().getQualifiedName() +
" is not in a base type"
}
}
/**
* A pointer type that does not have a pointee type.
*/
class InvalidPointerType extends TypeViolation {
InvalidPointerType() {
exists(PointerType p | p = this.getType() | count(p.getReferentType()) != 1)
}
override string getMessage() { result = "Invalid Pointertype.getPointeeType()" }
}
/**
* An array with an invalid `getElementType`.
*/
class ArrayTypeMissingElement extends TypeViolation {
ArrayTypeMissingElement() {
exists(ArrayType t | t = this.getType() | count(t.getElementType()) != 1)
}
override string getMessage() { result = "Invalid ArrayType.getElementType()" }
}
/**
* An array with an invalid `getRank`.
*/
class ArrayTypeInvalidRank extends TypeViolation {
ArrayTypeInvalidRank() { exists(ArrayType t | t = this.getType() | not t.getRank() > 0) }
override string getMessage() { result = "Invalid ArrayType.getRank()" }
}
/**
* A type should have at most one kind, except for missing referenced types
* where the interface/class is unknown.
*/
class KindViolation extends TypeViolation {
KindViolation() {
count(typeKind(this.getType())) != 1 and
exists(this.getType().getLocation())
}
override string getMessage() {
result = "Invalid kinds on type: " + concat(typeKind(this.getType()), " ")
}
}
/**
* The type of a kind must be consistent between a constructed generic and its
* unbound generic.
*/
class InconsistentKind extends TypeViolation {
InconsistentKind() {
typeKind(this.getType()) != typeKind(this.getType().getUnboundDeclaration())
}
override string getMessage() { result = "Inconsistent type kind of source declaration" }
}
private string typeKind(Type t) {
t instanceof Interface and result = "interface"
or
t instanceof Class and result = "class"
or
t instanceof TypeParameter and result = "type parameter"
or
t instanceof ArrayType and result = "array"
or
t instanceof PointerType and result = "pointer"
}
/**
* A violation in a `Member`.
*/
abstract class DeclarationViolation extends ConsistencyViolation, DeclarationCheck {
abstract override string getMessage();
/** Gets the member containing the potential violation. */
Declaration getDeclaration() { this = DeclarationCheck(result) }
override string toString() { result = this.getDeclaration().toString() }
}
/**
* Properties that have no accessors.
*/
class PropertyWithNoAccessors extends DeclarationViolation {
PropertyWithNoAccessors() {
exists(Property p | p = this.getDeclaration() | not exists(p.getAnAccessor()))
}
override string getMessage() { result = "Property has no accessors" }
}
/**
* An expression that have an unexpected push count.
*/
class ExprPushCount extends InstructionViolation {
ExprPushCount() {
instruction instanceof Expr and
not instruction instanceof Opcodes::Dup and
if instruction instanceof Call
then not instruction.getPushCount() in [0 .. 1]
else instruction.(Expr).getPushCount() != 1
}
override string getMessage() {
result = "Instruction has unexpected push count " + instruction.getPushCount()
}
}
/**
* An expression that does not have exactly one type.
* Note that calls with no return have type `System.Void`.
*/
class ExprMissingType extends InstructionViolation {
ExprMissingType() {
// Don't have types for the following op codes:
not instruction instanceof Opcodes::Ldftn and
not instruction instanceof Opcodes::Localloc and
not instruction instanceof Opcodes::Ldvirtftn and
not instruction instanceof Opcodes::Arglist and
not instruction instanceof Opcodes::Refanytype and
instruction.getPushCount() >= 1 and
count(instruction.getType()) != 1
}
override string getMessage() { result = "Expression is missing getType()" }
}
/**
* An instruction that has a push count of 0, yet is still used as an operand
*/
class InvalidExpressionViolation extends InstructionViolation {
InvalidExpressionViolation() {
instruction.getPushCount() = 0 and
exists(Instruction expr | instruction = expr.getAnOperand())
}
override string getMessage() {
result = "This instruction is used as an operand but pushes no values"
}
}
/**
* A type that has multiple entities with the same qualified name in `System`.
* .NET Core does sometimes duplicate types, so this check is disabled.
*/
class TypeMultiplyDefined extends TypeViolation, DisabledCheck {
TypeMultiplyDefined() {
this.getType().getParent().getName() = "System" and
not this.getType() instanceof ConstructedGeneric and
not this.getType() instanceof ArrayType and
this.getType().isPublic() and
count(Type t |
not t instanceof ConstructedGeneric and
t.toStringWithTypes() = this.getType().toStringWithTypes()
) != 1
}
override string getMessage() {
result =
"This type (" + this.getType().toStringWithTypes() + ") has " +
count(Type t |
not t instanceof ConstructedGeneric and
t.toStringWithTypes() = this.getType().toStringWithTypes()
) + " entities"
}
}
/**
* A C# declaration which is expected to have a corresponding CIL declaration, but for some reason does not.
*/
class MissingCilDeclaration extends ConsistencyViolation, MissingCSharpCheck {
MissingCilDeclaration() {
exists(CS::Declaration decl | this = MissingCSharpCheck(decl) |
expectedCilDeclaration(decl) and
not exists(Declaration d | decl = d.getCSharpDeclaration())
)
}
CS::Declaration getDeclaration() { this = MissingCSharpCheck(result) }
override string getMessage() {
result =
"Cannot locate CIL for " + this.getDeclaration().toStringWithTypes() + " of class " +
this.getDeclaration().getPrimaryQlClasses()
}
override string toString() { result = this.getDeclaration().toStringWithTypes() }
}
/**
* Holds if the C# declaration is expected to have a CIl declaration.
*/
private predicate expectedCilDeclaration(CS::Declaration decl) {
decl = decl.getUnboundDeclaration() and
not decl instanceof CS::ArrayType and
decl.getALocation() instanceof CS::Assembly and
not decl.(CS::Modifiable).isInternal() and
not decl.(CS::Constructor).getNumberOfParameters() = 0 and // These are sometimes implicit
not decl.(CS::Method).getReturnType() instanceof CS::UnknownType and
not exists(CS::Parameter p | p = decl.(CS::Parameterizable).getAParameter() |
not expectedCilDeclaration(p)
) and
not decl instanceof CS::AnonymousClass and
(decl instanceof CS::Parameter implies expectedCilDeclaration(decl.(CS::Parameter).getType())) and
(decl instanceof CS::Parameter implies expectedCilDeclaration(decl.getParent())) and
(decl instanceof CS::Member implies expectedCilDeclaration(decl.getParent())) and
(
decl instanceof CS::Field
or
decl instanceof CS::Property
or
decl instanceof CS::ValueOrRefType
or
decl instanceof CS::Event
or
decl instanceof CS::Constructor
or
decl instanceof CS::Destructor
or
decl instanceof CS::Operator
or
decl instanceof CS::Method
or
decl instanceof CS::Parameter
)
}
/** A member with an invalid name. */
class MemberWithInvalidName extends DeclarationViolation {
MemberWithInvalidName() {
exists(string name | name = this.getDeclaration().(Member).getName() |
exists(name.indexOf(".")) and
not name = ".ctor" and
not name = ".cctor"
)
}
override string getMessage() {
result = "Invalid name " + this.getDeclaration().(Member).getName()
}
}
class ConstructedSourceDeclarationMethod extends MethodViolation {
Method method;
ConstructedSourceDeclarationMethod() {
method = this.getMethod() and
method = method.getUnboundDeclaration() and
(
method instanceof ConstructedGeneric or
method.getDeclaringType() instanceof ConstructedGeneric
)
}
override string getMessage() {
result = "Source declaration " + method.toStringWithTypes() + " is constructed"
}
}
/** A declaration with multiple labels. */
class DeclarationWithMultipleLabels extends DeclarationViolation {
DeclarationWithMultipleLabels() {
exists(Declaration d | this = DeclarationCheck(d) | strictcount(d.getLabel()) > 1)
}
override string getMessage() {
result = "Multiple labels " + concat(this.getDeclaration().getLabel(), ", ")
}
}
/** A declaration without a label. */
class DeclarationWithoutLabel extends DeclarationViolation {
DeclarationWithoutLabel() {
exists(Declaration d | this = DeclarationCheck(d) |
d.isUnboundDeclaration() and
not d instanceof TypeParameter and
not exists(d.getLabel()) and
(d instanceof Callable or d instanceof Type)
)
}
override string getMessage() { result = "No label" }
}