diff --git a/java/ql/lib/java.qll b/java/ql/lib/java.qll index 9644343e93b6..82c372823792 100644 --- a/java/ql/lib/java.qll +++ b/java/ql/lib/java.qll @@ -26,6 +26,7 @@ import semmle.code.java.KotlinType import semmle.code.java.Member import semmle.code.java.Modifier import semmle.code.java.Modules +import semmle.code.java.Overlay import semmle.code.java.Package import semmle.code.java.Statement import semmle.code.java.Type diff --git a/java/ql/lib/semmle/code/Location.qll b/java/ql/lib/semmle/code/Location.qll index 14fc7a995328..d52b5b4dc358 100644 --- a/java/ql/lib/semmle/code/Location.qll +++ b/java/ql/lib/semmle/code/Location.qll @@ -221,3 +221,16 @@ private predicate fixedHasLocation(Top l, Location loc, File f) { not hasSourceLocation(l, _, _) and locations_default(loc, f, _, _, _, _) } + +overlay[local] +private predicate discardableLocation(string file, @location l) { + not isOverlay() and + file = getRawFileForLoc(l) and + not exists(@file f | hasLocation(f, l)) +} + +/** Discard base locations in files fully extracted in the overlay. */ +overlay[discard_entity] +private predicate discardLocation(@location l) { + exists(string file | discardableLocation(file, l) and extractedInOverlay(file)) +} diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 182bf5b7001f..3ae3c47e80a7 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -2701,3 +2701,15 @@ class RecordPatternExpr extends Expr, @recordpatternexpr { ) } } + +overlay[local] +private predicate discardableExpr(string file, @expr e) { + not isOverlay() and + file = getRawFile(e) +} + +/** Discard base expressions in files fully extracted in the overlay. */ +overlay[discard_entity] +private predicate discardExpr(@expr e) { + exists(string file | discardableExpr(file, e) and extractedInOverlay(file)) +} diff --git a/java/ql/lib/semmle/code/java/Javadoc.qll b/java/ql/lib/semmle/code/java/Javadoc.qll index ef8f77bf9baf..681c7fcd69fd 100644 --- a/java/ql/lib/semmle/code/java/Javadoc.qll +++ b/java/ql/lib/semmle/code/java/Javadoc.qll @@ -196,3 +196,15 @@ class KtCommentSection extends @ktcommentsection { /** Gets the string representation of this section. */ string toString() { result = this.getContent() } } + +overlay[local] +private predicate discardableJavadoc(string file, @javadoc d) { + not isOverlay() and + exists(@member m | file = getRawFile(m) and hasJavadoc(m, d)) +} + +/** Discard javadoc entities in files fully extracted in the overlay. */ +overlay[discard_entity] +private predicate discardJavadoc(@javadoc d) { + exists(string file | discardableJavadoc(file, d) and extractedInOverlay(file)) +} diff --git a/java/ql/lib/semmle/code/java/Member.qll b/java/ql/lib/semmle/code/java/Member.qll index 662eab06bd1e..58344ddcfe08 100644 --- a/java/ql/lib/semmle/code/java/Member.qll +++ b/java/ql/lib/semmle/code/java/Member.qll @@ -623,7 +623,13 @@ class SrcMethod extends Method { then implementsInterfaceMethod(result, this) else result.getASourceOverriddenMethod*() = this ) and - (exists(result.getBody()) or result.hasModifier("native")) + ( + // We allow empty method bodies for the local overlay variant to allow + // calls to methods only fully extracted in base. + isOverlay() or + exists(result.getBody()) or + result.hasModifier("native") + ) } } @@ -897,3 +903,33 @@ class ExtensionMethod extends Method { else result = 0 } } + +overlay[local] +private predicate discardableMethod(string file, @method m) { + not isOverlay() and + file = getRawFile(m) and + exists(@classorinterface c | methods(m, _, _, _, c, _) and isAnonymClass(c, _)) +} + +/** Discard base methods on anonymous classes in files fully extracted in the overlay. */ +overlay[discard_entity] +private predicate discardAnonMethod(@method m) { + exists(string file | discardableMethod(file, m) and extractedInOverlay(file)) +} + +overlay[local] +private predicate discardableBaseMethod(string file, @method m) { + not isOverlay() and + file = getRawFile(m) +} + +overlay[local] +private predicate usedOverlayMethod(@method m) { isOverlay() and methods(m, _, _, _, _, _) } + +/** Discard base methods in files fully extracted in the overlay that were not extracted in the overlay. */ +overlay[discard_entity] +private predicate discardMethod(@method m) { + exists(string file | + discardableBaseMethod(file, m) and extractedInOverlay(file) and not usedOverlayMethod(m) + ) +} diff --git a/java/ql/lib/semmle/code/java/Overlay.qll b/java/ql/lib/semmle/code/java/Overlay.qll new file mode 100644 index 000000000000..713bd9f7623d --- /dev/null +++ b/java/ql/lib/semmle/code/java/Overlay.qll @@ -0,0 +1,39 @@ +overlay[local?] +module; + +import java + +/** + * A local predicate that always holds for the overlay variant and + * never holds for the base variant. This is used to define local + * predicates that behave differently for the base and overlay variant. + */ +overlay[local] +predicate isOverlay() { databaseMetadata("isOverlay", "true") } + +/** Gets the raw file for a locatable. */ +overlay[local] +string getRawFile(@locatable el) { + exists(@location loc, @file file | + hasLocation(el, loc) and + locations_default(loc, file, _, _, _, _) and + files(file, result) + ) +} + +/** Gets the raw file for a location. */ +overlay[local] +string getRawFileForLoc(@location l) { + exists(@file f | locations_default(l, f, _, _, _, _) and files(f, result)) +} + +/** Holds for files fully extracted in the overlay. */ +overlay[local] +predicate extractedInOverlay(string file) { + isOverlay() and + // The incremental Java extractor extracts skeletons without method + // bodies for dependencies. To approximate fully extracted files in + // the overlay, we restrict attention to files that contain an expression + // with an enclosing callable. + exists(@expr e | callableEnclosingExpr(e, _) and file = getRawFile(e)) +} diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 73b0aac5cbdd..8651ffdc3fff 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -987,3 +987,15 @@ class SuperConstructorInvocationStmt extends Stmt, ConstructorCall, @superconstr override string getAPrimaryQlClass() { result = "SuperConstructorInvocationStmt" } } + +overlay[local] +private predicate discardableStmt(string file, @stmt s) { + not isOverlay() and + file = getRawFile(s) +} + +/** Discard base statements in files fully extracted in the overlay. */ +overlay[discard_entity] +private predicate discardStmt(@stmt s) { + exists(string file | discardableStmt(file, s) and extractedInOverlay(file)) +} diff --git a/java/ql/lib/semmle/code/java/Variable.qll b/java/ql/lib/semmle/code/java/Variable.qll index 50fd7a064846..a84eb624d4d5 100644 --- a/java/ql/lib/semmle/code/java/Variable.qll +++ b/java/ql/lib/semmle/code/java/Variable.qll @@ -133,3 +133,15 @@ class Parameter extends Element, @param, LocalScopeVariable { /** Holds if this is an anonymous parameter, `_` */ predicate isAnonymous() { this.getName() = "" } } + +overlay[local] +private predicate discardableLocalVarDecl(string file, @localscopevariable l) { + not isOverlay() and + file = getRawFile(l) +} + +/** Discard base local scoped variables in files fully extracted in the overlay. */ +overlay[discard_entity] +private predicate discardLocalVarDecl(@localscopevariable l) { + exists(string file | discardableLocalVarDecl(file, l) and extractedInOverlay(file)) +} diff --git a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll index 877a62fb9455..edd634a84c6d 100644 --- a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll +++ b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll @@ -36,6 +36,7 @@ Callable exactCallable(Call c) { private predicate implCount(MethodCall m, int c) { strictcount(viableImpl(m)) = c } /** Gets a viable implementation of the target of the given `Call`. */ +overlay[local] Callable viableCallable(Call c) { result = viableImpl(c) or diff --git a/java/ql/src/experimental/Security/CWE/CWE-020/Log4jJndiInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-020/Log4jJndiInjection.ql index 84c4bb01c126..3abaa7bdcfa3 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-020/Log4jJndiInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-020/Log4jJndiInjection.ql @@ -22,6 +22,7 @@ import semmle.code.java.dataflow.ExternalFlow private import semmle.code.java.security.Sanitizers import Log4jInjectionFlow::PathGraph +overlay[local?] deprecated private class ActivateModels extends ActiveExperimentalModels { ActivateModels() { this = "log4j-injection" } } diff --git a/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql b/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql index c84037719da9..0929ca3eb805 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql @@ -17,6 +17,7 @@ import semmle.code.java.dataflow.FlowSources import semmle.code.java.dataflow.ExternalFlow import RemoteUrlToOpenStreamFlow::PathGraph +overlay[local?] deprecated private class ActivateModels extends ActiveExperimentalModels { ActivateModels() { this = "openstream-called-on-tainted-url" } } diff --git a/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql index c87097458520..11bb600ffe85 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql @@ -22,6 +22,7 @@ import semmle.code.java.security.PathSanitizer private import semmle.code.java.security.Sanitizers import InjectFilePathFlow::PathGraph +overlay[local?] deprecated private class ActivateModels extends ActiveExperimentalModels { ActivateModels() { this = "file-path-injection" } } diff --git a/java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.ql b/java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.ql index 08f7631af828..c13bc3bb245c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.ql @@ -18,6 +18,7 @@ import semmle.code.java.security.CommandLineQuery import InputToArgumentToExecFlow::PathGraph private import semmle.code.java.dataflow.ExternalFlow +overlay[local?] deprecated private class ActivateModels extends ActiveExperimentalModels { ActivateModels() { this = "jsch-os-injection" } } diff --git a/java/ql/src/experimental/Security/CWE/CWE-200/AndroidWebResourceResponse.qll b/java/ql/src/experimental/Security/CWE/CWE-200/AndroidWebResourceResponse.qll index bd898df205a8..b988398e4c26 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-200/AndroidWebResourceResponse.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-200/AndroidWebResourceResponse.qll @@ -7,6 +7,7 @@ private import semmle.code.java.dataflow.ExternalFlow private import semmle.code.java.dataflow.FlowSteps private import semmle.code.java.frameworks.android.WebView +overlay[local?] private class ActivateModels extends ActiveExperimentalModels { ActivateModels() { this = "android-web-resource-response" } } diff --git a/java/ql/src/experimental/Security/CWE/CWE-400/ThreadResourceAbuse.qll b/java/ql/src/experimental/Security/CWE/CWE-400/ThreadResourceAbuse.qll index ce6de1a06798..12ba6769f742 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-400/ThreadResourceAbuse.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-400/ThreadResourceAbuse.qll @@ -8,6 +8,7 @@ import semmle.code.java.arithmetic.Overflow import semmle.code.java.dataflow.FlowSteps import semmle.code.java.controlflow.Guards +overlay[local?] private class ActivateModels extends ActiveExperimentalModels { ActivateModels() { this = "thread-resource-abuse" } } diff --git a/java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegexQuery.qll b/java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegexQuery.qll index 8fe997793f4a..f8e328902504 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegexQuery.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegexQuery.qll @@ -9,6 +9,7 @@ import semmle.code.java.controlflow.Guards import semmle.code.java.security.UrlRedirect import Regex +overlay[local?] private class ActivateModels extends ActiveExperimentalModels { ActivateModels() { this = "permissive-dot-regex-query" } } diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll index b2bdc0c12e67..67804f6d3f88 100644 --- a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll +++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll @@ -291,6 +291,7 @@ module MakeImplCommon Lang> { * to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing * callable of `lambdaCall`. */ + overlay[global] pragma[nomagic] predicate revLambdaFlow( Call lambdaCall, LambdaCallKind kind, Node node, Type t, boolean toReturn, boolean toJump,