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 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(...) |