From 6a284eeecb4feddcf7d5541461c377fef792e61c Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 23 Apr 2025 13:41:03 +0200 Subject: [PATCH 01/18] Merged `ES6Class` into `FunctionStyleClass` --- .../lib/semmle/javascript/dataflow/Nodes.qll | 277 ++++++++++-------- 1 file changed, 162 insertions(+), 115 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 2e2313835227..8b41ed5b82c2 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1214,81 +1214,6 @@ module ClassNode { DataFlow::Node getADecorator() { none() } } - /** - * An ES6 class as a `ClassNode` instance. - */ - private class ES6Class extends Range, DataFlow::ValueNode { - override ClassDefinition astNode; - - override string getName() { result = astNode.getName() } - - override string describe() { result = astNode.describe() } - - override FunctionNode getConstructor() { result = astNode.getConstructor().getBody().flow() } - - override FunctionNode getInstanceMember(string name, MemberKind kind) { - exists(MethodDeclaration method | - method = astNode.getMethod(name) and - not method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - kind = MemberKind::method() and - result = this.getConstructor().getReceiver().getAPropertySource(name) - } - - override FunctionNode getAnInstanceMember(MemberKind kind) { - exists(MethodDeclaration method | - method = astNode.getAMethod() and - not method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - kind = MemberKind::method() and - result = this.getConstructor().getReceiver().getAPropertySource() - } - - override FunctionNode getStaticMember(string name, MemberKind kind) { - exists(MethodDeclaration method | - method = astNode.getMethod(name) and - method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - kind.isMethod() and - result = this.getAPropertySource(name) - } - - override FunctionNode getAStaticMember(MemberKind kind) { - exists(MethodDeclaration method | - method = astNode.getAMethod() and - method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - kind.isMethod() and - result = this.getAPropertySource() - } - - override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() } - - override TypeAnnotation getFieldTypeAnnotation(string fieldName) { - exists(FieldDeclaration field | - field.getDeclaringClass() = astNode and - fieldName = field.getName() and - result = field.getTypeAnnotation() - ) - } - - override DataFlow::Node getADecorator() { - result = astNode.getADecorator().getExpression().flow() - } - } - private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) { result.getBase() = AccessPath::getAReferenceOrAssignmentTo(name) and result.getPropertyName() = "prototype" and @@ -1313,12 +1238,18 @@ module ClassNode { /** * A function definition, targeted by a `new`-call or with prototype manipulation, seen as a `ClassNode` instance. + * Or An ES6 class as a `ClassNode` instance. */ class FunctionStyleClass extends Range, DataFlow::ValueNode { - override Function astNode; + override AST::ValueNode astNode; AbstractFunction function; FunctionStyleClass() { + // ES6 class case + astNode instanceof ClassDefinition + or + // Function-style class case + astNode instanceof Function and function.getFunction() = astNode and ( exists(getAFunctionValueWithPrototype(function)) @@ -1333,13 +1264,30 @@ module ClassNode { ) } - override string getName() { result = astNode.getName() } + override string getName() { + astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).getName() + or + astNode instanceof Function and result = astNode.(Function).getName() + } - override string describe() { result = astNode.describe() } + override string describe() { + astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).describe() + or + astNode instanceof Function and result = astNode.(Function).describe() + } - override FunctionNode getConstructor() { result = this } + override FunctionNode getConstructor() { + // For ES6 classes + astNode instanceof ClassDefinition and + result = astNode.(ClassDefinition).getConstructor().getBody().flow() + or + // For function-style classes + astNode instanceof Function and result = this + } private PropertyAccessor getAnAccessor(MemberKind kind) { + // Only applies to function-style classes + astNode instanceof Function and result.getObjectExpr() = this.getAPrototypeReference().asExpr() and ( kind = MemberKind::getter() and @@ -1351,12 +1299,41 @@ module ClassNode { } override FunctionNode getInstanceMember(string name, MemberKind kind) { + // ES6 class methods + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getMethod(name) and + not method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or + // ES6 class property in constructor + astNode instanceof ClassDefinition and kind = MemberKind::method() and - result = this.getAPrototypeReference().getAPropertySource(name) + exists(ThisNode receiver | + receiver = this.getConstructor().getReceiver() and + receiver.hasPropertyWrite(name, result) + ) + or + // Function-style class methods via prototype + astNode instanceof Function and + kind = MemberKind::method() and + exists(DataFlow::SourceNode proto | + proto = this.getAPrototypeReference() and + proto.hasPropertyWrite(name, result) + ) or + // Function-style class methods via constructor + astNode instanceof Function and kind = MemberKind::method() and - result = this.getConstructor().getReceiver().getAPropertySource(name) + exists(ThisNode receiver | + receiver = this.getConstructor().getReceiver() and + receiver.hasPropertyWrite(name, result) + ) or + // Function-style class accessors + astNode instanceof Function and exists(PropertyAccessor accessor | accessor = this.getAnAccessor(kind) and accessor.getName() = name and @@ -1365,12 +1342,41 @@ module ClassNode { } override FunctionNode getAnInstanceMember(MemberKind kind) { + // ES6 class methods + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getAMethod() and + not method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or + // ES6 class property in constructor + astNode instanceof ClassDefinition and + kind = MemberKind::method() and + exists(ThisNode receiver | + receiver = this.getConstructor().getReceiver() and + result = receiver.getAPropertySource() + ) + or + // Function-style class methods via prototype + astNode instanceof Function and kind = MemberKind::method() and - result = this.getAPrototypeReference().getAPropertySource() + exists(DataFlow::SourceNode proto | + proto = this.getAPrototypeReference() and + result = proto.getAPropertySource() + ) or + // Function-style class methods via constructor + astNode instanceof Function and kind = MemberKind::method() and - result = this.getConstructor().getReceiver().getAPropertySource() + exists(ThisNode receiver | + receiver = this.getConstructor().getReceiver() and + result = receiver.getAPropertySource() + ) or + // Function-style class accessors + astNode instanceof Function and exists(PropertyAccessor accessor | accessor = this.getAnAccessor(kind) and result = accessor.getInit().flow() @@ -1378,60 +1384,101 @@ module ClassNode { } override FunctionNode getStaticMember(string name, MemberKind kind) { + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getMethod(name) and + method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or kind.isMethod() and result = this.getAPropertySource(name) } override FunctionNode getAStaticMember(MemberKind kind) { + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getAMethod() and + method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or kind.isMethod() and result = this.getAPropertySource() } /** * Gets a reference to the prototype of this class. + * Only applies to function-style classes. */ DataFlow::SourceNode getAPrototypeReference() { - exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) | - result = base.getAPropertyRead("prototype") + astNode instanceof Function and + ( + exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) | + result = base.getAPropertyRead("prototype") + or + result = base.getAPropertySource("prototype") + ) or - result = base.getAPropertySource("prototype") - ) - or - exists(string name | - this = AccessPath::getAnAssignmentTo(name) and - result = getAPrototypeReferenceInFile(name, this.getFile()) - ) - or - exists(ExtendCall call | - call.getDestinationOperand() = this.getAPrototypeReference() and - result = call.getASourceOperand() + exists(string name | + this = AccessPath::getAnAssignmentTo(name) and + result = getAPrototypeReferenceInFile(name, this.getFile()) + ) + or + exists(ExtendCall call | + call.getDestinationOperand() = this.getAPrototypeReference() and + result = call.getASourceOperand() + ) ) } override DataFlow::Node getASuperClassNode() { - // C.prototype = Object.create(D.prototype) - exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto | - this.getAPropertySource("prototype") = objectCreate and - objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and - superProto.flowsTo(objectCreate.getArgument(0)) and - superProto.getPropertyName() = "prototype" and - result = superProto.getBase() - ) + // ES6 class superclass + astNode instanceof ClassDefinition and + result = astNode.(ClassDefinition).getSuperClass().flow() or - // C.prototype = new D() - exists(DataFlow::NewNode newCall | - this.getAPropertySource("prototype") = newCall and - result = newCall.getCalleeNode() + // Function-style class superclass patterns + astNode instanceof Function and + ( + // C.prototype = Object.create(D.prototype) + exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto | + this.getAPropertySource("prototype") = objectCreate and + objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and + superProto.flowsTo(objectCreate.getArgument(0)) and + superProto.getPropertyName() = "prototype" and + result = superProto.getBase() + ) + or + // C.prototype = new D() + exists(DataFlow::NewNode newCall | + this.getAPropertySource("prototype") = newCall and + result = newCall.getCalleeNode() + ) + or + // util.inherits(C, D); + exists(DataFlow::CallNode inheritsCall | + inheritsCall = DataFlow::moduleMember("util", "inherits").getACall() + | + this = inheritsCall.getArgument(0).getALocalSource() and + result = inheritsCall.getArgument(1) + ) ) - or - // util.inherits(C, D); - exists(DataFlow::CallNode inheritsCall | - inheritsCall = DataFlow::moduleMember("util", "inherits").getACall() - | - this = inheritsCall.getArgument(0).getALocalSource() and - result = inheritsCall.getArgument(1) + } + + override TypeAnnotation getFieldTypeAnnotation(string fieldName) { + exists(FieldDeclaration field | + field.getDeclaringClass() = astNode and + fieldName = field.getName() and + result = field.getTypeAnnotation() ) } + + override DataFlow::Node getADecorator() { + astNode instanceof ClassDefinition and + result = astNode.(ClassDefinition).getADecorator().getExpression().flow() + } } } From c57172121e4c36555fcfa107e544b1ff8ad08e21 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 24 Apr 2025 12:06:32 +0200 Subject: [PATCH 02/18] Update Nodes.qll Applied suggestions Co-Authored-By: Asger F <316427+asgerf@users.noreply.github.com> --- javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll | 5 +++-- javascript/ql/test/library-tests/Classes/tests.expected | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 8b41ed5b82c2..6a8bacb585d7 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1242,11 +1242,12 @@ module ClassNode { */ class FunctionStyleClass extends Range, DataFlow::ValueNode { override AST::ValueNode astNode; - AbstractFunction function; + AbstractCallable function; FunctionStyleClass() { // ES6 class case - astNode instanceof ClassDefinition + astNode instanceof ClassDefinition and + function.(AbstractClass).getClass() = astNode or // Function-style class case astNode instanceof Function and diff --git a/javascript/ql/test/library-tests/Classes/tests.expected b/javascript/ql/test/library-tests/Classes/tests.expected index aadd449349c2..460614e02e10 100644 --- a/javascript/ql/test/library-tests/Classes/tests.expected +++ b/javascript/ql/test/library-tests/Classes/tests.expected @@ -194,6 +194,7 @@ test_ConstructorDefinitions | tst.js:11:9:11:8 | constructor() {} | test_ClassNodeConstructor | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:4:12:4:11 | () {} | +| dataflow.js:4:12:4:11 | () {} | dataflow.js:4:12:4:11 | () {} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | () {} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | points.js:2:14:5:3 | (x, y) ... y;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:14:24:3 | (x, y, ... c;\\n } | From 4705d30bac847d3a8326e8f258d77a2f2a4c6254 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 28 Apr 2025 15:12:24 +0200 Subject: [PATCH 03/18] Add call graph tests for prototype methods injected on class --- .../CallGraphs/AnnotatedTest/Test.expected | 1 + .../CallGraphs/AnnotatedTest/prototypes.js | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 0abd563b4193..2ad95355cf1b 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,6 +2,7 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | +| prototypes.js:19:3:19:13 | baz.shout() | prototypes.js:11:23:11:35 | function() {} | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js new file mode 100644 index 000000000000..640da2edb70d --- /dev/null +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js @@ -0,0 +1,25 @@ +class Baz { + baz() { + /** calls:Baz.greet */ + this.greet(); + } + /** name:Baz.greet */ + greet() {} +} + +/** name:Baz.shout */ +Baz.prototype.shout = function() {}; +/** name:Baz.staticShout */ +Baz.staticShout = function() {}; + +function foo(baz){ + /** calls:Baz.greet */ + baz.greet(); + /** calls:Baz.shout */ + baz.shout(); + /** calls:Baz.staticShout */ + Baz.staticShout(); +} + +const baz = new Baz(); +foo(baz); From ee3a3bd9f50a3c6be6b2834279278a93f69bfdaa Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 28 Apr 2025 15:17:26 +0200 Subject: [PATCH 04/18] Add support for prototype methods in class instance member resolution --- javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll | 3 --- .../test/library-tests/CallGraphs/AnnotatedTest/Test.expected | 1 - 2 files changed, 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 6a8bacb585d7..dfd451afc372 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1318,7 +1318,6 @@ module ClassNode { ) or // Function-style class methods via prototype - astNode instanceof Function and kind = MemberKind::method() and exists(DataFlow::SourceNode proto | proto = this.getAPrototypeReference() and @@ -1361,7 +1360,6 @@ module ClassNode { ) or // Function-style class methods via prototype - astNode instanceof Function and kind = MemberKind::method() and exists(DataFlow::SourceNode proto | proto = this.getAPrototypeReference() and @@ -1415,7 +1413,6 @@ module ClassNode { * Only applies to function-style classes. */ DataFlow::SourceNode getAPrototypeReference() { - astNode instanceof Function and ( exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) | result = base.getAPropertyRead("prototype") diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 2ad95355cf1b..0abd563b4193 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,7 +2,6 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | -| prototypes.js:19:3:19:13 | baz.shout() | prototypes.js:11:23:11:35 | function() {} | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | From 4fbf8ca5cf787eb611bead6d77e39b16850533d1 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Apr 2025 12:31:34 +0200 Subject: [PATCH 05/18] Added test cases with inheritance --- .../CallGraphs/AnnotatedTest/Test.expected | 3 + .../CallGraphs/AnnotatedTest/prototypes.js | 74 ++++++++++++++++++- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 0abd563b4193..01a9bb03e3ae 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,6 +2,9 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | +| prototypes.js:7:5:7:16 | this.greet() | prototypes.js:59:8:63:3 | () { \\n ... ); \\n } | -1 | calls | +| prototypes.js:62:5:62:34 | Baz.pro ... l(this) | prototypes.js:10:8:10:39 | () { co ... et"); } | -1 | calls | +| prototypes.js:77:3:77:32 | Baz.pro ... l(this) | prototypes.js:14:23:14:62 | functio ... ut"); } | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js index 640da2edb70d..e0088019f9f9 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js @@ -1,16 +1,19 @@ +import 'dummy' + class Baz { baz() { - /** calls:Baz.greet */ + console.log("Baz baz"); + /** calls:Baz.greet calls:Derived.greet1 calls:BazExtented.greet2 */ this.greet(); } /** name:Baz.greet */ - greet() {} + greet() { console.log("Baz greet"); } } /** name:Baz.shout */ -Baz.prototype.shout = function() {}; +Baz.prototype.shout = function() { console.log("Baz shout"); }; /** name:Baz.staticShout */ -Baz.staticShout = function() {}; +Baz.staticShout = function() { console.log("Baz staticShout"); }; function foo(baz){ /** calls:Baz.greet */ @@ -23,3 +26,66 @@ function foo(baz){ const baz = new Baz(); foo(baz); + +class Derived extends Baz { + /** name:Derived.greet1 */ + greet() { + console.log("Derived greet"); + super.greet(); + } + + /** name:Derived.shout1 */ + shout() { + console.log("Derived shout"); + super.shout(); + } +} + +function bar(derived){ + /** calls:Derived.greet1 */ + derived.greet(); + /** calls:Derived.shout1 */ + derived.shout(); +} + +bar(new Derived()); + +class BazExtented { + constructor() { + console.log("BazExtented construct"); + } + + /** name:BazExtented.greet2 */ + greet() { + console.log("BazExtented greet"); + /** calls:Baz.greet */ + Baz.prototype.greet.call(this); + }; +} + +BazExtented.prototype = Object.create(Baz.prototype); +BazExtented.prototype.constructor = BazExtented; +BazExtented.staticShout = Baz.staticShout; + +/** name:BazExtented.talk */ +BazExtented.prototype.talk = function() { console.log("BazExtented talk"); }; + +/** name:BazExtented.shout2 */ +BazExtented.prototype.shout = function() { + console.log("BazExtented shout"); + /** calls:Baz.shout */ + Baz.prototype.shout.call(this); +}; + +function barbar(bazExtented){ + /** calls:BazExtented.talk */ + bazExtented.talk(); + /** calls:BazExtented.shout2 */ + bazExtented.shout(); + /** calls:BazExtented.greet2 */ + bazExtented.greet(); + /** calls:Baz.staticShout */ + BazExtented.staticShout(); +} + +barbar(new BazExtented()); From a015003bda045c630c8858caaf762670921ad4cf Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Apr 2025 12:32:26 +0200 Subject: [PATCH 06/18] Updated test case to resolve reflected calls --- .../CallGraphs/AnnotatedTest/Test.expected | 2 -- .../library-tests/CallGraphs/AnnotatedTest/Test.ql | 14 ++++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 01a9bb03e3ae..0c47e2c2c6a8 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -3,8 +3,6 @@ missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | prototypes.js:7:5:7:16 | this.greet() | prototypes.js:59:8:63:3 | () { \\n ... ); \\n } | -1 | calls | -| prototypes.js:62:5:62:34 | Baz.pro ... l(this) | prototypes.js:10:8:10:39 | () { co ... et"); } | -1 | calls | -| prototypes.js:77:3:77:32 | Baz.pro ... l(this) | prototypes.js:14:23:14:62 | functio ... ut"); } | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql index 4388a2d388d1..82533ba74c4a 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql @@ -31,7 +31,7 @@ class AnnotatedCall extends DataFlow::Node { AnnotatedCall() { this instanceof DataFlow::InvokeNode and - calls = getAnnotation(this.asExpr(), kind) and + calls = getAnnotation(this.getEnclosingExpr(), kind) and kind = "calls" or this instanceof DataFlow::PropRef and @@ -79,12 +79,14 @@ query predicate spuriousCallee(AnnotatedCall call, Function target, int boundArg } query predicate missingCallee( - AnnotatedCall call, AnnotatedFunction target, int boundArgs, string kind + InvokeExpr invoke, AnnotatedFunction target, int boundArgs, string kind ) { - not callEdge(call, target, boundArgs) and - kind = call.getKind() and - target = call.getAnExpectedCallee(kind) and - boundArgs = call.getBoundArgsOrMinusOne() + forex(AnnotatedCall call | call.getEnclosingExpr() = invoke | + not callEdge(call, target, boundArgs) and + kind = call.getKind() and + target = call.getAnExpectedCallee(kind) and + boundArgs = call.getBoundArgsOrMinusOne() + ) } query predicate badAnnotation(string name) { From 0a9a7911c2eca86ceb5e8abbb2bb177e9953dd1c Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Apr 2025 12:39:44 +0200 Subject: [PATCH 07/18] Fixed issue where method calls weren't properly resolved when inheritance was implemented via prototype manipulation instead of ES6 class syntax. --- javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll | 2 -- .../test/library-tests/CallGraphs/AnnotatedTest/Test.expected | 1 - 2 files changed, 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index dfd451afc372..527031950ec3 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1437,8 +1437,6 @@ module ClassNode { astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).getSuperClass().flow() or - // Function-style class superclass patterns - astNode instanceof Function and ( // C.prototype = Object.create(D.prototype) exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto | diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 0c47e2c2c6a8..0abd563b4193 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,7 +2,6 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | -| prototypes.js:7:5:7:16 | this.greet() | prototypes.js:59:8:63:3 | () { \\n ... ); \\n } | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | From c8ee8dce98195117bee8993ddc424cb1ea21f943 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Apr 2025 13:04:07 +0200 Subject: [PATCH 08/18] Add test cases to verify correct call graph resolution with various JavaScript inheritance patterns --- .../CallGraphs/AnnotatedTest/Test.expected | 2 ++ .../CallGraphs/AnnotatedTest/prototypes.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 0abd563b4193..e6532c0816f1 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,6 +2,8 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | +| prototypes.js:96:5:96:15 | this.read() | prototypes.js:104:27:104:39 | function() {} | -1 | calls | +| prototypes.js:96:5:96:15 | this.read() | prototypes.js:109:27:109:39 | function() {} | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js index e0088019f9f9..2556e07a2796 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js @@ -89,3 +89,21 @@ function barbar(bazExtented){ } barbar(new BazExtented()); + +class Base { + constructor() { + /** calls:Base.read calls:Derived1.read calls:Derived2.read */ + this.read(); + } + /** name:Base.read */ + read() { } +} + +class Derived1 extends Base {} +/** name:Derived1.read */ +Derived1.prototype.read = function() {}; + +class Derived2 {} +Derived2.prototype = Object.create(Base.prototype); +/** name:Derived2.read */ +Derived2.prototype.read = function() {}; From e9ee7134ef07de977e3caa6885fd837ebe697de7 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Apr 2025 13:32:36 +0200 Subject: [PATCH 09/18] Refactor prototype reference retrieval in ClassNode and update expected test output --- .../lib/semmle/javascript/dataflow/Nodes.qll | 36 ++++++++++--------- .../CallGraphs/AnnotatedTest/Test.expected | 2 -- .../library-tests/ClassNode/tests.expected | 1 + 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 527031950ec3..5f1b647f9bb6 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1233,7 +1233,7 @@ module ClassNode { private DataFlow::SourceNode getAFunctionValueWithPrototype(AbstractValue func) { exists(result.getAPropertyReference("prototype")) and result.analyze().getAValue() = pragma[only_bind_into](func) and - func instanceof AbstractFunction // the join-order goes bad if `func` has type `AbstractFunction`. + func instanceof AbstractCallable // the join-order goes bad if `func` has type `AbstractFunction`. } /** @@ -1413,22 +1413,26 @@ module ClassNode { * Only applies to function-style classes. */ DataFlow::SourceNode getAPrototypeReference() { - ( - exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) | - result = base.getAPropertyRead("prototype") - or - result = base.getAPropertySource("prototype") - ) + exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) | + result = base.getAPropertyRead("prototype") or - exists(string name | - this = AccessPath::getAnAssignmentTo(name) and - result = getAPrototypeReferenceInFile(name, this.getFile()) - ) - or - exists(ExtendCall call | - call.getDestinationOperand() = this.getAPrototypeReference() and - result = call.getASourceOperand() - ) + result = base.getAPropertySource("prototype") + ) + or + exists(string name | + this = AccessPath::getAnAssignmentTo(name) and + result = getAPrototypeReferenceInFile(name, this.getFile()) + ) + or + exists(string name, DataFlow::SourceNode root | + result = + AccessPath::getAReferenceOrAssignmentTo(root, name + ".prototype").getALocalSource() and + this = AccessPath::getAnAssignmentTo(root, name) + ) + or + exists(ExtendCall call | + call.getDestinationOperand() = this.getAPrototypeReference() and + result = call.getASourceOperand() ) } diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index e6532c0816f1..0abd563b4193 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,8 +2,6 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | -| prototypes.js:96:5:96:15 | this.read() | prototypes.js:104:27:104:39 | function() {} | -1 | calls | -| prototypes.js:96:5:96:15 | this.read() | prototypes.js:109:27:109:39 | function() {} | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | diff --git a/javascript/ql/test/library-tests/ClassNode/tests.expected b/javascript/ql/test/library-tests/ClassNode/tests.expected index 687118ffa0bf..1337e0c56942 100644 --- a/javascript/ql/test/library-tests/ClassNode/tests.expected +++ b/javascript/ql/test/library-tests/ClassNode/tests.expected @@ -15,6 +15,7 @@ getAReceiverNode | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:4:17:4:16 | this | | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:7:6:7:5 | this | | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:9:10:9:9 | this | +| tst.js:3:9:3:8 | () {} | tst.js:3:9:3:8 | this | | tst.js:13:1:13:21 | class A ... ds A {} | tst.js:13:20:13:19 | this | | tst.js:15:1:15:15 | function B() {} | tst.js:15:1:15:0 | this | | tst.js:15:1:15:15 | function B() {} | tst.js:17:19:17:18 | this | From 7fec3aec95bc17be3425d40a9912797f232502d5 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Apr 2025 13:43:06 +0200 Subject: [PATCH 10/18] Renamed `FunctionStyleClass` class to `StandardClassNode` --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll | 4 ++-- .../ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 974fdd7c0cbf..423c0f5ed1b0 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1236,7 +1236,7 @@ module API { exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) | ref = cls.getAReceiverNode() or - ref = cls.(DataFlow::ClassNode::FunctionStyleClass).getAPrototypeReference() + ref = cls.(DataFlow::ClassNode::StandardClassNode).getAPrototypeReference() ) or nd = MkUse(ref) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 5f1b647f9bb6..22e8511509a1 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1240,11 +1240,11 @@ module ClassNode { * A function definition, targeted by a `new`-call or with prototype manipulation, seen as a `ClassNode` instance. * Or An ES6 class as a `ClassNode` instance. */ - class FunctionStyleClass extends Range, DataFlow::ValueNode { + class StandardClassNode extends Range, DataFlow::ValueNode { override AST::ValueNode astNode; AbstractCallable function; - FunctionStyleClass() { + StandardClassNode() { // ES6 class case astNode instanceof ClassDefinition and function.(AbstractClass).getClass() = astNode diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll index 541e3a6f3e90..44f827ddf3d3 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll @@ -254,7 +254,7 @@ module CallGraph { not exists(DataFlow::ClassNode cls | node = cls.getConstructor().getReceiver() or - node = cls.(DataFlow::ClassNode::FunctionStyleClass).getAPrototypeReference() + node = cls.(DataFlow::ClassNode::StandardClassNode).getAPrototypeReference() ) } From fc7520e9e7ce9ea7ae0d618b328969b72ef52cab Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Apr 2025 13:55:58 +0200 Subject: [PATCH 11/18] Added change note --- .../ql/lib/change-notes/2025-04-29-combined-es6-func.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2025-04-29-combined-es6-func.md diff --git a/javascript/ql/lib/change-notes/2025-04-29-combined-es6-func.md b/javascript/ql/lib/change-notes/2025-04-29-combined-es6-func.md new file mode 100644 index 000000000000..2303d3d8c629 --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-04-29-combined-es6-func.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improved analysis for `ES6 classes` mixed with `function prototypes`, leading to more accurate call graph resolution. From c0917434ebc01814f443836ea74da8da506feb55 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 30 Apr 2025 11:45:00 +0200 Subject: [PATCH 12/18] Removed code duplication --- .../lib/semmle/javascript/dataflow/Nodes.qll | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 22e8511509a1..52547d5f5905 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1309,8 +1309,7 @@ module ClassNode { result = method.getBody().flow() ) or - // ES6 class property in constructor - astNode instanceof ClassDefinition and + // ES6 class property or Function-style class methods via constructor kind = MemberKind::method() and exists(ThisNode receiver | receiver = this.getConstructor().getReceiver() and @@ -1324,14 +1323,6 @@ module ClassNode { proto.hasPropertyWrite(name, result) ) or - // Function-style class methods via constructor - astNode instanceof Function and - kind = MemberKind::method() and - exists(ThisNode receiver | - receiver = this.getConstructor().getReceiver() and - receiver.hasPropertyWrite(name, result) - ) - or // Function-style class accessors astNode instanceof Function and exists(PropertyAccessor accessor | @@ -1351,8 +1342,7 @@ module ClassNode { result = method.getBody().flow() ) or - // ES6 class property in constructor - astNode instanceof ClassDefinition and + // ES6 class property or Function-style class methods via constructor kind = MemberKind::method() and exists(ThisNode receiver | receiver = this.getConstructor().getReceiver() and @@ -1366,14 +1356,6 @@ module ClassNode { result = proto.getAPropertySource() ) or - // Function-style class methods via constructor - astNode instanceof Function and - kind = MemberKind::method() and - exists(ThisNode receiver | - receiver = this.getConstructor().getReceiver() and - result = receiver.getAPropertySource() - ) - or // Function-style class accessors astNode instanceof Function and exists(PropertyAccessor accessor | From 7430d0e5e0b80fcdcc27ce2183aeec00554f2dd0 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 30 Apr 2025 19:55:42 +0200 Subject: [PATCH 13/18] Added failing test with method as field --- .../CallGraphs/AnnotatedTest/Test.expected | 2 ++ .../CallGraphs/AnnotatedTest/prototypes.js | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 0abd563b4193..de49ab455906 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,6 +2,8 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | +| prototypes.js:117:5:117:19 | this.tmpClass() | prototypes.js:113:1:113:22 | functio ... ss() {} | -1 | calls | +| prototypes.js:131:5:131:23 | this.tmpPrototype() | prototypes.js:127:1:127:26 | functio ... pe() {} | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js index 2556e07a2796..815a9e7f1e77 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js @@ -107,3 +107,36 @@ class Derived2 {} Derived2.prototype = Object.create(Base.prototype); /** name:Derived2.read */ Derived2.prototype.read = function() {}; + + +/** name:BanClass.tmpClass */ +function tmpClass() {} + +function callerClass() { + /** calls:BanClass.tmpClass */ + this.tmpClass(); +} +class BanClass { + constructor() { + this.tmpClass = tmpClass; + this.callerClass = callerClass; + } +} + +/** name:BanProtytpe.tmpPrototype */ +function tmpPrototype() {} + +function callerPrototype() { + /** calls:BanProtytpe.tmpPrototype */ + this.tmpPrototype(); +} + +function BanProtytpe() { + this.tmpPrototype = tmpPrototype; + this.callerPrototype = callerPrototype; +} + +function banInstantiation(){ + const instance = new BanProtytpe(); + instance.callerPrototype(); +} From 9bab59363c1a5ba1f0efad4ca402e0b977b95532 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 1 May 2025 08:15:26 +0200 Subject: [PATCH 14/18] Fix class instance method detection in constructor receiver --- javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll | 6 ++++++ .../library-tests/CallGraphs/AnnotatedTest/Test.expected | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 52547d5f5905..646d9c798cc2 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1330,6 +1330,9 @@ module ClassNode { accessor.getName() = name and result = accessor.getInit().flow() ) + or + kind = MemberKind::method() and + result = this.getConstructor().getReceiver().getAPropertySource(name) } override FunctionNode getAnInstanceMember(MemberKind kind) { @@ -1362,6 +1365,9 @@ module ClassNode { accessor = this.getAnAccessor(kind) and result = accessor.getInit().flow() ) + or + kind = MemberKind::method() and + result = this.getConstructor().getReceiver().getAPropertySource() } override FunctionNode getStaticMember(string name, MemberKind kind) { diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index de49ab455906..0abd563b4193 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -2,8 +2,6 @@ spuriousCallee missingCallee | constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | | constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | calls | -| prototypes.js:117:5:117:19 | this.tmpClass() | prototypes.js:113:1:113:22 | functio ... ss() {} | -1 | calls | -| prototypes.js:131:5:131:23 | this.tmpPrototype() | prototypes.js:127:1:127:26 | functio ... pe() {} | -1 | calls | badAnnotation accessorCall | accessors.js:12:1:12:5 | obj.f | accessors.js:5:8:5:12 | () {} | From c7d764f6663635f2e17dd7621fc88a8fa7632623 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 1 May 2025 09:18:44 +0200 Subject: [PATCH 15/18] Brought back `FunctionStyleClass` marked as `deprecated` --- javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 646d9c798cc2..4cceb9192ea9 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -1236,6 +1236,8 @@ module ClassNode { func instanceof AbstractCallable // the join-order goes bad if `func` has type `AbstractFunction`. } + deprecated class FunctionStyleClass = StandardClassNode; + /** * A function definition, targeted by a `new`-call or with prototype manipulation, seen as a `ClassNode` instance. * Or An ES6 class as a `ClassNode` instance. From c430a36b4cfe73ba9c86a33c7c5a59ffcce8d693 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 1 May 2025 12:17:58 +0200 Subject: [PATCH 16/18] Refactored merge `StandardClassNode` into `ClassNode` --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- .../lib/semmle/javascript/dataflow/Nodes.qll | 520 +++++++++--------- .../dataflow/internal/CallGraphs.qll | 2 +- 3 files changed, 252 insertions(+), 272 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 423c0f5ed1b0..af1676086628 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1236,7 +1236,7 @@ module API { exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) | ref = cls.getAReceiverNode() or - ref = cls.(DataFlow::ClassNode::StandardClassNode).getAPrototypeReference() + ref = cls.(DataFlow::ClassNode).getAPrototypeReference() ) or nd = MkUse(ref) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 4cceb9192ea9..aab89e7baa3e 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -861,21 +861,60 @@ module MemberKind { * * Additional patterns can be recognized as class nodes, by extending `DataFlow::ClassNode::Range`. */ -class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { +class ClassNode extends DataFlow::ValueNode, DataFlow::SourceNode { + override AST::ValueNode astNode; + AbstractCallable function; + + ClassNode() { + // ES6 class case + astNode instanceof ClassDefinition and + function.(AbstractClass).getClass() = astNode + or + // Function-style class case + astNode instanceof Function and + function.getFunction() = astNode and + ( + exists(getAFunctionValueWithPrototype(function)) + or + function = any(NewNode new).getCalleeNode().analyze().getAValue() + or + exists(string name | this = AccessPath::getAnAssignmentTo(name) | + exists(getAPrototypeReferenceInFile(name, this.getFile())) + or + exists(getAnInstantiationInFile(name, this.getFile())) + ) + ) + } + /** * Gets the unqualified name of the class, if it has one or one can be determined from the context. */ - string getName() { result = super.getName() } + string getName() { + astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).getName() + or + astNode instanceof Function and result = astNode.(Function).getName() + } /** * Gets a description of the class. */ - string describe() { result = super.describe() } + string describe() { + astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).describe() + or + astNode instanceof Function and result = astNode.(Function).describe() + } /** * Gets the constructor function of this class. */ - FunctionNode getConstructor() { result = super.getConstructor() } + FunctionNode getConstructor() { + // For ES6 classes + astNode instanceof ClassDefinition and + result = astNode.(ClassDefinition).getConstructor().getBody().flow() + or + // For function-style classes + astNode instanceof Function and result = this + } /** * Gets an instance method declared in this class, with the given name, if any. @@ -883,7 +922,7 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { * Does not include methods from superclasses. */ FunctionNode getInstanceMethod(string name) { - result = super.getInstanceMember(name, MemberKind::method()) + result = this.getInstanceMember(name, MemberKind::method()) } /** @@ -893,7 +932,7 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { * * Does not include methods from superclasses. */ - FunctionNode getAnInstanceMethod() { result = super.getAnInstanceMember(MemberKind::method()) } + FunctionNode getAnInstanceMethod() { result = this.getAnInstanceMember(MemberKind::method()) } /** * Gets the instance method, getter, or setter with the given name and kind. @@ -901,7 +940,43 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { * Does not include members from superclasses. */ FunctionNode getInstanceMember(string name, MemberKind kind) { - result = super.getInstanceMember(name, kind) + // ES6 class methods + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getMethod(name) and + not method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or + // ES6 class property or Function-style class methods via constructor + kind = MemberKind::method() and + exists(ThisNode receiver | + receiver = this.getConstructor().getReceiver() and + receiver.hasPropertyWrite(name, result) + ) + or + // Function-style class methods via prototype + kind = MemberKind::method() and + exists(DataFlow::SourceNode proto | + proto = this.getAPrototypeReference() and + proto.hasPropertyWrite(name, result) + ) + or + // Function-style class accessors + astNode instanceof Function and + exists(PropertyAccessor accessor | + accessor = this.getAnAccessor(kind) and + accessor.getName() = name and + result = accessor.getInit().flow() + ) + or + kind = MemberKind::method() and + result = + [ + this.getConstructor().getReceiver().getAPropertySource(name), + this.getAPrototypeReference().getAPropertySource(name) + ] } /** @@ -909,20 +984,66 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { * * Does not include members from superclasses. */ - FunctionNode getAnInstanceMember(MemberKind kind) { result = super.getAnInstanceMember(kind) } + FunctionNode getAnInstanceMember(MemberKind kind) { + // ES6 class methods + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getAMethod() and + not method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or + // ES6 class property or Function-style class methods via constructor + kind = MemberKind::method() and + exists(ThisNode receiver | + receiver = this.getConstructor().getReceiver() and + result = receiver.getAPropertySource() + ) + or + // Function-style class methods via prototype + kind = MemberKind::method() and + exists(DataFlow::SourceNode proto | + proto = this.getAPrototypeReference() and + result = proto.getAPropertySource() + ) + or + // Function-style class accessors + astNode instanceof Function and + exists(PropertyAccessor accessor | + accessor = this.getAnAccessor(kind) and + result = accessor.getInit().flow() + ) + or + kind = MemberKind::method() and + result = + [ + this.getConstructor().getReceiver().getAPropertySource(), + this.getAPrototypeReference().getAPropertySource() + ] + } /** * Gets an instance method, getter, or setter declared in this class. * * Does not include members from superclasses. */ - FunctionNode getAnInstanceMember() { result = super.getAnInstanceMember(_) } + FunctionNode getAnInstanceMember() { result = this.getAnInstanceMember(_) } /** * Gets the static method, getter, or setter declared in this class with the given name and kind. */ FunctionNode getStaticMember(string name, MemberKind kind) { - result = super.getStaticMember(name, kind) + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getMethod(name) and + method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or + kind.isMethod() and + result = this.getAPropertySource(name) } /** @@ -935,7 +1056,18 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { /** * Gets a static method, getter, or setter declared in this class with the given kind. */ - FunctionNode getAStaticMember(MemberKind kind) { result = super.getAStaticMember(kind) } + FunctionNode getAStaticMember(MemberKind kind) { + exists(MethodDeclaration method | + astNode instanceof ClassDefinition and + method = astNode.(ClassDefinition).getAMethod() and + method.isStatic() and + kind = MemberKind::of(method) and + result = method.getBody().flow() + ) + or + kind.isMethod() and + result = this.getAPropertySource() + } /** * Gets a static method declared in this class. @@ -944,10 +1076,79 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { */ FunctionNode getAStaticMethod() { result = this.getAStaticMember(MemberKind::method()) } + /** + * Gets a reference to the prototype of this class. + * Only applies to function-style classes. + */ + DataFlow::SourceNode getAPrototypeReference() { + exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) | + result = base.getAPropertyRead("prototype") + or + result = base.getAPropertySource("prototype") + ) + or + exists(string name | + this = AccessPath::getAnAssignmentTo(name) and + result = getAPrototypeReferenceInFile(name, this.getFile()) + ) + or + exists(string name, DataFlow::SourceNode root | + result = AccessPath::getAReferenceOrAssignmentTo(root, name + ".prototype").getALocalSource() and + this = AccessPath::getAnAssignmentTo(root, name) + ) + or + exists(ExtendCall call | + call.getDestinationOperand() = this.getAPrototypeReference() and + result = call.getASourceOperand() + ) + } + + private PropertyAccessor getAnAccessor(MemberKind kind) { + // Only applies to function-style classes + astNode instanceof Function and + result.getObjectExpr() = this.getAPrototypeReference().asExpr() and + ( + kind = MemberKind::getter() and + result instanceof PropertyGetter + or + kind = MemberKind::setter() and + result instanceof PropertySetter + ) + } + /** * Gets a dataflow node that refers to the superclass of this class. */ - DataFlow::Node getASuperClassNode() { result = super.getASuperClassNode() } + DataFlow::Node getASuperClassNode() { + // ES6 class superclass + astNode instanceof ClassDefinition and + result = astNode.(ClassDefinition).getSuperClass().flow() + or + ( + // C.prototype = Object.create(D.prototype) + exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto | + this.getAPropertySource("prototype") = objectCreate and + objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and + superProto.flowsTo(objectCreate.getArgument(0)) and + superProto.getPropertyName() = "prototype" and + result = superProto.getBase() + ) + or + // C.prototype = new D() + exists(DataFlow::NewNode newCall | + this.getAPropertySource("prototype") = newCall and + result = newCall.getCalleeNode() + ) + or + // util.inherits(C, D); + exists(DataFlow::CallNode inheritsCall | + inheritsCall = DataFlow::moduleMember("util", "inherits").getACall() + | + this = inheritsCall.getArgument(0).getALocalSource() and + result = inheritsCall.getArgument(1) + ) + ) + } /** * Gets a direct super class of this class. @@ -1136,13 +1337,47 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range { * Gets the type annotation for the field `fieldName`, if any. */ TypeAnnotation getFieldTypeAnnotation(string fieldName) { - result = super.getFieldTypeAnnotation(fieldName) + exists(FieldDeclaration field | + field.getDeclaringClass() = astNode and + fieldName = field.getName() and + result = field.getTypeAnnotation() + ) } /** * Gets a decorator applied to this class. */ - DataFlow::Node getADecorator() { result = super.getADecorator() } + DataFlow::Node getADecorator() { + astNode instanceof ClassDefinition and + result = astNode.(ClassDefinition).getADecorator().getExpression().flow() + } +} + +/** + * Helper predicate to get a prototype reference in a file. + */ +private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) { + result.getBase() = AccessPath::getAReferenceOrAssignmentTo(name) and + result.getPropertyName() = "prototype" and + result.getFile() = f +} + +/** + * Helper predicate to get an instantiation in a file. + */ +private DataFlow::NewNode getAnInstantiationInFile(string name, File f) { + result = AccessPath::getAReferenceTo(name).(DataFlow::LocalSourceNode).getAnInstantiation() and + result.getFile() = f +} + +/** + * Gets a reference to the function `func`, where there exists a read/write of the "prototype" property on that reference. + */ +pragma[noinline] +private DataFlow::SourceNode getAFunctionValueWithPrototype(AbstractValue func) { + exists(result.getAPropertyReference("prototype")) and + result.analyze().getAValue() = pragma[only_bind_into](func) and + func instanceof AbstractCallable // the join-order goes bad if `func` has type `AbstractFunction`. } module ClassNode { @@ -1214,262 +1449,7 @@ module ClassNode { DataFlow::Node getADecorator() { none() } } - private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) { - result.getBase() = AccessPath::getAReferenceOrAssignmentTo(name) and - result.getPropertyName() = "prototype" and - result.getFile() = f - } - - pragma[nomagic] - private DataFlow::NewNode getAnInstantiationInFile(string name, File f) { - result = AccessPath::getAReferenceTo(name).(DataFlow::LocalSourceNode).getAnInstantiation() and - result.getFile() = f - } - - /** - * Gets a reference to the function `func`, where there exists a read/write of the "prototype" property on that reference. - */ - pragma[noinline] - private DataFlow::SourceNode getAFunctionValueWithPrototype(AbstractValue func) { - exists(result.getAPropertyReference("prototype")) and - result.analyze().getAValue() = pragma[only_bind_into](func) and - func instanceof AbstractCallable // the join-order goes bad if `func` has type `AbstractFunction`. - } - - deprecated class FunctionStyleClass = StandardClassNode; - - /** - * A function definition, targeted by a `new`-call or with prototype manipulation, seen as a `ClassNode` instance. - * Or An ES6 class as a `ClassNode` instance. - */ - class StandardClassNode extends Range, DataFlow::ValueNode { - override AST::ValueNode astNode; - AbstractCallable function; - - StandardClassNode() { - // ES6 class case - astNode instanceof ClassDefinition and - function.(AbstractClass).getClass() = astNode - or - // Function-style class case - astNode instanceof Function and - function.getFunction() = astNode and - ( - exists(getAFunctionValueWithPrototype(function)) - or - function = any(NewNode new).getCalleeNode().analyze().getAValue() - or - exists(string name | this = AccessPath::getAnAssignmentTo(name) | - exists(getAPrototypeReferenceInFile(name, this.getFile())) - or - exists(getAnInstantiationInFile(name, this.getFile())) - ) - ) - } - - override string getName() { - astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).getName() - or - astNode instanceof Function and result = astNode.(Function).getName() - } - - override string describe() { - astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).describe() - or - astNode instanceof Function and result = astNode.(Function).describe() - } - - override FunctionNode getConstructor() { - // For ES6 classes - astNode instanceof ClassDefinition and - result = astNode.(ClassDefinition).getConstructor().getBody().flow() - or - // For function-style classes - astNode instanceof Function and result = this - } - - private PropertyAccessor getAnAccessor(MemberKind kind) { - // Only applies to function-style classes - astNode instanceof Function and - result.getObjectExpr() = this.getAPrototypeReference().asExpr() and - ( - kind = MemberKind::getter() and - result instanceof PropertyGetter - or - kind = MemberKind::setter() and - result instanceof PropertySetter - ) - } - - override FunctionNode getInstanceMember(string name, MemberKind kind) { - // ES6 class methods - exists(MethodDeclaration method | - astNode instanceof ClassDefinition and - method = astNode.(ClassDefinition).getMethod(name) and - not method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - // ES6 class property or Function-style class methods via constructor - kind = MemberKind::method() and - exists(ThisNode receiver | - receiver = this.getConstructor().getReceiver() and - receiver.hasPropertyWrite(name, result) - ) - or - // Function-style class methods via prototype - kind = MemberKind::method() and - exists(DataFlow::SourceNode proto | - proto = this.getAPrototypeReference() and - proto.hasPropertyWrite(name, result) - ) - or - // Function-style class accessors - astNode instanceof Function and - exists(PropertyAccessor accessor | - accessor = this.getAnAccessor(kind) and - accessor.getName() = name and - result = accessor.getInit().flow() - ) - or - kind = MemberKind::method() and - result = this.getConstructor().getReceiver().getAPropertySource(name) - } - - override FunctionNode getAnInstanceMember(MemberKind kind) { - // ES6 class methods - exists(MethodDeclaration method | - astNode instanceof ClassDefinition and - method = astNode.(ClassDefinition).getAMethod() and - not method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - // ES6 class property or Function-style class methods via constructor - kind = MemberKind::method() and - exists(ThisNode receiver | - receiver = this.getConstructor().getReceiver() and - result = receiver.getAPropertySource() - ) - or - // Function-style class methods via prototype - kind = MemberKind::method() and - exists(DataFlow::SourceNode proto | - proto = this.getAPrototypeReference() and - result = proto.getAPropertySource() - ) - or - // Function-style class accessors - astNode instanceof Function and - exists(PropertyAccessor accessor | - accessor = this.getAnAccessor(kind) and - result = accessor.getInit().flow() - ) - or - kind = MemberKind::method() and - result = this.getConstructor().getReceiver().getAPropertySource() - } - - override FunctionNode getStaticMember(string name, MemberKind kind) { - exists(MethodDeclaration method | - astNode instanceof ClassDefinition and - method = astNode.(ClassDefinition).getMethod(name) and - method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - kind.isMethod() and - result = this.getAPropertySource(name) - } - - override FunctionNode getAStaticMember(MemberKind kind) { - exists(MethodDeclaration method | - astNode instanceof ClassDefinition and - method = astNode.(ClassDefinition).getAMethod() and - method.isStatic() and - kind = MemberKind::of(method) and - result = method.getBody().flow() - ) - or - kind.isMethod() and - result = this.getAPropertySource() - } - - /** - * Gets a reference to the prototype of this class. - * Only applies to function-style classes. - */ - DataFlow::SourceNode getAPrototypeReference() { - exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) | - result = base.getAPropertyRead("prototype") - or - result = base.getAPropertySource("prototype") - ) - or - exists(string name | - this = AccessPath::getAnAssignmentTo(name) and - result = getAPrototypeReferenceInFile(name, this.getFile()) - ) - or - exists(string name, DataFlow::SourceNode root | - result = - AccessPath::getAReferenceOrAssignmentTo(root, name + ".prototype").getALocalSource() and - this = AccessPath::getAnAssignmentTo(root, name) - ) - or - exists(ExtendCall call | - call.getDestinationOperand() = this.getAPrototypeReference() and - result = call.getASourceOperand() - ) - } - - override DataFlow::Node getASuperClassNode() { - // ES6 class superclass - astNode instanceof ClassDefinition and - result = astNode.(ClassDefinition).getSuperClass().flow() - or - ( - // C.prototype = Object.create(D.prototype) - exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto | - this.getAPropertySource("prototype") = objectCreate and - objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and - superProto.flowsTo(objectCreate.getArgument(0)) and - superProto.getPropertyName() = "prototype" and - result = superProto.getBase() - ) - or - // C.prototype = new D() - exists(DataFlow::NewNode newCall | - this.getAPropertySource("prototype") = newCall and - result = newCall.getCalleeNode() - ) - or - // util.inherits(C, D); - exists(DataFlow::CallNode inheritsCall | - inheritsCall = DataFlow::moduleMember("util", "inherits").getACall() - | - this = inheritsCall.getArgument(0).getALocalSource() and - result = inheritsCall.getArgument(1) - ) - ) - } - - override TypeAnnotation getFieldTypeAnnotation(string fieldName) { - exists(FieldDeclaration field | - field.getDeclaringClass() = astNode and - fieldName = field.getName() and - result = field.getTypeAnnotation() - ) - } - - override DataFlow::Node getADecorator() { - astNode instanceof ClassDefinition and - result = astNode.(ClassDefinition).getADecorator().getExpression().flow() - } - } + deprecated class FunctionStyleClass = ClassNode; } /** diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll index 44f827ddf3d3..cc4c883381ea 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll @@ -254,7 +254,7 @@ module CallGraph { not exists(DataFlow::ClassNode cls | node = cls.getConstructor().getReceiver() or - node = cls.(DataFlow::ClassNode::StandardClassNode).getAPrototypeReference() + node = cls.(DataFlow::ClassNode).getAPrototypeReference() ) } From b8be1bcee89b55de8bee6a1066ac8315816df539 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 2 May 2025 10:31:18 +0200 Subject: [PATCH 17/18] JS: Avoid duplication with constructor body --- javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll | 1 + javascript/ql/test/library-tests/ClassNode/tests.expected | 1 - javascript/ql/test/library-tests/Classes/tests.expected | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index aab89e7baa3e..353d50eea1e1 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -872,6 +872,7 @@ class ClassNode extends DataFlow::ValueNode, DataFlow::SourceNode { or // Function-style class case astNode instanceof Function and + not astNode = any(ClassDefinition cls).getConstructor().getBody() and function.getFunction() = astNode and ( exists(getAFunctionValueWithPrototype(function)) diff --git a/javascript/ql/test/library-tests/ClassNode/tests.expected b/javascript/ql/test/library-tests/ClassNode/tests.expected index 1337e0c56942..687118ffa0bf 100644 --- a/javascript/ql/test/library-tests/ClassNode/tests.expected +++ b/javascript/ql/test/library-tests/ClassNode/tests.expected @@ -15,7 +15,6 @@ getAReceiverNode | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:4:17:4:16 | this | | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:7:6:7:5 | this | | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:9:10:9:9 | this | -| tst.js:3:9:3:8 | () {} | tst.js:3:9:3:8 | this | | tst.js:13:1:13:21 | class A ... ds A {} | tst.js:13:20:13:19 | this | | tst.js:15:1:15:15 | function B() {} | tst.js:15:1:15:0 | this | | tst.js:15:1:15:15 | function B() {} | tst.js:17:19:17:18 | this | diff --git a/javascript/ql/test/library-tests/Classes/tests.expected b/javascript/ql/test/library-tests/Classes/tests.expected index 460614e02e10..aadd449349c2 100644 --- a/javascript/ql/test/library-tests/Classes/tests.expected +++ b/javascript/ql/test/library-tests/Classes/tests.expected @@ -194,7 +194,6 @@ test_ConstructorDefinitions | tst.js:11:9:11:8 | constructor() {} | test_ClassNodeConstructor | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:4:12:4:11 | () {} | -| dataflow.js:4:12:4:11 | () {} | dataflow.js:4:12:4:11 | () {} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | () {} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | points.js:2:14:5:3 | (x, y) ... y;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:14:24:3 | (x, y, ... c;\\n } | From 30694c11d61cb159193b3e4c4f96c77a55c9fde4 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Fri, 2 May 2025 11:12:06 +0200 Subject: [PATCH 18/18] Removed code duplication --- .../lib/semmle/javascript/dataflow/Nodes.qll | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll index 353d50eea1e1..f0d31df2a8f3 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll @@ -950,20 +950,6 @@ class ClassNode extends DataFlow::ValueNode, DataFlow::SourceNode { result = method.getBody().flow() ) or - // ES6 class property or Function-style class methods via constructor - kind = MemberKind::method() and - exists(ThisNode receiver | - receiver = this.getConstructor().getReceiver() and - receiver.hasPropertyWrite(name, result) - ) - or - // Function-style class methods via prototype - kind = MemberKind::method() and - exists(DataFlow::SourceNode proto | - proto = this.getAPrototypeReference() and - proto.hasPropertyWrite(name, result) - ) - or // Function-style class accessors astNode instanceof Function and exists(PropertyAccessor accessor | @@ -995,20 +981,6 @@ class ClassNode extends DataFlow::ValueNode, DataFlow::SourceNode { result = method.getBody().flow() ) or - // ES6 class property or Function-style class methods via constructor - kind = MemberKind::method() and - exists(ThisNode receiver | - receiver = this.getConstructor().getReceiver() and - result = receiver.getAPropertySource() - ) - or - // Function-style class methods via prototype - kind = MemberKind::method() and - exists(DataFlow::SourceNode proto | - proto = this.getAPrototypeReference() and - result = proto.getAPropertySource() - ) - or // Function-style class accessors astNode instanceof Function and exists(PropertyAccessor accessor |