diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 9ea03a653b9..51101e3182a 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -604,12 +604,36 @@ module API { or lbl = Label::promisedError() and PromiseFlow::storeStep(rhs, pred, Promises::errorProp()) + or + // The return-value of a getter G counts as a definition of property G + // (Ordinary methods and properties are handled as PropWrite nodes) + exists(string name | lbl = Label::member(name) | + rhs = pred.(DataFlow::ObjectLiteralNode).getPropertyGetter(name).getAReturn() + or + rhs = + pred.(DataFlow::ClassNode) + .getStaticMember(name, DataFlow::MemberKind::getter()) + .getAReturn() + ) + or + // If `new C()` escapes, generate edges to its instance members + exists(DataFlow::ClassNode cls, string name | + pred = cls.getAClassReference().getAnInstantiation() and + lbl = Label::member(name) + | + rhs = cls.getInstanceMethod(name) + or + rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn() + ) ) or exists(DataFlow::ClassNode cls, string name | base = MkClassInstance(cls) and - lbl = Label::member(name) and + lbl = Label::member(name) + | rhs = cls.getInstanceMethod(name) + or + rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn() ) or exists(DataFlow::FunctionNode f | diff --git a/javascript/ql/test/ApiGraphs/accessors/VerifyAssertions.expected b/javascript/ql/test/ApiGraphs/accessors/VerifyAssertions.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/javascript/ql/test/ApiGraphs/accessors/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/accessors/VerifyAssertions.ql new file mode 100644 index 00000000000..b9c54e26072 --- /dev/null +++ b/javascript/ql/test/ApiGraphs/accessors/VerifyAssertions.ql @@ -0,0 +1 @@ +import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/ApiGraphs/accessors/index.js b/javascript/ql/test/ApiGraphs/accessors/index.js new file mode 100644 index 00000000000..707d4dc01b8 --- /dev/null +++ b/javascript/ql/test/ApiGraphs/accessors/index.js @@ -0,0 +1,65 @@ +const foo = require('foo'); + +foo({ + myMethod(x) { /* use (parameter 0 (member myMethod (parameter 0 (member exports (module foo))))) */ + console.log(x); + } +}); + +foo({ + get myMethod() { + return function(x) { /* use (parameter 0 (member myMethod (parameter 0 (member exports (module foo))))) */ + console.log(x) + } + } +}); + +class C { + static myMethod(x) { /* use (parameter 0 (member myMethod (parameter 0 (member exports (module foo))))) */ + console.log(x); + } +} +foo(C); + +class D { + myMethod(x) { /* use (parameter 0 (member myMethod (parameter 0 (member exports (module foo))))) */ + console.log(x); + } +} +foo(new D()); + +class E { + get myMethod() { + return function(x) { /* use (parameter 0 (member myMethod (parameter 0 (member exports (module foo))))) */ + console.log(x); + } + } +} +foo(new E()); + +class F { + static get myMethod() { + return function(x) { /* use (parameter 0 (member myMethod (parameter 0 (member exports (module foo))))) */ + console.log(x); + } + } +} +foo(F); + +// Cases where the class is instantiated in `foo`: + +class G { + myMethod2(x) { /* use (parameter 0 (member myMethod2 (instance (parameter 0 (member exports (module foo)))))) */ + console.log(x); + } +} +foo(G); + +class H { + get myMethod2() { + return function (x) { /* use (parameter 0 (member myMethod2 (instance (parameter 0 (member exports (module foo)))))) */ + console.log(x); + } + } +} +foo(H);