Merge pull request #4564 from asgerf/js/react-hooks

Approved by esbena
This commit is contained in:
CodeQL CI
2020-10-30 21:00:31 +00:00
committed by GitHub
21 changed files with 669 additions and 66 deletions

View File

@@ -4,6 +4,10 @@
* Angular-specific taint sources and sinks are now recognized by the security queries. * Angular-specific taint sources and sinks are now recognized by the security queries.
* Support for React has improved, with better handling of react hooks, react-router path parameters, lazy-loaded components, and components transformed using `react-redux` and/or `styled-components`.
* Dynamic imports are now analyzed more precisely.
* Support for the following frameworks and libraries has been improved: * Support for the following frameworks and libraries has been improved:
- [@angular/*](https://www.npmjs.com/package/@angular/core) - [@angular/*](https://www.npmjs.com/package/@angular/core)
- [AWS Serverless](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) - [AWS Serverless](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html)
@@ -29,7 +33,13 @@
- [needle](https://www.npmjs.com/package/needle) - [needle](https://www.npmjs.com/package/needle)
- [object-inspect](https://www.npmjs.com/package/object-inspect) - [object-inspect](https://www.npmjs.com/package/object-inspect)
- [pretty-format](https://www.npmjs.com/package/pretty-format) - [pretty-format](https://www.npmjs.com/package/pretty-format)
- [react](https://www.npmjs.com/package/react)
- [react-router-dom](https://www.npmjs.com/package/react-router-dom)
- [react-redux](https://www.npmjs.com/package/react-redux)
- [redis](https://www.npmjs.com/package/redis)
- [redux](https://www.npmjs.com/package/redux)
- [stringify-object](https://www.npmjs.com/package/stringify-object) - [stringify-object](https://www.npmjs.com/package/stringify-object)
- [styled-components](https://www.npmjs.com/package/styled-components)
- [throttle-debounce](https://www.npmjs.com/package/throttle-debounce) - [throttle-debounce](https://www.npmjs.com/package/throttle-debounce)
- [underscore](https://www.npmjs.com/package/underscore) - [underscore](https://www.npmjs.com/package/underscore)

View File

@@ -57,6 +57,10 @@ class SSTINunjucksSink extends ServerSideTemplateInjectionSink {
} }
} }
class LodashTemplateSink extends ServerSideTemplateInjectionSink {
LodashTemplateSink() { this = LodashUnderscore::member("template").getACall().getArgument(0) }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, ServerSideTemplateInjectionConfiguration c from DataFlow::PathNode source, DataFlow::PathNode sink, ServerSideTemplateInjectionConfiguration c
where c.hasFlowPath(source, sink) where c.hasFlowPath(source, sink)
select sink.getNode(), source, sink, select sink.getNode(), source, sink,

View File

@@ -11,12 +11,22 @@
import javascript import javascript
import CallGraphQuality import CallGraphQuality
class BasicTaintConfiguration extends TaintTracking::Configuration {
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
}
predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) { predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) {
any(BasicTaintConfiguration cfg).isAdditionalFlowStep(pred, succ) and (
any(TaintTracking::AdditionalTaintStep dts).step(pred, succ)
or
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ)
or
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ, _, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStep(pred, succ, _)
or
any(DataFlow::AdditionalFlowStep cfg).storeStep(pred, succ, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _)
) and
not pred.getFile() instanceof IgnoredFile and not pred.getFile() instanceof IgnoredFile and
not succ.getFile() instanceof IgnoredFile not succ.getFile() instanceof IgnoredFile
} }

View File

@@ -641,3 +641,39 @@ private module ClosurePromise {
override predicate step(DataFlow::Node src, DataFlow::Node dst) { src = pred and dst = this } override predicate step(DataFlow::Node src, DataFlow::Node dst) { src = pred and dst = this }
} }
} }
private module DynamicImportSteps {
/**
* A step from an export value to its uses via dynamic imports.
*
* For example:
* ```js
* // foo.js
* export default Foo
*
* // bar.js
* let Foo = await import('./foo');
* ```
*/
class DynamicImportStep extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(DynamicImportExpr imprt |
pred = imprt.getImportedModule().getAnExportedValue("default") and
succ = imprt.flow() and
prop = Promises::valueProp()
)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
// Special-case code like `(await import(x)).Foo` to boost type tracking depth.
exists(
DynamicImportExpr imprt, string name, DataFlow::Node mid, DataFlow::SourceNode awaited
|
pred = imprt.getImportedModule().getAnExportedValue(name) and
mid.getALocalSource() = imprt.flow() and
PromiseFlow::loadStep(mid, awaited, Promises::valueProp()) and
succ = awaited.getAPropertyRead(name)
)
}
}
}

View File

@@ -307,7 +307,8 @@ module SourceNode {
astNode instanceof FunctionBindExpr or astNode instanceof FunctionBindExpr or
astNode instanceof DynamicImportExpr or astNode instanceof DynamicImportExpr or
astNode instanceof ImportSpecifier or astNode instanceof ImportSpecifier or
astNode instanceof ImportMetaExpr astNode instanceof ImportMetaExpr or
astNode instanceof TaggedTemplateExpr
) )
or or
DataFlow::parameterNode(this, _) DataFlow::parameterNode(this, _)

View File

