Skip to content

Commit 039bbb6

Browse files
authored
Merge pull request #13521 from hvitved/ql/final-extends
QL: Model `final extends`
2 parents 3a81d21 + e6e966b commit 039bbb6

File tree

12 files changed

+123
-12
lines changed

12 files changed

+123
-12
lines changed

ql/ql/src/codeql_ql/ast/Ast.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,8 @@ class ClassPredicate extends TClassPredicate, Predicate {
611611

612612
predicate overrides(ClassPredicate other) { predOverrides(this, other) }
613613

614+
predicate shadows(ClassPredicate other) { predShadows(this, other) }
615+
614616
override TypeExpr getReturnTypeExpr() { toQL(result) = pred.getReturnType() }
615617

616618
override AstNode getAChild(string pred_name) {
@@ -878,6 +880,9 @@ class Module extends TModule, ModuleDeclaration {
878880
class ModuleMember extends TModuleMember, AstNode {
879881
/** Holds if this member is declared as `private`. */
880882
predicate isPrivate() { this.hasAnnotation("private") }
883+
884+
/** Holds if this member is declared as `final`. */
885+
predicate isFinal() { this.hasAnnotation("final") }
881886
}
882887

883888
/** A declaration. E.g. a class, type, predicate, newtype... */

ql/ql/src/codeql_ql/ast/internal/Type.qll

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ private newtype TType =
2121
private predicate primTypeName(string s) { s = ["int", "float", "string", "boolean", "date"] }
2222

2323
private predicate isActualClass(Class c) {
24-
not exists(c.getAliasType()) and
24+
(not exists(c.getAliasType()) or c.isFinal()) and
2525
not exists(c.getUnionMember())
2626
}
2727

@@ -36,6 +36,10 @@ class Type extends TType {
3636
/**
3737
* Gets a supertype of this type. This follows the user-visible type hierarchy,
3838
* and doesn't include internal types like the characteristic and domain types of classes.
39+
*
40+
* For supertypes that are `final` aliases, this returns the alias itself, and for
41+
* types that are `final` aliases, this returns the supertypes of the type that is
42+
* being aliased.
3943
*/
4044
Type getASuperType() { none() }
4145

@@ -94,9 +98,23 @@ class ClassType extends Type, TClass {
9498

9599
override Class getDeclaration() { result = decl }
96100

97-
override Type getASuperType() { result = decl.getASuperType().getResolvedType() }
101+
override Type getASuperType() {
102+
result = decl.getASuperType().getResolvedType()
103+
or
104+
exists(ClassType alias |
105+
this.isFinalAlias(alias) and
106+
result = alias.getASuperType()
107+
)
108+
}
98109

99-
Type getAnInstanceofType() { result = decl.getAnInstanceofType().getResolvedType() }
110+
Type getAnInstanceofType() {
111+
result = decl.getAnInstanceofType().getResolvedType()
112+
or
113+
exists(ClassType alias |
114+
this.isFinalAlias(alias) and
115+
result = alias.getAnInstanceofType()
116+
)
117+
}
100118

101119
override Type getAnInternalSuperType() {
102120
result.(ClassCharType).getClassType() = this
@@ -110,6 +128,12 @@ class ClassType extends Type, TClass {
110128
other.getDeclaringType().getASuperType+() = result.getDeclaringType()
111129
)
112130
}
131+
132+
/** Holds if this class is a `final` alias of `c`. */
133+
predicate isFinalAlias(ClassType c) {
134+
decl.isFinal() and
135+
decl.getAliasType().getResolvedType() = c
136+
}
113137
}
114138

115139
class FileType extends Type, TFile {
@@ -136,23 +160,37 @@ private PredicateOrBuiltin declaredPred(Type ty, string name, int arity) {
136160
result.getDeclaringType() = ty and
137161
result.getName() = name and
138162
result.getArity() = arity
163+
or
164+
exists(ClassType alias |
165+
ty.(ClassType).isFinalAlias(alias) and
166+
result = declaredPred(alias, name, arity)
167+
)
139168
}
140169

141170
pragma[nomagic]
142-
private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity) {
143-
result = declaredPred(ty, name, arity)
171+
private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity, boolean isFinal) {
172+
result = declaredPred(ty, name, arity) and
173+
if ty.(ClassType).getDeclaration().isFinal() then isFinal = true else isFinal = false
144174
or
145175
not exists(declaredPred(ty, name, arity)) and
146-
result = inherClassPredCandidate(ty, name, arity)
176+
result = inherClassPredCandidate(ty, name, arity, isFinal)
147177
}
148178

149-
private PredicateOrBuiltin inherClassPredCandidate(Type ty, string name, int arity) {
150-
result = classPredCandidate(ty.getAnInternalSuperType(), name, arity) and
179+
private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity) {
180+
result = classPredCandidate(ty, name, arity, _)
181+
}
182+
183+
private PredicateOrBuiltin inherClassPredCandidate(Type ty, string name, int arity, boolean isFinal) {
184+
result = classPredCandidate(ty.getAnInternalSuperType(), name, arity, isFinal) and
151185
not result.isPrivate()
152186
}
153187

154188
predicate predOverrides(ClassPredicate sub, ClassPredicate sup) {
155-
sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity())
189+
sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity(), false)
190+
}
191+
192+
predicate predShadows(ClassPredicate sub, ClassPredicate sup) {
193+
sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity(), true)
156194
}
157195

158196
private VarDecl declaredField(ClassType ty, string name) {
@@ -376,7 +414,8 @@ private predicate defines(FileOrModule m, string name, Type t, boolean public) {
376414
exists(Class ty | t = ty.getAliasType().getResolvedType() |
377415
getEnclosingModule(ty) = m and
378416
ty.getName() = name and
379-
public = getPublicBool(ty)
417+
public = getPublicBool(ty) and
418+
not ty.isFinal()
380419
)
381420
or
382421
exists(Import im |

ql/ql/src/queries/performance/AbstractClassImport.ql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ Class getASubclassOfAbstract(Class ab) {
3838

3939
/** Gets a non-abstract subclass of `ab` that contributes to the extent of `ab`. */
4040
Class concreteExternalSubclass(Class ab) {
41-
ab.isAbstract() and
4241
not result.isAbstract() and
4342
result = getASubclassOfAbstract+(ab) and
4443
// Heuristic: An abstract class with subclasses in the same file and no other
4544
// imported subclasses is likely intentional.
4645
result.getLocation().getFile() != ab.getLocation().getFile() and
4746
// Exclude subclasses in tests and libraries that are only used in tests.
48-
liveNonTestFile(result.getLocation().getFile())
47+
liveNonTestFile(result.getLocation().getFile()) and
48+
// exclude `final` aliases
49+
not result.getType().isFinalAlias(_)
4950
}
5051

5152
/** Holds if there is a bidirectional import between the abstract class `ab` and its subclass `sub` */
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| 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 |
2+
| 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 |
3+
| 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 |
4+
| 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 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/performance/AbstractClassImport.ql
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import ql
2+
import AbstractClassImportTest2
3+
4+
abstract class Base extends AstNode { }
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import ql
2+
import AbstractClassImportTest1
3+
4+
class Sub21 extends Base {
5+
Sub21() { this instanceof TopLevel }
6+
}
7+
8+
class Sub22 extends Base instanceof Comment { }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import ql
2+
import AbstractClassImportTest1
3+
4+
class Sub31 extends Base {
5+
Sub31() { this instanceof Comment }
6+
}
7+
8+
class Sub32 extends Base instanceof Comment { }
9+
10+
final class BaseFinal = Base;
11+
12+
class Sub33 extends BaseFinal instanceof Comment { }
13+
14+
abstract class Sub34 extends Base { }
15+
16+
final class Sub34Final = Sub34;
17+
18+
class Sub35 extends Sub34Final instanceof Comment { }

ql/ql/test/queries/performance/AbstractClassImport/AbstractClassImportTestQuery.expected

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import ql
2+
import AbstractClassImportTest3
3+
4+
from AstNode n
5+
where none()
6+
select n

0 commit comments

Comments
 (0)