Skip to content

Commit ea8c734

Browse files
committed
Java: Make non-returning methods use local to global.
1 parent a80fbf0 commit ea8c734

File tree

1 file changed

+117
-100
lines changed

1 file changed

+117
-100
lines changed

java/ql/lib/semmle/code/java/ControlFlowGraph.qll

Lines changed: 117 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -431,115 +431,132 @@ private module ControlFlowGraphImpl {
431431
}
432432

433433
/**
434-
* A virtual method with a unique implementation. That is, the method does not
435-
* participate in overriding and there are no call targets that could dispatch
436-
* to both this and another method.
434+
* Module containing a global non-returning method analysis. The result is injected back into
435+
* a local predicate `nonReturningMethodCall()`, which is used in further local predicates
436+
* within this file.
437437
*/
438-
private class EffectivelyNonVirtualMethod extends SrcMethod {
439-
EffectivelyNonVirtualMethod() {
440-
exists(this.getBody()) and
441-
this.isVirtual() and
442-
not this = any(Method m).getASourceOverriddenMethod() and
443-
not this.overrides(_) and
444-
// guard against implicit overrides of default methods
445-
not this.getAPossibleImplementationOfSrcMethod() != this and
446-
// guard against interface implementations in inheriting subclasses
447-
not exists(SrcMethod m |
448-
1 < strictcount(m.getAPossibleImplementationOfSrcMethod()) and
449-
this = m.getAPossibleImplementationOfSrcMethod()
450-
) and
451-
// UnsupportedOperationException could indicate that this is meant to be overridden
452-
not exists(ClassInstanceExpr ex |
453-
this.getBody().getLastStmt().(ThrowStmt).getExpr() = ex and
454-
ex.getConstructedType().hasQualifiedName("java.lang", "UnsupportedOperationException")
455-
) and
456-
// an unused parameter could indicate that this is meant to be overridden
457-
forall(Parameter p | p = this.getAParameter() | exists(p.getAnAccess()))
438+
overlay[global]
439+
private module NonReturningAnalysis {
440+
/**
441+
* A virtual method with a unique implementation. That is, the method does not
442+
* participate in overriding and there are no call targets that could dispatch
443+
* to both this and another method.
444+
*/
445+
private class EffectivelyNonVirtualMethod extends SrcMethod {
446+
EffectivelyNonVirtualMethod() {
447+
exists(this.getBody()) and
448+
this.isVirtual() and
449+
not this = any(Method m).getASourceOverriddenMethod() and
450+
not this.overrides(_) and
451+
// guard against implicit overrides of default methods
452+
not this.getAPossibleImplementationOfSrcMethod() != this and
453+
// guard against interface implementations in inheriting subclasses
454+
not exists(SrcMethod m |
455+
1 < strictcount(m.getAPossibleImplementationOfSrcMethod()) and
456+
this = m.getAPossibleImplementationOfSrcMethod()
457+
) and
458+
// UnsupportedOperationException could indicate that this is meant to be overridden
459+
not exists(ClassInstanceExpr ex |
460+
this.getBody().getLastStmt().(ThrowStmt).getExpr() = ex and
461+
ex.getConstructedType().hasQualifiedName("java.lang", "UnsupportedOperationException")
462+
) and
463+
// an unused parameter could indicate that this is meant to be overridden
464+
forall(Parameter p | p = this.getAParameter() | exists(p.getAnAccess()))
465+
}
466+
467+
/** Gets a `MethodCall` that calls this method. */
468+
MethodCall getAnAccess() { result.getMethod().getAPossibleImplementation() = this }
458469
}
459470

460-
/** Gets a `MethodCall` that calls this method. */
461-
MethodCall getAnAccess() { result.getMethod().getAPossibleImplementation() = this }
462-
}
471+
/** Holds if a call to `m` indicates that `m` is expected to return. */
472+
private predicate expectedReturn(EffectivelyNonVirtualMethod m) {
473+
exists(Stmt s, BlockStmt b |
474+
m.getAnAccess().getEnclosingStmt() = s and
475+
b.getAStmt() = s and
476+
not b.getLastStmt() = s
477+
)
478+
}
463479

464-
/** Holds if a call to `m` indicates that `m` is expected to return. */
465-
private predicate expectedReturn(EffectivelyNonVirtualMethod m) {
466-
exists(Stmt s, BlockStmt b |
467-
m.getAnAccess().getEnclosingStmt() = s and
468-
b.getAStmt() = s and
469-
not b.getLastStmt() = s
470-
)
471-
}
480+
/**
481+
* Gets a non-overridable method that always throws an exception or calls `exit`.
482+
*/
483+
private Method nonReturningMethod() {
484+
result instanceof MethodExit
485+
or
486+
not result.isOverridable() and
487+
exists(BlockStmt body |
488+
body = result.getBody() and
489+
not exists(ReturnStmt ret | ret.getEnclosingCallable() = result)
490+
|
491+
not result.getReturnType() instanceof VoidType or
492+
body.getLastStmt() = nonReturningStmt()
493+
)
494+
}
472495

473-
/**
474-
* Gets a non-overridable method that always throws an exception or calls `exit`.
475-
*/
476-
private Method nonReturningMethod() {
477-
result instanceof MethodExit
478-
or
479-
not result.isOverridable() and
480-
exists(BlockStmt body |
481-
body = result.getBody() and
482-
not exists(ReturnStmt ret | ret.getEnclosingCallable() = result)
483-
|
484-
not result.getReturnType() instanceof VoidType or
485-
body.getLastStmt() = nonReturningStmt()
486-
)
487-
}
496+
/**
497+
* Gets a virtual method that always throws an exception or calls `exit`.
498+
*/
499+
private EffectivelyNonVirtualMethod likelyNonReturningMethod() {
500+
result.getReturnType() instanceof VoidType and
501+
not exists(ReturnStmt ret | ret.getEnclosingCallable() = result) and
502+
not expectedReturn(result) and
503+
forall(Parameter p | p = result.getAParameter() | exists(p.getAnAccess())) and
504+
result.getBody().getLastStmt() = nonReturningStmt()
505+
}
488506

489-
/**
490-
* Gets a virtual method that always throws an exception or calls `exit`.
491-
*/
492-
private EffectivelyNonVirtualMethod likelyNonReturningMethod() {
493-
result.getReturnType() instanceof VoidType and
494-
not exists(ReturnStmt ret | ret.getEnclosingCallable() = result) and
495-
not expectedReturn(result) and
496-
forall(Parameter p | p = result.getAParameter() | exists(p.getAnAccess())) and
497-
result.getBody().getLastStmt() = nonReturningStmt()
498-
}
507+
/**
508+
* Gets a `MethodCall` that always throws an exception or calls `exit`.
509+
*/
510+
private MethodCall nonReturningMethodCallGlobal() {
511+
result.getMethod().getSourceDeclaration() = nonReturningMethod() or
512+
result = likelyNonReturningMethod().getAnAccess()
513+
}
499514

500-
/**
501-
* Gets a `MethodCall` that always throws an exception or calls `exit`.
502-
*/
503-
private MethodCall nonReturningMethodCall() {
504-
result.getMethod().getSourceDeclaration() = nonReturningMethod() or
505-
result = likelyNonReturningMethod().getAnAccess()
506-
}
515+
/**
516+
* Gets a `MethodCall` that always throws an exception or calls `exit`.
517+
*
518+
* This predicate is local so gives the local anaylyis for base and the global
519+
* analyis for overlay cases.
520+
*/
521+
overlay[local]
522+
MethodCall nonReturningMethodCall() = localToGlobal(nonReturningMethodCallGlobal/0)(result)
507523

508-
/**
509-
* Gets a statement that always throws an exception or calls `exit`.
510-
*/
511-
private Stmt nonReturningStmt() {
512-
result instanceof ThrowStmt
513-
or
514-
result.(ExprStmt).getExpr() = nonReturningExpr()
515-
or
516-
result.(BlockStmt).getLastStmt() = nonReturningStmt()
517-
or
518-
exists(IfStmt ifstmt | ifstmt = result |
519-
ifstmt.getThen() = nonReturningStmt() and
520-
ifstmt.getElse() = nonReturningStmt()
521-
)
522-
or
523-
exists(TryStmt try | try = result |
524-
try.getBlock() = nonReturningStmt() and
525-
forall(CatchClause cc | cc = try.getACatchClause() | cc.getBlock() = nonReturningStmt())
526-
)
527-
}
524+
/**
525+
* Gets a statement that always throws an exception or calls `exit`.
526+
*/
527+
private Stmt nonReturningStmt() {
528+
result instanceof ThrowStmt
529+
or
530+
result.(ExprStmt).getExpr() = nonReturningExpr()
531+
or
532+
result.(BlockStmt).getLastStmt() = nonReturningStmt()
533+
or
534+
exists(IfStmt ifstmt | ifstmt = result |
535+
ifstmt.getThen() = nonReturningStmt() and
536+
ifstmt.getElse() = nonReturningStmt()
537+
)
538+
or
539+
exists(TryStmt try | try = result |
540+
try.getBlock() = nonReturningStmt() and
541+
forall(CatchClause cc | cc = try.getACatchClause() | cc.getBlock() = nonReturningStmt())
542+
)
543+
}
528544

529-
/**
530-
* Gets an expression that always throws an exception or calls `exit`.
531-
*/
532-
private Expr nonReturningExpr() {
533-
result = nonReturningMethodCall()
534-
or
535-
result.(StmtExpr).getStmt() = nonReturningStmt()
536-
or
537-
exists(WhenExpr whenexpr | whenexpr = result |
538-
whenexpr.getBranch(_).isElseBranch() and
539-
forex(WhenBranch whenbranch | whenbranch = whenexpr.getBranch(_) |
540-
whenbranch.getRhs() = nonReturningStmt()
545+
/**
546+
* Gets an expression that always throws an exception or calls `exit`.
547+
*/
548+
private Expr nonReturningExpr() {
549+
result = nonReturningMethodCallGlobal()
550+
or
551+
result.(StmtExpr).getStmt() = nonReturningStmt()
552+
or
553+
exists(WhenExpr whenexpr | whenexpr = result |
554+
whenexpr.getBranch(_).isElseBranch() and
555+
forex(WhenBranch whenbranch | whenbranch = whenexpr.getBranch(_) |
556+
whenbranch.getRhs() = nonReturningStmt()
557+
)
541558
)
542-
)
559+
}
543560
}
544561

545562
// Join order engineering -- first determine the switch block and the case indices required, then retrieve them.
@@ -766,7 +783,7 @@ private module ControlFlowGraphImpl {
766783
not this instanceof BooleanLiteral and
767784
not this instanceof ReturnStmt and
768785
not this instanceof ThrowStmt and
769-
not this = nonReturningMethodCall()
786+
not this = NonReturningAnalysis::nonReturningMethodCall()
770787
}
771788
}
772789

0 commit comments

Comments
 (0)