@@ -327,7 +327,7 @@ module TaintTracking {
* taint to flow from `v` to any read of `c2.state.p`, where `c2` * taint to flow from `v` to any read of `c2.state.p`, where `c2`
* also is an instance of `C`. * also is an instance of `C`.
*/ */
private class ReactComponentStateTaintStep extends AdditionalTaintStep, DataFlow::ValueNode { private class ReactComponentStateTaintStep extends AdditionalTaintStep {
DataFlow::Node source; DataFlow::Node source;
ReactComponentStateTaintStep() { ReactComponentStateTaintStep() {
@@ -358,7 +358,7 @@ module TaintTracking {
* taint to flow from `v` to any read of `c2.props.p`, where `c2` * taint to flow from `v` to any read of `c2.props.p`, where `c2`
* also is an instance of `C`. * also is an instance of `C`.
*/ */
private class ReactComponentPropsTaintStep extends AdditionalTaintStep, DataFlow::ValueNode { private class ReactComponentPropsTaintStep extends AdditionalTaintStep {
DataFlow::Node source; DataFlow::Node source;
ReactComponentPropsTaintStep() { ReactComponentPropsTaintStep() {

View File

@@ -5,31 +5,116 @@
import javascript import javascript
/** /**
* A function composed from a collection of functions. * A call to a function that constructs a function composition `f(g(h(...)))` from a
* series functions `f, g, h, ...`.
*/ */
private class ComposedFunction extends DataFlow::CallNode { class FunctionCompositionCall extends DataFlow::CallNode {
ComposedFunction() { FunctionCompositionCall::Range range;
exists(string name |
name = "just-compose" or FunctionCompositionCall() { this = range }
name = "compose-function"
| /**
this = DataFlow::moduleImport(name).getACall() * Gets the `i`th function in the composition `f(g(h(...)))`, counting from left to right.
) *
or * Note that this is the opposite of the order in which the function are invoked,
this = LodashUnderscore::member("flow").getACall() * that is, `g` occurs later than `f` in `f(g(...))` but is invoked before `f`.
*/
DataFlow::Node getOperandNode(int i) { result = range.getOperandNode(i) }
/** Gets a node holding one of the functions to be composed. */
final DataFlow::Node getAnOperandNode() { result = getOperandNode(_) }
/**
* Gets the function flowing into the `i`th function in the composition `f(g(h(...)))`.
*
* Note that this is the opposite of the order in which the function are invoked,
* that is, `g` occurs later than `f` in `f(g(...))` but is invoked before `f`.
*/
final DataFlow::FunctionNode getOperandFunction(int i) {
result = getOperandNode(i).getALocalSource()
}
/** Gets any of the functions being composed. */
final DataFlow::Node getAnOperandFunction() { result = getOperandFunction(_) }
/** Gets the number of functions being composed. */
int getNumOperand() { result = range.getNumOperand() }
}
/**
* Companion module to the `FunctionCompositionCall` class.
*/
module FunctionCompositionCall {
/**
* Class that determines the set of values in `FunctionCompositionCall`.
*
* May be subclassed to classify more calls as function compositions.
*/
abstract class Range extends DataFlow::CallNode {
/**
* Gets the function flowing into the `i`th function in the composition `f(g(h(...)))`.
*/
abstract DataFlow::Node getOperandNode(int i);
/** Gets the number of functions being composed. */
abstract int getNumOperand();
} }
/** /**
* Gets the ith function in this composition. * A function composition call that accepts its operands in an array or
* via the arguments list.
*
* For simplicity, we model every composition function as if it supported this.
*/ */
DataFlow::FunctionNode getFunction(int i) { result.flowsTo(getArgument(i)) } abstract private class WithArrayOverloading extends Range {
/** Gets the `i`th argument to the call or the `i`th array element passed into the call. */
DataFlow::Node getEffectiveArgument(int i) {
result = getArgument(0).(DataFlow::ArrayCreationNode).getElement(i)
or
not getArgument(0) instanceof DataFlow::ArrayCreationNode and
result = getArgument(i)
}
override int getNumOperand() {
result = getArgument(0).(DataFlow::ArrayCreationNode).getSize()
or
not getArgument(0) instanceof DataFlow::ArrayCreationNode and
result = getNumArgument()
}
}
/** A call whose arguments are functions `f,g,h` which are composed into `f(g(h(...))` */
private class RightToLeft extends WithArrayOverloading {
RightToLeft() {
this = DataFlow::moduleImport(["compose-function"]).getACall()
or
this = DataFlow::moduleMember(["redux", "ramda"], "compose").getACall()
or
this = LodashUnderscore::member("flowRight").getACall()
}
override DataFlow::Node getOperandNode(int i) { result = getEffectiveArgument(i) }
}
/** A call whose arguments are functions `f,g,h` which are composed into `f(g(h(...))` */
private class LeftToRight extends WithArrayOverloading {
LeftToRight() {
this = DataFlow::moduleImport("just-compose").getACall()
or
this = LodashUnderscore::member("flow").getACall()
}
override DataFlow::Node getOperandNode(int i) {
result = getEffectiveArgument(getNumOperand() - i - 1)
}
}
} }
/** /**
* A taint step for a composed function. * A taint step for a composed function.
*/ */
private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintStep { private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintStep {
ComposedFunction composed; FunctionCompositionCall composed;
DataFlow::CallNode call; DataFlow::CallNode call;
ComposedFunctionTaintStep() { ComposedFunctionTaintStep() {
@@ -38,25 +123,24 @@ private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintSt
} }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(int fnIndex, DataFlow::FunctionNode fn | fn = composed.getFunction(fnIndex) | exists(int fnIndex, DataFlow::FunctionNode fn | fn = composed.getOperandFunction(fnIndex) |
// flow out of the composed call // flow into the first function
fnIndex = composed.getNumArgument() - 1 and fnIndex = composed.getNumOperand() - 1 and
pred = fn.getAReturn() and exists(int callArgIndex |
succ = this pred = call.getArgument(callArgIndex) and
succ = fn.getParameter(callArgIndex)
)
or or
if fnIndex = 0 // flow through the composed functions
then exists(DataFlow::FunctionNode predFn | predFn = composed.getOperandFunction(fnIndex + 1) |
// flow into the first composed function pred = predFn.getReturnNode() and
exists(int callArgIndex | succ = fn.getParameter(0)
pred = call.getArgument(callArgIndex) and )
succ = fn.getParameter(callArgIndex) or
) // flow out of the composed call
else fnIndex = 0 and
// flow through the composed functions pred = fn.getReturnNode() and
exists(DataFlow::FunctionNode predFn | predFn = composed.getFunction(fnIndex - 1) | succ = this
pred = predFn.getAReturn() and
succ = fn.getParameter(0)
)
) )
} }
} }

View File

@@ -407,7 +407,12 @@ module LodashUnderscore {
"shuffle", "sample", "toArray", "partition", "compact", "first", "initial", "last", "shuffle", "sample", "toArray", "partition", "compact", "first", "initial", "last",
"rest", "flatten", "without", "difference", "uniq", "unique", "unzip", "transpose", "rest", "flatten", "without", "difference", "uniq", "unique", "unzip", "transpose",
"object", "chunk", "values", "mapObject", "pick", "omit", "defaults", "clone", "tap", "object", "chunk", "values", "mapObject", "pick", "omit", "defaults", "clone", "tap",
"identity"] and "identity",
// String category
"camelCase", "capitalize", "deburr", "kebabCase", "lowerCase", "lowerFirst", "pad",
"padEnd", "padStart", "repeat", "replace", "snakeCase", "split", "startCase", "toLower",
"toUpper", "trim", "trimEnd", "trimStart", "truncate", "unescape", "upperCase",
"upperFirst", "words"] and
pred = call.getArgument(0) and pred = call.getArgument(0) and
succ = call succ = call
or or

View File

@@ -3,6 +3,8 @@
*/ */
import javascript import javascript
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
private import semmle.javascript.dataflow.internal.PreCallGraphStep
/** /**
* Gets a reference to the 'React' object. * Gets a reference to the 'React' object.
@@ -548,3 +550,229 @@ private class ReactJSXElement extends JSXElement {
*/ */
ReactComponent getComponent() { result = component } ReactComponent getComponent() { result = component }
} }
/**
* Step through the state variable of a `useState` call.
*
* It returns a pair of the current state, and a callback to change the state.
*
* For example:
* ```js
* let [state, setState] = useState(initialValue);
* let [state, setState] = useState(() => initialValue); // lazy initial state
*
* setState(newState);
* setState(prevState => { ... });
* ```
*/
private class UseStateStep extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call | call = react().getAMemberCall("useState") |
pred =
[call.getArgument(0), // initial state
call.getCallback(0).getReturnNode(), // lazy initial state
call.getAPropertyRead("1").getACall().getArgument(0), // setState invocation
call.getAPropertyRead("1").getACall().getCallback(0).getReturnNode()] and // setState with callback
succ = call.getAPropertyRead("0")
or
// Propagate current state into the callback argument of `setState(prevState => { ... })`
pred = call.getAPropertyRead("0") and
succ = call.getAPropertyRead("1").getACall().getCallback(0).getParameter(0)
)
}
}
/**
* A step through a React context object.
*
* For example:
* ```js
* let MyContext = React.createContext('foo');
*
* <MyContext.Provider value={pred}>
* <Foo/>
* </MyContext.Provider>
*
* function Foo() {
* let succ = useContext(MyContext);
* }
* ```
*/
private class UseContextStep extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode context |
pred = getAContextInput(context) and
succ = getAContextOutput(context)
)
}
}
/**
* Gets a data flow node referring to the result of the given `createContext` call.
*/
private DataFlow::SourceNode getAContextRef(DataFlow::CallNode createContext) {
createContext = react().getAMemberCall("createContext") and
result = createContext
or
// Track through imports/exports, but not full type tracking, so this can be used as a PreCallGraphStep.
exists(DataFlow::Node mid |
getAContextRef(createContext).flowsTo(mid) and
FlowSteps::propertyFlowStep(mid, result)
)
}
/**
* Gets a data flow node whose value is provided to the given context object.
*
* For example:
* ```jsx
* React.createContext(x);
* <MyContext.Provider value={x}>
* ```
*/
pragma[nomagic]
private DataFlow::Node getAContextInput(DataFlow::CallNode createContext) {
createContext = react().getAMemberCall("createContext") and
result = createContext.getArgument(0) // initial value
or
exists(JSXElement provider |
getAContextRef(createContext)
.getAPropertyRead("Provider")
.flowsTo(provider.getNameExpr().flow()) and
result = provider.getAttributeByName("value").getValue().flow()
)
}
/**
* Gets a data flow node whose value is obtained from the given context object.
*
* For example:
* ```js
* let value = useContext(MyContext);
* ```
*/
pragma[nomagic]
private DataFlow::SourceNode getAContextOutput(DataFlow::CallNode createContext) {
exists(DataFlow::CallNode call |
call = react().getAMemberCall("useContext") and
getAContextRef(createContext).flowsTo(call.getArgument(0)) and
result = call
)
or
exists(DataFlow::ClassNode cls |
getAContextRef(createContext).flowsTo(cls.getAPropertyWrite("contextType").getRhs()) and
result = cls.getAReceiverNode().getAPropertyRead("context")
)
}
/**
* A step through a `useMemo` call; for example:
* ```js
* let succ = useMemo(() => pred, []);
* ```
*/
private class UseMemoStep extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call | call = react().getAMemberCall("useMemo") |
pred = call.getCallback(0).getReturnNode() and
succ = call
)
}
}
private DataFlow::SourceNode reactRouterDom() {
result = DataFlow::moduleImport("react-router-dom")
}
private class ReactRouterSource extends RemoteFlowSource {
ReactRouterSource() {
this = reactRouterDom().getAMemberCall("useParams")
or
this = reactRouterDom().getAMemberCall("useRouteMatch").getAPropertyRead(["params", "url"])
}
override string getSourceType() { result = "react-router path parameters" }
}
/**
* Holds if `mod` transitively depends on `react-router-dom`.
*
* We assume any React component in such a file may be used in a context where react-router
* injects the `location` property in its `props` object.
*/
private predicate dependsOnReactRouter(Module mod) {
mod.getAnImport().getImportedPath().getValue() = "react-router-dom"
or
dependsOnReactRouter(mod.getAnImportedModule())
}
/**
* A reference to the DOM location obtained through `react-router-dom`
*
* For example:
* ```js
* let location = useLocation();
*
* function MyComponent(props) {
* props.location;
* }
* export default withRouter(MyComponent);
*/
private class ReactRouterLocationSource extends DOM::LocationSource::Range {
ReactRouterLocationSource() {
this = reactRouterDom().getAMemberCall("useLocation")
or
exists(ReactComponent component |
dependsOnReactRouter(component.getTopLevel()) and
this = component.getAPropRead("location")
)
}
}
/**
* Gets a reference to a function which, if called with a React component, returns wrapped
* version of that component, which we model as a direct reference to the underlying component.
*/
private DataFlow::SourceNode higherOrderComponentBuilder() {
// `memo(f)` returns a function that behaves as `f` but caches results
// It is sometimes used to wrap an entire functional component.
result = react().getAPropertyRead("memo")
or
result = DataFlow::moduleMember("react-redux", "connect").getACall()
or
result = reactRouterDom().getAPropertyRead("withRouter")
or
exists(FunctionCompositionCall compose |
higherOrderComponentBuilder().flowsTo(compose.getAnOperandNode()) and
result = compose
)
}
private class HigherOrderComponentStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
// `lazy(() => P)` returns a proxy for the component eventually returned by
// the promise P. We model this call as simply returning the value in P.
// It is primarily used for lazy-loading of React components.
exists(DataFlow::CallNode call |
call = react().getAMemberCall("lazy") and
pred = call.getCallback(0).getReturnNode() and
succ = call and
prop = Promises::valueProp()
)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call |
call = higherOrderComponentBuilder().getACall() and
pred = call.getArgument(0) and
succ = call
)
or
exists(TaggedTemplateExpr expr, DataFlow::CallNode call |
call = DataFlow::moduleImport("styled-components").getACall() and
pred = call.getArgument(0) and
call.flowsTo(expr.getTag().flow()) and
succ = expr.flow()
)
}
}

