diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql
new file mode 100644
index 00000000000..49d6bb31c4c
--- /dev/null
+++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/exprs.ql
@@ -0,0 +1,55 @@
+class Expr extends @expr {
+ string toString() { result = "expr" }
+}
+
+class LocalVariableDeclExpr extends @localvariabledeclexpr, Expr { }
+
+class InstanceOfExpr extends @instanceofexpr, Expr { }
+
+class SimplePatternInstanceOfExpr extends InstanceOfExpr {
+ SimplePatternInstanceOfExpr() { getNthChild(this, 2) instanceof LocalVariableDeclExpr }
+}
+
+class Type extends @type {
+ string toString() { result = "type" }
+}
+
+class ExprParent extends @exprparent {
+ string toString() { result = "exprparent" }
+}
+
+Expr getNthChild(ExprParent parent, int i) { exprs(result, _, _, parent, i) }
+
+// Where an InstanceOfExpr has a 2nd child that is a LocalVariableDeclExpr, that expression should becomes its 0th child and the existing 0th child should become its initialiser.
+// Any RecordPatternExpr should be replaced with an error expression, as it can't be represented in the downgraded dbscheme.
+// This reverts a reorganisation of the representation of "o instanceof String s", which used to be InstanceOfExpr -0-> LocalVariableDeclExpr --init-> o
+// \-name-> s
+// It is now InstanceOfExpr --0-> o
+// \-2-> LocalVariableDeclExpr -name-> s
+//
+// Other children are unaffected.
+predicate hasNewParent(Expr e, ExprParent newParent, int newIndex) {
+ exists(SimplePatternInstanceOfExpr oldParent, int oldIndex |
+ e = getNthChild(oldParent, oldIndex)
+ |
+ oldIndex = 0 and newParent = getNthChild(oldParent, 2) and newIndex = 0
+ or
+ oldIndex = 1 and newParent = oldParent and newIndex = oldIndex
+ or
+ oldIndex = 2 and newParent = oldParent and newIndex = 0
+ )
+ or
+ not exists(SimplePatternInstanceOfExpr oldParent | e = getNthChild(oldParent, _)) and
+ exprs(e, _, _, newParent, newIndex)
+}
+
+from Expr e, int oldKind, int newKind, Type typeid, ExprParent parent, int index
+where
+ exprs(e, oldKind, typeid, _, _) and
+ hasNewParent(e, parent, index) and
+ (
+ if oldKind = /* record pattern */ 89
+ then newKind = /* error expression */ 74
+ else oldKind = newKind
+ )
+select e, newKind, typeid, parent, index
diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/old.dbscheme b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/old.dbscheme
new file mode 100644
index 00000000000..dee651b58d1
--- /dev/null
+++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/old.dbscheme
@@ -0,0 +1,1265 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+compilation_info(
+ int id : @compilation ref,
+ string info_key: string ref,
+ string info_value: string ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The expanded arguments that were passed to the extractor for a
+ * compiler invocation. This is similar to `compilation_args`, but
+ * for a `@@@someFile` argument, it includes the arguments from that
+ * file, rather than just taking the argument literally.
+ */
+#keyset[id, num]
+compilation_expanded_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes_or_interfaces(
+ unique int id: @classorinterface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @classorinterface ref
+);
+
+file_class(
+ int id: @classorinterface ref
+);
+
+class_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @classorinterface ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isInterface(
+ unique int id: @classorinterface ref
+);
+
+isRecord(
+ unique int id: @classorinterface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @classorinterface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @classorinterface ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @classorinterface ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+isCanonicalConstr(
+ int constructorid: @constructor ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @classorinterface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+| 89 = @recordpatternexpr
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+isNullDefaultCase(
+ int id: @case ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @classorinterface | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar | @typevariable;
+
+@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+ // 3: ENUM_ENTRIES
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @classorinterface ref
+)
diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/semmlecode.dbscheme b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/semmlecode.dbscheme
new file mode 100644
index 00000000000..ecfcf050952
--- /dev/null
+++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/semmlecode.dbscheme
@@ -0,0 +1,1256 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+compilation_info(
+ int id : @compilation ref,
+ string info_key: string ref,
+ string info_value: string ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The expanded arguments that were passed to the extractor for a
+ * compiler invocation. This is similar to `compilation_args`, but
+ * for a `@@@someFile` argument, it includes the arguments from that
+ * file, rather than just taking the argument literally.
+ */
+#keyset[id, num]
+compilation_expanded_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes_or_interfaces(
+ unique int id: @classorinterface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @classorinterface ref
+);
+
+file_class(
+ int id: @classorinterface ref
+);
+
+class_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @classorinterface ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isInterface(
+ unique int id: @classorinterface ref
+);
+
+isRecord(
+ unique int id: @classorinterface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @classorinterface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @classorinterface ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @classorinterface ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @classorinterface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @classorinterface | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar | @typevariable;
+
+@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+ // 3: ENUM_ENTRIES
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @classorinterface ref
+)
diff --git a/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/upgrade.properties b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/upgrade.properties
new file mode 100644
index 00000000000..b60dcc89cc7
--- /dev/null
+++ b/java/downgrades/dee651b58d1e5455ca2d07eca37775a21d772fcc/upgrade.properties
@@ -0,0 +1,5 @@
+description: Remove tables for canonical constructors and `case null, default`, and the expression kind for record patterns. Also revert the representation for instanceof with a binding pattern.
+compatibility: backwards
+exprs.rel: run exprs.qlo
+isCanonicalConstr.rel: delete
+isNullDefaultCase.rel: delete
diff --git a/java/ql/consistency-queries/children.ql b/java/ql/consistency-queries/children.ql
index 7386ee79c00..755c680e790 100644
--- a/java/ql/consistency-queries/children.ql
+++ b/java/ql/consistency-queries/children.ql
@@ -46,7 +46,13 @@ predicate gapInChildren(Element e, int i) {
// value should be, because kotlinc doesn't load annotation defaults and we
// want to leave a space for another extractor to fill in the default if it
// is able.
- not e instanceof Annotation
+ not e instanceof Annotation and
+ // Pattern case statements legitimately have a TypeAccess (-2) and a pattern (0) but not a rule (-1)
+ not (i = -1 and e instanceof PatternCase and not e.(PatternCase).isRule()) and
+ // Instanceof with a record pattern is not expected to have a type access in position 1
+ not (i = 1 and e.(InstanceOfExpr).getPattern() instanceof RecordPatternExpr) and
+ // RecordPatternExpr extracts type-accesses only for its LocalVariableDeclExpr children
+ not (i < 0 and e instanceof RecordPatternExpr)
}
predicate lateFirstChild(Element e, int i) {
diff --git a/java/ql/lib/change-notes/2023-11-03-jdk21-support.md b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md
new file mode 100644
index 00000000000..f561b3211ca
--- /dev/null
+++ b/java/ql/lib/change-notes/2023-11-03-jdk21-support.md
@@ -0,0 +1,6 @@
+---
+category: minorAnalysis
+---
+* Switch cases using binding patterns and `case null[, default]` are now supported. Classes `PatternCase` and `NullDefaultCase` are introduced to represent new kinds of case statement.
+* Both switch cases and instanceof expressions using record patterns are now supported. The new class `RecordPatternExpr` is introduced to represent record patterns, and `InstanceOfExpr` gains `getPattern` to replace `getLocalVariableDeclExpr`.
+* The control-flow graph and therefore dominance information regarding switch blocks in statement context but with an expression rule (e.g. `switch(...) { case 1 -> System.out.println("Hello world!") }`) has been fixed. This reduces false positives and negatives from various queries relating to functions featuring such statements.
diff --git a/java/ql/lib/config/semmlecode.dbscheme b/java/ql/lib/config/semmlecode.dbscheme
index ecfcf050952..dee651b58d1 100644
--- a/java/ql/lib/config/semmlecode.dbscheme
+++ b/java/ql/lib/config/semmlecode.dbscheme
@@ -576,6 +576,10 @@ lambdaKind(
int bodyKind: int ref
);
+isCanonicalConstr(
+ int constructorid: @constructor ref
+);
+
arrays(
unique int id: @array,
string nodeName: string ref,
@@ -774,6 +778,7 @@ case @expr.kind of
| 86 = @valueeqexpr
| 87 = @valueneexpr
| 88 = @propertyref
+| 89 = @recordpatternexpr
;
/** Holds if this `when` expression was written as an `if` expression. */
@@ -992,6 +997,10 @@ providesWith(
string serviceImpl: string ref
);
+isNullDefaultCase(
+ int id: @case ref
+);
+
/*
* Javadoc
*/
diff --git a/java/ql/lib/config/semmlecode.dbscheme.stats b/java/ql/lib/config/semmlecode.dbscheme.stats
index f069e1773d8..c7b35b17ef7 100644
--- a/java/ql/lib/config/semmlecode.dbscheme.stats
+++ b/java/ql/lib/config/semmlecode.dbscheme.stats
@@ -568,6 +568,10 @@
@propertyref
8439
+
+ @recordpatternexpr
+ 50
+
@localvar
385272
@@ -9478,6 +9482,17 @@
+
+ isCanonicalConstr
+ 417
+
+
+ constructorid
+ 417
+
+
+
+
fielddecls
210035
@@ -26950,5 +26965,16 @@
+
+ isNullDefaultCase
+ 50
+
+
+ id
+ 50
+
+
+
+
diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll
index 572c8629626..24a506f21ce 100644
--- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll
+++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll
@@ -82,6 +82,7 @@
import java
private import Completion
private import controlflow.internal.Preconditions
+private import controlflow.internal.SwitchCases
/** A node in the expression-level control-flow graph. */
class ControlFlowNode extends Top, @exprparent {
@@ -317,6 +318,8 @@ private module ControlFlowGraphImpl {
whenexpr.getBranch(_).getAResult() = b
)
or
+ b = any(PatternCase pc).getGuard()
+ or
inBooleanContext(b.(ExprStmt).getExpr())
or
inBooleanContext(b.(StmtExpr).getStmt())
@@ -434,6 +437,73 @@ private module ControlFlowGraphImpl {
)
}
+ // Join order engineering -- first determine the switch block and the case indices required, then retrieve them.
+ bindingset[switch, i]
+ pragma[inline_late]
+ private predicate isNthCaseOf(SwitchBlock switch, SwitchCase c, int i) {
+ c.isNthCaseOf(switch, i)
+ }
+
+ /**
+ * Gets a `SwitchCase` that may be `pred`'s direct successor, where `pred` is declared in block `switch`.
+ *
+ * This means any switch case that comes after `pred` up to the next pattern case, if any, except for `case null`.
+ *
+ * Because we know the switch block contains at least one pattern, we know by https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11
+ * that any default case comes after the last pattern case.
+ */
+ private SwitchCase getASuccessorSwitchCase(PatternCase pred, SwitchBlock switch) {
+ // Note we do include `case null, default` (as well as plain old `default`) here.
+ not result.(ConstCase).getValue(_) instanceof NullLiteral and
+ exists(int maxCaseIndex |
+ switch = pred.getParent() and
+ if exists(getNextPatternCase(pred))
+ then maxCaseIndex = getNextPatternCase(pred).getCaseIndex()
+ else maxCaseIndex = lastCaseIndex(switch)
+ |
+ isNthCaseOf(switch, result, [pred.getCaseIndex() + 1 .. maxCaseIndex])
+ )
+ }
+
+ /**
+ * Gets a `SwitchCase` that may occur first in `switch`.
+ *
+ * If the block contains at least one PatternCase, this is any case up to and including that case, or
+ * the case handling the null literal if any.
+ *
+ * Otherwise it is any case in the switch block.
+ */
+ private SwitchCase getAFirstSwitchCase(SwitchBlock switch) {
+ result.getParent() = switch and
+ (
+ result.(ConstCase).getValue(_) instanceof NullLiteral
+ or
+ result instanceof NullDefaultCase
+ or
+ not exists(getFirstPatternCase(switch))
+ or
+ result.getIndex() <= getFirstPatternCase(switch).getIndex()
+ )
+ }
+
+ private Stmt getSwitchStatement(SwitchBlock switch, int i) { result.isNthChildOf(switch, i) }
+
+ /**
+ * Holds if `last` is the last node in a pattern case `pc`'s succeeding bind-and-test operation,
+ * immediately before either falling through to execute successor statements or execute a rule body
+ * if present. `completion` is the completion kind of the last operation.
+ */
+ private predicate lastPatternCaseMatchingOp(
+ PatternCase pc, ControlFlowNode last, Completion completion
+ ) {
+ last(pc.getPattern(), last, completion) and
+ completion = NormalCompletion() and
+ not exists(pc.getGuard())
+ or
+ last(pc.getGuard(), last, completion) and
+ completion = BooleanCompletion(true, _)
+ }
+
/**
* Expressions and statements with CFG edges in post-order AST traversal.
*
@@ -466,8 +536,7 @@ private module ControlFlowGraphImpl {
or
this instanceof NotInstanceOfExpr
or
- this instanceof LocalVariableDeclExpr and
- not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr()
+ this instanceof LocalVariableDeclExpr
or
this instanceof StringTemplateExpr
or
@@ -493,7 +562,11 @@ private module ControlFlowGraphImpl {
or
this.(BlockStmt).getNumStmt() = 0
or
- this instanceof SwitchCase and not this.(SwitchCase).isRule()
+ this instanceof SwitchCase and
+ not this.(SwitchCase).isRule() and
+ not this instanceof PatternCase
+ or
+ this instanceof RecordPatternExpr
or
this instanceof EmptyStmt
or
@@ -571,6 +644,8 @@ private module ControlFlowGraphImpl {
index = 0 and result = this.(ThrowStmt).getExpr()
or
index = 0 and result = this.(AssertStmt).getExpr()
+ or
+ result = this.(RecordPatternExpr).getSubPattern(index)
}
/** Gets the first child node, if any. */
@@ -711,7 +786,7 @@ private module ControlFlowGraphImpl {
*/
private predicate last(ControlFlowNode n, ControlFlowNode last, Completion completion) {
// Exceptions are propagated from any sub-expression.
- // As are any break, continue, or return completions.
+ // As are any break, yield, continue, or return completions.
exists(Expr e | e.getParent() = n |
last(e, last, completion) and not completion instanceof NormalOrBooleanCompletion
)
@@ -726,7 +801,10 @@ private module ControlFlowGraphImpl {
or
last(n, last, BooleanCompletion(_, _)) and
not inBooleanContext(n) and
- completion = NormalCompletion()
+ completion = NormalCompletion() and
+ // PatternCase has both a boolean-true completion (guard success) and a normal one
+ // (variable declaration completion, when no guard is present).
+ not n instanceof PatternCase
or
// Logic expressions and conditional expressions are executed in AST pre-order to facilitate
// proper short-circuit representation. All other expressions are executed in post-order.
@@ -765,7 +843,7 @@ private module ControlFlowGraphImpl {
exists(InstanceOfExpr ioe | ioe.isPattern() and ioe = n |
last = n and completion = basicBooleanCompletion(false)
or
- last = ioe.getLocalVariableDeclExpr() and completion = basicBooleanCompletion(true)
+ last(ioe.getPattern(), last, NormalCompletion()) and completion = basicBooleanCompletion(true)
)
or
// The last node of a node executed in post-order is the node itself.
@@ -847,14 +925,19 @@ private module ControlFlowGraphImpl {
// any other abnormal completion is propagated
last(switch.getAStmt(), last, completion) and
completion != anonymousBreakCompletion() and
- completion != NormalCompletion()
+ not completion instanceof NormalOrBooleanCompletion
or
// if the last case completes normally, then so does the switch
last(switch.getStmt(strictcount(switch.getAStmt()) - 1), last, NormalCompletion()) and
completion = NormalCompletion()
or
// if no default case exists, then normal completion of the expression may terminate the switch
+ // Note this can't happen if there are pattern cases or a null literal, as
+ // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 requires that such
+ // an enhanced switch block is exhaustive.
not exists(switch.getDefaultCase()) and
+ not exists(switch.getAPatternCase()) and
+ not switch.hasNullCase() and
last(switch.getExpr(), last, completion) and
completion = NormalCompletion()
)
@@ -867,24 +950,42 @@ private module ControlFlowGraphImpl {
// any other abnormal completion is propagated
last(switch.getAStmt(), last, completion) and
not completion instanceof YieldCompletion and
- completion != NormalCompletion()
+ not completion instanceof NormalOrBooleanCompletion
)
or
- // the last node in a case rule is the last node in the right-hand side
- // if the rhs is a statement we wrap the completion as a break
- exists(Completion caseCompletion |
- last(n.(SwitchCase).getRuleStatement(), last, caseCompletion) and
+ // If a case rule right-hand-side completes then the switch breaks or yields, depending
+ // on whether this is a switch expression or statement. If it completes abruptly then the
+ // switch completes the same way.
+ exists(Completion caseCompletion, SwitchCase case |
+ case = n and
+ (
+ last(case.getRuleStatement(), last, caseCompletion)
+ or
+ last(case.getRuleExpression(), last, caseCompletion)
+ )
+ |
if caseCompletion instanceof NormalOrBooleanCompletion
- then completion = anonymousBreakCompletion()
+ then
+ case.getParent() instanceof SwitchStmt and completion = anonymousBreakCompletion()
+ or
+ case.getParent() instanceof SwitchExpr and completion = YieldCompletion(caseCompletion)
else completion = caseCompletion
)
or
- // ...and if the rhs is an expression we wrap the completion as a yield
- exists(Completion caseCompletion |
- last(n.(SwitchCase).getRuleExpression(), last, caseCompletion) and
- if caseCompletion instanceof NormalOrBooleanCompletion
- then completion = YieldCompletion(caseCompletion)
- else completion = caseCompletion
+ // A pattern case statement can complete:
+ // * On failure of its type test (boolean false)
+ // * On failure of its guard test if any (boolean false)
+ // * On completion of its variable declarations, if it is not a rule and has no guard (normal completion)
+ // * On success of its guard test, if it is not a rule (boolean true)
+ // (the latter two cases are accounted for by lastPatternCaseMatchingOp)
+ exists(PatternCase pc | n = pc |
+ last = pc and completion = basicBooleanCompletion(false)
+ or
+ last(pc.getGuard(), last, completion) and
+ completion = BooleanCompletion(false, _)
+ or
+ not pc.isRule() and
+ lastPatternCaseMatchingOp(pc, last, completion)
)
or
// the last statement of a synchronized statement is the last statement of its body
@@ -1035,7 +1136,7 @@ private module ControlFlowGraphImpl {
last(ioe.getExpr(), n, completion) and completion = NormalCompletion() and result = ioe
or
n = ioe and
- result = ioe.getLocalVariableDeclExpr() and
+ result = first(ioe.getPattern()) and
completion = basicBooleanCompletion(true)
)
or
@@ -1196,39 +1297,67 @@ private module ControlFlowGraphImpl {
last(cc.getVariable(), n, completion) and result = first(cc.getBlock())
)
or
- // Switch statements
- exists(SwitchStmt switch | completion = NormalCompletion() |
- // From the entry point control is transferred first to the expression...
- n = switch and result = first(switch.getExpr())
- or
- // ...and then to one of the cases.
- last(switch.getExpr(), n, completion) and result = first(switch.getACase())
+ // Switch statements and expressions
+ exists(SwitchBlock switch |
+ exists(Expr switchExpr |
+ switchExpr = switch.(SwitchStmt).getExpr() or switchExpr = switch.(SwitchExpr).getExpr()
+ |
+ // From the entry point control is transferred first to the expression...
+ n = switch and result = first(switchExpr) and completion = NormalCompletion()
+ or
+ // ...and then to any case up to and including the first pattern case, if any.
+ last(switchExpr, n, completion) and
+ result = first(getAFirstSwitchCase(switch)) and
+ completion = NormalCompletion()
+ )
or
// Statements within a switch body execute sequentially.
+ // Note this includes non-rule case statements and the successful pattern match successor
+ // of a non-rule pattern case statement. Rule case statements do not complete normally
+ // (they always break or yield).
exists(int i |
- last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1))
+ last(getSwitchStatement(switch, i), n, completion) and
+ result = first(getSwitchStatement(switch, i + 1)) and
+ (completion = NormalCompletion() or completion = BooleanCompletion(true, _))
+ )
+ or
+ // A pattern case that completes boolean false (type test or guard failure) continues to consider other cases:
+ exists(PatternCase case | completion = BooleanCompletion(false, _) |
+ last(case, n, completion) and result = getASuccessorSwitchCase(case, switch)
)
)
or
- // Switch expressions
- exists(SwitchExpr switch | completion = NormalCompletion() |
- // From the entry point control is transferred first to the expression...
- n = switch and result = first(switch.getExpr())
+ // Pattern cases have internal edges:
+ // * Type test success -true-> variable declarations
+ // * Variable declarations -normal-> guard evaluation
+ // * Variable declarations -normal-> rule execution (when there is no guard)
+ // * Guard success -true-> rule execution
+ exists(PatternCase pc |
+ n = pc and
+ completion = basicBooleanCompletion(true) and
+ result = first(pc.getPattern())
or
- // ...and then to one of the cases.
- last(switch.getExpr(), n, completion) and result = first(switch.getACase())
+ last(pc.getPattern(), n, completion) and
+ completion = NormalCompletion() and
+ result = first(pc.getGuard())
or
- // Statements within a switch body execute sequentially.
- exists(int i |
- last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i + 1))
+ lastPatternCaseMatchingOp(pc, n, completion) and
+ (
+ result = first(pc.getRuleExpression())
+ or
+ result = first(pc.getRuleStatement())
)
)
or
- // No edges in a non-rule SwitchCase - the constant expression in a ConstCase isn't included in the CFG.
- exists(SwitchCase case | completion = NormalCompletion() |
- n = case and result = first(case.getRuleExpression())
- or
- n = case and result = first(case.getRuleStatement())
+ // Non-pattern cases have an internal edge leading to their rule body if any when the case matches.
+ exists(SwitchCase case | n = case |
+ not case instanceof PatternCase and
+ completion = NormalCompletion() and
+ (
+ result = first(case.getRuleExpression())
+ or
+ result = first(case.getRuleStatement())
+ )
)
or
// Yield
@@ -1365,5 +1494,5 @@ class ConditionNode extends ControlFlowNode {
ControlFlowNode getAFalseSuccessor() { result = this.getABranchSuccessor(false) }
/** Gets the condition of this `ConditionNode`. This is equal to the node itself. */
- Expr getCondition() { result = this }
+ ExprParent getCondition() { result = this }
}
diff --git a/java/ql/lib/semmle/code/java/Dependency.qll b/java/ql/lib/semmle/code/java/Dependency.qll
index 17236c6d05e..1bdf0140079 100644
--- a/java/ql/lib/semmle/code/java/Dependency.qll
+++ b/java/ql/lib/semmle/code/java/Dependency.qll
@@ -78,6 +78,13 @@ predicate depends(RefType t, RefType dep) {
// the type accessed in an `instanceof` expression in `t`.
exists(InstanceOfExpr ioe | t = ioe.getEnclosingCallable().getDeclaringType() |
usesType(ioe.getCheckedType(), dep)
+ or
+ usesType(ioe.getPattern().getAChildExpr*().getType(), dep)
+ )
+ or
+ // A type accessed in a pattern-switch case statement in `t`.
+ exists(PatternCase pc | t = pc.getEnclosingCallable().getDeclaringType() |
+ usesType(pc.getPattern().getAChildExpr*().getType(), dep)
)
)
}
diff --git a/java/ql/lib/semmle/code/java/DependencyCounts.qll b/java/ql/lib/semmle/code/java/DependencyCounts.qll
index b34e774f1e1..ca0acdedcde 100644
--- a/java/ql/lib/semmle/code/java/DependencyCounts.qll
+++ b/java/ql/lib/semmle/code/java/DependencyCounts.qll
@@ -101,6 +101,13 @@ predicate numDepends(RefType t, RefType dep, int value) {
t = ioe.getEnclosingCallable().getDeclaringType()
|
usesType(ioe.getCheckedType(), dep)
+ or
+ usesType(ioe.getPattern().getAChildExpr*().getType(), dep)
+ )
+ or
+ // the type accessed in a pattern-switch case statement in `t`.
+ exists(PatternCase pc | elem = pc and t = pc.getEnclosingCallable().getDeclaringType() |
+ usesType(pc.getPattern().getAChildExpr*().getType(), dep)
)
)
}
diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll
index 89e79b3ad0a..be3976b8458 100644
--- a/java/ql/lib/semmle/code/java/Expr.qll
+++ b/java/ql/lib/semmle/code/java/Expr.qll
@@ -1509,17 +1509,33 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
*/
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
+ /**
+ * Gets the `i`th case of this `switch` expression,
+ * which may be either a normal `case` or a `default`.
+ */
+ SwitchCase getCase(int i) {
+ result =
+ rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
+ }
+
/**
* Gets a case of this `switch` expression,
* which may be either a normal `case` or a `default`.
*/
- SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
+ SwitchCase getACase() { result.getParent() = this }
/** Gets a (non-default) `case` of this `switch` expression. */
- ConstCase getAConstCase() { result.getParent() = this }
+ ConstCase getAConstCase() { result = this.getACase() }
- /** Gets the `default` case of this switch expression, if any. */
- DefaultCase getDefaultCase() { result.getParent() = this }
+ /** Gets a (non-default) pattern `case` of this `switch` expression. */
+ PatternCase getAPatternCase() { result = this.getACase() }
+
+ /**
+ * Gets the `default` case of this switch statement, if any.
+ *
+ * Note this may be `default` or `case null, default`.
+ */
+ DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the expression of this `switch` expression. */
Expr getExpr() { result.getParent() = this }
@@ -1531,6 +1547,12 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
exists(YieldStmt yield | yield.getTarget() = this and result = yield.getValue())
}
+ /** Holds if this switch has a case handling a null literal. */
+ predicate hasNullCase() {
+ this.getAConstCase().getValue(_) instanceof NullLiteral or
+ this.getACase() instanceof NullDefaultCase
+ }
+
/** Gets a printable representation of this expression. */
override string toString() { result = "switch (...)" }
@@ -1540,27 +1562,61 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
/** An `instanceof` expression. */
class InstanceOfExpr extends Expr, @instanceofexpr {
/** Gets the expression on the left-hand side of the `instanceof` operator. */
- Expr getExpr() {
- if this.isPattern()
- then result = this.getLocalVariableDeclExpr().getInit()
- else result.isNthChildOf(this, 0)
- }
+ Expr getExpr() { result.isNthChildOf(this, 0) }
+
+ /**
+ * Gets the pattern of an `x instanceof T pattern` expression, if any.
+ */
+ PatternExpr getPattern() { result.isNthChildOf(this, 2) }
/**
* Holds if this `instanceof` expression uses pattern matching.
*/
- predicate isPattern() { exists(this.getLocalVariableDeclExpr()) }
+ predicate isPattern() { exists(this.getPattern()) }
/**
- * Gets the local variable declaration of this `instanceof` expression if pattern matching is used.
+ * Gets the local variable declaration of this `instanceof` expression if simple pattern matching is used.
+ *
+ * Note that this won't get anything when record pattern matching is used-- for more general patterns,
+ * use `getPattern`.
*/
- LocalVariableDeclExpr getLocalVariableDeclExpr() { result.isNthChildOf(this, 0) }
+ LocalVariableDeclExpr getLocalVariableDeclExpr() { result = this.getPattern().asBindingPattern() }
- /** Gets the access to the type on the right-hand side of the `instanceof` operator. */
+ /**
+ * Gets the access to the type on the right-hand side of the `instanceof` operator.
+ *
+ * This does not match record patterns, which have a record pattern (use `getPattern`) not a type access.
+ */
Expr getTypeName() { result.isNthChildOf(this, 1) }
- /** Gets the type this `instanceof` expression checks for. */
- RefType getCheckedType() { result = this.getTypeName().getType() }
+ /**
+ * Gets the type this `instanceof` expression checks for.
+ *
+ * For a match against a record pattern, this is the type of the outermost record type, and only holds if
+ * the record pattern matches that type unconditionally, i.e. it does not restrict field types more tightly
+ * than the fields' declared types and therefore match a subset of `rpe.getType()`.
+ */
+ RefType getCheckedType() {
+ result = this.getTypeName().getType()
+ or
+ exists(RecordPatternExpr rpe | rpe = this.getPattern().asRecordPattern() |
+ result = rpe.getType() and rpe.isUnrestricted()
+ )
+ }
+
+ /**
+ * Gets the type this `instanceof` expression checks for.
+ *
+ * For a match against a record pattern, this is the type of the outermost record type. Note that because
+ * the record match might additionally constrain field or sub-record fields to have a more specific type,
+ * and so while if the `instanceof` test passes we know that `this.getExpr()` has this type, if it fails
+ * we do not know that it doesn't.
+ */
+ RefType getSyntacticCheckedType() {
+ result = this.getTypeName().getType()
+ or
+ result = this.getPattern().asRecordPattern().getType()
+ }
/** Gets a printable representation of this expression. */
override string toString() { result = "...instanceof..." }
@@ -1592,7 +1648,9 @@ class NotInstanceOfExpr extends Expr, @notinstanceofexpr {
* A local variable declaration expression.
*
* Contexts in which such expressions may occur include
- * local variable declaration statements and `for` loops.
+ * local variable declaration statements, `for` loops,
+ * and binding patterns such as `if (x instanceof T t)` and
+ * `case String s:`.
*/
class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
/** Gets an access to the variable declared by this local variable declaration expression. */
@@ -1612,18 +1670,86 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
exists(EnhancedForStmt efs | efs.getVariable() = this | result.isNthChildOf(efs, -1))
or
exists(InstanceOfExpr ioe | this.getParent() = ioe | result.isNthChildOf(ioe, 1))
+ or
+ exists(PatternCase pc | this.getParent() = pc | result.isNthChildOf(pc, -2))
+ or
+ exists(RecordPatternExpr rpe, int index |
+ this.isNthChildOf(rpe, index) and result.isNthChildOf(rpe, -(index + 1))
+ )
}
/** Gets the name of the variable declared by this local variable declaration expression. */
string getName() { result = this.getVariable().getName() }
- /** Gets the initializer expression of this local variable declaration expression, if any. */
+ /**
+ * Gets the switch statement or expression whose pattern declares this identifier, if any.
+ */
+ SwitchBlock getAssociatedSwitch() {
+ exists(PatternCase pc |
+ pc = result.(SwitchStmt).getAPatternCase()
+ or
+ pc = result.(SwitchExpr).getAPatternCase()
+ |
+ this = pc.getPattern().getAChildExpr*()
+ )
+ }
+
+ /** Holds if this is a declaration stemming from a pattern switch case. */
+ predicate hasAssociatedSwitch() { exists(this.getAssociatedSwitch()) }
+
+ /**
+ * Gets the instanceof expression whose pattern declares this identifier, if any.
+ */
+ InstanceOfExpr getAssociatedInstanceOfExpr() { result.getPattern().getAChildExpr*() = this }
+
+ /** Holds if this is a declaration stemming from a pattern instanceof expression. */
+ predicate hasAssociatedInstanceOfExpr() { exists(this.getAssociatedInstanceOfExpr()) }
+
+ /**
+ * Gets the initializer expression of this local variable declaration expression, if any.
+ *
+ * Note this applies specifically to a syntactic initialization like `T varname = init`;
+ * to include also `e instanceof T varname` and `switch(e) ... case T varname`, which both
+ * have the effect of initializing `varname` to a known local expression without using
+ * that syntax, use `getInitOrPatternSource`.
+ */
Expr getInit() { result.isNthChildOf(this, 0) }
+ /**
+ * Gets the local expression that initializes this variable declaration, if any.
+ *
+ * Note this includes explicit `T varname = init;`, as well as `e instanceof T varname`
+ * and `switch(e) ... case T varname`. To get only explicit initializers, use `getInit`.
+ *
+ * Note that record pattern variables like `e instance of T Record(T varname)` do not have
+ * either an explicit initializer or a pattern source.
+ */
+ Expr getInitOrPatternSource() {
+ result = this.getInit()
+ or
+ exists(SwitchStmt switch |
+ result = switch.getExpr() and
+ this = switch.getAPatternCase().getPattern().asBindingPattern()
+ )
+ or
+ exists(SwitchExpr switch |
+ result = switch.getExpr() and
+ this = switch.getAPatternCase().getPattern().asBindingPattern()
+ )
+ or
+ exists(InstanceOfExpr ioe |
+ result = ioe.getExpr() and
+ this = ioe.getPattern().asBindingPattern()
+ )
+ }
+
/** Holds if this variable declaration implicitly initializes the variable. */
predicate hasImplicitInit() {
- exists(CatchClause cc | cc.getVariable() = this) or
+ exists(CatchClause cc | cc.getVariable() = this)
+ or
exists(EnhancedForStmt efs | efs.getVariable() = this)
+ or
+ this.getParent() instanceof RecordPatternExpr
}
/** Gets a printable representation of this expression. */
@@ -1632,6 +1758,11 @@ class LocalVariableDeclExpr extends Expr, @localvariabledeclexpr {
override string getAPrimaryQlClass() { result = "LocalVariableDeclExpr" }
}
+/** A local variable declaration that occurs within a record pattern. */
+class RecordBindingVariableExpr extends LocalVariableDeclExpr {
+ RecordBindingVariableExpr() { this.getParent() instanceof RecordPatternExpr }
+}
+
/** An update of a variable or an initialization of the variable. */
class VariableUpdate extends Expr {
VariableUpdate() {
@@ -1660,12 +1791,12 @@ class VariableAssign extends VariableUpdate {
/**
* Gets the source (right-hand side) of this assignment, if any.
*
- * An initialization in a `CatchClause` or `EnhancedForStmt` is implicit and
- * does not have a source.
+ * An initialization in a `CatchClause`, `EnhancedForStmt` or `RecordPatternExpr`
+ * is implicit and does not have a source.
*/
Expr getSource() {
result = this.(AssignExpr).getSource() or
- result = this.(LocalVariableDeclExpr).getInit()
+ result = this.(LocalVariableDeclExpr).getInitOrPatternSource()
}
}
@@ -2512,3 +2643,59 @@ class NotNullExpr extends UnaryExpr, @notnullexpr {
override string getAPrimaryQlClass() { result = "NotNullExpr" }
}
+
+/**
+ * A binding or record pattern.
+ *
+ * Note binding patterns are represented as `LocalVariableDeclExpr`s.
+ */
+class PatternExpr extends Expr {
+ PatternExpr() {
+ (
+ this.getParent() instanceof SwitchCase or
+ this.getParent() instanceof InstanceOfExpr or
+ this.getParent() instanceof RecordPatternExpr
+ ) and
+ (this instanceof LocalVariableDeclExpr or this instanceof RecordPatternExpr)
+ }
+
+ /**
+ * Gets this pattern cast to a binding pattern.
+ */
+ LocalVariableDeclExpr asBindingPattern() { result = this }
+
+ /**
+ * Gets this pattern cast to a record pattern.
+ */
+ RecordPatternExpr asRecordPattern() { result = this }
+}
+
+/** A record pattern expr, as in `if (x instanceof SomeRecord(int field))`. */
+class RecordPatternExpr extends Expr, @recordpatternexpr {
+ override string toString() { result = this.getType().toString() + "(...)" }
+
+ override string getAPrimaryQlClass() { result = "RecordPatternExpr" }
+
+ /**
+ * Gets the `i`th subpattern of this record pattern.
+ */
+ PatternExpr getSubPattern(int i) { result.isNthChildOf(this, i) }
+
+ /**
+ * Holds if this record pattern matches any record of its type.
+ *
+ * For example, for `record R(Object o) { }`, pattern `R(Object o)` is unrestricted, whereas
+ * pattern `R(String s)` is not because it matches a subset of `R` instances, those containing `String`s.
+ */
+ predicate isUnrestricted() {
+ forall(PatternExpr subPattern, int idx | subPattern = this.getSubPattern(idx) |
+ subPattern.getType() =
+ this.getType().(Record).getCanonicalConstructor().getParameter(idx).getType() and
+ (
+ subPattern instanceof LocalVariableDeclExpr
+ or
+ subPattern.(RecordPatternExpr).isUnrestricted()
+ )
+ )
+ }
+}
diff --git a/java/ql/lib/semmle/code/java/Member.qll b/java/ql/lib/semmle/code/java/Member.qll
index 49c9107d5d1..7c625ae48c6 100644
--- a/java/ql/lib/semmle/code/java/Member.qll
+++ b/java/ql/lib/semmle/code/java/Member.qll
@@ -737,10 +737,17 @@ class FieldDeclaration extends ExprParent, @fielddecl, Annotatable {
/** Gets the number of fields declared in this declaration. */
int getNumField() { result = max(int idx | fieldDeclaredIn(_, this, idx) | idx) + 1 }
+ private string stringifyType() {
+ // Necessary because record fields are missing their type access.
+ if exists(this.getTypeAccess())
+ then result = this.getTypeAccess().toString()
+ else result = this.getAField().getType().toString()
+ }
+
override string toString() {
if this.getNumField() = 1
- then result = this.getTypeAccess() + " " + this.getField(0) + ";"
- else result = this.getTypeAccess() + " " + this.getField(0) + ", ...;"
+ then result = this.stringifyType() + " " + this.getField(0) + ";"
+ else result = this.stringifyType() + " " + this.getField(0) + ", ...;"
}
override string getAPrimaryQlClass() { result = "FieldDeclaration" }
diff --git a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll
index f7ddbee4abc..6a5e5aa698b 100644
--- a/java/ql/lib/semmle/code/java/PrettyPrintAst.qll
+++ b/java/ql/lib/semmle/code/java/PrettyPrintAst.qll
@@ -383,15 +383,18 @@ private class PpInstanceOfExpr extends PpAst, InstanceOfExpr {
override string getPart(int i) {
i = 1 and result = " instanceof "
or
- i = 3 and result = " " and this.isPattern()
+ i = 3 and result = " " and this.getPattern() instanceof LocalVariableDeclExpr
or
- i = 4 and result = this.getLocalVariableDeclExpr().getName()
+ i = 4 and
+ result = this.getPattern().asBindingPattern().getName()
}
override PpAst getChild(int i) {
i = 0 and result = this.getExpr()
or
i = 2 and result = this.getTypeName()
+ or
+ i = 2 and result = this.getPattern().asRecordPattern()
}
}
@@ -742,11 +745,19 @@ private class PpSwitchStmt extends PpAst, SwitchStmt {
}
}
+private predicate isNonNullDefaultCase(SwitchCase sc) {
+ sc instanceof DefaultCase and not sc instanceof NullDefaultCase
+}
+
private class PpSwitchCase extends PpAst, SwitchCase {
+ PpSwitchCase() { not this instanceof PatternCase }
+
override string getPart(int i) {
- i = 0 and result = "default" and this instanceof DefaultCase
+ i = 0 and result = "default" and isNonNullDefaultCase(this)
or
- i = 0 and result = "case " and this instanceof ConstCase
+ i = 0 and result = "case null, default" and this instanceof NullDefaultCase
+ or
+ i = 0 and result = "case " and not this instanceof DefaultCase
or
exists(int j | i = 2 * j and j != 0 and result = ", " and exists(this.(ConstCase).getValue(j)))
or
@@ -758,7 +769,7 @@ private class PpSwitchCase extends PpAst, SwitchCase {
}
private int lastConstCaseValueIndex() {
- result = 1 + 2 * max(int j | j = 0 or exists(this.(ConstCase).getValue(j)))
+ result = 1 + 2 * (max(int j | j = 0 or exists(this.(ConstCase).getValue(j))))
}
override PpAst getChild(int i) {
@@ -770,6 +781,32 @@ private class PpSwitchCase extends PpAst, SwitchCase {
}
}
+private class PpPatternCase extends PpAst, PatternCase {
+ override string getPart(int i) {
+ i = 0 and result = "case "
+ or
+ i = 2 and this.getPattern() instanceof LocalVariableDeclExpr and result = " "
+ or
+ i = 3 and result = this.getPattern().asBindingPattern().getName()
+ or
+ i = 4 and result = ":" and not this.isRule()
+ or
+ i = 4 and result = " -> " and this.isRule()
+ or
+ i = 6 and result = ";" and exists(this.getRuleExpression())
+ }
+
+ override PpAst getChild(int i) {
+ i = 1 and result = this.getPattern().asBindingPattern().getTypeAccess()
+ or
+ i = 1 and result = this.getPattern().asRecordPattern()
+ or
+ i = 5 and result = this.getRuleExpression()
+ or
+ i = 5 and result = this.getRuleStatement()
+ }
+}
+
private class PpSynchronizedStmt extends PpAst, SynchronizedStmt {
override string getPart(int i) {
i = 0 and result = "synchronized ("
@@ -885,6 +922,8 @@ private class PpAssertStmt extends PpAst, AssertStmt {
private class PpLocalVariableDeclStmt extends PpAst, LocalVariableDeclStmt {
override string getPart(int i) {
+ i = 0 and not exists(this.getAVariable().getTypeAccess()) and result = "var"
+ or
i = 1 and result = " "
or
exists(int v | v > 1 and i = 2 * v - 1 and result = ", " and v = this.getAVariableIndex())
@@ -1025,3 +1064,37 @@ private class PpCallable extends PpAst, Callable {
i = 5 + 4 * this.getNumberOfParameters() and result = this.getBody()
}
}
+
+private class PpRecordPattern extends PpAst, RecordPatternExpr {
+ override string getPart(int i) {
+ i = 0 and result = this.getType().getName()
+ or
+ i = 1 and result = "("
+ or
+ i = 1 + ((any(int x | x >= 1 and exists(this.getSubPattern(x)))) * 4) and result = ", "
+ or
+ i = 1 + (count(this.getSubPattern(_)) * 4) and result = ")"
+ or
+ exists(int x, LocalVariableDeclExpr v, int offset | v = this.getSubPattern(x) |
+ i = (offset) + (x * 4) and
+ (
+ offset = 2 and not exists(v.getTypeAccess()) and result = "var"
+ or
+ offset = 3 and result = " "
+ )
+ )
+ }
+
+ override PpAst getChild(int i) {
+ exists(int x, PatternExpr subPattern, int offset | subPattern = this.getSubPattern(x) |
+ i = (offset) + (x * 4) and
+ (
+ result = subPattern.(RecordPatternExpr) and offset = 2
+ or
+ result = subPattern.(LocalVariableDeclExpr).getTypeAccess() and offset = 2
+ or
+ result = subPattern.(LocalVariableDeclExpr) and offset = 4
+ )
+ )
+ }
+}
diff --git a/java/ql/lib/semmle/code/java/PrintAst.qll b/java/ql/lib/semmle/code/java/PrintAst.qll
index 44e5f9fa22e..4315e66ec06 100644
--- a/java/ql/lib/semmle/code/java/PrintAst.qll
+++ b/java/ql/lib/semmle/code/java/PrintAst.qll
@@ -423,7 +423,8 @@ private class SingleLocalVarDeclParent extends ExprOrStmt {
SingleLocalVarDeclParent() {
this instanceof EnhancedForStmt or
this instanceof CatchClause or
- this.(InstanceOfExpr).isPattern()
+ this.(InstanceOfExpr).isPattern() or
+ this instanceof PatternCase
}
/** Gets the variable declaration that this element contains */
diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll
index fe0ba23093a..05d105e4de3 100644
--- a/java/ql/lib/semmle/code/java/Statement.qll
+++ b/java/ql/lib/semmle/code/java/Statement.qll
@@ -382,21 +382,43 @@ class SwitchStmt extends Stmt, @switchstmt {
*/
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
+ /**
+ * Gets the `i`th case of this `switch` statement,
+ * which may be either a normal `case` or a `default`.
+ */
+ SwitchCase getCase(int i) {
+ result =
+ rank[i + 1](SwitchCase case, int idx | case.isNthChildOf(this, idx) | case order by idx)
+ }
+
/**
* Gets a case of this `switch` statement,
* which may be either a normal `case` or a `default`.
*/
- SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
+ SwitchCase getACase() { result.getParent() = this }
- /** Gets a (non-default) `case` of this `switch` statement. */
- ConstCase getAConstCase() { result.getParent() = this }
+ /** Gets a (non-default) constant `case` of this `switch` statement. */
+ ConstCase getAConstCase() { result = this.getACase() }
- /** Gets the `default` case of this switch statement, if any. */
- DefaultCase getDefaultCase() { result.getParent() = this }
+ /** Gets a (non-default) pattern `case` of this `switch` statement. */
+ PatternCase getAPatternCase() { result = this.getACase() }
+
+ /**
+ * Gets the `default` case of this switch statement, if any.
+ *
+ * Note this may be `default` or `case null, default`.
+ */
+ DefaultCase getDefaultCase() { result = this.getACase() }
/** Gets the expression of this `switch` statement. */
Expr getExpr() { result.getParent() = this }
+ /** Holds if this switch has a case handling a null literal. */
+ predicate hasNullCase() {
+ this.getAConstCase().getValue(_) instanceof NullLiteral or
+ this.getACase() instanceof NullDefaultCase
+ }
+
override string pp() { result = "switch (...)" }
override string toString() { result = "switch (...)" }
@@ -406,6 +428,13 @@ class SwitchStmt extends Stmt, @switchstmt {
override string getAPrimaryQlClass() { result = "SwitchStmt" }
}
+/**
+ * A `switch` statement or expression.
+ */
+class SwitchBlock extends StmtParent {
+ SwitchBlock() { this instanceof SwitchStmt or this instanceof SwitchExpr }
+}
+
/**
* A case of a `switch` statement or expression.
*
@@ -428,6 +457,21 @@ class SwitchCase extends Stmt, @case {
result = this.getSwitch().getExpr() or result = this.getSwitchExpr().getExpr()
}
+ /**
+ * Gets this case's ordinal in its switch block.
+ */
+ int getCaseIndex() {
+ this = any(SwitchStmt ss).getCase(result) or this = any(SwitchExpr se).getCase(result)
+ }
+
+ /**
+ * Holds if this is the `n`th case of switch block `parent`.
+ */
+ pragma[nomagic]
+ predicate isNthCaseOf(SwitchBlock parent, int n) {
+ this.getCaseIndex() = n and this.getParent() = parent
+ }
+
/**
* Holds if this `case` is a switch labeled rule of the form `... -> ...`.
*/
@@ -462,17 +506,27 @@ class SwitchCase extends Stmt, @case {
}
}
-/** A constant `case` of a switch statement. */
+/**
+ * A constant `case` of a switch statement.
+ *
+ * Note this excludes `case null, default` even though that includes a null constant. It
+ * does however include plain `case null`.
+ */
class ConstCase extends SwitchCase {
- ConstCase() { exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
+ ConstCase() {
+ exists(Expr e | e.getParent() = this and e.getIndex() >= 0 and not e instanceof PatternExpr) and
+ // For backward compatibility, we don't include `case null, default:` here, on the assumption
+ // this will come as a surprise to CodeQL that predates that statement's validity.
+ not isNullDefaultCase(this)
+ }
/** Gets the `case` constant at index 0. */
- Expr getValue() { result.getParent() = this and result.getIndex() = 0 }
+ Expr getValue() { result.isNthChildOf(this, 0) }
/**
- * Gets the `case` constant at the specified index.
+ * Gets the `case` constant at index `i`.
*/
- Expr getValue(int i) { result.getParent() = this and result.getIndex() = i and i >= 0 }
+ Expr getValue(int i) { result.isNthChildOf(this, i) and i >= 0 }
override string pp() { result = "case ..." }
@@ -483,9 +537,36 @@ class ConstCase extends SwitchCase {
override string getAPrimaryQlClass() { result = "ConstCase" }
}
-/** A `default` case of a `switch` statement */
+/** A pattern case of a `switch` statement */
+class PatternCase extends SwitchCase {
+ PatternExpr pattern;
+
+ PatternCase() { pattern.isNthChildOf(this, 0) }
+
+ /** Gets this case's pattern. */
+ PatternExpr getPattern() { result = pattern }
+
+ /** Gets the guard applicable to this pattern case, if any. */
+ Expr getGuard() { result.isNthChildOf(this, -3) }
+
+ override string pp() { result = "case " }
+
+ override string toString() { result = "case " }
+
+ override string getHalsteadID() { result = "PatternCase" }
+
+ override string getAPrimaryQlClass() { result = "PatternCase" }
+}
+
+/**
+ * A `default` or `case null, default` case of a `switch` statement or expression.
+ */
class DefaultCase extends SwitchCase {
- DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
+ DefaultCase() {
+ isNullDefaultCase(this)
+ or
+ not exists(Expr e | e.getParent() = this | e.getIndex() >= 0)
+ }
override string pp() { result = "default" }
@@ -496,6 +577,19 @@ class DefaultCase extends SwitchCase {
override string getAPrimaryQlClass() { result = "DefaultCase" }
}
+/** A `case null, default` statement of a `switch` statement or expression. */
+class NullDefaultCase extends DefaultCase {
+ NullDefaultCase() { isNullDefaultCase(this) }
+
+ override string pp() { result = "case null, default" }
+
+ override string toString() { result = "case null, default" }
+
+ override string getHalsteadID() { result = "NullDefaultCase" }
+
+ override string getAPrimaryQlClass() { result = "NullDefaultCase" }
+}
+
/** A `synchronized` statement. */
class SynchronizedStmt extends Stmt, @synchronizedstmt {
/** Gets the expression on which this `synchronized` statement synchronizes. */
diff --git a/java/ql/lib/semmle/code/java/Type.qll b/java/ql/lib/semmle/code/java/Type.qll
index afe78d522f2..5976bc12f00 100644
--- a/java/ql/lib/semmle/code/java/Type.qll
+++ b/java/ql/lib/semmle/code/java/Type.qll
@@ -746,6 +746,13 @@ class DataClass extends Class {
*/
class Record extends Class {
Record() { isRecord(this) }
+
+ /**
+ * Gets the canonical constructor of this record.
+ */
+ Constructor getCanonicalConstructor() {
+ result = this.getAConstructor() and isCanonicalConstr(result)
+ }
}
/** An intersection type. */
diff --git a/java/ql/lib/semmle/code/java/Variable.qll b/java/ql/lib/semmle/code/java/Variable.qll
index 82314824395..8ed650d5f16 100644
--- a/java/ql/lib/semmle/code/java/Variable.qll
+++ b/java/ql/lib/semmle/code/java/Variable.qll
@@ -15,9 +15,12 @@ class Variable extends @variable, Annotatable, Element, Modifiable {
/** Gets an access to this variable. */
VarAccess getAnAccess() { variableBinding(result, this) }
- /** Gets an expression on the right-hand side of an assignment to this variable. */
+ /**
+ * Gets an expression assigned to this variable, either appearing on the right-hand side of an
+ * assignment or bound to it via a binding `instanceof` expression or `switch` block.
+ */
Expr getAnAssignedValue() {
- exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit())
+ exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInitOrPatternSource())
or
exists(AssignExpr e | e.getDest() = this.getAnAccess() and result = e.getSource())
}
diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll
index 25809f4c16a..a97cf1f8f57 100644
--- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll
+++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll
@@ -7,6 +7,7 @@ import java
private import semmle.code.java.controlflow.Dominance
private import semmle.code.java.controlflow.internal.GuardsLogic
private import semmle.code.java.controlflow.internal.Preconditions
+private import semmle.code.java.controlflow.internal.SwitchCases
/**
* A basic block that terminates in a condition, splitting the subsequent control flow.
@@ -18,7 +19,7 @@ class ConditionBlock extends BasicBlock {
ConditionNode getConditionNode() { result = this.getLastNode() }
/** Gets the condition of the last node of this basic block. */
- Expr getCondition() { result = this.getConditionNode().getCondition() }
+ ExprParent getCondition() { result = this.getConditionNode().getCondition() }
/** Gets a `true`- or `false`-successor of the last node of this basic block. */
BasicBlock getTestSuccessor(boolean testIsTrue) {
@@ -72,6 +73,54 @@ class ConditionBlock extends BasicBlock {
}
}
+// Join order engineering -- first determine the switch block and the case indices required, then retrieve them.
+bindingset[switch, i]
+pragma[inline_late]
+private predicate isNthCaseOf(SwitchBlock switch, SwitchCase c, int i) { c.isNthCaseOf(switch, i) }
+
+/**
+ * Gets a switch case >= pred, up to but not including `pred`'s successor pattern case,
+ * where `pred` is declared on `switch`.
+ */
+private SwitchCase getACaseUpToNextPattern(PatternCase pred, SwitchBlock switch) {
+ // Note we do include `case null, default` (as well as plain old `default`) here.
+ not result.(ConstCase).getValue(_) instanceof NullLiteral and
+ exists(int maxCaseIndex |
+ switch = pred.getParent() and
+ if exists(getNextPatternCase(pred))
+ then maxCaseIndex = getNextPatternCase(pred).getCaseIndex() - 1
+ else maxCaseIndex = lastCaseIndex(switch)
+ |
+ isNthCaseOf(switch, result, [pred.getCaseIndex() .. maxCaseIndex])
+ )
+}
+
+/**
+ * Gets the closest pattern case preceding `case`, including `case` itself, if any.
+ */
+private PatternCase getClosestPrecedingPatternCase(SwitchCase case) {
+ case = getACaseUpToNextPattern(result, _)
+}
+
+/**
+ * Holds if `pred` is a control-flow predecessor of switch case `sc` that is not a
+ * fall-through from a previous case.
+ *
+ * For classic switches that means flow from the selector expression; for switches
+ * involving pattern cases it can also mean flow from a previous pattern case's type
+ * test or guard failing and proceeding to then consider subsequent cases.
+ */
+private predicate isNonFallThroughPredecessor(SwitchCase sc, ControlFlowNode pred) {
+ pred = sc.getControlFlowNode().getAPredecessor() and
+ (
+ pred.(Expr).getParent*() = sc.getSelectorExpr()
+ or
+ pred.(Expr).getParent*() = getClosestPrecedingPatternCase(sc).getGuard()
+ or
+ pred = getClosestPrecedingPatternCase(sc)
+ )
+}
+
/**
* A condition that can be evaluated to either true or false. This can either
* be an `Expr` of boolean type that isn't a boolean literal, or a case of a
@@ -103,13 +152,21 @@ class Guard extends ExprParent {
}
/**
- * Gets the basic block containing this guard or the basic block containing
- * the switch expression if the guard is a switch case.
+ * Gets the basic block containing this guard or the basic block that tests the
+ * applicability of this switch case -- for a pattern case this is the case statement
+ * itself; for a non-pattern case this is the most recent pattern case or the top of
+ * the switch block if there is none.
*/
BasicBlock getBasicBlock() {
- result = this.(Expr).getBasicBlock() or
- result = this.(SwitchCase).getSwitch().getExpr().getBasicBlock() or
- result = this.(SwitchCase).getSwitchExpr().getExpr().getBasicBlock()
+ // Not a switch case
+ result = this.(Expr).getBasicBlock()
+ or
+ // Return the closest pattern case statement before this one, including this one.
+ result = getClosestPrecedingPatternCase(this).getBasicBlock()
+ or
+ // Not a pattern case and no preceding pattern case -- return the top of the switch block.
+ not exists(getClosestPrecedingPatternCase(this)) and
+ result = this.(SwitchCase).getSelectorExpr().getBasicBlock()
}
/**
@@ -126,6 +183,39 @@ class Guard extends ExprParent {
)
}
+ /**
+ * Holds if this guard tests whether `testedExpr` has type `testedType`.
+ *
+ * `restricted` is true if the test applies additional restrictions on top of just `testedType`, and so
+ * this guard failing does not guarantee `testedExpr` is *not* a `testedType`-- for example,
+ * matching `record R(Object o)` with `case R(String s)` is a guard with an additional restriction on the
+ * type of field `o`, so the guard passing guarantees `testedExpr` is an `R`, but it failing does not
+ * guarantee `testedExpr` is not an `R`.
+ */
+ predicate appliesTypeTest(Expr testedExpr, Type testedType, boolean restricted) {
+ (
+ exists(InstanceOfExpr ioe | this = ioe |
+ testedExpr = ioe.getExpr() and
+ testedType = ioe.getSyntacticCheckedType()
+ )
+ or
+ exists(PatternCase pc | this = pc |
+ pc.getSelectorExpr() = testedExpr and
+ testedType = pc.getPattern().getType()
+ )
+ ) and
+ (
+ if
+ exists(RecordPatternExpr rpe |
+ rpe = [this.(InstanceOfExpr).getPattern(), this.(PatternCase).getPattern()]
+ |
+ not rpe.isUnrestricted()
+ )
+ then restricted = true
+ else restricted = false
+ )
+ }
+
/**
* Holds if the evaluation of this guard to `branch` corresponds to the edge
* from `bb1` to `bb2`.
@@ -139,10 +229,11 @@ class Guard extends ExprParent {
or
exists(SwitchCase sc, ControlFlowNode pred |
sc = this and
+ // Pattern cases are handled as ConditionBlocks above.
+ not sc instanceof PatternCase and
branch = true and
bb2.getFirstNode() = sc.getControlFlowNode() and
- pred = sc.getControlFlowNode().getAPredecessor() and
- pred.(Expr).getParent*() = sc.getSelectorExpr() and
+ isNonFallThroughPredecessor(sc, pred) and
bb1 = pred.getBasicBlock()
)
or
@@ -176,12 +267,14 @@ class Guard extends ExprParent {
}
private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {
- exists(BasicBlock caseblock, Expr selector |
- selector = sc.getSelectorExpr() and
+ exists(BasicBlock caseblock |
+ // Pattern cases are handled as condition blocks
+ not sc instanceof PatternCase and
caseblock.getFirstNode() = sc.getControlFlowNode() and
caseblock.bbDominates(bb) and
+ // Check we can't fall through from a previous block:
forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() |
- pred.(Expr).getParent*() = selector
+ isNonFallThroughPredecessor(sc, pred)
)
)
}
diff --git a/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll b/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll
index 89a9f08850d..3145371561a 100644
--- a/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll
+++ b/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll
@@ -170,6 +170,7 @@ class ConstSwitchStmt extends SwitchStmt {
/** Gets the matching case, if it can be deduced. */
SwitchCase getMatchingCase() {
// Must be a value we can deduce
+ // TODO: handle other known constants (enum constants, String constants)
exists(this.getExpr().(ConstantExpr).getIntValue()) and
if exists(this.getMatchingConstCase())
then result = this.getMatchingConstCase()
diff --git a/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll
new file mode 100644
index 00000000000..1d94f075abb
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/controlflow/internal/SwitchCases.qll
@@ -0,0 +1,29 @@
+/** Provides utility predicates relating to switch cases. */
+
+import java
+
+/**
+ * Gets the `i`th `PatternCase` defined on `switch`, if one exists.
+ */
+private PatternCase getPatternCase(SwitchBlock switch, int i) {
+ result =
+ rank[i + 1](PatternCase pc, int caseIdx | pc.isNthCaseOf(switch, caseIdx) | pc order by caseIdx)
+}
+
+/**
+ * Gets the first `PatternCase` defined on `switch`, if one exists.
+ */
+PatternCase getFirstPatternCase(SwitchBlock switch) { result = getPatternCase(switch, 0) }
+
+/**
+ * Gets the PatternCase after pc, if one exists.
+ */
+PatternCase getNextPatternCase(PatternCase pc) {
+ exists(int idx, SwitchBlock switch |
+ pc = getPatternCase(switch, idx) and result = getPatternCase(switch, idx + 1)
+ )
+}
+
+int lastCaseIndex(SwitchBlock switch) {
+ result = max(int i | any(SwitchCase c).isNthCaseOf(switch, i))
+}
diff --git a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll
index 4a759670506..fb2fc668cf3 100644
--- a/java/ql/lib/semmle/code/java/dataflow/Nullness.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/Nullness.qll
@@ -100,9 +100,9 @@ predicate dereference(Expr e) {
or
exists(SynchronizedStmt synch | synch.getExpr() = e)
or
- exists(SwitchStmt switch | switch.getExpr() = e)
+ exists(SwitchStmt switch | switch.getExpr() = e and not switch.hasNullCase())
or
- exists(SwitchExpr switch | switch.getExpr() = e)
+ exists(SwitchExpr switch | switch.getExpr() = e and not switch.hasNullCase())
or
exists(FieldAccess fa, Field f | fa.getQualifier() = e and fa.getField() = f and not f.isStatic())
or
@@ -460,7 +460,7 @@ private predicate interestingCond(SsaSourceVariable npecand, ConditionBlock cond
varMaybeNullInBlock(_, npecand, cond, _) or
varConditionallyNull(npecand.getAnSsaVariable(), cond, _)
) and
- not cond.getCondition().getAChildExpr*() = npecand.getAnAccess()
+ not cond.getCondition().(Expr).getAChildExpr*() = npecand.getAnAccess()
}
/** A pair of correlated conditions for a given NPE candidate. */
@@ -588,7 +588,7 @@ private predicate trackingVar(
exists(ConditionBlock cond |
interestingCond(npecand, cond) and
varMaybeNullInBlock(_, npecand, cond, _) and
- cond.getCondition().getAChildExpr*() = trackvar.getAnAccess() and
+ cond.getCondition().(Expr).getAChildExpr*() = trackvar.getAnAccess() and
trackssa.getSourceVariable() = trackvar and
trackssa.getDefiningExpr().(VariableAssign).getSource() = init
|
diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll
index c4b95645bc8..ea0df55d60f 100644
--- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll
@@ -414,29 +414,27 @@ private predicate downcastSuccessor(VarAccess va, RefType t) {
}
/**
- * Holds if `va` is an access to a value that is guarded by `instanceof t`.
+ * Holds if `va` is an access to a value that is guarded by `instanceof t` or `case e t`.
*/
-private predicate instanceOfGuarded(VarAccess va, RefType t) {
- exists(InstanceOfExpr ioe, BaseSsaVariable v |
- ioe.getExpr() = v.getAUse() and
- t = ioe.getCheckedType() and
+private predicate typeTestGuarded(VarAccess va, RefType t) {
+ exists(Guard typeTest, BaseSsaVariable v |
+ typeTest.appliesTypeTest(v.getAUse(), t, _) and
va = v.getAUse() and
- guardControls_v1(ioe, va.getBasicBlock(), true)
+ guardControls_v1(typeTest, va.getBasicBlock(), true)
)
}
/**
- * Holds if `aa` is an access to a value that is guarded by `instanceof t`.
+ * Holds if `aa` is an access to a value that is guarded by `instanceof t` or `case e t`.
*/
-predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) {
- exists(InstanceOfExpr ioe, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
- ioe.getExpr() = aa1 and
- t = ioe.getCheckedType() and
+predicate arrayTypeTestGuarded(ArrayAccess aa, RefType t) {
+ exists(Guard typeTest, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
+ typeTest.appliesTypeTest(aa1, t, _) and
aa1.getArray() = v1.getAUse() and
aa1.getIndexExpr() = v2.getAUse() and
aa.getArray() = v1.getAUse() and
aa.getIndexExpr() = v2.getAUse() and
- guardControls_v1(ioe, aa.getBasicBlock(), true)
+ guardControls_v1(typeTest, aa.getBasicBlock(), true)
)
}
@@ -462,8 +460,8 @@ private predicate typeFlowBaseCand(TypeFlowNode n, RefType t) {
upcast(n, srctype) or
upcastEnhancedForStmt(n.asSsa(), srctype) or
downcastSuccessor(n.asExpr(), srctype) or
- instanceOfGuarded(n.asExpr(), srctype) or
- arrayInstanceOfGuarded(n.asExpr(), srctype) or
+ typeTestGuarded(n.asExpr(), srctype) or
+ arrayTypeTestGuarded(n.asExpr(), srctype) or
n.asExpr().(FunctionalExpr).getConstructedType() = srctype or
superAccess(n.asExpr(), srctype)
|
@@ -629,7 +627,7 @@ private predicate instanceofDisjunctionGuarded(TypeFlowNode n, RefType t) {
bb.bbDominates(va.getBasicBlock()) and
va = v.getAUse() and
instanceofDisjunct(ioe, bb, v) and
- t = ioe.getCheckedType() and
+ t = ioe.getSyntacticCheckedType() and
n.asExpr() = va
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
index 79e507dd598..f5466b2d739 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
@@ -232,6 +232,27 @@ predicate storeStep(Node node1, ContentSet f, Node node2) {
pragma[only_bind_out](node2.getEnclosingCallable())
}
+// Manual join hacking, to avoid a parameters x fields product.
+pragma[noinline]
+private predicate hasNamedField(Record r, Field f, string name) {
+ f = r.getAField() and name = f.getName()
+}
+
+pragma[noinline]
+private predicate hasNamedCanonicalParameter(Record r, Parameter p, int idx, string name) {
+ p = r.getCanonicalConstructor().getParameter(idx) and name = p.getName()
+}
+
+private Field getLexicallyOrderedRecordField(Record r, int idx) {
+ result =
+ rank[idx + 1](Field f, int i, Parameter p, string name |
+ hasNamedCanonicalParameter(r, p, i, name) and
+ hasNamedField(r, f, name)
+ |
+ f order by i
+ )
+}
+
/**
* Holds if data can flow from `node1` to `node2` via a read of `f`.
* Thus, `node1` references an object with a field `f` whose value ends up in
@@ -256,6 +277,13 @@ predicate readStep(Node node1, ContentSet f, Node node2) {
node2.asExpr() = get
)
or
+ exists(RecordPatternExpr rpe, PatternExpr subPattern, int i |
+ node1.asExpr() = rpe and
+ subPattern = rpe.getSubPattern(i) and
+ node2.asExpr() = subPattern and
+ f.(FieldContent).getField() = getLexicallyOrderedRecordField(rpe.getType(), i)
+ )
+ or
f instanceof ArrayContent and arrayReadStep(node1, node2, _)
or
f instanceof CollectionContent and collectionReadStep(node1, node2)
@@ -347,7 +375,18 @@ predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { compatibleTypes0(t
/** A node that performs a type cast. */
class CastNode extends ExprNode {
- CastNode() { this.getExpr() instanceof CastingExpr }
+ CastNode() {
+ this.getExpr() instanceof CastingExpr
+ or
+ exists(SsaExplicitUpdate upd |
+ upd.getDefiningExpr().(VariableAssign).getSource() =
+ [
+ any(SwitchStmt ss).getExpr(), any(SwitchExpr se).getExpr(),
+ any(InstanceOfExpr ioe).getExpr()
+ ] and
+ this.asExpr() = upd.getAFirstUse()
+ )
+ }
}
private newtype TDataFlowCallable =
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll
index af86063cadd..723b7784b1e 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll
@@ -190,6 +190,19 @@ predicate simpleAstFlowStep(Expr e1, Expr e2) {
e2 = any(NotNullExpr nne | e1 = nne.getExpr())
or
e2.(WhenExpr).getBranch(_).getAResult() = e1
+ or
+ // In the following three cases only record patterns need this flow edge, leading from the bound instanceof
+ // or switch tested expression to a record pattern that will read its fields. Simple binding patterns are
+ // handled via VariableAssign.getSource instead.
+ exists(SwitchExpr se |
+ e1 = se.getExpr() and e2 = se.getACase().(PatternCase).getPattern().asRecordPattern()
+ )
+ or
+ exists(SwitchStmt ss |
+ e1 = ss.getExpr() and e2 = ss.getACase().(PatternCase).getPattern().asRecordPattern()
+ )
+ or
+ exists(InstanceOfExpr ioe | e1 = ioe.getExpr() and e2 = ioe.getPattern().asRecordPattern())
}
private predicate simpleLocalFlowStep0(Node node1, Node node2) {
@@ -197,7 +210,8 @@ private predicate simpleLocalFlowStep0(Node node1, Node node2) {
// Variable flow steps through adjacent def-use and use-use pairs.
exists(SsaExplicitUpdate upd |
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or
- upd.getDefiningExpr().(AssignOp) = node1.asExpr()
+ upd.getDefiningExpr().(AssignOp) = node1.asExpr() or
+ upd.getDefiningExpr().(RecordBindingVariableExpr) = node1.asExpr()
|
node2.asExpr() = upd.getAFirstUse() and
not capturedVariableRead(node2)
diff --git a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll
index 3fe6f9ad303..2510149141f 100644
--- a/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll
+++ b/java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll
@@ -194,13 +194,12 @@ private module Dispatch {
*/
private predicate impossibleDispatchTarget(MethodCall source, Method tgt) {
tgt = viableImpl_v1_cand(source) and
- exists(InstanceOfExpr ioe, BaseSsaVariable v, Expr q, RefType t |
+ exists(Guard typeTest, BaseSsaVariable v, Expr q, RefType t |
source.getQualifier() = q and
v.getAUse() = q and
- guardControls_v1(ioe, q.getBasicBlock(), false) and
- ioe.getExpr() = v.getAUse() and
- ioe.getCheckedType().getErasure() = t and
- tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t
+ typeTest.appliesTypeTest(v.getAUse(), t, false) and
+ guardControls_v1(typeTest, q.getBasicBlock(), false) and
+ tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t.getErasure()
)
}
diff --git a/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll b/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll
index b6df9769ab3..a888050185e 100644
--- a/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll
+++ b/java/ql/lib/semmle/code/java/metrics/MetricCallable.qll
@@ -73,13 +73,13 @@ class MetricCallable extends Callable {
// so there should be a branching point for each non-default switch
// case (ignoring those that just fall through to the next case).
private predicate branchingSwitchCase(ConstCase sc) {
- not sc.(ControlFlowNode).getASuccessor() instanceof ConstCase and
- not sc.(ControlFlowNode).getASuccessor() instanceof DefaultCase and
+ not sc.(ControlFlowNode).getASuccessor() instanceof SwitchCase and
not defaultFallThrough(sc)
}
private predicate defaultFallThrough(ConstCase sc) {
- exists(DefaultCase default | default.(ControlFlowNode).getASuccessor() = sc) or
+ exists(DefaultCase default | default.(ControlFlowNode).getASuccessor() = sc)
+ or
defaultFallThrough(sc.(ControlFlowNode).getAPredecessor())
}
@@ -90,6 +90,7 @@ private predicate branchingStmt(Stmt stmt) {
stmt instanceof DoStmt or
stmt instanceof ForStmt or
stmt instanceof EnhancedForStmt or
+ stmt instanceof PatternCase or
branchingSwitchCase(stmt) or
stmt instanceof CatchClause
}
diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql
new file mode 100644
index 00000000000..f5f10c8783a
--- /dev/null
+++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/exprs.ql
@@ -0,0 +1,47 @@
+class Expr extends @expr {
+ string toString() { result = "expr" }
+}
+
+class LocalVariableDeclExpr extends @localvariabledeclexpr, Expr { }
+
+class InstanceOfExpr extends @instanceofexpr, Expr { }
+
+class Type extends @type {
+ string toString() { result = "type" }
+}
+
+class ExprParent extends @exprparent {
+ string toString() { result = "exprparent" }
+}
+
+// Initialisers of local variable declarations that occur as the 0th child of an instanceof expression should be reparented to be the 0th child of the instanceof itself,
+// while the LocalVariableDeclExpr, now without an initialiser, should become its 2nd child.
+// This implements a reorganisation of the representation of "o instanceof String s", which used to be InstanceOfExpr -0-> LocalVariableDeclExpr --init-> o
+// \-name-> s
+// It is now InstanceOfExpr --0-> o
+// \-2-> LocalVariableDeclExpr -name-> s
+//
+// Other children are unaffected.
+ExprParent getParent(Expr e) { exprs(e, _, _, result, _) }
+
+predicate hasNewParent(Expr e, ExprParent newParent, int newIndex) {
+ if
+ getParent(e) instanceof LocalVariableDeclExpr and
+ getParent(getParent(e)) instanceof InstanceOfExpr
+ then (
+ // Initialiser moves to hang directly off the instanceof expression
+ newParent = getParent(getParent(e)) and newIndex = 0
+ ) else (
+ if e instanceof LocalVariableDeclExpr and getParent(e) instanceof InstanceOfExpr
+ then
+ // Variable declaration moves to be the instanceof expression's 2nd child
+ newParent = getParent(e) and newIndex = 2
+ else exprs(e, _, _, newParent, newIndex) // Other expressions unchanged
+ )
+}
+
+from Expr e, int kind, Type typeid, ExprParent parent, int index
+where
+ exprs(e, kind, typeid, _, _) and
+ hasNewParent(e, parent, index)
+select e, kind, typeid, parent, index
diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/old.dbscheme b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/old.dbscheme
new file mode 100644
index 00000000000..ecfcf050952
--- /dev/null
+++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/old.dbscheme
@@ -0,0 +1,1256 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+compilation_info(
+ int id : @compilation ref,
+ string info_key: string ref,
+ string info_value: string ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The expanded arguments that were passed to the extractor for a
+ * compiler invocation. This is similar to `compilation_args`, but
+ * for a `@@@someFile` argument, it includes the arguments from that
+ * file, rather than just taking the argument literally.
+ */
+#keyset[id, num]
+compilation_expanded_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes_or_interfaces(
+ unique int id: @classorinterface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @classorinterface ref
+);
+
+file_class(
+ int id: @classorinterface ref
+);
+
+class_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @classorinterface ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isInterface(
+ unique int id: @classorinterface ref
+);
+
+isRecord(
+ unique int id: @classorinterface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @classorinterface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @classorinterface ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @classorinterface ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @classorinterface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @classorinterface | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar | @typevariable;
+
+@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+ // 3: ENUM_ENTRIES
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @classorinterface ref
+)
diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/semmlecode.dbscheme b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/semmlecode.dbscheme
new file mode 100644
index 00000000000..dee651b58d1
--- /dev/null
+++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/semmlecode.dbscheme
@@ -0,0 +1,1265 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+compilation_info(
+ int id : @compilation ref,
+ string info_key: string ref,
+ string info_value: string ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The expanded arguments that were passed to the extractor for a
+ * compiler invocation. This is similar to `compilation_args`, but
+ * for a `@@@someFile` argument, it includes the arguments from that
+ * file, rather than just taking the argument literally.
+ */
+#keyset[id, num]
+compilation_expanded_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes_or_interfaces(
+ unique int id: @classorinterface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @classorinterface ref
+);
+
+file_class(
+ int id: @classorinterface ref
+);
+
+class_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @classorinterface ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isInterface(
+ unique int id: @classorinterface ref
+);
+
+isRecord(
+ unique int id: @classorinterface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @classorinterface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @classorinterface ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @classorinterface ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+isCanonicalConstr(
+ int constructorid: @constructor ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @classorinterface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+| 89 = @recordpatternexpr
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @classorinterface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+isNullDefaultCase(
+ int id: @case ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @classorinterface | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @classorinterface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar | @typevariable;
+
+@member_modifiable = @classorinterface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+ // 3: ENUM_ENTRIES
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @classorinterface ref
+)
diff --git a/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/upgrade.properties b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/upgrade.properties
new file mode 100644
index 00000000000..baa3016cf03
--- /dev/null
+++ b/java/ql/lib/upgrades/ecfcf050952e54b1155fc89525db84af6ad34aaf/upgrade.properties
@@ -0,0 +1,3 @@
+description: Add tables for canonical constructors and `case null, default`, plus an expression kind for record patterns. Also alter the representation for instanceof with a binding pattern.
+compatibility: backwards
+exprs.rel: run exprs.qlo
diff --git a/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql
index ef9957685d7..55cfe5d6b00 100644
--- a/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql
+++ b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql
@@ -17,7 +17,7 @@ from InstanceOfExpr ioe, RefType t, RefType ct
where
ioe.getExpr() instanceof ThisAccess and
t = ioe.getExpr().getType() and
- ct = ioe.getCheckedType() and
+ ct = ioe.getSyntacticCheckedType() and
ct.getAnAncestor() = t
select ioe,
"Testing whether 'this' is an instance of $@ in $@ introduces a dependency cycle between the two types.",
diff --git a/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql
index a3fb91e99b6..5b90341660b 100644
--- a/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql
+++ b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql
@@ -40,6 +40,6 @@ where
) and
// Also, any value that `v` is initialized to is a fresh container,
forall(Expr e | e = v.getAnAssignedValue() | e instanceof FreshContainer) and
- // and `v` is not implicitly initialized by a for-each loop.
- not exists(EnhancedForStmt efs | efs.getVariable().getVariable() = v)
+ // and `v` is not implicitly initialized.
+ not v.(LocalVariableDecl).getDeclExpr().hasImplicitInit()
select v, "The contents of this container are never initialized."
diff --git a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql
index 8002c8d6841..8c8cb6105b3 100644
--- a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql
+++ b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql
@@ -39,6 +39,6 @@ where
) and
// Also, any value that `v` is initialized to is a new container,
forall(Expr e | e = v.getAnAssignedValue() | e instanceof ClassInstanceExpr) and
- // and `v` is not implicitly initialized by a for-each loop.
- not exists(EnhancedForStmt efs | efs.getVariable().getVariable() = v)
+ // and `v` is not implicitly initialized
+ not v.(LocalVariableDecl).getDeclExpr().hasImplicitInit()
select v, "The contents of this container are never accessed."
diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql
index b1b23038262..417c299fcf2 100644
--- a/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql
+++ b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql
@@ -18,7 +18,7 @@ predicate instanceofInEquals(EqualsMethod m, InstanceOfExpr e) {
e.getEnclosingCallable() = m and
e.getExpr().(VarAccess).getVariable() = m.getParameter() and
exists(RefType instanceofType |
- instanceofType = e.getCheckedType() and
+ instanceofType = e.getSyntacticCheckedType() and
not instanceofType.isFinal()
)
}
diff --git a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql
index 33c16eb598c..1749dc2ffa4 100644
--- a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql
+++ b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql
@@ -63,7 +63,7 @@ from LocalBoxedVar v
where
forall(Expr e | e = v.getAnAssignedValue() | e.getType() = v.getPrimitiveType()) and
(
- not v.getDeclExpr().getParent() instanceof EnhancedForStmt or
+ not v.getDeclExpr().hasImplicitInit() or
v.getDeclExpr().getParent().(EnhancedForStmt).getExpr().getType().(Array).getComponentType() =
v.getPrimitiveType()
) and
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql
index dbed42872c9..b96ce5069ac 100644
--- a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql
@@ -11,22 +11,13 @@
import java
import DeadLocals
-predicate exceptionVariable(LocalVariableDeclExpr ve) {
- exists(CatchClause catch | catch.getVariable() = ve)
-}
-
-predicate enhancedForVariable(LocalVariableDeclExpr ve) {
- exists(EnhancedForStmt for | for.getVariable() = ve)
-}
-
from LocalVariableDeclExpr ve, LocalVariableDecl v
where
v = ve.getVariable() and
not assigned(v) and
not read(v) and
(not exists(ve.getInit()) or exprHasNoEffect(ve.getInit())) and
- // Remove contexts where Java forces a variable declaration: enhanced-for and catch clauses.
+ // Remove contexts where Java forces a variable declaration: enhanced-for, catch clauses and pattern cases.
// Rules about catch clauses belong in an exception handling query
- not exceptionVariable(ve) and
- not enhancedForVariable(ve)
+ not ve.hasImplicitInit()
select v, "Variable " + v.getName() + " is not used."
diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql
index bfcd7bdaf71..312a77878fe 100644
--- a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql
+++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql
@@ -17,8 +17,7 @@ import Common
from SwitchStmt s, Stmt c
where
c = s.getACase() and
- not c.(ControlFlowNode).getASuccessor() instanceof ConstCase and
- not c.(ControlFlowNode).getASuccessor() instanceof DefaultCase and
+ not c.(ControlFlowNode).getASuccessor() instanceof SwitchCase and
not s.(Annotatable).suppressesWarningsAbout("fallthrough") and
mayDropThroughWithoutComment(s, c)
select c,
diff --git a/java/ql/test/library-tests/dependency/Depends.expected b/java/ql/test/library-tests/dependency/Depends.expected
index a3367e5fca1..4d5b4e5ada9 100644
--- a/java/ql/test/library-tests/dependency/Depends.expected
+++ b/java/ql/test/library-tests/dependency/Depends.expected
@@ -14,8 +14,17 @@
| dependency/A.java:15:8:15:8 | E | java.lang.Object |
| dependency/A.java:22:7:22:7 | F | java.lang.Object |
| dependency/A.java:24:7:24:7 | G | java.lang.Throwable |
+| dependency/A.java:26:7:26:7 | H | dependency.H$Used1 |
+| dependency/A.java:26:7:26:7 | H | dependency.H$Used2 |
+| dependency/A.java:26:7:26:7 | H | dependency.H$Used3 |
| dependency/A.java:26:7:26:7 | H | java.lang.Number |
| dependency/A.java:26:7:26:7 | H | java.lang.Object |
| dependency/A.java:26:7:26:7 | H | java.lang.String |
| dependency/A.java:26:7:26:7 | H | java.util.Collection |
| dependency/A.java:27:3:27:18 | T | java.lang.String |
+| dependency/A.java:41:22:41:26 | Used1 | dependency.H |
+| dependency/A.java:41:22:41:26 | Used1 | java.lang.Object |
+| dependency/A.java:42:22:42:26 | Used2 | dependency.H |
+| dependency/A.java:42:22:42:26 | Used2 | java.lang.Object |
+| dependency/A.java:43:22:43:26 | Used3 | dependency.H |
+| dependency/A.java:43:22:43:26 | Used3 | java.lang.Object |
diff --git a/java/ql/test/library-tests/dependency/PrintAst.expected b/java/ql/test/library-tests/dependency/PrintAst.expected
index 6b16d893e55..ab71b03fe55 100644
--- a/java/ql/test/library-tests/dependency/PrintAst.expected
+++ b/java/ql/test/library-tests/dependency/PrintAst.expected
@@ -49,3 +49,39 @@ dependency/A.java:
# 28| 0: [WildcardTypeAccess] ? ...
# 28| 0: [TypeAccess] Number
# 28| 5: [BlockStmt] { ... }
+# 29| 4: [Method] test3
+# 29| 3: [TypeAccess] void
+#-----| 4: (Parameters)
+# 29| 0: [Parameter] o
+# 29| 0: [TypeAccess] Object
+# 29| 5: [BlockStmt] { ... }
+# 30| 0: [IfStmt] if (...)
+# 30| 0: [InstanceOfExpr] ...instanceof...
+# 30| 0: [VarAccess] o
+# 30| 1: [TypeAccess] Used1
+# 30| 1: [ReturnStmt] return ...
+# 31| 1: [SwitchStmt] switch (...)
+# 31| -1: [VarAccess] o
+# 32| 0: [PatternCase] case
+#-----| 0: (Single Local Variable Declaration)
+# 32| 0: [TypeAccess] Used2
+# 32| 1: [LocalVariableDeclExpr] u2
+# 32| 1: [BreakStmt] break
+# 33| 2: [DefaultCase] default
+# 33| 3: [BreakStmt] break
+# 35| 2: [LocalVariableDeclStmt] var ...;
+# 35| 1: [LocalVariableDeclExpr] x
+# 35| 0: [SwitchExpr] switch (...)
+# 35| -1: [VarAccess] o
+# 36| 0: [PatternCase] case
+#-----| 0: (Single Local Variable Declaration)
+# 36| 0: [TypeAccess] Used3
+# 36| 1: [LocalVariableDeclExpr] u3
+# 36| 1: [YieldStmt] yield ...
+# 36| 0: [IntegerLiteral] 1
+# 37| 2: [DefaultCase] default
+# 37| 3: [YieldStmt] yield ...
+# 37| 0: [IntegerLiteral] 2
+# 41| 5: [Class] Used1
+# 42| 6: [Class] Used2
+# 43| 7: [Class] Used3
diff --git a/java/ql/test/library-tests/dependency/UsesType.expected b/java/ql/test/library-tests/dependency/UsesType.expected
index a380a5bf6dc..792e20366d4 100644
--- a/java/ql/test/library-tests/dependency/UsesType.expected
+++ b/java/ql/test/library-tests/dependency/UsesType.expected
@@ -10,4 +10,7 @@
| dependency/A.java:22:7:22:7 | F | dependency/A.java:22:7:22:7 | F |
| dependency/A.java:24:7:24:7 | G | dependency/A.java:24:7:24:7 | G |
| dependency/A.java:26:7:26:7 | H | dependency/A.java:26:7:26:7 | H |
+| dependency/A.java:41:22:41:26 | Used1 | dependency/A.java:41:22:41:26 | Used1 |
+| dependency/A.java:42:22:42:26 | Used2 | dependency/A.java:42:22:42:26 | Used2 |
+| dependency/A.java:43:22:43:26 | Used3 | dependency/A.java:43:22:43:26 | Used3 |
| file://:0:0:0:0 | C[] | dependency/A.java:9:7:9:7 | C |
diff --git a/java/ql/test/library-tests/dependency/dependency/A.java b/java/ql/test/library-tests/dependency/dependency/A.java
index 98c625f96be..41f53ccfa55 100644
--- a/java/ql/test/library-tests/dependency/dependency/A.java
+++ b/java/ql/test/library-tests/dependency/dependency/A.java
@@ -26,4 +26,19 @@ class G extends Throwable { }
class H {
T test(T t) { return t; }
void test2(java.util.Collection extends Number> t) {}
+ void test3(Object o) {
+ if (o instanceof Used1) return;
+ switch (o) {
+ case Used2 u2: break;
+ default: break;
+ }
+ var x = switch (o) {
+ case Used3 u3: yield 1;
+ default: yield 2;
+ };
+ }
+
+ static class Used1 { }
+ static class Used2 { }
+ static class Used3 { }
}
diff --git a/java/ql/test/library-tests/dependency/options b/java/ql/test/library-tests/dependency/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/dependency/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/flow-through-binding/Test.java b/java/ql/test/library-tests/flow-through-binding/Test.java
new file mode 100644
index 00000000000..5a026a8ce22
--- /dev/null
+++ b/java/ql/test/library-tests/flow-through-binding/Test.java
@@ -0,0 +1,75 @@
+public class Test {
+
+ public static Object testFlowThroughSwitchStmt(String s, Integer i, boolean unknown) {
+ Object o = unknown ? s : i;
+ switch (o) {
+ case Integer i2 -> { return (Object)i2; }
+ default -> { return null; }
+ }
+ }
+
+ public static Object testFlowThroughSwitchExpr(String s, Integer i, boolean unknown) {
+ Object o = unknown ? s : i;
+ Object toRet = switch (o) {
+ case Integer i2 -> (Object)i2;
+ default -> null;
+ };
+ return toRet;
+ }
+
+ public static Object testFlowThroughBindingInstanceOf(String s, Integer i, boolean unknown) {
+ Object o = unknown ? s : i;
+ if (o instanceof Integer i2)
+ return (Object)i2;
+ else
+ return null;
+ }
+
+ public static Object testFlowThroughSwitchStmtWrapper(Wrapper s, Wrapper i, boolean unknown) {
+ Wrapper o = unknown ? s : i;
+ switch (o) {
+ case Wrapper(Integer i2) -> { return (Object)i2; }
+ default -> { return null; }
+ }
+ }
+
+ public static Object testFlowThroughSwitchExprWrapper(Wrapper s, Wrapper i, boolean unknown) {
+ Wrapper o = unknown ? s : i;
+ Object toRet = switch (o) {
+ case Wrapper(Integer i2) -> (Object)i2;
+ default -> null;
+ };
+ return toRet;
+ }
+
+ public static Object testFlowThroughBindingInstanceOfWrapper(Wrapper s, Wrapper i, boolean unknown) {
+ Wrapper o = unknown ? s : i;
+ if (o instanceof Wrapper(Integer i2))
+ return (Object)i2;
+ else
+ return null;
+ }
+
+ public static T source() { return null; }
+
+ public static void sink(Object o) { }
+
+ public static void test(boolean unknown, boolean unknown2) {
+
+ String source1 = source();
+ Integer source2 = source();
+ sink(testFlowThroughSwitchStmt(source1, source2, unknown));
+ sink(testFlowThroughSwitchExpr(source1, source2, unknown));
+ sink(testFlowThroughBindingInstanceOf(source1, source2, unknown));
+
+ Wrapper source1Wrapper = new Wrapper((String)source());
+ Wrapper source2Wrapper = new Wrapper((Integer)source());
+ sink(testFlowThroughSwitchStmtWrapper(source1Wrapper, source2Wrapper, unknown));
+ sink(testFlowThroughSwitchExprWrapper(source1Wrapper, source2Wrapper, unknown));
+ sink(testFlowThroughBindingInstanceOfWrapper(source1Wrapper, source2Wrapper, unknown));
+
+ }
+
+ record Wrapper(Object o) { }
+
+}
diff --git a/java/ql/test/library-tests/flow-through-binding/options b/java/ql/test/library-tests/flow-through-binding/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/flow-through-binding/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/flow-through-binding/test.expected b/java/ql/test/library-tests/flow-through-binding/test.expected
new file mode 100644
index 00000000000..b1931300279
--- /dev/null
+++ b/java/ql/test/library-tests/flow-through-binding/test.expected
@@ -0,0 +1,6 @@
+| Test.java:60:23:60:30 | source(...) | Test.java:61:10:61:61 | testFlowThroughSwitchStmt(...) |
+| Test.java:60:23:60:30 | source(...) | Test.java:62:10:62:61 | testFlowThroughSwitchExpr(...) |
+| Test.java:60:23:60:30 | source(...) | Test.java:63:10:63:68 | testFlowThroughBindingInstanceOf(...) |
+| Test.java:66:51:66:58 | source(...) | Test.java:67:10:67:82 | testFlowThroughSwitchStmtWrapper(...) |
+| Test.java:66:51:66:58 | source(...) | Test.java:68:10:68:82 | testFlowThroughSwitchExprWrapper(...) |
+| Test.java:66:51:66:58 | source(...) | Test.java:69:10:69:89 | testFlowThroughBindingInstanceOfWrapper(...) |
diff --git a/java/ql/test/library-tests/flow-through-binding/test.ql b/java/ql/test/library-tests/flow-through-binding/test.ql
new file mode 100644
index 00000000000..26c4f88db06
--- /dev/null
+++ b/java/ql/test/library-tests/flow-through-binding/test.ql
@@ -0,0 +1,18 @@
+import java
+import semmle.code.java.dataflow.DataFlow
+
+module TestConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr() = any(MethodCall mc | mc.getCallee().getName() = "source")
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
+ }
+}
+
+module Flow = DataFlow::Global;
+
+from DataFlow::Node source, DataFlow::Node sink
+where Flow::flow(source, sink)
+select source, sink
diff --git a/java/ql/test/library-tests/guards12/PrintAst.expected b/java/ql/test/library-tests/guards12/PrintAst.expected
index a8a96261ec0..86a8ed3b778 100644
--- a/java/ql/test/library-tests/guards12/PrintAst.expected
+++ b/java/ql/test/library-tests/guards12/PrintAst.expected
@@ -6,6 +6,8 @@ Test.java:
#-----| 4: (Parameters)
# 2| 0: [Parameter] s
# 2| 0: [TypeAccess] String
+# 2| 1: [Parameter] unknown
+# 2| 0: [TypeAccess] boolean
# 2| 5: [BlockStmt] { ... }
# 3| 0: [LocalVariableDeclStmt] var ...;
# 3| 0: [TypeAccess] int
@@ -38,3 +40,45 @@ Test.java:
# 12| 0: [StringLiteral] "d"
# 13| 3: [DefaultCase] default
# 13| -1: [BlockStmt] { ... }
+# 15| 2: [LocalVariableDeclStmt] var ...;
+# 15| 0: [TypeAccess] int
+# 15| 1: [LocalVariableDeclExpr] len
+# 15| 0: [MethodCall] length(...)
+# 15| -1: [VarAccess] s
+# 16| 3: [SwitchStmt] switch (...)
+# 16| -1: [VarAccess] s
+# 17| 0: [PatternCase] case
+# 17| -3: [EQExpr] ... == ...
+# 17| 0: [VarAccess] len
+# 17| 1: [IntegerLiteral] 4
+# 17| -1: [BlockStmt] { ... }
+#-----| 0: (Single Local Variable Declaration)
+# 17| 0: [TypeAccess] String
+# 17| 1: [LocalVariableDeclExpr] s2
+# 18| 1: [ConstCase] case ...
+# 18| -1: [BlockStmt] { ... }
+# 18| 0: [StringLiteral] "e"
+# 19| 2: [DefaultCase] default
+# 19| -1: [BlockStmt] { ... }
+# 21| 4: [SwitchStmt] switch (...)
+# 21| -1: [ConditionalExpr] ...?...:...
+# 21| 0: [VarAccess] unknown
+# 21| 1: [VarAccess] s
+# 21| 2: [MethodCall] toLowerCase(...)
+# 21| -1: [VarAccess] s
+# 22| 0: [ConstCase] case ...
+# 22| -1: [BlockStmt] { ... }
+# 22| 0: [StringLiteral] "f"
+# 23| 1: [PatternCase] case
+# 23| -3: [EQExpr] ... == ...
+# 23| 0: [VarAccess] len
+# 23| 1: [IntegerLiteral] 4
+# 23| -1: [BlockStmt] { ... }
+#-----| 0: (Single Local Variable Declaration)
+# 23| 0: [TypeAccess] String
+# 23| 1: [LocalVariableDeclExpr] s2
+# 24| 2: [ConstCase] case ...
+# 24| -1: [BlockStmt] { ... }
+# 24| 0: [StringLiteral] "g"
+# 25| 3: [DefaultCase] default
+# 25| -1: [BlockStmt] { ... }
diff --git a/java/ql/test/library-tests/guards12/Test.java b/java/ql/test/library-tests/guards12/Test.java
index 3ce8f6f4828..ee80e17df8d 100644
--- a/java/ql/test/library-tests/guards12/Test.java
+++ b/java/ql/test/library-tests/guards12/Test.java
@@ -1,5 +1,5 @@
class Test {
- void foo(String s) {
+ void foo(String s, boolean unknown) {
int x = switch(s) {
case "a", "b" -> 1;
case "c" -> 2;
@@ -12,5 +12,17 @@ class Test {
case "d" -> { }
default -> { }
}
+ int len = s.length();
+ switch (s) {
+ case String s2 when len == 4 -> { }
+ case "e" -> { }
+ default -> { }
+ }
+ switch (unknown ? s : s.toLowerCase()) {
+ case "f" -> { }
+ case String s2 when len == 4 -> { }
+ case "g" -> { }
+ default -> { }
+ }
}
}
diff --git a/java/ql/test/library-tests/guards12/guard.expected b/java/ql/test/library-tests/guards12/guard.expected
index 71d1818f29c..29ca4cafb41 100644
--- a/java/ql/test/library-tests/guards12/guard.expected
+++ b/java/ql/test/library-tests/guards12/guard.expected
@@ -1,3 +1,37 @@
+hasBranchEdge
+| Test.java:4:7:4:22 | case ... | Test.java:2:39:27:3 | { ... } | Test.java:4:7:4:22 | case ... | true |
+| Test.java:5:7:5:17 | case ... | Test.java:2:39:27:3 | { ... } | Test.java:5:7:5:17 | case ... | true |
+| Test.java:6:7:6:17 | case ... | Test.java:2:39:27:3 | { ... } | Test.java:6:7:6:17 | case ... | true |
+| Test.java:7:7:7:16 | default | Test.java:2:39:27:3 | { ... } | Test.java:7:7:7:16 | default | true |
+| Test.java:10:7:10:22 | case ... | Test.java:3:9:3:21 | x | Test.java:10:7:10:22 | case ... | true |
+| Test.java:11:7:11:17 | case ... | Test.java:3:9:3:21 | x | Test.java:11:7:11:17 | case ... | true |
+| Test.java:12:7:12:17 | case ... | Test.java:3:9:3:21 | x | Test.java:12:7:12:17 | case ... | true |
+| Test.java:13:7:13:16 | default | Test.java:3:9:3:21 | x | Test.java:13:7:13:16 | default | true |
+| Test.java:17:7:17:37 | case | Test.java:15:5:15:25 | var ...; | Test.java:17:19:17:20 | s2 | true |
+| Test.java:17:7:17:37 | case | Test.java:15:5:15:25 | var ...; | Test.java:18:7:18:17 | case ... | false |
+| Test.java:17:7:17:37 | case | Test.java:15:5:15:25 | var ...; | Test.java:19:7:19:16 | default | false |
+| Test.java:17:27:17:34 | ... == ... | Test.java:17:19:17:20 | s2 | Test.java:17:39:17:41 | { ... } | true |
+| Test.java:17:27:17:34 | ... == ... | Test.java:17:19:17:20 | s2 | Test.java:18:7:18:17 | case ... | false |
+| Test.java:17:27:17:34 | ... == ... | Test.java:17:19:17:20 | s2 | Test.java:19:7:19:16 | default | false |
+| Test.java:18:7:18:17 | case ... | Test.java:15:5:15:25 | var ...; | Test.java:18:7:18:17 | case ... | true |
+| Test.java:18:7:18:17 | case ... | Test.java:17:19:17:20 | s2 | Test.java:18:7:18:17 | case ... | true |
+| Test.java:19:7:19:16 | default | Test.java:15:5:15:25 | var ...; | Test.java:19:7:19:16 | default | true |
+| Test.java:19:7:19:16 | default | Test.java:17:19:17:20 | s2 | Test.java:19:7:19:16 | default | true |
+| Test.java:21:13:21:19 | unknown | Test.java:21:5:21:42 | switch (...) | Test.java:21:23:21:23 | s | true |
+| Test.java:21:13:21:19 | unknown | Test.java:21:5:21:42 | switch (...) | Test.java:21:27:21:27 | s | false |
+| Test.java:22:7:22:17 | case ... | Test.java:21:23:21:23 | s | Test.java:22:7:22:17 | case ... | true |
+| Test.java:22:7:22:17 | case ... | Test.java:21:27:21:27 | s | Test.java:22:7:22:17 | case ... | true |
+| Test.java:23:7:23:37 | case | Test.java:23:7:23:37 | case | Test.java:23:19:23:20 | s2 | true |
+| Test.java:23:7:23:37 | case | Test.java:23:7:23:37 | case | Test.java:24:7:24:17 | case ... | false |
+| Test.java:23:7:23:37 | case | Test.java:23:7:23:37 | case | Test.java:25:7:25:16 | default | false |
+| Test.java:23:27:23:34 | ... == ... | Test.java:23:19:23:20 | s2 | Test.java:23:39:23:41 | { ... } | true |
+| Test.java:23:27:23:34 | ... == ... | Test.java:23:19:23:20 | s2 | Test.java:24:7:24:17 | case ... | false |
+| Test.java:23:27:23:34 | ... == ... | Test.java:23:19:23:20 | s2 | Test.java:25:7:25:16 | default | false |
+| Test.java:24:7:24:17 | case ... | Test.java:23:7:23:37 | case | Test.java:24:7:24:17 | case ... | true |
+| Test.java:24:7:24:17 | case ... | Test.java:23:19:23:20 | s2 | Test.java:24:7:24:17 | case ... | true |
+| Test.java:25:7:25:16 | default | Test.java:23:7:23:37 | case | Test.java:25:7:25:16 | default | true |
+| Test.java:25:7:25:16 | default | Test.java:23:19:23:20 | s2 | Test.java:25:7:25:16 | default | true |
+#select
| Test.java:5:7:5:17 | case ... | Test.java:3:20:3:20 | s | Test.java:5:12:5:14 | "c" | true | false | Test.java:7:7:7:16 | default |
| Test.java:5:7:5:17 | case ... | Test.java:3:20:3:20 | s | Test.java:5:12:5:14 | "c" | true | true | Test.java:5:7:5:17 | case ... |
| Test.java:6:7:6:17 | case ... | Test.java:3:20:3:20 | s | Test.java:6:12:6:14 | "d" | true | false | Test.java:7:7:7:16 | default |
@@ -6,3 +40,11 @@
| Test.java:11:7:11:17 | case ... | Test.java:9:13:9:13 | s | Test.java:11:12:11:14 | "c" | true | true | Test.java:11:7:11:17 | case ... |
| Test.java:12:7:12:17 | case ... | Test.java:9:13:9:13 | s | Test.java:12:12:12:14 | "d" | true | false | Test.java:13:7:13:16 | default |
| Test.java:12:7:12:17 | case ... | Test.java:9:13:9:13 | s | Test.java:12:12:12:14 | "d" | true | true | Test.java:12:7:12:17 | case ... |
+| Test.java:17:27:17:34 | ... == ... | Test.java:17:27:17:29 | len | Test.java:17:34:17:34 | 4 | true | true | Test.java:17:39:17:41 | { ... } |
+| Test.java:18:7:18:17 | case ... | Test.java:16:13:16:13 | s | Test.java:18:12:18:14 | "e" | true | false | Test.java:19:7:19:16 | default |
+| Test.java:18:7:18:17 | case ... | Test.java:16:13:16:13 | s | Test.java:18:12:18:14 | "e" | true | true | Test.java:18:7:18:17 | case ... |
+| Test.java:22:7:22:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:22:12:22:14 | "f" | true | false | Test.java:25:7:25:16 | default |
+| Test.java:22:7:22:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:22:12:22:14 | "f" | true | true | Test.java:22:7:22:17 | case ... |
+| Test.java:23:27:23:34 | ... == ... | Test.java:23:27:23:29 | len | Test.java:23:34:23:34 | 4 | true | true | Test.java:23:39:23:41 | { ... } |
+| Test.java:24:7:24:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:24:12:24:14 | "g" | true | false | Test.java:25:7:25:16 | default |
+| Test.java:24:7:24:17 | case ... | Test.java:21:13:21:41 | ...?...:... | Test.java:24:12:24:14 | "g" | true | true | Test.java:24:7:24:17 | case ... |
diff --git a/java/ql/test/library-tests/guards12/guard.ql b/java/ql/test/library-tests/guards12/guard.ql
index 206e85f8bb8..cff2845ad9f 100644
--- a/java/ql/test/library-tests/guards12/guard.ql
+++ b/java/ql/test/library-tests/guards12/guard.ql
@@ -1,8 +1,13 @@
import java
import semmle.code.java.controlflow.Guards
-from Guard g, BasicBlock bb, boolean branch, VarAccess e1, Expr e2, boolean pol
+query predicate hasBranchEdge(Guard g, BasicBlock bb1, BasicBlock bb2, boolean branch) {
+ g.hasBranchEdge(bb1, bb2, branch)
+}
+
+from Guard g, BasicBlock bb, boolean branch, Expr e1, Expr e2, boolean pol
where
g.controls(bb, branch) and
- g.isEquality(e1, e2, pol)
+ g.isEquality(e1, e2, pol) and
+ not e1 instanceof Literal
select g, e1, e2, pol, branch, bb
diff --git a/java/ql/test/library-tests/guards12/options b/java/ql/test/library-tests/guards12/options
index 3f12170222c..a0d1b7e7002 100644
--- a/java/ql/test/library-tests/guards12/options
+++ b/java/ql/test/library-tests/guards12/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args -source 14 -target 14
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/Test.java b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/Test.java
new file mode 100644
index 00000000000..445682392ad
--- /dev/null
+++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/Test.java
@@ -0,0 +1,29 @@
+public class Test {
+
+ interface Intf { }
+ static class Specific implements Intf { public String toString() { return "Specific"; } }
+ static class Alternative implements Intf { public String toString() { return "Alternative"; } }
+
+ public static String caller() {
+
+ Alternative a = new Alternative(); // Instantiate this somewhere so there are at least two candidate types in general
+ return test(new Specific());
+
+ }
+
+ public static String test(Object o) {
+
+ if (o instanceof Object o2) {
+ // So we should know o2.toString is really Specific.toString():
+ return o2.toString();
+ }
+
+ switch (o) {
+ case Object o2 when o2.hashCode() > 0 -> { return o2.toString(); } // Same goes for this `o2`
+ default -> { return "Not an Intf"; }
+ }
+
+ }
+
+}
+
diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/options b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.expected b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.expected
new file mode 100644
index 00000000000..be4c24ea044
--- /dev/null
+++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.expected
@@ -0,0 +1,9 @@
+| Test.java:1:14:1:17 | super(...) | java.lang.Object.Object |
+| Test.java:4:16:4:23 | super(...) | java.lang.Object.Object |
+| Test.java:5:16:5:26 | super(...) | java.lang.Object.Object |
+| Test.java:9:21:9:37 | new Alternative(...) | Test$Alternative.Alternative |
+| Test.java:10:12:10:31 | test(...) | Test.test |
+| Test.java:10:17:10:30 | new Specific(...) | Test$Specific.Specific |
+| Test.java:18:14:18:26 | toString(...) | Test$Specific.toString |
+| Test.java:22:27:22:39 | hashCode(...) | java.lang.Object.hashCode |
+| Test.java:22:57:22:69 | toString(...) | Test$Specific.toString |
diff --git a/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql
new file mode 100644
index 00000000000..dea1e994a93
--- /dev/null
+++ b/java/ql/test/library-tests/object-tostring-flow-binding-patterns/test.ql
@@ -0,0 +1,6 @@
+import java
+import semmle.code.java.dispatch.VirtualDispatch
+
+from Call c, Callable c2
+where c2 = viableCallable(c)
+select c, c2.getQualifiedName()
diff --git a/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected b/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected
new file mode 100644
index 00000000000..e36a0688199
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/PrintAst.expected
@@ -0,0 +1,78 @@
+Test.java:
+# 0| [CompilationUnit] Test
+# 1| 1: [Class] Test
+# 3| 2: [Method] test
+# 3| 3: [TypeAccess] void
+#-----| 4: (Parameters)
+# 3| 0: [Parameter] inp
+# 3| 0: [TypeAccess] boolean
+# 3| 5: [BlockStmt] { ... }
+# 5| 0: [LocalVariableDeclStmt] var ...;
+# 5| 0: [TypeAccess] String
+# 5| 1: [LocalVariableDeclExpr] directTaint
+# 5| 0: [MethodCall] source(...)
+# 6| 1: [LocalVariableDeclStmt] var ...;
+# 6| 0: [TypeAccess] String
+# 6| 1: [LocalVariableDeclExpr] indirectTaint
+# 6| 0: [MethodCall] source(...)
+# 8| 2: [LocalVariableDeclStmt] var ...;
+# 8| 0: [TypeAccess] Object
+# 8| 1: [LocalVariableDeclExpr] o
+# 8| 0: [ConditionalExpr] ...?...:...
+# 8| 0: [VarAccess] inp
+# 8| 1: [VarAccess] directTaint
+# 8| 2: [ClassInstanceExpr] new Outer(...)
+# 8| -3: [TypeAccess] Outer
+# 8| 0: [ClassInstanceExpr] new Inner(...)
+# 8| -3: [TypeAccess] Inner
+# 8| 0: [VarAccess] indirectTaint
+# 8| 1: [StringLiteral] "not tainted"
+# 8| 1: [StringLiteral] "not tainted 2"
+# 10| 3: [IfStmt] if (...)
+# 10| 0: [InstanceOfExpr] ...instanceof...
+# 10| 0: [VarAccess] o
+#-----| 2: (Single Local Variable Declaration)
+# 10| 0: [TypeAccess] String
+# 10| 1: [LocalVariableDeclExpr] s
+# 10| 1: [BlockStmt] { ... }
+# 11| 0: [ExprStmt] ;
+# 11| 0: [MethodCall] sink(...)
+# 11| 0: [VarAccess] s
+# 14| 4: [IfStmt] if (...)
+# 14| 0: [InstanceOfExpr] ...instanceof...
+# 14| 0: [VarAccess] o
+# 14| 2: [RecordPatternExpr] Outer(...)
+# 14| -2: [TypeAccess] String
+# 14| 0: [RecordPatternExpr] Inner(...)
+# 14| -2: [TypeAccess] String
+# 14| -1: [TypeAccess] String
+# 14| 0: [LocalVariableDeclExpr] tainted
+# 14| 1: [LocalVariableDeclExpr] notTainted
+# 14| 1: [LocalVariableDeclExpr] alsoNotTainted
+# 14| 1: [BlockStmt] { ... }
+# 15| 0: [ExprStmt] ;
+# 15| 0: [MethodCall] sink(...)
+# 15| 0: [VarAccess] tainted
+# 16| 1: [ExprStmt] ;
+# 16| 0: [MethodCall] sink(...)
+# 16| 0: [VarAccess] notTainted
+# 17| 2: [ExprStmt] ;
+# 17| 0: [MethodCall] sink(...)
+# 17| 0: [VarAccess] alsoNotTainted
+# 22| 3: [Method] source
+# 22| 3: [TypeAccess] String
+# 22| 5: [BlockStmt] { ... }
+# 22| 0: [ReturnStmt] return ...
+# 22| 0: [StringLiteral] "tainted"
+# 23| 4: [Method] sink
+# 23| 3: [TypeAccess] void
+#-----| 4: (Parameters)
+# 23| 0: [Parameter] sunk
+# 23| 0: [TypeAccess] String
+# 23| 5: [BlockStmt] { ... }
+# 27| 2: [Class] Outer
+# 27| 7: [FieldDeclaration] Inner i;
+# 27| 8: [FieldDeclaration] String otherField;
+# 28| 3: [Class] Inner
+# 28| 7: [FieldDeclaration] String taintedField;
+# 28| 8: [FieldDeclaration] String nonTaintedField;
diff --git a/java/ql/test/library-tests/pattern-instanceof/PrintAst.qlref b/java/ql/test/library-tests/pattern-instanceof/PrintAst.qlref
new file mode 100644
index 00000000000..c7fd5faf239
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/PrintAst.qlref
@@ -0,0 +1 @@
+semmle/code/java/PrintAst.ql
\ No newline at end of file
diff --git a/java/ql/test/library-tests/pattern-instanceof/Test.java b/java/ql/test/library-tests/pattern-instanceof/Test.java
new file mode 100644
index 00000000000..1cff6cf254f
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/Test.java
@@ -0,0 +1,28 @@
+public class Test {
+
+ public static void test(boolean inp) {
+
+ String directTaint = source();
+ String indirectTaint = source();
+
+ Object o = inp ? directTaint : new Outer(new Inner(indirectTaint, "not tainted"), "not tainted 2");
+
+ if (o instanceof String s) {
+ sink(s);
+ }
+
+ if (o instanceof Outer(Inner(String tainted, String notTainted), String alsoNotTainted)) {
+ sink(tainted);
+ sink(notTainted);
+ sink(alsoNotTainted);
+ }
+
+ }
+
+ public static String source() { return "tainted"; }
+ public static void sink(String sunk) { }
+
+}
+
+record Outer(Inner i, String otherField) { }
+record Inner(String taintedField, String nonTaintedField) { }
diff --git a/java/ql/test/library-tests/pattern-instanceof/cfg.expected b/java/ql/test/library-tests/pattern-instanceof/cfg.expected
new file mode 100644
index 00000000000..29de1e4a3a8
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/cfg.expected
@@ -0,0 +1,72 @@
+| Test.java:1:14:1:17 | super(...) | Test.java:1:14:1:17 | Test |
+| Test.java:1:14:1:17 | { ... } | Test.java:1:14:1:17 | super(...) |
+| Test.java:3:40:20:3 | { ... } | Test.java:5:5:5:34 | var ...; |
+| Test.java:5:5:5:34 | var ...; | Test.java:5:26:5:33 | source(...) |
+| Test.java:5:12:5:33 | directTaint | Test.java:6:5:6:36 | var ...; |
+| Test.java:5:26:5:33 | source(...) | Test.java:5:12:5:33 | directTaint |
+| Test.java:6:5:6:36 | var ...; | Test.java:6:28:6:35 | source(...) |
+| Test.java:6:12:6:35 | indirectTaint | Test.java:8:5:8:103 | var ...; |
+| Test.java:6:28:6:35 | source(...) | Test.java:6:12:6:35 | indirectTaint |
+| Test.java:8:5:8:103 | var ...; | Test.java:8:16:8:102 | ...?...:... |
+| Test.java:8:12:8:102 | o | Test.java:10:5:10:30 | if (...) |
+| Test.java:8:16:8:18 | inp | Test.java:8:22:8:32 | directTaint |
+| Test.java:8:16:8:18 | inp | Test.java:8:56:8:68 | indirectTaint |
+| Test.java:8:16:8:102 | ...?...:... | Test.java:8:16:8:18 | inp |
+| Test.java:8:22:8:32 | directTaint | Test.java:8:12:8:102 | o |
+| Test.java:8:36:8:102 | new Outer(...) | Test.java:8:12:8:102 | o |
+| Test.java:8:46:8:84 | new Inner(...) | Test.java:8:87:8:101 | "not tainted 2" |
+| Test.java:8:56:8:68 | indirectTaint | Test.java:8:71:8:83 | "not tainted" |
+| Test.java:8:71:8:83 | "not tainted" | Test.java:8:46:8:84 | new Inner(...) |
+| Test.java:8:87:8:101 | "not tainted 2" | Test.java:8:36:8:102 | new Outer(...) |
+| Test.java:10:5:10:30 | if (...) | Test.java:10:9:10:9 | o |
+| Test.java:10:9:10:9 | o | Test.java:10:9:10:29 | ...instanceof... |
+| Test.java:10:9:10:29 | ...instanceof... | Test.java:10:29:10:29 | s |
+| Test.java:10:9:10:29 | ...instanceof... | Test.java:14:5:14:92 | if (...) |
+| Test.java:10:29:10:29 | s | Test.java:10:32:12:5 | { ... } |
+| Test.java:10:32:12:5 | { ... } | Test.java:11:7:11:14 | ; |
+| Test.java:11:7:11:13 | sink(...) | Test.java:14:5:14:92 | if (...) |
+| Test.java:11:7:11:14 | ; | Test.java:11:12:11:12 | s |
+| Test.java:11:12:11:12 | s | Test.java:11:7:11:13 | sink(...) |
+| Test.java:14:5:14:92 | if (...) | Test.java:14:9:14:9 | o |
+| Test.java:14:9:14:9 | o | Test.java:14:9:14:91 | ...instanceof... |
+| Test.java:14:9:14:91 | ...instanceof... | Test.java:3:22:3:25 | test |
+| Test.java:14:9:14:91 | ...instanceof... | Test.java:14:41:14:47 | tainted |
+| Test.java:14:22:14:91 | Outer(...) | Test.java:14:94:18:5 | { ... } |
+| Test.java:14:28:14:67 | Inner(...) | Test.java:14:77:14:90 | alsoNotTainted |
+| Test.java:14:41:14:47 | tainted | Test.java:14:57:14:66 | notTainted |
+| Test.java:14:57:14:66 | notTainted | Test.java:14:28:14:67 | Inner(...) |
+| Test.java:14:77:14:90 | alsoNotTainted | Test.java:14:22:14:91 | Outer(...) |
+| Test.java:14:94:18:5 | { ... } | Test.java:15:7:15:20 | ; |
+| Test.java:15:7:15:19 | sink(...) | Test.java:16:7:16:23 | ; |
+| Test.java:15:7:15:20 | ; | Test.java:15:12:15:18 | tainted |
+| Test.java:15:12:15:18 | tainted | Test.java:15:7:15:19 | sink(...) |
+| Test.java:16:7:16:22 | sink(...) | Test.java:17:7:17:27 | ; |
+| Test.java:16:7:16:23 | ; | Test.java:16:12:16:21 | notTainted |
+| Test.java:16:12:16:21 | notTainted | Test.java:16:7:16:22 | sink(...) |
+| Test.java:17:7:17:26 | sink(...) | Test.java:3:22:3:25 | test |
+| Test.java:17:7:17:27 | ; | Test.java:17:12:17:25 | alsoNotTainted |
+| Test.java:17:12:17:25 | alsoNotTainted | Test.java:17:7:17:26 | sink(...) |
+| Test.java:22:33:22:53 | { ... } | Test.java:22:42:22:50 | "tainted" |
+| Test.java:22:35:22:51 | return ... | Test.java:22:24:22:29 | source |
+| Test.java:22:42:22:50 | "tainted" | Test.java:22:35:22:51 | return ... |
+| Test.java:23:40:23:42 | { ... } | Test.java:23:22:23:25 | sink |
+| Test.java:27:8:27:12 | ...=... | Test.java:27:8:27:12 | ; |
+| Test.java:27:8:27:12 | ...=... | Test.java:27:8:27:12 | Outer |
+| Test.java:27:8:27:12 | ; | Test.java:27:8:27:12 | this |
+| Test.java:27:8:27:12 | ; | Test.java:27:8:27:12 | this |
+| Test.java:27:8:27:12 | i | Test.java:27:8:27:12 | ...=... |
+| Test.java:27:8:27:12 | otherField | Test.java:27:8:27:12 | ...=... |
+| Test.java:27:8:27:12 | super(...) | Test.java:27:8:27:12 | ; |
+| Test.java:27:8:27:12 | this | Test.java:27:8:27:12 | i |
+| Test.java:27:8:27:12 | this | Test.java:27:8:27:12 | otherField |
+| Test.java:27:8:27:12 | { ... } | Test.java:27:8:27:12 | super(...) |
+| Test.java:28:8:28:12 | ...=... | Test.java:28:8:28:12 | ; |
+| Test.java:28:8:28:12 | ...=... | Test.java:28:8:28:12 | Inner |
+| Test.java:28:8:28:12 | ; | Test.java:28:8:28:12 | this |
+| Test.java:28:8:28:12 | ; | Test.java:28:8:28:12 | this |
+| Test.java:28:8:28:12 | nonTaintedField | Test.java:28:8:28:12 | ...=... |
+| Test.java:28:8:28:12 | super(...) | Test.java:28:8:28:12 | ; |
+| Test.java:28:8:28:12 | taintedField | Test.java:28:8:28:12 | ...=... |
+| Test.java:28:8:28:12 | this | Test.java:28:8:28:12 | nonTaintedField |
+| Test.java:28:8:28:12 | this | Test.java:28:8:28:12 | taintedField |
+| Test.java:28:8:28:12 | { ... } | Test.java:28:8:28:12 | super(...) |
diff --git a/java/ql/test/library-tests/pattern-instanceof/cfg.ql b/java/ql/test/library-tests/pattern-instanceof/cfg.ql
new file mode 100644
index 00000000000..0b07e8c4708
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/cfg.ql
@@ -0,0 +1,5 @@
+import java
+
+from ControlFlowNode cn
+where cn.getFile().getBaseName() = "Test.java"
+select cn, cn.getASuccessor()
diff --git a/java/ql/test/library-tests/pattern-instanceof/dfg.expected b/java/ql/test/library-tests/pattern-instanceof/dfg.expected
new file mode 100644
index 00000000000..db1abed3fa6
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/dfg.expected
@@ -0,0 +1,2 @@
+| Test.java:5:26:5:33 | source(...) | Test.java:11:12:11:12 | s |
+| Test.java:6:28:6:35 | source(...) | Test.java:15:12:15:18 | tainted |
diff --git a/java/ql/test/library-tests/pattern-instanceof/dfg.ql b/java/ql/test/library-tests/pattern-instanceof/dfg.ql
new file mode 100644
index 00000000000..8c4a082dd33
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/dfg.ql
@@ -0,0 +1,31 @@
+import java
+import semmle.code.java.controlflow.Guards
+import semmle.code.java.dataflow.DataFlow
+
+private predicate isSafe(Guard g, Expr checked, boolean branch) {
+ exists(MethodCall mc | g = mc |
+ mc.getMethod().hasName("isSafe") and
+ checked = mc.getAnArgument() and
+ branch = true
+ )
+}
+
+module TestConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "source")
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
+ }
+
+ predicate isBarrier(DataFlow::Node node) {
+ node = DataFlow::BarrierGuard::getABarrierNode()
+ }
+}
+
+module Flow = DataFlow::Global;
+
+from DataFlow::Node source, DataFlow::Node sink
+where Flow::flow(source, sink)
+select source, sink
diff --git a/java/ql/test/library-tests/pattern-instanceof/options b/java/ql/test/library-tests/pattern-instanceof/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-instanceof/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Exhaustive.java b/java/ql/test/library-tests/pattern-switch/cfg/Exhaustive.java
new file mode 100644
index 00000000000..c438b8253a2
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/cfg/Exhaustive.java
@@ -0,0 +1,31 @@
+public class Exhaustive {
+
+ enum E { A, B, C };
+ sealed interface I permits X, Y { }
+ final class X implements I { }
+ final class Y implements I { }
+
+ public static void test(E e, I i, Object o) {
+
+ // Check the CFGs of three different ways to be exhaustive -- in particular there shouldn't be a fall-through nothing-matched edge.
+ switch (o) {
+ case String s -> { }
+ case Object o2 -> { }
+ }
+
+ // Exhaustiveness not yet detected by CodeQL, because it is legal to omit some enum entries without a `default` case,
+ // so we'd need to check every enum entry in the type of E occurs in some case.
+ switch (e) {
+ case A -> { }
+ case B -> { }
+ case C -> { }
+ }
+
+ switch (i) {
+ case X x -> { }
+ case Y y -> { }
+ }
+
+ }
+
+}
diff --git a/java/ql/test/library-tests/pattern-switch/cfg/Test.java b/java/ql/test/library-tests/pattern-switch/cfg/Test.java
new file mode 100644
index 00000000000..9894d7fd197
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/cfg/Test.java
@@ -0,0 +1,106 @@
+public class Test {
+
+ public static void test(Object thing) {
+
+ switch (thing) {
+ case String s -> System.out.println(s);
+ case Integer i -> System.out.println("An integer: " + i);
+ default -> { }
+ }
+
+ switch (thing) {
+ case String s:
+ System.out.println(s);
+ break;
+ case Integer i:
+ System.out.println("An integer:" + i);
+ break;
+ default:
+ break;
+ }
+
+ var thingAsString = switch(thing) {
+ case String s -> s;
+ case Integer i -> "An integer: " + i;
+ default -> "Something else";
+ };
+
+ var thingAsString2 = switch(thing) {
+ case String s:
+ yield s;
+ case Integer i:
+ yield "An integer: " + i;
+ default:
+ yield "Something else";
+ };
+
+ switch(thing) {
+ case String s when s.length() == 3:
+ System.out.println("Length 3");
+ break;
+ case String s when s.length() == 5:
+ System.out.println("Length 5");
+ break;
+ default:
+ System.out.println("Anything else");
+ break;
+ }
+
+ switch(thing) {
+ case String s when s.length() == 3 -> System.out.println("Length 3");
+ case String s when s.length() == 5 -> System.out.println("Length 5");
+ default -> { }
+ }
+
+ switch((String)thing) {
+ case "Const1":
+ System.out.println("It's Const1!");
+ case "Const2":
+ System.out.println("It's Const1 or Const2!");
+ break;
+ case String s when s.length() <= 6:
+ System.out.println("It's <= 6 chars long, and neither Const1 nor Const2");
+ case "Const3":
+ System.out.println("It's (<= 6 chars long, and neither Const1 nor Const2), or Const3");
+ break;
+ case "Const30":
+ System.out.println("It's Const30");
+ break;
+ case null, default:
+ System.out.println("It's null, or something else");
+ }
+
+ switch(thing) {
+ case String s:
+ System.out.println(s);
+ break;
+ case null:
+ System.out.println("It's null");
+ break;
+ case Integer i:
+ System.out.println("An integer:" + i);
+ break;
+ default:
+ break;
+ }
+
+ switch(thing) {
+ case A(B(int x, String y), float z):
+ break;
+ default:
+ break;
+ }
+
+ switch(thing) {
+ case A(B(var x, var y), var z):
+ break;
+ default:
+ break;
+ }
+
+ }
+
+}
+
+record A(B b, float field3) { }
+record B(int field1, String field2) { }
diff --git a/java/ql/test/library-tests/pattern-switch/cfg/options b/java/ql/test/library-tests/pattern-switch/cfg/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/cfg/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.expected b/java/ql/test/library-tests/pattern-switch/cfg/test.expected
new file mode 100644
index 00000000000..31605711281
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/cfg/test.expected
@@ -0,0 +1,313 @@
+| Exhaustive.java:1:14:1:23 | super(...) | Exhaustive.java:1:14:1:23 | Exhaustive |
+| Exhaustive.java:1:14:1:23 | { ... } | Exhaustive.java:1:14:1:23 | super(...) |
+| Exhaustive.java:3:8:3:8 | super(...) | Exhaustive.java:3:8:3:8 | E |
+| Exhaustive.java:3:8:3:8 | { ... } | Exhaustive.java:3:8:3:8 | super(...) |
+| Exhaustive.java:3:8:3:8 | { ... } | Exhaustive.java:3:12:3:12 | ; |
+| Exhaustive.java:3:12:3:12 | ...=... | Exhaustive.java:3:15:3:15 | ; |
+| Exhaustive.java:3:12:3:12 | ; | Exhaustive.java:3:12:3:12 | new E(...) |
+| Exhaustive.java:3:12:3:12 | new E(...) | Exhaustive.java:3:12:3:12 | ...=... |
+| Exhaustive.java:3:15:3:15 | ...=... | Exhaustive.java:3:18:3:18 | ; |
+| Exhaustive.java:3:15:3:15 | ; | Exhaustive.java:3:15:3:15 | new E(...) |
+| Exhaustive.java:3:15:3:15 | new E(...) | Exhaustive.java:3:15:3:15 | ...=... |
+| Exhaustive.java:3:18:3:18 | ...=... | Exhaustive.java:3:8:3:8 | |
+| Exhaustive.java:3:18:3:18 | ; | Exhaustive.java:3:18:3:18 | new E(...) |
+| Exhaustive.java:3:18:3:18 | new E(...) | Exhaustive.java:3:18:3:18 | ...=... |
+| Exhaustive.java:5:15:5:15 | super(...) | Exhaustive.java:5:15:5:15 | X |
+| Exhaustive.java:5:15:5:15 | { ... } | Exhaustive.java:5:15:5:15 | super(...) |
+| Exhaustive.java:6:15:6:15 | super(...) | Exhaustive.java:6:15:6:15 | Y |
+| Exhaustive.java:6:15:6:15 | { ... } | Exhaustive.java:6:15:6:15 | super(...) |
+| Exhaustive.java:8:47:29:3 | { ... } | Exhaustive.java:11:5:11:14 | switch (...) |
+| Exhaustive.java:11:5:11:14 | switch (...) | Exhaustive.java:11:13:11:13 | o |
+| Exhaustive.java:11:13:11:13 | o | Exhaustive.java:12:7:12:22 | case |
+| Exhaustive.java:12:7:12:22 | case | Exhaustive.java:12:19:12:19 | s |
+| Exhaustive.java:12:7:12:22 | case | Exhaustive.java:13:7:13:23 | case |
+| Exhaustive.java:12:19:12:19 | s | Exhaustive.java:12:24:12:26 | { ... } |
+| Exhaustive.java:12:24:12:26 | { ... } | Exhaustive.java:18:5:18:14 | switch (...) |
+| Exhaustive.java:13:7:13:23 | case | Exhaustive.java:13:19:13:20 | o2 |
+| Exhaustive.java:13:19:13:20 | o2 | Exhaustive.java:13:25:13:27 | { ... } |
+| Exhaustive.java:13:25:13:27 | { ... } | Exhaustive.java:18:5:18:14 | switch (...) |
+| Exhaustive.java:18:5:18:14 | switch (...) | Exhaustive.java:18:13:18:13 | e |
+| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:19:7:19:15 | case ... |
+| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:20:7:20:15 | case ... |
+| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:21:7:21:15 | case ... |
+| Exhaustive.java:18:13:18:13 | e | Exhaustive.java:24:5:24:14 | switch (...) |
+| Exhaustive.java:19:7:19:15 | case ... | Exhaustive.java:19:17:19:19 | { ... } |
+| Exhaustive.java:19:17:19:19 | { ... } | Exhaustive.java:24:5:24:14 | switch (...) |
+| Exhaustive.java:20:7:20:15 | case ... | Exhaustive.java:20:17:20:19 | { ... } |
+| Exhaustive.java:20:17:20:19 | { ... } | Exhaustive.java:24:5:24:14 | switch (...) |
+| Exhaustive.java:21:7:21:15 | case ... | Exhaustive.java:21:17:21:19 | { ... } |
+| Exhaustive.java:21:17:21:19 | { ... } | Exhaustive.java:24:5:24:14 | switch (...) |
+| Exhaustive.java:24:5:24:14 | switch (...) | Exhaustive.java:24:13:24:13 | i |
+| Exhaustive.java:24:13:24:13 | i | Exhaustive.java:25:7:25:17 | case |
+| Exhaustive.java:25:7:25:17 | case | Exhaustive.java:25:14:25:14 | x |
+| Exhaustive.java:25:7:25:17 | case | Exhaustive.java:26:7:26:17 | case |
+| Exhaustive.java:25:14:25:14 | x | Exhaustive.java:25:19:25:21 | { ... } |
+| Exhaustive.java:25:19:25:21 | { ... } | Exhaustive.java:8:22:8:25 | test |
+| Exhaustive.java:26:7:26:17 | case | Exhaustive.java:26:14:26:14 | y |
+| Exhaustive.java:26:14:26:14 | y | Exhaustive.java:26:19:26:21 | { ... } |
+| Exhaustive.java:26:19:26:21 | { ... } | Exhaustive.java:8:22:8:25 | test |
+| Test.java:1:14:1:17 | super(...) | Test.java:1:14:1:17 | Test |
+| Test.java:1:14:1:17 | { ... } | Test.java:1:14:1:17 | super(...) |
+| Test.java:3:41:101:3 | { ... } | Test.java:5:6:5:19 | switch (...) |
+| Test.java:5:6:5:19 | switch (...) | Test.java:5:14:5:18 | thing |
+| Test.java:5:14:5:18 | thing | Test.java:6:8:6:23 | case |
+| Test.java:6:8:6:23 | case | Test.java:6:20:6:20 | s |
+| Test.java:6:8:6:23 | case | Test.java:7:8:7:24 | case |
+| Test.java:6:20:6:20 | s | Test.java:6:25:6:34 | System.out |
+| Test.java:6:25:6:34 | System.out | Test.java:6:44:6:44 | s |
+| Test.java:6:25:6:45 | println(...) | Test.java:11:6:11:19 | switch (...) |
+| Test.java:6:25:6:46 | ; | Test.java:6:25:6:34 | System.out |
+| Test.java:6:44:6:44 | s | Test.java:6:25:6:45 | println(...) |
+| Test.java:7:8:7:24 | case | Test.java:7:21:7:21 | i |
+| Test.java:7:8:7:24 | case | Test.java:8:8:8:17 | default |
+| Test.java:7:21:7:21 | i | Test.java:7:26:7:35 | System.out |
+| Test.java:7:26:7:35 | System.out | Test.java:7:45:7:58 | "An integer: " |
+| Test.java:7:26:7:63 | println(...) | Test.java:11:6:11:19 | switch (...) |
+| Test.java:7:26:7:64 | ; | Test.java:7:26:7:35 | System.out |
+| Test.java:7:45:7:58 | "An integer: " | Test.java:7:62:7:62 | i |
+| Test.java:7:45:7:62 | ... + ... | Test.java:7:26:7:63 | println(...) |
+| Test.java:7:62:7:62 | i | Test.java:7:45:7:62 | ... + ... |
+| Test.java:8:8:8:17 | default | Test.java:8:19:8:21 | { ... } |
+| Test.java:8:19:8:21 | { ... } | Test.java:11:6:11:19 | switch (...) |
+| Test.java:11:6:11:19 | switch (...) | Test.java:11:14:11:18 | thing |
+| Test.java:11:14:11:18 | thing | Test.java:12:8:12:21 | case |
+| Test.java:12:8:12:21 | case | Test.java:12:20:12:20 | s |
+| Test.java:12:8:12:21 | case | Test.java:15:8:15:22 | case |
+| Test.java:12:20:12:20 | s | Test.java:13:10:13:31 | ; |
+| Test.java:13:10:13:19 | System.out | Test.java:13:29:13:29 | s |
+| Test.java:13:10:13:30 | println(...) | Test.java:14:10:14:15 | break |
+| Test.java:13:10:13:31 | ; | Test.java:13:10:13:19 | System.out |
+| Test.java:13:29:13:29 | s | Test.java:13:10:13:30 | println(...) |
+| Test.java:14:10:14:15 | break | Test.java:22:6:26:7 | var ...; |
+| Test.java:15:8:15:22 | case | Test.java:15:21:15:21 | i |
+| Test.java:15:8:15:22 | case | Test.java:18:8:18:15 | default |
+| Test.java:15:21:15:21 | i | Test.java:16:10:16:47 | ; |
+| Test.java:16:10:16:19 | System.out | Test.java:16:29:16:41 | "An integer:" |
+| Test.java:16:10:16:46 | println(...) | Test.java:17:10:17:15 | break |
+| Test.java:16:10:16:47 | ; | Test.java:16:10:16:19 | System.out |
+| Test.java:16:29:16:41 | "An integer:" | Test.java:16:45:16:45 | i |
+| Test.java:16:29:16:45 | ... + ... | Test.java:16:10:16:46 | println(...) |
+| Test.java:16:45:16:45 | i | Test.java:16:29:16:45 | ... + ... |
+| Test.java:17:10:17:15 | break | Test.java:22:6:26:7 | var ...; |
+| Test.java:18:8:18:15 | default | Test.java:19:10:19:15 | break |
+| Test.java:19:10:19:15 | break | Test.java:22:6:26:7 | var ...; |
+| Test.java:22:6:26:7 | var ...; | Test.java:22:26:22:38 | switch (...) |
+| Test.java:22:10:22:38 | thingAsString | Test.java:28:6:35:7 | var ...; |
+| Test.java:22:26:22:38 | switch (...) | Test.java:22:33:22:37 | thing |
+| Test.java:22:33:22:37 | thing | Test.java:23:8:23:23 | case |
+| Test.java:23:8:23:23 | case | Test.java:23:20:23:20 | s |
+| Test.java:23:8:23:23 | case | Test.java:24:8:24:24 | case |
+| Test.java:23:20:23:20 | s | Test.java:23:25:23:25 | s |
+| Test.java:23:25:23:25 | s | Test.java:22:10:22:38 | thingAsString |
+| Test.java:24:8:24:24 | case | Test.java:24:21:24:21 | i |
+| Test.java:24:8:24:24 | case | Test.java:25:8:25:17 | default |
+| Test.java:24:21:24:21 | i | Test.java:24:26:24:39 | "An integer: " |
+| Test.java:24:26:24:39 | "An integer: " | Test.java:24:43:24:43 | i |
+| Test.java:24:26:24:43 | ... + ... | Test.java:22:10:22:38 | thingAsString |
+| Test.java:24:43:24:43 | i | Test.java:24:26:24:43 | ... + ... |
+| Test.java:25:8:25:17 | default | Test.java:25:19:25:34 | "Something else" |
+| Test.java:25:19:25:34 | "Something else" | Test.java:22:10:22:38 | thingAsString |
+| Test.java:28:6:35:7 | var ...; | Test.java:28:27:28:39 | switch (...) |
+| Test.java:28:10:28:39 | thingAsString2 | Test.java:37:6:37:18 | switch (...) |
+| Test.java:28:27:28:39 | switch (...) | Test.java:28:34:28:38 | thing |
+| Test.java:28:34:28:38 | thing | Test.java:29:8:29:21 | case |
+| Test.java:29:8:29:21 | case | Test.java:29:20:29:20 | s |
+| Test.java:29:8:29:21 | case | Test.java:31:8:31:22 | case |
+| Test.java:29:20:29:20 | s | Test.java:30:10:30:17 | yield ... |
+| Test.java:30:10:30:17 | yield ... | Test.java:30:16:30:16 | s |
+| Test.java:30:16:30:16 | s | Test.java:28:10:28:39 | thingAsString2 |
+| Test.java:31:8:31:22 | case | Test.java:31:21:31:21 | i |
+| Test.java:31:8:31:22 | case | Test.java:33:8:33:15 | default |
+| Test.java:31:21:31:21 | i | Test.java:32:10:32:34 | yield ... |
+| Test.java:32:10:32:34 | yield ... | Test.java:32:16:32:29 | "An integer: " |
+| Test.java:32:16:32:29 | "An integer: " | Test.java:32:33:32:33 | i |
+| Test.java:32:16:32:33 | ... + ... | Test.java:28:10:28:39 | thingAsString2 |
+| Test.java:32:33:32:33 | i | Test.java:32:16:32:33 | ... + ... |
+| Test.java:33:8:33:15 | default | Test.java:34:10:34:32 | yield ... |
+| Test.java:34:10:34:32 | yield ... | Test.java:34:16:34:31 | "Something else" |
+| Test.java:34:16:34:31 | "Something else" | Test.java:28:10:28:39 | thingAsString2 |
+| Test.java:37:6:37:18 | switch (...) | Test.java:37:13:37:17 | thing |
+| Test.java:37:13:37:17 | thing | Test.java:38:8:38:42 | case |
+| Test.java:38:8:38:42 | case | Test.java:38:20:38:20 | s |
+| Test.java:38:8:38:42 | case | Test.java:41:8:41:42 | case |
+| Test.java:38:20:38:20 | s | Test.java:38:27:38:27 | s |
+| Test.java:38:27:38:27 | s | Test.java:38:27:38:36 | length(...) |
+| Test.java:38:27:38:36 | length(...) | Test.java:38:41:38:41 | 3 |
+| Test.java:38:27:38:41 | ... == ... | Test.java:39:10:39:40 | ; |
+| Test.java:38:27:38:41 | ... == ... | Test.java:41:8:41:42 | case |
+| Test.java:38:41:38:41 | 3 | Test.java:38:27:38:41 | ... == ... |
+| Test.java:39:10:39:19 | System.out | Test.java:39:29:39:38 | "Length 3" |
+| Test.java:39:10:39:39 | println(...) | Test.java:40:10:40:15 | break |
+| Test.java:39:10:39:40 | ; | Test.java:39:10:39:19 | System.out |
+| Test.java:39:29:39:38 | "Length 3" | Test.java:39:10:39:39 | println(...) |
+| Test.java:40:10:40:15 | break | Test.java:49:6:49:18 | switch (...) |
+| Test.java:41:8:41:42 | case | Test.java:41:20:41:20 | s |
+| Test.java:41:8:41:42 | case | Test.java:44:8:44:15 | default |
+| Test.java:41:20:41:20 | s | Test.java:41:27:41:27 | s |
+| Test.java:41:27:41:27 | s | Test.java:41:27:41:36 | length(...) |
+| Test.java:41:27:41:36 | length(...) | Test.java:41:41:41:41 | 5 |
+| Test.java:41:27:41:41 | ... == ... | Test.java:42:10:42:40 | ; |
+| Test.java:41:27:41:41 | ... == ... | Test.java:44:8:44:15 | default |
+| Test.java:41:41:41:41 | 5 | Test.java:41:27:41:41 | ... == ... |
+| Test.java:42:10:42:19 | System.out | Test.java:42:29:42:38 | "Length 5" |
+| Test.java:42:10:42:39 | println(...) | Test.java:43:10:43:15 | break |
+| Test.java:42:10:42:40 | ; | Test.java:42:10:42:19 | System.out |
+| Test.java:42:29:42:38 | "Length 5" | Test.java:42:10:42:39 | println(...) |
+| Test.java:43:10:43:15 | break | Test.java:49:6:49:18 | switch (...) |
+| Test.java:44:8:44:15 | default | Test.java:45:10:45:45 | ; |
+| Test.java:45:10:45:19 | System.out | Test.java:45:29:45:43 | "Anything else" |
+| Test.java:45:10:45:44 | println(...) | Test.java:46:10:46:15 | break |
+| Test.java:45:10:45:45 | ; | Test.java:45:10:45:19 | System.out |
+| Test.java:45:29:45:43 | "Anything else" | Test.java:45:10:45:44 | println(...) |
+| Test.java:46:10:46:15 | break | Test.java:49:6:49:18 | switch (...) |
+| Test.java:49:6:49:18 | switch (...) | Test.java:49:13:49:17 | thing |
+| Test.java:49:13:49:17 | thing | Test.java:50:8:50:44 | case |
+| Test.java:50:8:50:44 | case | Test.java:50:20:50:20 | s |
+| Test.java:50:8:50:44 | case | Test.java:51:8:51:44 | case |
+| Test.java:50:20:50:20 | s | Test.java:50:27:50:27 | s |
+| Test.java:50:27:50:27 | s | Test.java:50:27:50:36 | length(...) |
+| Test.java:50:27:50:36 | length(...) | Test.java:50:41:50:41 | 3 |
+| Test.java:50:27:50:41 | ... == ... | Test.java:50:46:50:55 | System.out |
+| Test.java:50:27:50:41 | ... == ... | Test.java:51:8:51:44 | case |
+| Test.java:50:41:50:41 | 3 | Test.java:50:27:50:41 | ... == ... |
+| Test.java:50:46:50:55 | System.out | Test.java:50:65:50:74 | "Length 3" |
+| Test.java:50:46:50:75 | println(...) | Test.java:55:6:55:26 | switch (...) |
+| Test.java:50:46:50:76 | ; | Test.java:50:46:50:55 | System.out |
+| Test.java:50:65:50:74 | "Length 3" | Test.java:50:46:50:75 | println(...) |
+| Test.java:51:8:51:44 | case | Test.java:51:20:51:20 | s |
+| Test.java:51:8:51:44 | case | Test.java:52:8:52:17 | default |
+| Test.java:51:20:51:20 | s | Test.java:51:27:51:27 | s |
+| Test.java:51:27:51:27 | s | Test.java:51:27:51:36 | length(...) |
+| Test.java:51:27:51:36 | length(...) | Test.java:51:41:51:41 | 5 |
+| Test.java:51:27:51:41 | ... == ... | Test.java:51:46:51:55 | System.out |
+| Test.java:51:27:51:41 | ... == ... | Test.java:52:8:52:17 | default |
+| Test.java:51:41:51:41 | 5 | Test.java:51:27:51:41 | ... == ... |
+| Test.java:51:46:51:55 | System.out | Test.java:51:65:51:74 | "Length 5" |
+| Test.java:51:46:51:75 | println(...) | Test.java:55:6:55:26 | switch (...) |
+| Test.java:51:46:51:76 | ; | Test.java:51:46:51:55 | System.out |
+| Test.java:51:65:51:74 | "Length 5" | Test.java:51:46:51:75 | println(...) |
+| Test.java:52:8:52:17 | default | Test.java:52:19:52:21 | { ... } |
+| Test.java:52:19:52:21 | { ... } | Test.java:55:6:55:26 | switch (...) |
+| Test.java:55:6:55:26 | switch (...) | Test.java:55:21:55:25 | thing |
+| Test.java:55:13:55:25 | (...)... | Test.java:56:8:56:21 | case ... |
+| Test.java:55:13:55:25 | (...)... | Test.java:58:8:58:21 | case ... |
+| Test.java:55:13:55:25 | (...)... | Test.java:61:8:61:42 | case |
+| Test.java:55:13:55:25 | (...)... | Test.java:69:8:69:26 | case null, default |
+| Test.java:55:21:55:25 | thing | Test.java:55:13:55:25 | (...)... |
+| Test.java:56:8:56:21 | case ... | Test.java:57:10:57:44 | ; |
+| Test.java:57:10:57:19 | System.out | Test.java:57:29:57:42 | "It's Const1!" |
+| Test.java:57:10:57:43 | println(...) | Test.java:58:8:58:21 | case ... |
+| Test.java:57:10:57:44 | ; | Test.java:57:10:57:19 | System.out |
+| Test.java:57:29:57:42 | "It's Const1!" | Test.java:57:10:57:43 | println(...) |
+| Test.java:58:8:58:21 | case ... | Test.java:59:10:59:54 | ; |
+| Test.java:59:10:59:19 | System.out | Test.java:59:29:59:52 | "It's Const1 or Const2!" |
+| Test.java:59:10:59:53 | println(...) | Test.java:60:10:60:15 | break |
+| Test.java:59:10:59:54 | ; | Test.java:59:10:59:19 | System.out |
+| Test.java:59:29:59:52 | "It's Const1 or Const2!" | Test.java:59:10:59:53 | println(...) |
+| Test.java:60:10:60:15 | break | Test.java:73:6:73:18 | switch (...) |
+| Test.java:61:8:61:42 | case | Test.java:61:20:61:20 | s |
+| Test.java:61:8:61:42 | case | Test.java:63:8:63:21 | case ... |
+| Test.java:61:8:61:42 | case | Test.java:66:8:66:22 | case ... |
+| Test.java:61:8:61:42 | case | Test.java:69:8:69:26 | case null, default |
+| Test.java:61:20:61:20 | s | Test.java:61:27:61:27 | s |
+| Test.java:61:27:61:27 | s | Test.java:61:27:61:36 | length(...) |
+| Test.java:61:27:61:36 | length(...) | Test.java:61:41:61:41 | 6 |
+| Test.java:61:27:61:41 | ... <= ... | Test.java:62:10:62:83 | ; |
+| Test.java:61:27:61:41 | ... <= ... | Test.java:63:8:63:21 | case ... |
+| Test.java:61:27:61:41 | ... <= ... | Test.java:66:8:66:22 | case ... |
+| Test.java:61:27:61:41 | ... <= ... | Test.java:69:8:69:26 | case null, default |
+| Test.java:61:41:61:41 | 6 | Test.java:61:27:61:41 | ... <= ... |
+| Test.java:62:10:62:19 | System.out | Test.java:62:29:62:81 | "It's <= 6 chars long, and neither Const1 nor Const2" |
+| Test.java:62:10:62:82 | println(...) | Test.java:63:8:63:21 | case ... |
+| Test.java:62:10:62:83 | ; | Test.java:62:10:62:19 | System.out |
+| Test.java:62:29:62:81 | "It's <= 6 chars long, and neither Const1 nor Const2" | Test.java:62:10:62:82 | println(...) |
+| Test.java:63:8:63:21 | case ... | Test.java:64:10:64:96 | ; |
+| Test.java:64:10:64:19 | System.out | Test.java:64:29:64:94 | "It's (<= 6 chars long, and neither Const1 nor Const2), or Const3" |
+| Test.java:64:10:64:95 | println(...) | Test.java:65:10:65:15 | break |
+| Test.java:64:10:64:96 | ; | Test.java:64:10:64:19 | System.out |
+| Test.java:64:29:64:94 | "It's (<= 6 chars long, and neither Const1 nor Const2), or Const3" | Test.java:64:10:64:95 | println(...) |
+| Test.java:65:10:65:15 | break | Test.java:73:6:73:18 | switch (...) |
+| Test.java:66:8:66:22 | case ... | Test.java:67:10:67:44 | ; |
+| Test.java:67:10:67:19 | System.out | Test.java:67:29:67:42 | "It's Const30" |
+| Test.java:67:10:67:43 | println(...) | Test.java:68:10:68:15 | break |
+| Test.java:67:10:67:44 | ; | Test.java:67:10:67:19 | System.out |
+| Test.java:67:29:67:42 | "It's Const30" | Test.java:67:10:67:43 | println(...) |
+| Test.java:68:10:68:15 | break | Test.java:73:6:73:18 | switch (...) |
+| Test.java:69:8:69:26 | case null, default | Test.java:70:10:70:60 | ; |
+| Test.java:70:10:70:19 | System.out | Test.java:70:29:70:58 | "It's null, or something else" |
+| Test.java:70:10:70:59 | println(...) | Test.java:73:6:73:18 | switch (...) |
+| Test.java:70:10:70:60 | ; | Test.java:70:10:70:19 | System.out |
+| Test.java:70:29:70:58 | "It's null, or something else" | Test.java:70:10:70:59 | println(...) |
+| Test.java:73:6:73:18 | switch (...) | Test.java:73:13:73:17 | thing |
+| Test.java:73:13:73:17 | thing | Test.java:74:8:74:21 | case |
+| Test.java:73:13:73:17 | thing | Test.java:77:8:77:17 | case ... |
+| Test.java:74:8:74:21 | case | Test.java:74:20:74:20 | s |
+| Test.java:74:8:74:21 | case | Test.java:80:8:80:22 | case |
+| Test.java:74:20:74:20 | s | Test.java:75:10:75:31 | ; |
+| Test.java:75:10:75:19 | System.out | Test.java:75:29:75:29 | s |
+| Test.java:75:10:75:30 | println(...) | Test.java:76:10:76:15 | break |
+| Test.java:75:10:75:31 | ; | Test.java:75:10:75:19 | System.out |
+| Test.java:75:29:75:29 | s | Test.java:75:10:75:30 | println(...) |
+| Test.java:76:10:76:15 | break | Test.java:87:6:87:18 | switch (...) |
+| Test.java:77:8:77:17 | case ... | Test.java:78:10:78:41 | ; |
+| Test.java:78:10:78:19 | System.out | Test.java:78:29:78:39 | "It's null" |
+| Test.java:78:10:78:40 | println(...) | Test.java:79:10:79:15 | break |
+| Test.java:78:10:78:41 | ; | Test.java:78:10:78:19 | System.out |
+| Test.java:78:29:78:39 | "It's null" | Test.java:78:10:78:40 | println(...) |
+| Test.java:79:10:79:15 | break | Test.java:87:6:87:18 | switch (...) |
+| Test.java:80:8:80:22 | case | Test.java:80:21:80:21 | i |
+| Test.java:80:8:80:22 | case | Test.java:83:8:83:15 | default |
+| Test.java:80:21:80:21 | i | Test.java:81:10:81:47 | ; |
+| Test.java:81:10:81:19 | System.out | Test.java:81:29:81:41 | "An integer:" |
+| Test.java:81:10:81:46 | println(...) | Test.java:82:10:82:15 | break |
+| Test.java:81:10:81:47 | ; | Test.java:81:10:81:19 | System.out |
+| Test.java:81:29:81:41 | "An integer:" | Test.java:81:45:81:45 | i |
+| Test.java:81:29:81:45 | ... + ... | Test.java:81:10:81:46 | println(...) |
+| Test.java:81:45:81:45 | i | Test.java:81:29:81:45 | ... + ... |
+| Test.java:82:10:82:15 | break | Test.java:87:6:87:18 | switch (...) |
+| Test.java:83:8:83:15 | default | Test.java:84:10:84:15 | break |
+| Test.java:84:10:84:15 | break | Test.java:87:6:87:18 | switch (...) |
+| Test.java:87:6:87:18 | switch (...) | Test.java:87:13:87:17 | thing |
+| Test.java:87:13:87:17 | thing | Test.java:88:8:88:43 | case |
+| Test.java:88:8:88:43 | case | Test.java:88:21:88:21 | x |
+| Test.java:88:8:88:43 | case | Test.java:90:8:90:15 | default |
+| Test.java:88:13:88:42 | A(...) | Test.java:89:10:89:15 | break |
+| Test.java:88:15:88:32 | B(...) | Test.java:88:41:88:41 | z |
+| Test.java:88:21:88:21 | x | Test.java:88:31:88:31 | y |
+| Test.java:88:31:88:31 | y | Test.java:88:15:88:32 | B(...) |
+| Test.java:88:41:88:41 | z | Test.java:88:13:88:42 | A(...) |
+| Test.java:89:10:89:15 | break | Test.java:94:6:94:18 | switch (...) |
+| Test.java:90:8:90:15 | default | Test.java:91:10:91:15 | break |
+| Test.java:91:10:91:15 | break | Test.java:94:6:94:18 | switch (...) |
+| Test.java:94:6:94:18 | switch (...) | Test.java:94:13:94:17 | thing |
+| Test.java:94:13:94:17 | thing | Test.java:95:8:95:38 | case |
+| Test.java:95:8:95:38 | case | Test.java:95:21:95:21 | x |
+| Test.java:95:8:95:38 | case | Test.java:97:8:97:15 | default |
+| Test.java:95:13:95:37 | A(...) | Test.java:96:10:96:15 | break |
+| Test.java:95:15:95:29 | B(...) | Test.java:95:36:95:36 | z |
+| Test.java:95:21:95:21 | x | Test.java:95:28:95:28 | y |
+| Test.java:95:28:95:28 | y | Test.java:95:15:95:29 | B(...) |
+| Test.java:95:36:95:36 | z | Test.java:95:13:95:37 | A(...) |
+| Test.java:96:10:96:15 | break | Test.java:3:22:3:25 | test |
+| Test.java:97:8:97:15 | default | Test.java:98:10:98:15 | break |
+| Test.java:98:10:98:15 | break | Test.java:3:22:3:25 | test |
+| Test.java:105:8:105:8 | ...=... | Test.java:105:8:105:8 | ; |
+| Test.java:105:8:105:8 | ...=... | Test.java:105:8:105:8 | A |
+| Test.java:105:8:105:8 | ; | Test.java:105:8:105:8 | this |
+| Test.java:105:8:105:8 | ; | Test.java:105:8:105:8 | this |
+| Test.java:105:8:105:8 | b | Test.java:105:8:105:8 | ...=... |
+| Test.java:105:8:105:8 | field3 | Test.java:105:8:105:8 | ...=... |
+| Test.java:105:8:105:8 | super(...) | Test.java:105:8:105:8 | ; |
+| Test.java:105:8:105:8 | this | Test.java:105:8:105:8 | b |
+| Test.java:105:8:105:8 | this | Test.java:105:8:105:8 | field3 |
+| Test.java:105:8:105:8 | { ... } | Test.java:105:8:105:8 | super(...) |
+| Test.java:106:8:106:8 | ...=... | Test.java:106:8:106:8 | ; |
+| Test.java:106:8:106:8 | ...=... | Test.java:106:8:106:8 | B |
+| Test.java:106:8:106:8 | ; | Test.java:106:8:106:8 | this |
+| Test.java:106:8:106:8 | ; | Test.java:106:8:106:8 | this |
+| Test.java:106:8:106:8 | field1 | Test.java:106:8:106:8 | ...=... |
+| Test.java:106:8:106:8 | field2 | Test.java:106:8:106:8 | ...=... |
+| Test.java:106:8:106:8 | super(...) | Test.java:106:8:106:8 | ; |
+| Test.java:106:8:106:8 | this | Test.java:106:8:106:8 | field1 |
+| Test.java:106:8:106:8 | this | Test.java:106:8:106:8 | field2 |
+| Test.java:106:8:106:8 | { ... } | Test.java:106:8:106:8 | super(...) |
diff --git a/java/ql/test/library-tests/pattern-switch/cfg/test.ql b/java/ql/test/library-tests/pattern-switch/cfg/test.ql
new file mode 100644
index 00000000000..4511277ee7d
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/cfg/test.ql
@@ -0,0 +1,5 @@
+import java
+
+from ControlFlowNode cn
+where cn.getFile().getBaseName() = ["Test.java", "Exhaustive.java"]
+select cn, cn.getASuccessor()
diff --git a/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java b/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java
new file mode 100644
index 00000000000..d0550e4b695
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/dfg/GuardTest.java
@@ -0,0 +1,30 @@
+public class GuardTest {
+
+ public static void sink(String s) { }
+ public static boolean isSafe(String s) { return s.length() < 10; }
+
+ public static void test(Object o) {
+
+ switch (o) {
+
+ case String s:
+ sink(s);
+ break;
+ default:
+ break;
+
+ }
+
+ switch (o) {
+
+ case String s when isSafe(s):
+ sink(s);
+ break;
+ default:
+ break;
+
+ }
+
+ }
+
+}
diff --git a/java/ql/test/library-tests/pattern-switch/dfg/RecordTest.java b/java/ql/test/library-tests/pattern-switch/dfg/RecordTest.java
new file mode 100644
index 00000000000..608e173dd57
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/dfg/RecordTest.java
@@ -0,0 +1,48 @@
+public class RecordTest {
+
+ interface I { }
+ record Middle(String field) { }
+ record A(Middle afield) implements I { }
+ record B(Middle bfield) implements I { }
+
+ public static String sink(String s) { return s; }
+
+ public static void test(boolean inp) {
+
+ I i = inp ? new A(new Middle("A")) : new B(new Middle("B"));
+
+ switch(i) {
+ case A(Middle(String field)):
+ sink(field);
+ break;
+ case B(Middle(String field)):
+ sink(field);
+ break;
+ default:
+ break;
+ }
+
+ switch(i) {
+ case A(Middle(String field)) -> sink(field);
+ case B(Middle(String field)) -> sink(field);
+ default -> { }
+ }
+
+ var x = switch(i) {
+ case A(Middle(String field)):
+ yield sink(field);
+ case B(Middle(String field)):
+ yield sink(field);
+ default:
+ yield "Default case";
+ };
+
+ var y = switch(i) {
+ case A(Middle(String field)) -> sink(field);
+ case B(Middle(String field)) -> sink(field);
+ default -> "Default case";
+ };
+
+ }
+
+}
diff --git a/java/ql/test/library-tests/pattern-switch/dfg/Test.java b/java/ql/test/library-tests/pattern-switch/dfg/Test.java
new file mode 100644
index 00000000000..49271b0aa85
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/dfg/Test.java
@@ -0,0 +1,47 @@
+public class Test {
+
+ interface I { String get(); }
+ static class A implements I { public String afield; public A(String a) { this.afield = a; } public String get() { return afield; } }
+ static class B implements I { public String bfield; public B(String b) { this.bfield = b; } public String get() { return bfield; } }
+
+ public static String sink(String s) { return s; }
+
+ public static void test(boolean inp) {
+
+ I i = inp ? new A("A") : new B("B");
+
+ switch(i) {
+ case A a:
+ sink(a.get());
+ break;
+ case B b:
+ sink(b.get());
+ break;
+ default:
+ break;
+ }
+
+ switch(i) {
+ case A a -> sink(a.get());
+ case B b -> sink(b.get());
+ default -> { }
+ }
+
+ var x = switch(i) {
+ case A a:
+ yield sink(a.get());
+ case B b:
+ yield sink(b.get());
+ default:
+ yield "Default case";
+ };
+
+ var y = switch(i) {
+ case A a -> sink(a.get());
+ case B b -> sink(b.get());
+ default -> "Default case";
+ };
+
+ }
+
+}
diff --git a/java/ql/test/library-tests/pattern-switch/dfg/options b/java/ql/test/library-tests/pattern-switch/dfg/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/dfg/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.expected b/java/ql/test/library-tests/pattern-switch/dfg/test.expected
new file mode 100644
index 00000000000..29e53bbd346
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/dfg/test.expected
@@ -0,0 +1,17 @@
+| GuardTest.java:6:27:6:34 | o | GuardTest.java:11:14:11:14 | s |
+| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:16:14:16:18 | field |
+| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:26:44:26:48 | field |
+| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:33:20:33:24 | field |
+| RecordTest.java:12:34:12:36 | "A" | RecordTest.java:41:44:41:48 | field |
+| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:19:14:19:18 | field |
+| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:27:44:27:48 | field |
+| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:35:20:35:24 | field |
+| RecordTest.java:12:59:12:61 | "B" | RecordTest.java:42:44:42:48 | field |
+| Test.java:11:23:11:25 | "A" | Test.java:15:14:15:20 | get(...) |
+| Test.java:11:23:11:25 | "A" | Test.java:25:24:25:30 | get(...) |
+| Test.java:11:23:11:25 | "A" | Test.java:32:20:32:26 | get(...) |
+| Test.java:11:23:11:25 | "A" | Test.java:40:24:40:30 | get(...) |
+| Test.java:11:36:11:38 | "B" | Test.java:18:14:18:20 | get(...) |
+| Test.java:11:36:11:38 | "B" | Test.java:26:24:26:30 | get(...) |
+| Test.java:11:36:11:38 | "B" | Test.java:34:20:34:26 | get(...) |
+| Test.java:11:36:11:38 | "B" | Test.java:41:24:41:30 | get(...) |
diff --git a/java/ql/test/library-tests/pattern-switch/dfg/test.ql b/java/ql/test/library-tests/pattern-switch/dfg/test.ql
new file mode 100644
index 00000000000..3e76b82f221
--- /dev/null
+++ b/java/ql/test/library-tests/pattern-switch/dfg/test.ql
@@ -0,0 +1,31 @@
+import java
+import semmle.code.java.controlflow.Guards
+import semmle.code.java.dataflow.DataFlow
+
+private predicate isSafe(Guard g, Expr checked, boolean branch) {
+ exists(MethodCall mc | g = mc |
+ mc.getMethod().hasName("isSafe") and
+ checked = mc.getAnArgument() and
+ branch = true
+ )
+}
+
+module TestConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof StringLiteral or source.asParameter().getCallable().hasName("test")
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
+ }
+
+ predicate isBarrier(DataFlow::Node node) {
+ node = DataFlow::BarrierGuard::getABarrierNode()
+ }
+}
+
+module Flow = DataFlow::Global;
+
+from DataFlow::Node source, DataFlow::Node sink
+where Flow::flow(source, sink)
+select source, sink
diff --git a/java/ql/test/library-tests/prettyprint/Test.java b/java/ql/test/library-tests/prettyprint/Test.java
new file mode 100644
index 00000000000..c643955751e
--- /dev/null
+++ b/java/ql/test/library-tests/prettyprint/Test.java
@@ -0,0 +1,50 @@
+public class Test {
+
+ record S(int x) { }
+ record R(S s, String y) { }
+
+ public static void test(Object o) {
+
+ switch(o) {
+ case String s:
+ break;
+ case R(S(int x), String y):
+ break;
+ default:
+ break;
+ }
+
+ switch(o) {
+ case String s -> { }
+ case R(S(int x), String y) -> { }
+ case null, default -> { }
+ }
+
+ var a = switch(o) {
+ case String s:
+ yield 1;
+ case R(S(int x), String y):
+ yield x;
+ case null, default:
+ yield 2;
+ };
+
+ var b = switch(o) {
+ case String s -> 1;
+ case R(S(int x), String y) -> x;
+ default -> 2;
+ };
+
+ if (o instanceof String s) { }
+ if (o instanceof R(S(int x), String y)) { }
+
+ switch(o) {
+ case R(S(var x), var y) -> { }
+ case null, default -> { }
+ }
+
+ if (o instanceof R(S(var x), var y)) { }
+
+ }
+
+}
diff --git a/java/ql/test/library-tests/prettyprint/options b/java/ql/test/library-tests/prettyprint/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/prettyprint/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/prettyprint/pp.expected b/java/ql/test/library-tests/prettyprint/pp.expected
new file mode 100644
index 00000000000..eb4fbaf0633
--- /dev/null
+++ b/java/ql/test/library-tests/prettyprint/pp.expected
@@ -0,0 +1,88 @@
+| Test.java:1:14:1:17 | Test | 0 | public class Test { |
+| Test.java:1:14:1:17 | Test | 1 | public Test() { |
+| Test.java:1:14:1:17 | Test | 2 | super(); |
+| Test.java:1:14:1:17 | Test | 3 | } |
+| Test.java:1:14:1:17 | Test | 4 | |
+| Test.java:1:14:1:17 | Test | 5 | static final class S { |
+| Test.java:1:14:1:17 | Test | 6 | public final boolean equals(Object p0) { } |
+| Test.java:1:14:1:17 | Test | 7 | |
+| Test.java:1:14:1:17 | Test | 8 | public final int hashCode() { } |
+| Test.java:1:14:1:17 | Test | 9 | |
+| Test.java:1:14:1:17 | Test | 10 | public final String toString() { } |
+| Test.java:1:14:1:17 | Test | 11 | |
+| Test.java:1:14:1:17 | Test | 12 | public int x() { } |
+| Test.java:1:14:1:17 | Test | 13 | |
+| Test.java:1:14:1:17 | Test | 14 | S(int x) { |
+| Test.java:1:14:1:17 | Test | 15 | super(); |
+| Test.java:1:14:1:17 | Test | 16 | this.x = x; |
+| Test.java:1:14:1:17 | Test | 17 | } |
+| Test.java:1:14:1:17 | Test | 18 | |
+| Test.java:1:14:1:17 | Test | 19 | private final int x; |
+| Test.java:1:14:1:17 | Test | 20 | } |
+| Test.java:1:14:1:17 | Test | 21 | |
+| Test.java:1:14:1:17 | Test | 22 | static final class R { |
+| Test.java:1:14:1:17 | Test | 23 | public final boolean equals(Object p0) { } |
+| Test.java:1:14:1:17 | Test | 24 | |
+| Test.java:1:14:1:17 | Test | 25 | public final int hashCode() { } |
+| Test.java:1:14:1:17 | Test | 26 | |
+| Test.java:1:14:1:17 | Test | 27 | public S s() { } |
+| Test.java:1:14:1:17 | Test | 28 | |
+| Test.java:1:14:1:17 | Test | 29 | public final String toString() { } |
+| Test.java:1:14:1:17 | Test | 30 | |
+| Test.java:1:14:1:17 | Test | 31 | public String y() { } |
+| Test.java:1:14:1:17 | Test | 32 | |
+| Test.java:1:14:1:17 | Test | 33 | R(S s, String y) { |
+| Test.java:1:14:1:17 | Test | 34 | super(); |
+| Test.java:1:14:1:17 | Test | 35 | this.s = s; |
+| Test.java:1:14:1:17 | Test | 36 | this.y = y; |
+| Test.java:1:14:1:17 | Test | 37 | } |
+| Test.java:1:14:1:17 | Test | 38 | |
+| Test.java:1:14:1:17 | Test | 39 | private final S s; |
+| Test.java:1:14:1:17 | Test | 40 | |
+| Test.java:1:14:1:17 | Test | 41 | private final String y; |
+| Test.java:1:14:1:17 | Test | 42 | } |
+| Test.java:1:14:1:17 | Test | 43 | |
+| Test.java:1:14:1:17 | Test | 44 | public static void test(Object o) { |
+| Test.java:1:14:1:17 | Test | 45 | switch (o) { |
+| Test.java:1:14:1:17 | Test | 46 | case String s: |
+| Test.java:1:14:1:17 | Test | 47 | break; |
+| Test.java:1:14:1:17 | Test | 48 | case R(S(int x), String y): |
+| Test.java:1:14:1:17 | Test | 49 | break; |
+| Test.java:1:14:1:17 | Test | 50 | default: |
+| Test.java:1:14:1:17 | Test | 51 | break; |
+| Test.java:1:14:1:17 | Test | 52 | } |
+| Test.java:1:14:1:17 | Test | 53 | switch (o) { |
+| Test.java:1:14:1:17 | Test | 54 | case String s -> { |
+| Test.java:1:14:1:17 | Test | 55 | } |
+| Test.java:1:14:1:17 | Test | 56 | case R(S(int x), String y) -> { |
+| Test.java:1:14:1:17 | Test | 57 | } |
+| Test.java:1:14:1:17 | Test | 58 | case null, default -> { |
+| Test.java:1:14:1:17 | Test | 59 | } |
+| Test.java:1:14:1:17 | Test | 60 | } |
+| Test.java:1:14:1:17 | Test | 61 | var a = switch (o) { |
+| Test.java:1:14:1:17 | Test | 62 | case String s: |
+| Test.java:1:14:1:17 | Test | 63 | yield 1; |
+| Test.java:1:14:1:17 | Test | 64 | case R(S(int x), String y): |
+| Test.java:1:14:1:17 | Test | 65 | yield x; |
+| Test.java:1:14:1:17 | Test | 66 | case null, default: |
+| Test.java:1:14:1:17 | Test | 67 | yield 2; |
+| Test.java:1:14:1:17 | Test | 68 | }; |
+| Test.java:1:14:1:17 | Test | 69 | var b = switch (o) { |
+| Test.java:1:14:1:17 | Test | 70 | case String s -> 1; |
+| Test.java:1:14:1:17 | Test | 71 | case R(S(int x), String y) -> x; |
+| Test.java:1:14:1:17 | Test | 72 | default -> 2; |
+| Test.java:1:14:1:17 | Test | 73 | }; |
+| Test.java:1:14:1:17 | Test | 74 | if (o instanceof String s) { |
+| Test.java:1:14:1:17 | Test | 75 | } |
+| Test.java:1:14:1:17 | Test | 76 | if (o instanceof R(S(int x), String y)) { |
+| Test.java:1:14:1:17 | Test | 77 | } |
+| Test.java:1:14:1:17 | Test | 78 | switch (o) { |
+| Test.java:1:14:1:17 | Test | 79 | case R(S(var x), var y) -> { |
+| Test.java:1:14:1:17 | Test | 80 | } |
+| Test.java:1:14:1:17 | Test | 81 | case null, default -> { |
+| Test.java:1:14:1:17 | Test | 82 | } |
+| Test.java:1:14:1:17 | Test | 83 | } |
+| Test.java:1:14:1:17 | Test | 84 | if (o instanceof R(S(var x), var y)) { |
+| Test.java:1:14:1:17 | Test | 85 | } |
+| Test.java:1:14:1:17 | Test | 86 | } |
+| Test.java:1:14:1:17 | Test | 87 | } |
diff --git a/java/ql/test/library-tests/prettyprint/pp.ql b/java/ql/test/library-tests/prettyprint/pp.ql
new file mode 100644
index 00000000000..baa92fc6a86
--- /dev/null
+++ b/java/ql/test/library-tests/prettyprint/pp.ql
@@ -0,0 +1,5 @@
+import semmle.code.java.PrettyPrintAst
+
+from ClassOrInterface cori, string s, int line
+where pp(cori, s, line) and cori.fromSource()
+select cori, line, s order by line
diff --git a/java/ql/test/library-tests/printAst/A.java b/java/ql/test/library-tests/printAst/A.java
index 4cf4e8699da..219fe13c5f1 100644
--- a/java/ql/test/library-tests/printAst/A.java
+++ b/java/ql/test/library-tests/printAst/A.java
@@ -50,6 +50,61 @@ class A {
if (thing instanceof String s) {
throw new RuntimeException(s);
}
+ switch (thing) {
+ case String s -> System.out.println(s);
+ case Integer i -> System.out.println("An integer: " + i);
+ default -> { }
+ }
+ switch (thing) {
+ case String s:
+ System.out.println(s);
+ break;
+ case Integer i:
+ System.out.println("An integer:" + i);
+ break;
+ default:
+ break;
+ }
+ var thingAsString = switch(thing) {
+ case String s -> s;
+ case Integer i -> "An integer: " + i;
+ default -> "Something else";
+ };
+ var thingAsString2 = switch(thing) {
+ case String s:
+ yield s;
+ case Integer i:
+ yield "An integer: " + i;
+ default:
+ yield "Something else";
+ };
+ var nullTest = switch(thing) {
+ case null -> "Null";
+ default -> "Not null";
+ };
+ var whenTest = switch((String)thing) {
+ case "constant" -> "It's constant";
+ case String s when s.length() == 3 -> "It's 3 letters long";
+ case String s when s.length() == 5 -> "it's 5 letters long";
+ default -> "It's something else";
+ };
+ var nullDefaultTest = switch(thing) {
+ case String s -> "It's a string";
+ case null, default -> "It's something else";
+ };
+ var qualifiedEnumTest = switch(thing) {
+ case E.A -> "It's E.A";
+ default -> "It's something else";
+ };
+ var unnecessaryQualifiedEnumTest = switch((E)thing) {
+ case A -> "It's E.A";
+ case E.B -> "It's E.B";
+ default -> "It's something else";
+ };
+ var recordPatterntest = switch(thing) {
+ case Middle(Inner(String field)) -> field;
+ default -> "Doesn't match pattern Middle(Inner(...))";
+ };
}
}
catch (RuntimeException rte) {
@@ -70,4 +125,7 @@ class A {
* Javadoc for fields
*/
int i, j, k;
-}
\ No newline at end of file
+}
+
+record Inner(String field) { }
+record Middle(Inner inner) { }
diff --git a/java/ql/test/library-tests/printAst/PrintAst.expected b/java/ql/test/library-tests/printAst/PrintAst.expected
index 219889b35f6..be523390620 100644
--- a/java/ql/test/library-tests/printAst/PrintAst.expected
+++ b/java/ql/test/library-tests/printAst/PrintAst.expected
@@ -110,45 +110,225 @@ A.java:
# 48| 0: [ReturnStmt] return ...
# 50| 1: [IfStmt] if (...)
# 50| 0: [InstanceOfExpr] ...instanceof...
-#-----| 0: (Single Local Variable Declaration)
+# 50| 0: [VarAccess] thing
+#-----| 2: (Single Local Variable Declaration)
# 50| 0: [TypeAccess] String
# 50| 1: [LocalVariableDeclExpr] s
-# 50| 0: [VarAccess] thing
# 50| 1: [BlockStmt] { ... }
# 51| 0: [ThrowStmt] throw ...
# 51| 0: [ClassInstanceExpr] new RuntimeException(...)
# 51| -3: [TypeAccess] RuntimeException
# 51| 0: [VarAccess] s
-# 55| 0: [CatchClause] catch (...)
+# 53| 2: [SwitchStmt] switch (...)
+# 53| -1: [VarAccess] thing
+# 54| 0: [PatternCase] case
+# 54| -1: [ExprStmt] ;
+# 54| 0: [MethodCall] println(...)
+# 54| -1: [VarAccess] System.out
+# 54| -1: [TypeAccess] System
+# 54| 0: [VarAccess] s
+#-----| 0: (Single Local Variable Declaration)
+# 54| 0: [TypeAccess] String
+# 54| 1: [LocalVariableDeclExpr] s
+# 55| 1: [PatternCase] case
+# 55| -1: [ExprStmt] ;
+# 55| 0: [MethodCall] println(...)
+# 55| -1: [VarAccess] System.out
+# 55| -1: [TypeAccess] System
+# 55| 0: [AddExpr] ... + ...
+# 55| 0: [StringLiteral] "An integer: "
+# 55| 1: [VarAccess] i
+#-----| 0: (Single Local Variable Declaration)
+# 55| 0: [TypeAccess] Integer
+# 55| 1: [LocalVariableDeclExpr] i
+# 56| 2: [DefaultCase] default
+# 56| -1: [BlockStmt] { ... }
+# 58| 3: [SwitchStmt] switch (...)
+# 58| -1: [VarAccess] thing
+# 59| 0: [PatternCase] case
+#-----| 0: (Single Local Variable Declaration)
+# 59| 0: [TypeAccess] String
+# 59| 1: [LocalVariableDeclExpr] s
+# 60| 1: [ExprStmt] ;
+# 60| 0: [MethodCall] println(...)
+# 60| -1: [VarAccess] System.out
+# 60| -1: [TypeAccess] System
+# 60| 0: [VarAccess] s
+# 61| 2: [BreakStmt] break
+# 62| 3: [PatternCase] case
+#-----| 0: (Single Local Variable Declaration)
+# 62| 0: [TypeAccess] Integer
+# 62| 1: [LocalVariableDeclExpr] i
+# 63| 4: [ExprStmt] ;
+# 63| 0: [MethodCall] println(...)
+# 63| -1: [VarAccess] System.out
+# 63| -1: [TypeAccess] System
+# 63| 0: [AddExpr] ... + ...
+# 63| 0: [StringLiteral] "An integer:"
+# 63| 1: [VarAccess] i
+# 64| 5: [BreakStmt] break
+# 65| 6: [DefaultCase] default
+# 66| 7: [BreakStmt] break
+# 68| 4: [LocalVariableDeclStmt] var ...;
+# 68| 1: [LocalVariableDeclExpr] thingAsString
+# 68| 0: [SwitchExpr] switch (...)
+# 68| -1: [VarAccess] thing
+# 69| 0: [PatternCase] case
+# 69| -1: [VarAccess] s
+#-----| 0: (Single Local Variable Declaration)
+# 69| 0: [TypeAccess] String
+# 69| 1: [LocalVariableDeclExpr] s
+# 70| 1: [PatternCase] case
+# 70| -1: [AddExpr] ... + ...
+# 70| 0: [StringLiteral] "An integer: "
+# 70| 1: [VarAccess] i
+#-----| 0: (Single Local Variable Declaration)
+# 70| 0: [TypeAccess] Integer
+# 70| 1: [LocalVariableDeclExpr] i
+# 71| 2: [DefaultCase] default
+# 71| -1: [StringLiteral] "Something else"
+# 73| 5: [LocalVariableDeclStmt] var ...;
+# 73| 1: [LocalVariableDeclExpr] thingAsString2
+# 73| 0: [SwitchExpr] switch (...)
+# 73| -1: [VarAccess] thing
+# 74| 0: [PatternCase] case
+#-----| 0: (Single Local Variable Declaration)
+# 74| 0: [TypeAccess] String
+# 74| 1: [LocalVariableDeclExpr] s
+# 75| 1: [YieldStmt] yield ...
+# 75| 0: [VarAccess] s
+# 76| 2: [PatternCase] case
+#-----| 0: (Single Local Variable Declaration)
+# 76| 0: [TypeAccess] Integer
+# 76| 1: [LocalVariableDeclExpr] i
+# 77| 3: [YieldStmt] yield ...
+# 77| 0: [AddExpr] ... + ...
+# 77| 0: [StringLiteral] "An integer: "
+# 77| 1: [VarAccess] i
+# 78| 4: [DefaultCase] default
+# 79| 5: [YieldStmt] yield ...
+# 79| 0: [StringLiteral] "Something else"
+# 81| 6: [LocalVariableDeclStmt] var ...;
+# 81| 1: [LocalVariableDeclExpr] nullTest
+# 81| 0: [SwitchExpr] switch (...)
+# 81| -1: [VarAccess] thing
+# 82| 0: [ConstCase] case ...
+# 82| -1: [StringLiteral] "Null"
+# 82| 0: [NullLiteral] null
+# 83| 1: [DefaultCase] default
+# 83| -1: [StringLiteral] "Not null"
+# 85| 7: [LocalVariableDeclStmt] var ...;
+# 85| 1: [LocalVariableDeclExpr] whenTest
+# 85| 0: [SwitchExpr] switch (...)
+# 85| -1: [CastExpr] (...)...
+# 85| 0: [TypeAccess] String
+# 85| 1: [VarAccess] thing
+# 86| 0: [ConstCase] case ...
+# 86| -1: [StringLiteral] "It's constant"
+# 86| 0: [StringLiteral] "constant"
+# 87| 1: [PatternCase] case
+# 87| -3: [EQExpr] ... == ...
+# 87| 0: [MethodCall] length(...)
+# 87| -1: [VarAccess] s
+# 87| 1: [IntegerLiteral] 3
+# 87| -1: [StringLiteral] "It's 3 letters long"
+#-----| 0: (Single Local Variable Declaration)
+# 87| 0: [TypeAccess] String
+# 87| 1: [LocalVariableDeclExpr] s
+# 88| 2: [PatternCase] case
+# 88| -3: [EQExpr] ... == ...
+# 88| 0: [MethodCall] length(...)
+# 88| -1: [VarAccess] s
+# 88| 1: [IntegerLiteral] 5
+# 88| -1: [StringLiteral] "it's 5 letters long"
+#-----| 0: (Single Local Variable Declaration)
+# 88| 0: [TypeAccess] String
+# 88| 1: [LocalVariableDeclExpr] s
+# 89| 3: [DefaultCase] default
+# 89| -1: [StringLiteral] "It's something else"
+# 91| 8: [LocalVariableDeclStmt] var ...;
+# 91| 1: [LocalVariableDeclExpr] nullDefaultTest
+# 91| 0: [SwitchExpr] switch (...)
+# 91| -1: [VarAccess] thing
+# 92| 0: [PatternCase] case
+# 92| -1: [StringLiteral] "It's a string"
+#-----| 0: (Single Local Variable Declaration)
+# 92| 0: [TypeAccess] String
+# 92| 1: [LocalVariableDeclExpr] s
+# 93| 1: [NullDefaultCase] case null, default
+# 93| -1: [StringLiteral] "It's something else"
+# 93| 0: [NullLiteral] null
+# 95| 9: [LocalVariableDeclStmt] var ...;
+# 95| 1: [LocalVariableDeclExpr] qualifiedEnumTest
+# 95| 0: [SwitchExpr] switch (...)
+# 95| -1: [VarAccess] thing
+# 96| 0: [ConstCase] case ...
+# 96| -1: [StringLiteral] "It's E.A"
+# 96| 0: [VarAccess] E.A
+# 96| -1: [TypeAccess] E
+# 97| 1: [DefaultCase] default
+# 97| -1: [StringLiteral] "It's something else"
+# 99| 10: [LocalVariableDeclStmt] var ...;
+# 99| 1: [LocalVariableDeclExpr] unnecessaryQualifiedEnumTest
+# 99| 0: [SwitchExpr] switch (...)
+# 99| -1: [CastExpr] (...)...
+# 99| 0: [TypeAccess] E
+# 99| 1: [VarAccess] thing
+# 100| 0: [ConstCase] case ...
+# 100| -1: [StringLiteral] "It's E.A"
+# 100| 0: [VarAccess] A
+# 101| 1: [ConstCase] case ...
+# 101| -1: [StringLiteral] "It's E.B"
+# 101| 0: [VarAccess] E.B
+# 101| -1: [TypeAccess] E
+# 102| 2: [DefaultCase] default
+# 102| -1: [StringLiteral] "It's something else"
+# 104| 11: [LocalVariableDeclStmt] var ...;
+# 104| 1: [LocalVariableDeclExpr] recordPatterntest
+# 104| 0: [SwitchExpr] switch (...)
+# 104| -1: [VarAccess] thing
+# 105| 0: [PatternCase] case
+# 105| -1: [VarAccess] field
+# 105| 0: [RecordPatternExpr] Middle(...)
+# 105| 0: [RecordPatternExpr] Inner(...)
+# 105| -1: [TypeAccess] String
+# 105| 0: [LocalVariableDeclExpr] field
+# 106| 1: [DefaultCase] default
+# 106| -1: [StringLiteral] "Doesn't match pattern Middle(Inner(...))"
+# 110| 0: [CatchClause] catch (...)
#-----| 0: (Single Local Variable Declaration)
-# 55| 0: [TypeAccess] RuntimeException
-# 55| 1: [LocalVariableDeclExpr] rte
-# 55| 1: [BlockStmt] { ... }
-# 56| 0: [ReturnStmt] return ...
-# 60| 10: [Class] E
-# 64| 3: [FieldDeclaration] E A;
+# 110| 0: [TypeAccess] RuntimeException
+# 110| 1: [LocalVariableDeclExpr] rte
+# 110| 1: [BlockStmt] { ... }
+# 111| 0: [ReturnStmt] return ...
+# 115| 10: [Class] E
+# 119| 3: [FieldDeclaration] E A;
#-----| -3: (Javadoc)
-# 61| 1: [Javadoc] /** Javadoc for enum constant */
-# 62| 0: [JavadocText] Javadoc for enum constant
-# 64| -1: [TypeAccess] E
-# 64| 0: [ClassInstanceExpr] new E(...)
-# 64| -3: [TypeAccess] E
-# 65| 4: [FieldDeclaration] E B;
+# 116| 1: [Javadoc] /** Javadoc for enum constant */
+# 117| 0: [JavadocText] Javadoc for enum constant
+# 119| -1: [TypeAccess] E
+# 119| 0: [ClassInstanceExpr] new E(...)
+# 119| -3: [TypeAccess] E
+# 120| 4: [FieldDeclaration] E B;
#-----| -3: (Javadoc)
-# 61| 1: [Javadoc] /** Javadoc for enum constant */
-# 62| 0: [JavadocText] Javadoc for enum constant
-# 65| -1: [TypeAccess] E
-# 65| 0: [ClassInstanceExpr] new E(...)
-# 65| -3: [TypeAccess] E
-# 66| 5: [FieldDeclaration] E C;
+# 116| 1: [Javadoc] /** Javadoc for enum constant */
+# 117| 0: [JavadocText] Javadoc for enum constant
+# 120| -1: [TypeAccess] E
+# 120| 0: [ClassInstanceExpr] new E(...)
+# 120| -3: [TypeAccess] E
+# 121| 5: [FieldDeclaration] E C;
#-----| -3: (Javadoc)
-# 61| 1: [Javadoc] /** Javadoc for enum constant */
-# 62| 0: [JavadocText] Javadoc for enum constant
-# 66| -1: [TypeAccess] E
-# 66| 0: [ClassInstanceExpr] new E(...)
-# 66| -3: [TypeAccess] E
-# 72| 11: [FieldDeclaration] int i, ...;
+# 116| 1: [Javadoc] /** Javadoc for enum constant */
+# 117| 0: [JavadocText] Javadoc for enum constant
+# 121| -1: [TypeAccess] E
+# 121| 0: [ClassInstanceExpr] new E(...)
+# 121| -3: [TypeAccess] E
+# 127| 11: [FieldDeclaration] int i, ...;
#-----| -3: (Javadoc)
-# 69| 1: [Javadoc] /** Javadoc for fields */
-# 70| 0: [JavadocText] Javadoc for fields
-# 72| -1: [TypeAccess] int
+# 124| 1: [Javadoc] /** Javadoc for fields */
+# 125| 0: [JavadocText] Javadoc for fields
+# 127| -1: [TypeAccess] int
+# 130| 2: [Class] Inner
+# 130| 2: [FieldDeclaration] String field;
+# 131| 3: [Class] Middle
+# 131| 2: [FieldDeclaration] Inner inner;
diff --git a/java/ql/test/library-tests/printAst/options b/java/ql/test/library-tests/printAst/options
index a2f4d45311b..a0d1b7e7002 100644
--- a/java/ql/test/library-tests/printAst/options
+++ b/java/ql/test/library-tests/printAst/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args -source 17 -target 17
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java
new file mode 100644
index 00000000000..cf6e67e5847
--- /dev/null
+++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java
@@ -0,0 +1,63 @@
+public class Test {
+
+ public static int source() { return 0; }
+ public static void sink(int x) { }
+
+ interface I { void take(int x); }
+ static class C1 implements I { public void take(int x) { sink(x); } }
+ static class C2 implements I { public void take(int x) { sink(x); } }
+ record Wrapper(Object o) implements I { public void take(int x) { sink(x); } }
+ record WrapperWrapper(Wrapper w) implements I { public void take(int x) { sink(x); } }
+
+ public static void test(int unknown, int alsoUnknown) {
+
+ I i = unknown == 0 ? new C1() : unknown == 1 ? new C2() : unknown == 2 ? new Wrapper(new Object()) : new WrapperWrapper(new Wrapper(new Object()));
+
+ switch(i) {
+ case C1 c1 when alsoUnknown == 1 -> { }
+ default -> i.take(source()); // Could call any implementation
+ }
+
+ switch(i) {
+ case C1 c1 -> { }
+ default -> i.take(source()); // Can't call C1.take
+ }
+
+ switch(i) {
+ case C1 c1 -> { }
+ case null, default -> i.take(source()); // Can't call C1.take (but we don't currently notice)
+ }
+
+ switch(i) {
+ case Wrapper w -> { }
+ default -> i.take(source()); // Can't call Wrapper.take
+ }
+
+ switch(i) {
+ case Wrapper(Object o) -> { }
+ default -> i.take(source()); // Can't call Wrapper.take
+ }
+
+ switch(i) {
+ case Wrapper(String s) -> { }
+ default -> i.take(source()); // Could call any implementation, because this might be a Wrapper(Integer) for example.
+ }
+
+ switch(i) {
+ case WrapperWrapper(Wrapper(Object o)) -> { }
+ default -> i.take(source()); // Can't call WrapperWrapper.take
+ }
+
+ switch(i) {
+ case WrapperWrapper(Wrapper(String s)) -> { }
+ default -> i.take(source()); // Could call any implementation, because this might be a WrapperWrapper(Wrapper((Integer)) for example.
+ }
+
+ switch(i) {
+ case C1 c1: break;
+ case null: default: i.take(source()); // Can't call C1.take (but we don't currently notice)
+ }
+
+ }
+
+}
diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/options b/java/ql/test/library-tests/switch-default-impossible-dispatch/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected
new file mode 100644
index 00000000000..17d478d4298
--- /dev/null
+++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.expected
@@ -0,0 +1,32 @@
+| Test.java:18:25:18:32 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:18:25:18:32 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:18:25:18:32 | source(...) | Test.java:9:74:9:74 | x |
+| Test.java:18:25:18:32 | source(...) | Test.java:10:82:10:82 | x |
+| Test.java:23:25:23:32 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:23:25:23:32 | source(...) | Test.java:9:74:9:74 | x |
+| Test.java:23:25:23:32 | source(...) | Test.java:10:82:10:82 | x |
+| Test.java:28:36:28:43 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:28:36:28:43 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:28:36:28:43 | source(...) | Test.java:9:74:9:74 | x |
+| Test.java:28:36:28:43 | source(...) | Test.java:10:82:10:82 | x |
+| Test.java:33:25:33:32 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:33:25:33:32 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:33:25:33:32 | source(...) | Test.java:10:82:10:82 | x |
+| Test.java:38:25:38:32 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:38:25:38:32 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:38:25:38:32 | source(...) | Test.java:10:82:10:82 | x |
+| Test.java:43:25:43:32 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:43:25:43:32 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:43:25:43:32 | source(...) | Test.java:9:74:9:74 | x |
+| Test.java:43:25:43:32 | source(...) | Test.java:10:82:10:82 | x |
+| Test.java:48:25:48:32 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:48:25:48:32 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:48:25:48:32 | source(...) | Test.java:9:74:9:74 | x |
+| Test.java:53:25:53:32 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:53:25:53:32 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:53:25:53:32 | source(...) | Test.java:9:74:9:74 | x |
+| Test.java:53:25:53:32 | source(...) | Test.java:10:82:10:82 | x |
+| Test.java:58:34:58:41 | source(...) | Test.java:7:65:7:65 | x |
+| Test.java:58:34:58:41 | source(...) | Test.java:8:65:8:65 | x |
+| Test.java:58:34:58:41 | source(...) | Test.java:9:74:9:74 | x |
+| Test.java:58:34:58:41 | source(...) | Test.java:10:82:10:82 | x |
diff --git a/java/ql/test/library-tests/switch-default-impossible-dispatch/test.ql b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.ql
new file mode 100644
index 00000000000..c8e832e9f26
--- /dev/null
+++ b/java/ql/test/library-tests/switch-default-impossible-dispatch/test.ql
@@ -0,0 +1,18 @@
+import java
+import semmle.code.java.dataflow.DataFlow
+
+module TestConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "source")
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(MethodCall mc | mc.getMethod().getName() = "sink").getAnArgument()
+ }
+}
+
+module Flow = DataFlow::Global;
+
+from DataFlow::Node source, DataFlow::Node sink
+where Flow::flow(source, sink)
+select source, sink
diff --git a/java/ql/test/library-tests/typeflow/A.java b/java/ql/test/library-tests/typeflow/A.java
index d4ed45df158..ad19901f151 100644
--- a/java/ql/test/library-tests/typeflow/A.java
+++ b/java/ql/test/library-tests/typeflow/A.java
@@ -92,4 +92,30 @@ public class A extends ArrayList {
Object r = n;
}
}
+
+ public void m9(Object[] xs, int i) {
+ switch (xs[i]) {
+ case Integer i2 -> {
+ Object n = xs[i];
+ Object r = n;
+ }
+ default -> { }
+ }
+ }
+
+ public void m10(Object o) {
+ String s = "Hello world!";
+ Object o2 = s; // Alas, the type information, it is lost
+
+ if (o2 instanceof CharSequence cs) {
+ // Partially recovered statically, but we should know cs is an alias of o and therefore it's really a string.
+ Object target = cs;
+ }
+
+ // The same applies to a pattern case
+ switch (o2) {
+ case CharSequence cs -> { Object target = cs; }
+ default -> { }
+ }
+ }
}
diff --git a/java/ql/test/library-tests/typeflow/UnionTypes.java b/java/ql/test/library-tests/typeflow/UnionTypes.java
index a82b3828d2f..44fb54336c6 100644
--- a/java/ql/test/library-tests/typeflow/UnionTypes.java
+++ b/java/ql/test/library-tests/typeflow/UnionTypes.java
@@ -44,6 +44,10 @@ public class UnionTypes {
if (x instanceof Inter) {
x.hashCode();
}
+ var hashCode = switch (x) {
+ case Inter i -> x.hashCode();
+ default -> 0;
+ };
}
void m3(Object d) {
diff --git a/java/ql/test/library-tests/typeflow/options b/java/ql/test/library-tests/typeflow/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/typeflow/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/typeflow/typeflow.expected b/java/ql/test/library-tests/typeflow/typeflow.expected
index 021d04b55d3..f0cb2356cb8 100644
--- a/java/ql/test/library-tests/typeflow/typeflow.expected
+++ b/java/ql/test/library-tests/typeflow/typeflow.expected
@@ -13,4 +13,10 @@
| A.java:67:22:67:22 | x | Integer | false |
| A.java:70:23:70:24 | x2 | Integer | false |
| A.java:92:18:92:18 | n | Integer | false |
+| A.java:100:20:100:20 | n | Integer | false |
+| A.java:110:9:110:10 | o2 | String | false |
+| A.java:112:23:112:24 | cs | String | false |
+| A.java:116:13:116:14 | o2 | String | false |
+| A.java:117:49:117:50 | cs | String | false |
| UnionTypes.java:45:7:45:7 | x | Inter | false |
+| UnionTypes.java:48:23:48:23 | x | Inter | false |
diff --git a/java/ql/test/library-tests/typeflow/uniontypeflow.expected b/java/ql/test/library-tests/typeflow/uniontypeflow.expected
index 7c595175301..c203583249d 100644
--- a/java/ql/test/library-tests/typeflow/uniontypeflow.expected
+++ b/java/ql/test/library-tests/typeflow/uniontypeflow.expected
@@ -19,6 +19,11 @@
| UnionTypes.java:44:9:44:9 | x | 3 | A3 | false |
| UnionTypes.java:45:7:45:7 | x | 2 | A1 | false |
| UnionTypes.java:45:7:45:7 | x | 2 | A2 | true |
-| UnionTypes.java:51:7:51:7 | d | 3 | A1 | false |
-| UnionTypes.java:51:7:51:7 | d | 3 | A2 | false |
-| UnionTypes.java:51:7:51:7 | d | 3 | A3 | false |
+| UnionTypes.java:47:28:47:28 | x | 3 | A1 | false |
+| UnionTypes.java:47:28:47:28 | x | 3 | A2 | true |
+| UnionTypes.java:47:28:47:28 | x | 3 | A3 | false |
+| UnionTypes.java:48:23:48:23 | x | 2 | A1 | false |
+| UnionTypes.java:48:23:48:23 | x | 2 | A2 | true |
+| UnionTypes.java:55:7:55:7 | d | 3 | A1 | false |
+| UnionTypes.java:55:7:55:7 | d | 3 | A2 | false |
+| UnionTypes.java:55:7:55:7 | d | 3 | A3 | false |
diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/Test.java b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/Test.java
new file mode 100644
index 00000000000..47b333c408b
--- /dev/null
+++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/Test.java
@@ -0,0 +1,29 @@
+public class Test {
+
+ private interface Intf { String get(); }
+ private static class Specific implements Intf { public String get() { return "Specific"; } }
+ private static class Alternative implements Intf { public String get() { return "Alternative"; } }
+
+ public static String caller() {
+
+ Alternative a = new Alternative(); // Instantiate this somewhere so there are at least two candidate types in general
+ return test(new Specific());
+
+ }
+
+ public static String test(Object o) {
+
+ if (o instanceof Intf i) {
+ // So we should know i.get is really Specific.get():
+ return i.get();
+ }
+
+ switch (o) {
+ case Intf i -> { return i.get(); } // Same goes for this `i`
+ default -> { return "Not an Intf"; }
+ }
+
+ }
+
+}
+
diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/options b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/options
new file mode 100644
index 00000000000..a0d1b7e7002
--- /dev/null
+++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args --release 21
diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.expected b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.expected
new file mode 100644
index 00000000000..6cbaf7579f7
--- /dev/null
+++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.expected
@@ -0,0 +1,8 @@
+| Test.java:1:14:1:17 | super(...) | java.lang.Object.Object |
+| Test.java:4:24:4:31 | super(...) | java.lang.Object.Object |
+| Test.java:5:24:5:34 | super(...) | java.lang.Object.Object |
+| Test.java:9:21:9:37 | new Alternative(...) | Test$Alternative.Alternative |
+| Test.java:10:12:10:31 | test(...) | Test.test |
+| Test.java:10:17:10:30 | new Specific(...) | Test$Specific.Specific |
+| Test.java:18:14:18:20 | get(...) | Test$Specific.get |
+| Test.java:22:31:22:37 | get(...) | Test$Specific.get |
diff --git a/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql
new file mode 100644
index 00000000000..dea1e994a93
--- /dev/null
+++ b/java/ql/test/library-tests/virtual-dispatch-binding-patterns/test.ql
@@ -0,0 +1,6 @@
+import java
+import semmle.code.java.dispatch.VirtualDispatch
+
+from Call c, Callable c2
+where c2 = viableCallable(c)
+select c, c2.getQualifiedName()
diff --git a/java/ql/test/query-tests/Nullness/G.java b/java/ql/test/query-tests/Nullness/G.java
new file mode 100644
index 00000000000..9a525e8d14b
--- /dev/null
+++ b/java/ql/test/query-tests/Nullness/G.java
@@ -0,0 +1,27 @@
+public class G {
+
+ public static void test(String s) {
+
+ if (s == null) {
+ System.out.println("Is null");
+ }
+
+ switch(s) { // OK; null case means this doesn't throw.
+ case null -> System.out.println("Null");
+ case "foo" -> System.out.println("Foo");
+ default -> System.out.println("Something else");
+ }
+
+ var x = switch(s) { // OK; null case (combined with default) means this doesn't throw.
+ case "foo" -> "foo";
+ case null, default -> "bar";
+ };
+
+ switch(s) { // BAD; lack of a null case means this may throw.
+ case "foo" -> System.out.println("Foo");
+ case String s2 -> System.out.println("Other string of length " + s2.length());
+ }
+
+ }
+
+}
diff --git a/java/ql/test/query-tests/Nullness/NullMaybe.expected b/java/ql/test/query-tests/Nullness/NullMaybe.expected
index 8fc6aed34ea..80cf8f00f8d 100644
--- a/java/ql/test/query-tests/Nullness/NullMaybe.expected
+++ b/java/ql/test/query-tests/Nullness/NullMaybe.expected
@@ -35,3 +35,4 @@
| C.java:233:7:233:8 | xs | Variable $@ may be null at this access because of $@ assignment. | C.java:231:5:231:56 | int[] xs | xs | C.java:231:11:231:55 | xs | this |
| F.java:11:5:11:7 | obj | Variable $@ may be null at this access as suggested by $@ null guard. | F.java:8:18:8:27 | obj | obj | F.java:9:9:9:19 | ... == ... | this |
| F.java:17:5:17:7 | obj | Variable $@ may be null at this access as suggested by $@ null guard. | F.java:14:18:14:27 | obj | obj | F.java:15:9:15:19 | ... == ... | this |
+| G.java:20:12:20:12 | s | Variable $@ may be null at this access as suggested by $@ null guard. | G.java:3:27:3:34 | s | s | G.java:5:9:5:17 | ... == ... | this |
diff --git a/java/ql/test/query-tests/Nullness/options b/java/ql/test/query-tests/Nullness/options
index b996e88e0b0..c1e2dcae283 100644
--- a/java/ql/test/query-tests/Nullness/options
+++ b/java/ql/test/query-tests/Nullness/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/junit-4.11:${testdir}/../../stubs/hamcrest-2.2:${testdir}/../../stubs/junit-jupiter-api-5.2.0
+//semmle-extractor-options: --javac-args --release 21 -cp ${testdir}/../../stubs/junit-4.11:${testdir}/../../stubs/hamcrest-2.2:${testdir}/../../stubs/junit-jupiter-api-5.2.0
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected
index 66dfeacc496..fc1d87f06b1 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlConcatenated.expected
@@ -9,3 +9,4 @@
| Test.java:98:47:98:60 | queryFromField | Query built by concatenation with $@, which may be untrusted. | Test.java:97:8:97:19 | categoryName | this expression |
| Test.java:108:47:108:61 | querySbToString | Query built by concatenation with $@, which may be untrusted. | Test.java:104:19:104:30 | categoryName | this expression |
| Test.java:118:47:118:62 | querySb2ToString | Query built by concatenation with $@, which may be untrusted. | Test.java:114:46:114:57 | categoryName | this expression |
+| Test.java:221:81:221:111 | ... + ... | Query built by concatenation with $@, which may be untrusted. | Test.java:221:95:221:102 | category | this expression |
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
index 1884d76f811..45577278a7e 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
@@ -13,10 +13,13 @@ edges
| Test.java:60:29:60:35 | querySb : StringBuilder | Test.java:60:29:60:46 | toString(...) : String |
| Test.java:60:29:60:46 | toString(...) : String | Test.java:62:47:62:61 | querySbToString |
| Test.java:183:33:183:45 | args : String[] | Test.java:209:47:209:68 | queryWithUserTableName |
-| Test.java:213:26:213:38 | args : String[] | Test.java:214:11:214:14 | args : String[] |
-| Test.java:213:26:213:38 | args : String[] | Test.java:218:14:218:17 | args : String[] |
-| Test.java:214:11:214:14 | args : String[] | Test.java:29:30:29:42 | args : String[] |
-| Test.java:218:14:218:17 | args : String[] | Test.java:183:33:183:45 | args : String[] |
+| Test.java:213:34:213:46 | args : String[] | Test.java:221:81:221:111 | ... + ... |
+| Test.java:227:26:227:38 | args : String[] | Test.java:228:11:228:14 | args : String[] |
+| Test.java:227:26:227:38 | args : String[] | Test.java:232:14:232:17 | args : String[] |
+| Test.java:227:26:227:38 | args : String[] | Test.java:233:15:233:18 | args : String[] |
+| Test.java:228:11:228:14 | args : String[] | Test.java:29:30:29:42 | args : String[] |
+| Test.java:232:14:232:17 | args : String[] | Test.java:183:33:183:45 | args : String[] |
+| Test.java:233:15:233:18 | args : String[] | Test.java:213:34:213:46 | args : String[] |
nodes
| Mongo.java:10:29:10:41 | args : String[] | semmle.label | args : String[] |
| Mongo.java:17:45:17:67 | parse(...) | semmle.label | parse(...) |
@@ -35,17 +38,21 @@ nodes
| Test.java:78:46:78:50 | query | semmle.label | query |
| Test.java:183:33:183:45 | args : String[] | semmle.label | args : String[] |
| Test.java:209:47:209:68 | queryWithUserTableName | semmle.label | queryWithUserTableName |
-| Test.java:213:26:213:38 | args : String[] | semmle.label | args : String[] |
-| Test.java:214:11:214:14 | args : String[] | semmle.label | args : String[] |
-| Test.java:218:14:218:17 | args : String[] | semmle.label | args : String[] |
+| Test.java:213:34:213:46 | args : String[] | semmle.label | args : String[] |
+| Test.java:221:81:221:111 | ... + ... | semmle.label | ... + ... |
+| Test.java:227:26:227:38 | args : String[] | semmle.label | args : String[] |
+| Test.java:228:11:228:14 | args : String[] | semmle.label | args : String[] |
+| Test.java:232:14:232:17 | args : String[] | semmle.label | args : String[] |
+| Test.java:233:15:233:18 | args : String[] | semmle.label | args : String[] |
subpaths
#select
| Mongo.java:17:45:17:67 | parse(...) | Mongo.java:10:29:10:41 | args : String[] | Mongo.java:17:45:17:67 | parse(...) | This query depends on a $@. | Mongo.java:10:29:10:41 | args | user-provided value |
| Mongo.java:21:49:21:52 | json | Mongo.java:10:29:10:41 | args : String[] | Mongo.java:21:49:21:52 | json | This query depends on a $@. | Mongo.java:10:29:10:41 | args | user-provided value |
-| Test.java:36:47:36:52 | query1 | Test.java:213:26:213:38 | args : String[] | Test.java:36:47:36:52 | query1 | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
-| Test.java:42:57:42:62 | query2 | Test.java:213:26:213:38 | args : String[] | Test.java:42:57:42:62 | query2 | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
-| Test.java:50:62:50:67 | query3 | Test.java:213:26:213:38 | args : String[] | Test.java:50:62:50:67 | query3 | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
-| Test.java:62:47:62:61 | querySbToString | Test.java:213:26:213:38 | args : String[] | Test.java:62:47:62:61 | querySbToString | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
-| Test.java:70:40:70:44 | query | Test.java:213:26:213:38 | args : String[] | Test.java:70:40:70:44 | query | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
-| Test.java:78:46:78:50 | query | Test.java:213:26:213:38 | args : String[] | Test.java:78:46:78:50 | query | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
-| Test.java:209:47:209:68 | queryWithUserTableName | Test.java:213:26:213:38 | args : String[] | Test.java:209:47:209:68 | queryWithUserTableName | This query depends on a $@. | Test.java:213:26:213:38 | args | user-provided value |
+| Test.java:36:47:36:52 | query1 | Test.java:227:26:227:38 | args : String[] | Test.java:36:47:36:52 | query1 | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
+| Test.java:42:57:42:62 | query2 | Test.java:227:26:227:38 | args : String[] | Test.java:42:57:42:62 | query2 | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
+| Test.java:50:62:50:67 | query3 | Test.java:227:26:227:38 | args : String[] | Test.java:50:62:50:67 | query3 | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
+| Test.java:62:47:62:61 | querySbToString | Test.java:227:26:227:38 | args : String[] | Test.java:62:47:62:61 | querySbToString | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
+| Test.java:70:40:70:44 | query | Test.java:227:26:227:38 | args : String[] | Test.java:70:40:70:44 | query | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
+| Test.java:78:46:78:50 | query | Test.java:227:26:227:38 | args : String[] | Test.java:78:46:78:50 | query | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
+| Test.java:209:47:209:68 | queryWithUserTableName | Test.java:227:26:227:38 | args : String[] | Test.java:209:47:209:68 | queryWithUserTableName | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
+| Test.java:221:81:221:111 | ... + ... | Test.java:227:26:227:38 | args : String[] | Test.java:221:81:221:111 | ... + ... | This query depends on a $@. | Test.java:227:26:227:38 | args | user-provided value |
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java b/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java
index f6e4ff61dc4..dee0db129eb 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java
@@ -210,12 +210,27 @@ abstract class Test {
}
}
+ private static void bindingVars(String[] args) throws IOException, SQLException {
+ // BAD: the category might have SQL special characters in it
+ {
+ String category = args[1];
+ Statement statement = connection.createStatement();
+ String prefix = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='";
+ String suffix = "' ORDER BY PRICE";
+ switch(prefix) {
+ case String prefixAlias when prefix.length() > 10 -> statement.executeQuery(prefixAlias + category + suffix);
+ default -> { }
+ }
+ }
+ }
+
public static void main(String[] args) throws IOException, SQLException {
tainted(args);
unescaped();
good(args);
controlledStrings();
tableNames(args);
+ bindingVars(args);
}
}
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected
index cdb777f659e..ad632dfc8c2 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected
@@ -1,6 +1,16 @@
| | 1 | Test.java:20:2:20:9 | FloorWax |
| | 1 | Test.java:20:12:20:18 | Topping |
| | 1 | Test.java:20:21:20:28 | Biscuits |
+| bindingVars | 3 | Test.java:216:48:216:48 | 1 |
+| bindingVars | 5 | Test.java:218:20:218:73 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" |
+| bindingVars | 6 | Test.java:219:20:219:37 | "' ORDER BY PRICE" |
+| bindingVars | 7 | Test.java:220:11:220:16 | prefix |
+| bindingVars | 8 | Test.java:221:34:221:39 | prefix |
+| bindingVars | 8 | Test.java:221:34:221:48 | length(...) |
+| bindingVars | 8 | Test.java:221:34:221:53 | ... > ... |
+| bindingVars | 8 | Test.java:221:52:221:53 | 10 |
+| bindingVars | 8 | Test.java:221:81:221:91 | prefixAlias |
+| bindingVars | 8 | Test.java:221:106:221:111 | suffix |
| checkIdentifier | 1 | Validation.java:7:12:7:16 | i |
| checkIdentifier | 1 | Validation.java:7:16:7:16 | 0 |
| checkIdentifier | 1 | Validation.java:7:19:7:19 | i |
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected
index 2b6d12b426d..fe8b3aede67 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected
@@ -1,3 +1,7 @@
+| bindingVars | 5 | Test.java:218:20:218:73 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" |
+| bindingVars | 7 | Test.java:220:11:220:16 | prefix |
+| bindingVars | 8 | Test.java:221:34:221:39 | prefix |
+| bindingVars | 8 | Test.java:221:81:221:91 | prefixAlias |
| controlledStrings | 4 | Test.java:137:26:137:79 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" |
| controlledStrings | 12 | Test.java:145:27:145:80 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" |
| controlledStrings | 20 | Test.java:153:35:153:88 | "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" |
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/options b/java/ql/test/query-tests/security/CWE-089/semmle/examples/options
index 1b24aa77776..8c08f833401 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/options
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/apache-hive
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/apache-hive --release 21
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected
index 67c6629093a..e6f4d14eaad 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected
@@ -59,9 +59,15 @@
| Test.java:183:22:183:31 | tableNames | 23 | Test.java:206:36:208:55 | ... + ... |
| Test.java:183:22:183:31 | tableNames | 24 | Test.java:207:8:207:18 | userTabName |
| Test.java:183:22:183:31 | tableNames | 26 | Test.java:209:47:209:68 | queryWithUserTableName |
-| Test.java:213:21:213:24 | main | 1 | Test.java:214:11:214:14 | args |
-| Test.java:213:21:213:24 | main | 3 | Test.java:216:8:216:11 | args |
-| Test.java:213:21:213:24 | main | 5 | Test.java:218:14:218:17 | args |
+| Test.java:213:22:213:32 | bindingVars | 3 | Test.java:216:43:216:46 | args |
+| Test.java:213:22:213:32 | bindingVars | 3 | Test.java:216:43:216:49 | ...[...] |
+| Test.java:213:22:213:32 | bindingVars | 8 | Test.java:221:81:221:102 | ... + ... |
+| Test.java:213:22:213:32 | bindingVars | 8 | Test.java:221:81:221:111 | ... + ... |
+| Test.java:213:22:213:32 | bindingVars | 8 | Test.java:221:95:221:102 | category |
+| Test.java:227:21:227:24 | main | 1 | Test.java:228:11:228:14 | args |
+| Test.java:227:21:227:24 | main | 3 | Test.java:230:8:230:11 | args |
+| Test.java:227:21:227:24 | main | 5 | Test.java:232:14:232:17 | args |
+| Test.java:227:21:227:24 | main | 6 | Test.java:233:15:233:18 | args |
| Validation.java:6:21:6:35 | checkIdentifier | 1 | Validation.java:7:23:7:24 | id |
| Validation.java:6:21:6:35 | checkIdentifier | 2 | Validation.java:8:13:8:14 | id |
| Validation.java:6:21:6:35 | checkIdentifier | 2 | Validation.java:8:13:8:24 | charAt(...) |