QL: Model final extends

This commit is contained in:
Tom Hvitved
2023-06-20 15:44:34 +02:00
parent 0edd80001b
commit e6e966bd22
5 changed files with 61 additions and 16 deletions

View File

@@ -611,6 +611,8 @@ class ClassPredicate extends TClassPredicate, Predicate {
predicate overrides(ClassPredicate other) { predOverrides(this, other) }
predicate shadows(ClassPredicate other) { predShadows(this, other) }
override TypeExpr getReturnTypeExpr() { toQL(result) = pred.getReturnType() }
override AstNode getAChild(string pred_name) {
@@ -878,6 +880,9 @@ class Module extends TModule, ModuleDeclaration {
class ModuleMember extends TModuleMember, AstNode {
/** Holds if this member is declared as `private`. */
predicate isPrivate() { this.hasAnnotation("private") }
/** Holds if this member is declared as `final`. */
predicate isFinal() { this.hasAnnotation("final") }
}
/** A declaration. E.g. a class, type, predicate, newtype... */

View File

@@ -21,7 +21,7 @@ private newtype TType =
private predicate primTypeName(string s) { s = ["int", "float", "string", "boolean", "date"] }
private predicate isActualClass(Class c) {
not exists(c.getAliasType()) and
(not exists(c.getAliasType()) or c.isFinal()) and
not exists(c.getUnionMember())
}
@@ -36,6 +36,10 @@ class Type extends TType {
/**
* Gets a supertype of this type. This follows the user-visible type hierarchy,
* and doesn't include internal types like the characteristic and domain types of classes.
*
* For supertypes that are `final` aliases, this returns the alias itself, and for
* types that are `final` aliases, this returns the supertypes of the type that is
* being aliased.
*/
Type getASuperType() { none() }
@@ -94,9 +98,23 @@ class ClassType extends Type, TClass {
override Class getDeclaration() { result = decl }
override Type getASuperType() { result = decl.getASuperType().getResolvedType() }
override Type getASuperType() {
result = decl.getASuperType().getResolvedType()
or
exists(ClassType alias |
this.isFinalAlias(alias) and
result = alias.getASuperType()
)
}
Type getAnInstanceofType() { result = decl.getAnInstanceofType().getResolvedType() }
Type getAnInstanceofType() {
result = decl.getAnInstanceofType().getResolvedType()
or
exists(ClassType alias |
this.isFinalAlias(alias) and
result = alias.getAnInstanceofType()
)
}
override Type getAnInternalSuperType() {
result.(ClassCharType).getClassType() = this
@@ -110,6 +128,12 @@ class ClassType extends Type, TClass {
other.getDeclaringType().getASuperType+() = result.getDeclaringType()
)
}
/** Holds if this class is a `final` alias of `c`. */
predicate isFinalAlias(ClassType c) {
decl.isFinal() and
decl.getAliasType().getResolvedType() = c
}
}
class FileType extends Type, TFile {
@@ -136,23 +160,37 @@ private PredicateOrBuiltin declaredPred(Type ty, string name, int arity) {
result.getDeclaringType() = ty and
result.getName() = name and
result.getArity() = arity
or
exists(ClassType alias |
ty.(ClassType).isFinalAlias(alias) and
result = declaredPred(alias, name, arity)
)
}
pragma[nomagic]
private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity) {
result = declaredPred(ty, name, arity)
private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity, boolean isFinal) {
result = declaredPred(ty, name, arity) and
if ty.(ClassType).getDeclaration().isFinal() then isFinal = true else isFinal = false
or
not exists(declaredPred(ty, name, arity)) and
result = inherClassPredCandidate(ty, name, arity)
result = inherClassPredCandidate(ty, name, arity, isFinal)
}
private PredicateOrBuiltin inherClassPredCandidate(Type ty, string name, int arity) {
result = classPredCandidate(ty.getAnInternalSuperType(), name, arity) and
private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity) {
result = classPredCandidate(ty, name, arity, _)
}
private PredicateOrBuiltin inherClassPredCandidate(Type ty, string name, int arity, boolean isFinal) {
result = classPredCandidate(ty.getAnInternalSuperType(), name, arity, isFinal) and
not result.isPrivate()
}
predicate predOverrides(ClassPredicate sub, ClassPredicate sup) {
sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity())
sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity(), false)
}
predicate predShadows(ClassPredicate sub, ClassPredicate sup) {
sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity(), true)
}
private VarDecl declaredField(ClassType ty, string name) {
@@ -376,7 +414,8 @@ private predicate defines(FileOrModule m, string name, Type t, boolean public) {
exists(Class ty | t = ty.getAliasType().getResolvedType() |
getEnclosingModule(ty) = m and
ty.getName() = name and
public = getPublicBool(ty)
public = getPublicBool(ty) and
not ty.isFinal()
)
or
exists(Import im |

View File

@@ -38,14 +38,15 @@ Class getASubclassOfAbstract(Class ab) {
/** Gets a non-abstract subclass of `ab` that contributes to the extent of `ab`. */
Class concreteExternalSubclass(Class ab) {
ab.isAbstract() and
not result.isAbstract() and
result = getASubclassOfAbstract+(ab) and
// Heuristic: An abstract class with subclasses in the same file and no other
// imported subclasses is likely intentional.
result.getLocation().getFile() != ab.getLocation().getFile() and
// Exclude subclasses in tests and libraries that are only used in tests.
liveNonTestFile(result.getLocation().getFile())
liveNonTestFile(result.getLocation().getFile()) and
// exclude `final` aliases
not result.getType().isFinalAlias(_)
}
/** Holds if there is a bidirectional import between the abstract class `ab` and its subclass `sub` */

View File

@@ -1,2 +1,4 @@
| AbstractClassImportTest1.qll:4:16:4:19 | Class Base | This abstract class imports its subclass $@ but doesn't import 4 other subclasses, such as $@. | AbstractClassImportTest2.qll:4:7:4:11 | Class Sub21 | Sub21 | AbstractClassImportTest3.qll:12:7:12:11 | Class Sub33 | Sub33 |
| AbstractClassImportTest1.qll:4:16:4:19 | Class Base | This abstract class imports its subclass $@ but doesn't import 4 other subclasses, such as $@. | AbstractClassImportTest2.qll:8:7:8:11 | Class Sub22 | Sub22 | AbstractClassImportTest3.qll:12:7:12:11 | Class Sub33 | Sub33 |
| AbstractClassImportTest1.qll:4:16:4:19 | Class Base | This abstract class doesn't import its subclass $@ but imports 2 other subclasses, such as $@. | AbstractClassImportTest3.qll:4:7:4:11 | Class Sub31 | Sub31 | AbstractClassImportTest2.qll:4:7:4:11 | Class Sub21 | Sub21 |
| AbstractClassImportTest1.qll:4:16:4:19 | Class Base | This abstract class doesn't import its subclass $@ but imports 2 other subclasses, such as $@. | AbstractClassImportTest3.qll:8:7:8:11 | Class Sub32 | Sub32 | AbstractClassImportTest2.qll:4:7:4:11 | Class Sub21 | Sub21 |
| AbstractClassImportTest1.qll:4:16:4:19 | Class Base | This abstract class imports its subclass $@ but doesn't import 2 other subclasses, such as $@. | AbstractClassImportTest2.qll:4:7:4:11 | Class Sub21 | Sub21 | AbstractClassImportTest3.qll:4:7:4:11 | Class Sub31 | Sub31 |
| AbstractClassImportTest1.qll:4:16:4:19 | Class Base | This abstract class imports its subclass $@ but doesn't import 2 other subclasses, such as $@. | AbstractClassImportTest2.qll:8:7:8:11 | Class Sub22 | Sub22 | AbstractClassImportTest3.qll:4:7:4:11 | Class Sub31 | Sub31 |

View File

@@ -1,4 +1,2 @@
| Test.qll:12:13:12:16 | ClassPredicate test | Wrong.test overrides $@ but does not have an override annotation. | Test.qll:4:13:4:16 | ClassPredicate test | Super.test |
| Test.qll:18:13:18:16 | ClassPredicate test | Wrong2.test overrides $@ but does not have an override annotation. | Test.qll:4:13:4:16 | ClassPredicate test | Super.test |
| Test.qll:24:13:24:16 | ClassPredicate test | Correct2.test overrides $@ but does not have an override annotation. | Test.qll:4:13:4:16 | ClassPredicate test | Super.test |
| Test.qll:36:13:36:16 | ClassPredicate test | Correct4.test overrides $@ but does not have an override annotation. | Test.qll:32:13:32:16 | ClassPredicate test | Super2.test |