View File

@@ -1508,6 +1508,7 @@ sources
| tst.js:50:14:53:3 | return of constructor of class A | | tst.js:50:14:53:3 | return of constructor of class A |
| tst.js:51:5:51:13 | super(42) | | tst.js:51:5:51:13 | super(42) |
| tst.js:58:1:58:3 | tag | | tst.js:58:1:58:3 | tag |
| tst.js:58:1:58:13 | tag `x: ${x}` |
| tst.js:61:1:61:5 | ::o.m | | tst.js:61:1:61:5 | ::o.m |
| tst.js:61:3:61:5 | o.m | | tst.js:61:3:61:5 | o.m |
| tst.js:62:1:62:4 | o::g | | tst.js:62:1:62:4 | o::g |

View File

@@ -1,13 +1,13 @@
| tst.js:10:10:10:15 | source | | tst.js:10:10:10:15 | source |
| tst.js:15:10:15:13 | f1() | | tst.js:15:10:15:13 | f1() |
| tst.js:20:10:20:23 | compose1(f2)() | | tst.js:20:10:20:24 | lcompose1(f2)() |
| tst.js:28:10:28:27 | compose1(f3, f4)() | | tst.js:28:10:28:28 | lcompose1(f3, f4)() |
| tst.js:33:10:33:28 | compose1(o.f, f5)() | | tst.js:33:10:33:29 | lcompose1(o.f, f5)() |
| tst.js:41:10:41:27 | compose1(f6, f7)() | | tst.js:41:10:41:28 | lcompose1(f6, f7)() |
| tst.js:49:10:49:33 | compose ... source) | | tst.js:49:10:49:34 | lcompos ... source) |
| tst.js:61:10:61:40 | compose ... source) | | tst.js:61:10:61:41 | lcompos ... source) |
| tst.js:66:10:66:30 | compose ... source) | | tst.js:66:10:66:31 | lcompos ... source) |
| tst.js:89:10:89:31 | f18(und ... source) | | tst.js:89:10:89:31 | f18(und ... source) |
| tst.js:94:10:94:24 | compose2(f19)() | | tst.js:94:10:94:30 | rcompos ... o.f)() |
| tst.js:99:10:99:24 | compose3(f20)() | | tst.js:99:10:99:30 | lcompos ... f20)() |
| tst.js:104:10:104:24 | compose4(f21)() | | tst.js:104:10:104:30 | lcompos ... f21)() |

View File

@@ -1,8 +1,8 @@
import compose1 from 'just-compose'; import lcompose1 from 'just-compose';
import compose2 from 'compose-function'; import rcompose2 from 'compose-function';
import compose3 from 'lodash.flow'; import lcompose3 from 'lodash.flow';
import _ from 'lodash'; import _ from 'lodash';
var compose4 = _.flow; var lcompose4 = _.flow;
(function(){ (function(){
var source = SOURCE(); var source = SOURCE();
@@ -17,7 +17,7 @@ var compose4 = _.flow;
function f2(){ function f2(){
return source; return source;
} }
SINK(compose1(f2)()); SINK(lcompose1(f2)());
function f3(){ function f3(){
@@ -25,12 +25,12 @@ var compose4 = _.flow;
function f4(){ function f4(){
return source; return source;
} }
SINK(compose1(f3, f4)()); SINK(lcompose1(f3, f4)());
function f5(){ function f5(){
return source; return source;
} }
SINK(compose1(o.f, f5)()); SINK(lcompose1(o.f, f5)());
function f6(){ function f6(){
return source; return source;
@@ -38,7 +38,7 @@ var compose4 = _.flow;
function f7(x){ function f7(x){
return x; return x;
} }
SINK(compose1(f6, f7)()); SINK(lcompose1(f6, f7)());
function f8(x){ function f8(x){
return x; return x;
@@ -46,7 +46,7 @@ var compose4 = _.flow;
function f9(x){ function f9(x){
return x; return x;
} }
SINK(compose1(f8, f9)(source)); SINK(lcompose1(f8, f9)(source));
function f10(x){ function f10(x){
@@ -58,12 +58,12 @@ var compose4 = _.flow;
function f12(x){ function f12(x){
return x; return x;
} }
SINK(compose1(f10, f11, f12)(source)); SINK(lcompose1(f10, f11, f12)(source));
function f13(x){ function f13(x){
return x + 'foo' ; return x + 'foo' ;
} }
SINK(compose1(f13)(source)); SINK(lcompose1(f13)(source));
function f14(){ function f14(){
return undefined; return undefined;
@@ -76,7 +76,7 @@ var compose4 = _.flow;
function f16(){ function f16(){
return undefined; return undefined;
} }
SINK(compose1(f15, f16)()); // NO FLOW SINK(lcompose1(f15, f16)()); // NO FLOW
function f17(x, y){ function f17(x, y){
return y; return y;
@@ -91,16 +91,21 @@ var compose4 = _.flow;
function f19(){ function f19(){
return source; return source;
} }
SINK(compose2(f19)()); SINK(rcompose2(f19, o.f)());
function f20(){ function f20(){
return source; return source;
} }
SINK(compose3(f20)()); SINK(lcompose3(f16, f20)());
function f21(){ function f21(){
return source; return source;
} }
SINK(compose4(f21)()); SINK(lcompose4(f16, f21)());
function f22(){
return source;
}
SINK(lcompose3(f22, f16)()); // NO FLOW
})(); })();

View File

@@ -0,0 +1,24 @@
import { memo } from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import styled from 'styled-components';
import unknownFunction from 'somewhere';
import { MyComponent } from './exportedComponent';
const StyledComponent = styled(MyComponent)`
color: red;
`;
function mapStateToProps(x) {
return x;
}
function mapDispatchToProps(x) {
return x;
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const ConnectedComponent = compose(withConnect, unknownFunction)(StyledComponent);
export default memo(ConnectedComponent);

View File

@@ -227,6 +227,9 @@ test_ReactComponent_getACandidatePropsValue
| props.js:30:46:30:67 | "propFr ... tProps" | | props.js:30:46:30:67 | "propFr ... tProps" |
| props.js:32:22:32:34 | "propFromJSX" | | props.js:32:22:32:34 | "propFromJSX" |
| props.js:34:33:34:53 | "propFr ... ructor" | | props.js:34:33:34:53 | "propFr ... ructor" |
| useHigherOrderComponent.jsx:5:33:5:37 | "red" |
| useHigherOrderComponent.jsx:11:39:11:44 | "lazy" |
| useHigherOrderComponent.jsx:17:40:17:46 | "lazy2" |
test_ReactComponent test_ReactComponent
| es5.js:1:31:11:1 | {\\n dis ... ;\\n }\\n} | | es5.js:1:31:11:1 | {\\n dis ... ;\\n }\\n} |
| es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} |
@@ -285,6 +288,9 @@ test_JSXname
| thisAccesses.js:60:19:60:41 | <this.n ... s.name> | thisAccesses.js:60:20:60:28 | this.name | this.name | dot | | thisAccesses.js:60:19:60:41 | <this.n ... s.name> | thisAccesses.js:60:20:60:28 | this.name | this.name | dot |
| thisAccesses.js:61:19:61:41 | <this.t ... s.this> | thisAccesses.js:61:20:61:28 | this.this | this.this | dot | | thisAccesses.js:61:19:61:41 | <this.t ... s.this> | thisAccesses.js:61:20:61:28 | this.this | this.this | dot |
| thisAccesses_importedMappers.js:13:16:13:21 | <div/> | thisAccesses_importedMappers.js:13:17:13:19 | div | div | Identifier | | thisAccesses_importedMappers.js:13:16:13:21 | <div/> | thisAccesses_importedMappers.js:13:17:13:19 | div | div | Identifier |
| useHigherOrderComponent.jsx:5:12:5:39 | <SomeCo ... "red"/> | useHigherOrderComponent.jsx:5:13:5:25 | SomeComponent | SomeComponent | Identifier |
| useHigherOrderComponent.jsx:11:12:11:46 | <LazyLo ... lazy"/> | useHigherOrderComponent.jsx:11:13:11:31 | LazyLoadedComponent | LazyLoadedComponent | Identifier |
| useHigherOrderComponent.jsx:17:12:17:48 | <LazyLo ... azy2"/> | useHigherOrderComponent.jsx:17:13:17:32 | LazyLoadedComponent2 | LazyLoadedComponent2 | Identifier |
test_JSXName_this test_JSXName_this
| es5.js:4:12:4:45 | <div>He ... }</div> | es5.js:4:24:4:27 | this | | es5.js:4:12:4:45 | <div>He ... }</div> | es5.js:4:24:4:27 | this |
| es5.js:20:12:20:44 | <h1>Hel ... e}</h1> | es5.js:20:24:20:27 | this | | es5.js:20:12:20:44 | <h1>Hel ... e}</h1> | es5.js:20:24:20:27 | this |

View File

@@ -0,0 +1,18 @@
import SomeComponent from './higherOrderComponent';
import { lazy } from 'react';
function foo() {
return <SomeComponent color="red"/>
}
const LazyLoadedComponent = lazy(() => import('./higherOrderComponent'));
function bar() {
return <LazyLoadedComponent color="lazy"/>
}
const LazyLoadedComponent2 = lazy(() => import('./exportedComponent').then(m => m.MyComponent));
function barz() {
return <LazyLoadedComponent2 color="lazy2"/>
}

View File

@@ -174,6 +174,36 @@ nodes
| react-native.js:8:18:8:24 | tainted | | react-native.js:8:18:8:24 | tainted |
| react-native.js:9:27:9:33 | tainted | | react-native.js:9:27:9:33 | tainted |
| react-native.js:9:27:9:33 | tainted | | react-native.js:9:27:9:33 | tainted |
| react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:16:26:16:36 | window.name |
| react-use-context.js:16:26:16:36 | window.name |
| react-use-context.js:16:26:16:36 | window.name |
| react-use-state.js:4:9:4:49 | state |
| react-use-state.js:4:10:4:14 | state |
| react-use-state.js:4:38:4:48 | window.name |
| react-use-state.js:4:38:4:48 | window.name |
| react-use-state.js:5:51:5:55 | state |
| react-use-state.js:5:51:5:55 | state |
| react-use-state.js:9:9:9:43 | state |
| react-use-state.js:9:10:9:14 | state |
| react-use-state.js:10:14:10:24 | window.name |
| react-use-state.js:10:14:10:24 | window.name |
| react-use-state.js:11:51:11:55 | state |
| react-use-state.js:11:51:11:55 | state |
| react-use-state.js:15:9:15:43 | state |
| react-use-state.js:15:10:15:14 | state |
| react-use-state.js:16:20:16:30 | window.name |
| react-use-state.js:16:20:16:30 | window.name |
| react-use-state.js:17:51:17:55 | state |
| react-use-state.js:17:51:17:55 | state |
| react-use-state.js:21:10:21:14 | state |
| react-use-state.js:22:14:22:17 | prev |
| react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:25:20:25:30 | window.name |
| react-use-state.js:25:20:25:30 | window.name |
| sanitiser.js:16:7:16:27 | tainted | | sanitiser.js:16:7:16:27 | tainted |
| sanitiser.js:16:17:16:27 | window.name | | sanitiser.js:16:17:16:27 | window.name |
| sanitiser.js:16:17:16:27 | window.name | | sanitiser.js:16:17:16:27 | window.name |
@@ -737,6 +767,28 @@ edges
| react-native.js:7:7:7:33 | tainted | react-native.js:9:27:9:33 | tainted | | react-native.js:7:7:7:33 | tainted | react-native.js:9:27:9:33 | tainted |
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
| react-use-context.js:10:22:10:32 | window.name | react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:16:26:16:36 | window.name | react-use-context.js:16:26:16:36 | window.name |
| react-use-state.js:4:9:4:49 | state | react-use-state.js:5:51:5:55 | state |
| react-use-state.js:4:9:4:49 | state | react-use-state.js:5:51:5:55 | state |
| react-use-state.js:4:10:4:14 | state | react-use-state.js:4:9:4:49 | state |
| react-use-state.js:4:38:4:48 | window.name | react-use-state.js:4:10:4:14 | state |
| react-use-state.js:4:38:4:48 | window.name | react-use-state.js:4:10:4:14 | state |
| react-use-state.js:9:9:9:43 | state | react-use-state.js:11:51:11:55 | state |
| react-use-state.js:9:9:9:43 | state | react-use-state.js:11:51:11:55 | state |
| react-use-state.js:9:10:9:14 | state | react-use-state.js:9:9:9:43 | state |
| react-use-state.js:10:14:10:24 | window.name | react-use-state.js:9:10:9:14 | state |
| react-use-state.js:10:14:10:24 | window.name | react-use-state.js:9:10:9:14 | state |
| react-use-state.js:15:9:15:43 | state | react-use-state.js:17:51:17:55 | state |
| react-use-state.js:15:9:15:43 | state | react-use-state.js:17:51:17:55 | state |
| react-use-state.js:15:10:15:14 | state | react-use-state.js:15:9:15:43 | state |
| react-use-state.js:16:20:16:30 | window.name | react-use-state.js:15:10:15:14 | state |
| react-use-state.js:16:20:16:30 | window.name | react-use-state.js:15:10:15:14 | state |
| react-use-state.js:21:10:21:14 | state | react-use-state.js:22:14:22:17 | prev |
| react-use-state.js:22:14:22:17 | prev | react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:22:14:22:17 | prev | react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:25:20:25:30 | window.name | react-use-state.js:21:10:21:14 | state |
| react-use-state.js:25:20:25:30 | window.name | react-use-state.js:21:10:21:14 | state |
| sanitiser.js:16:7:16:27 | tainted | sanitiser.js:23:29:23:35 | tainted | | sanitiser.js:16:7:16:27 | tainted | sanitiser.js:23:29:23:35 | tainted |
| sanitiser.js:16:7:16:27 | tainted | sanitiser.js:30:29:30:35 | tainted | | sanitiser.js:16:7:16:27 | tainted | sanitiser.js:30:29:30:35 | tainted |
| sanitiser.js:16:7:16:27 | tainted | sanitiser.js:33:29:33:35 | tainted | | sanitiser.js:16:7:16:27 | tainted | sanitiser.js:33:29:33:35 | tainted |
@@ -1140,6 +1192,12 @@ edges
| optionalSanitizer.js:45:18:45:56 | sanitiz ... target | optionalSanitizer.js:26:16:26:32 | document.location | optionalSanitizer.js:45:18:45:56 | sanitiz ... target | Cross-site scripting vulnerability due to $@. | optionalSanitizer.js:26:16:26:32 | document.location | user-provided value | | optionalSanitizer.js:45:18:45:56 | sanitiz ... target | optionalSanitizer.js:26:16:26:32 | document.location | optionalSanitizer.js:45:18:45:56 | sanitiz ... target | Cross-site scripting vulnerability due to $@. | optionalSanitizer.js:26:16:26:32 | document.location | user-provided value |
| react-native.js:8:18:8:24 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:18:8:24 | tainted | Cross-site scripting vulnerability due to $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | | react-native.js:8:18:8:24 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:18:8:24 | tainted | Cross-site scripting vulnerability due to $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value |
| react-native.js:9:27:9:33 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:9:27:9:33 | tainted | Cross-site scripting vulnerability due to $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | | react-native.js:9:27:9:33 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:9:27:9:33 | tainted | Cross-site scripting vulnerability due to $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value |
| react-use-context.js:10:22:10:32 | window.name | react-use-context.js:10:22:10:32 | window.name | react-use-context.js:10:22:10:32 | window.name | Cross-site scripting vulnerability due to $@. | react-use-context.js:10:22:10:32 | window.name | user-provided value |
| react-use-context.js:16:26:16:36 | window.name | react-use-context.js:16:26:16:36 | window.name | react-use-context.js:16:26:16:36 | window.name | Cross-site scripting vulnerability due to $@. | react-use-context.js:16:26:16:36 | window.name | user-provided value |
| react-use-state.js:5:51:5:55 | state | react-use-state.js:4:38:4:48 | window.name | react-use-state.js:5:51:5:55 | state | Cross-site scripting vulnerability due to $@. | react-use-state.js:4:38:4:48 | window.name | user-provided value |
| react-use-state.js:11:51:11:55 | state | react-use-state.js:10:14:10:24 | window.name | react-use-state.js:11:51:11:55 | state | Cross-site scripting vulnerability due to $@. | react-use-state.js:10:14:10:24 | window.name | user-provided value |
| react-use-state.js:17:51:17:55 | state | react-use-state.js:16:20:16:30 | window.name | react-use-state.js:17:51:17:55 | state | Cross-site scripting vulnerability due to $@. | react-use-state.js:16:20:16:30 | window.name | user-provided value |
| react-use-state.js:23:35:23:38 | prev | react-use-state.js:25:20:25:30 | window.name | react-use-state.js:23:35:23:38 | prev | Cross-site scripting vulnerability due to $@. | react-use-state.js:25:20:25:30 | window.name | user-provided value |
| sanitiser.js:23:21:23:44 | '<b>' + ... '</b>' | sanitiser.js:16:17:16:27 | window.name | sanitiser.js:23:21:23:44 | '<b>' + ... '</b>' | Cross-site scripting vulnerability due to $@. | sanitiser.js:16:17:16:27 | window.name | user-provided value | | sanitiser.js:23:21:23:44 | '<b>' + ... '</b>' | sanitiser.js:16:17:16:27 | window.name | sanitiser.js:23:21:23:44 | '<b>' + ... '</b>' | Cross-site scripting vulnerability due to $@. | sanitiser.js:16:17:16:27 | window.name | user-provided value |
| sanitiser.js:30:21:30:44 | '<b>' + ... '</b>' | sanitiser.js:16:17:16:27 | window.name | sanitiser.js:30:21:30:44 | '<b>' + ... '</b>' | Cross-site scripting vulnerability due to $@. | sanitiser.js:16:17:16:27 | window.name | user-provided value | | sanitiser.js:30:21:30:44 | '<b>' + ... '</b>' | sanitiser.js:16:17:16:27 | window.name | sanitiser.js:30:21:30:44 | '<b>' + ... '</b>' | Cross-site scripting vulnerability due to $@. | sanitiser.js:16:17:16:27 | window.name | user-provided value |
| sanitiser.js:33:21:33:44 | '<b>' + ... '</b>' | sanitiser.js:16:17:16:27 | window.name | sanitiser.js:33:21:33:44 | '<b>' + ... '</b>' | Cross-site scripting vulnerability due to $@. | sanitiser.js:16:17:16:27 | window.name | user-provided value | | sanitiser.js:33:21:33:44 | '<b>' + ... '</b>' | sanitiser.js:16:17:16:27 | window.name | sanitiser.js:33:21:33:44 | '<b>' + ... '</b>' | Cross-site scripting vulnerability due to $@. | sanitiser.js:16:17:16:27 | window.name | user-provided value |

View File

@@ -174,6 +174,36 @@ nodes
| react-native.js:8:18:8:24 | tainted | | react-native.js:8:18:8:24 | tainted |
| react-native.js:9:27:9:33 | tainted | | react-native.js:9:27:9:33 | tainted |
| react-native.js:9:27:9:33 | tainted | | react-native.js:9:27:9:33 | tainted |
| react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:16:26:16:36 | window.name |
| react-use-context.js:16:26:16:36 | window.name |
| react-use-context.js:16:26:16:36 | window.name |
| react-use-state.js:4:9:4:49 | state |
| react-use-state.js:4:10:4:14 | state |
| react-use-state.js:4:38:4:48 | window.name |
| react-use-state.js:4:38:4:48 | window.name |
| react-use-state.js:5:51:5:55 | state |
| react-use-state.js:5:51:5:55 | state |
| react-use-state.js:9:9:9:43 | state |
| react-use-state.js:9:10:9:14 | state |
| react-use-state.js:10:14:10:24 | window.name |
| react-use-state.js:10:14:10:24 | window.name |
| react-use-state.js:11:51:11:55 | state |
| react-use-state.js:11:51:11:55 | state |
| react-use-state.js:15:9:15:43 | state |
| react-use-state.js:15:10:15:14 | state |
| react-use-state.js:16:20:16:30 | window.name |
| react-use-state.js:16:20:16:30 | window.name |
| react-use-state.js:17:51:17:55 | state |
| react-use-state.js:17:51:17:55 | state |
| react-use-state.js:21:10:21:14 | state |
| react-use-state.js:22:14:22:17 | prev |
| react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:25:20:25:30 | window.name |
| react-use-state.js:25:20:25:30 | window.name |
| sanitiser.js:16:7:16:27 | tainted | | sanitiser.js:16:7:16:27 | tainted |
| sanitiser.js:16:17:16:27 | window.name | | sanitiser.js:16:17:16:27 | window.name |
| sanitiser.js:16:17:16:27 | window.name | | sanitiser.js:16:17:16:27 | window.name |
@@ -741,6 +771,28 @@ edges
| react-native.js:7:7:7:33 | tainted | react-native.js:9:27:9:33 | tainted | | react-native.js:7:7:7:33 | tainted | react-native.js:9:27:9:33 | tainted |
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
| react-use-context.js:10:22:10:32 | window.name | react-use-context.js:10:22:10:32 | window.name |
| react-use-context.js:16:26:16:36 | window.name | react-use-context.js:16:26:16:36 | window.name |
| react-use-state.js:4:9:4:49 | state | react-use-state.js:5:51:5:55 | state |
| react-use-state.js:4:9:4:49 | state | react-use-state.js:5:51:5:55 | state |
| react-use-state.js:4:10:4:14 | state | react-use-state.js:4:9:4:49 | state |
| react-use-state.js:4:38:4:48 | window.name | react-use-state.js:4:10:4:14 | state |
| react-use-state.js:4:38:4:48 | window.name | react-use-state.js:4:10:4:14 | state |
| react-use-state.js:9:9:9:43 | state | react-use-state.js:11:51:11:55 | state |
| react-use-state.js:9:9:9:43 | state | react-use-state.js:11:51:11:55 | state |
| react-use-state.js:9:10:9:14 | state | react-use-state.js:9:9:9:43 | state |
| react-use-state.js:10:14:10:24 | window.name | react-use-state.js:9:10:9:14 | state |
| react-use-state.js:10:14:10:24 | window.name | react-use-state.js:9:10:9:14 | state |
| react-use-state.js:15:9:15:43 | state | react-use-state.js:17:51:17:55 | state |
| react-use-state.js:15:9:15:43 | state | react-use-state.js:17:51:17:55 | state |
| react-use-state.js:15:10:15:14 | state | react-use-state.js:15:9:15:43 | state |
| react-use-state.js:16:20:16:30 | window.name | react-use-state.js:15:10:15:14 | state |
| react-use-state.js:16:20:16:30 | window.name | react-use-state.js:15:10:15:14 | state |
| react-use-state.js:21:10:21:14 | state | react-use-state.js:22:14:22:17 | prev |
| react-use-state.js:22:14:22:17 | prev | react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:22:14:22:17 | prev | react-use-state.js:23:35:23:38 | prev |
| react-use-state.js:25:20:25:30 | window.name | react-use-state.js:21:10:21:14 | state |
| react-use-state.js:25:20:25:30 | window.name | react-use-state.js:21:10:21:14 | state |
| sanitiser.js:16:7:16:27 | tainted | sanitiser.js:23:29:23:35 | tainted | | sanitiser.js:16:7:16:27 | tainted | sanitiser.js:23:29:23:35 | tainted |
| sanitiser.js:16:7:16:27 | tainted | sanitiser.js:30:29:30:35 | tainted | | sanitiser.js:16:7:16:27 | tainted | sanitiser.js:30:29:30:35 | tainted |
| sanitiser.js:16:7:16:27 | tainted | sanitiser.js:33:29:33:35 | tainted | | sanitiser.js:16:7:16:27 | tainted | sanitiser.js:33:29:33:35 | tainted |

View File

@@ -0,0 +1,3 @@
import { createContext } from 'react';
export let MyContext = createContext({root: null});

View File

@@ -0,0 +1,5 @@
import { MyContext } from './react-create-context';
export function renderMain() {
return <MyContext.Provider value={{root: document.body}}></MyContext.Provider>
}

View File

@@ -0,0 +1,20 @@
import { useContext, Component } from 'react';
import { MyContext } from './react-create-context';
function useMyContext() {
return useContext(MyContext);
}
export function useDoc1() {
let { root } = useMyContext();
root.appendChild(window.name); // NOT OK
}
class C extends Component {
foo() {
let { root } = this.context;
root.appendChild(window.name); // NOT OK
}
}
C.contextType = MyContext;

View File

@@ -0,0 +1,33 @@
import { useState } from 'react';
function initialState() {
let [state, setState] = useState(window.name);
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // NOT OK
}
function setStateValue() {
let [state, setState] = useState('foo');
setState(window.name);
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // NOT OK
}
function setStateValueLazy() {
let [state, setState] = useState('foo');
setState(() => window.name);
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // NOT OK
}
function setStateValueLazy() {
let [state, setState] = useState('foo');
setState(prev => {
document.body.innerHTML = prev; // NOT OK
})
setState(() => window.name);
}
function setStateValueSafe() {
let [state, setState] = useState('foo');
setState('safe');
setState(() => 'also safe');
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // OK
}