Skip to content

Commit 853fc1a

Browse files
authored
Merge pull request #19852 from asgerf/js/react-use-server
JS: Model React 'use' and 'use server'
2 parents ddae471 + 4fc5738 commit 853fc1a

39 files changed

+363
-317
lines changed

javascript/ql/lib/ext/react.model.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: summaryModel
5+
data:
6+
- ["react", "Member[use]", "Argument[0].Awaited", "ReturnValue", "value"]

javascript/ql/lib/semmle/javascript/frameworks/React.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,3 +875,22 @@ private class ReactPropAsViewComponentInput extends ViewComponentInput {
875875

876876
override string getSourceType() { result = "React props" }
877877
}
878+
879+
private predicate isServerFunction(DataFlow::FunctionNode func) {
880+
exists(Directive::UseServerDirective useServer |
881+
useServer.getContainer() = func.getFunction()
882+
or
883+
useServer.getContainer().(Module).getAnExportedValue(_).getAFunctionValue() = func
884+
)
885+
}
886+
887+
private class ServerFunctionRemoteFlowSource extends RemoteFlowSource {
888+
ServerFunctionRemoteFlowSource() {
889+
exists(DataFlow::FunctionNode func |
890+
isServerFunction(func) and
891+
this = func.getAParameter()
892+
)
893+
}
894+
895+
override string getSourceType() { result = "React server function parameter" }
896+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* Taint is now tracked through the React `use` function.
5+
* Parameters of React server functions, marked with the `"use server"` directive, are now seen as taint sources.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { use } from "react";
2+
3+
async function fetchData() {
4+
return new Promise((resolve) => {
5+
resolve(source("fetchedData"));
6+
});
7+
}
8+
9+
function Component() {
10+
const data = use(fetchData());
11+
sink(data); // $ hasValueFlow=fetchedData
12+
}

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent.qll

Lines changed: 0 additions & 3 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getACandidatePropsValue.qll

Lines changed: 0 additions & 5 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getACandidateStateSource.qll

Lines changed: 0 additions & 7 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getADirectPropsSource.qll

Lines changed: 0 additions & 5 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getAPreviousStateSource.qll

Lines changed: 0 additions & 7 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getAPropRead.qll

Lines changed: 0 additions & 5 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getInstanceMethod.qll

Lines changed: 0 additions & 5 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_ref.qll

Lines changed: 0 additions & 3 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/ReactName.qll

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
var Hello = React.createClass({
22
displayName: 'Hello',
33
render: function() {
4-
return <div>Hello {this.props.name}</div>;
4+
return <div>Hello {this.props.name}</div>; // $ threatModelSource=view-component-input
55
},
66
getDefaultProps: function() {
77
return {
8-
name: 'world'
8+
name: 'world' // $ getACandidatePropsValue
99
};
1010
}
11-
});
11+
}); // $ reactComponent
1212

1313
Hello.info = function() {
1414
return "Nothing to see here.";
@@ -17,6 +17,6 @@ Hello.info = function() {
1717
var createReactClass = require('create-react-class');
1818
var Greeting = createReactClass({
1919
render: function() {
20-
return <h1>Hello, {this.props.name}</h1>;
20+
return <h1>Hello, {this.props.name}</h1>; // $ threatModelSource=view-component-input
2121
}
22-
});
22+
}); // $ reactComponent
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
class Hello extends React.Component {
1+
class Hello extends React.Component { // $ threatModelSource=view-component-input
22
render() {
3-
return <div>Hello {this.props.name}</div>;
3+
return <div>Hello {this.props.name}</div>; // $ threatModelSource=view-component-input
44
}
55
static info() {
66
return "Nothing to see here.";
77
}
8-
}
8+
} // $ reactComponent
99
Hello.displayName = 'Hello';
1010
Hello.defaultProps = {
1111
name: 'world'
@@ -17,4 +17,4 @@ class Hello2 extends React.Component {
1717
this.state.bar.foo = 42;
1818
this.state = { baz: 42};
1919
}
20-
}
20+
} // $ reactComponent
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export function MyComponent(props) {
1+
export function MyComponent(props) { // $ threatModelSource=view-component-input
22
return <div style={{color: props.color}}/>
3-
}
3+
} // $ reactComponent

javascript/ql/test/library-tests/frameworks/ReactJS/getADirectStateAccess.qll

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MyComponent } from "./exportedComponent";
22

3-
export function render({color, location}) {
4-
return <MyComponent color={color}/>
5-
}
3+
export function render({color, location}) { // $ threatModelSource=view-component-input locationSource threatModelSource=remote
4+
return <MyComponent color={color}/> // $ getACandidatePropsValue
5+
} // $ reactComponent
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component } from "react";
22

3-
class C extends Component {}
3+
class C extends Component {} // $ threatModelSource=view-component-input reactComponent
44

5-
class D extends C {}
5+
class D extends C {} // $ threatModelSource=view-component-input reactComponent
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
function Hello(props) {
1+
function Hello(props) { // $ threatModelSource=view-component-input
22
return <div>Hello {props.name}</div>;
3-
}
3+
} // $ reactComponent
44

5-
function Hello2(props) {
5+
function Hello2(props) { // $ threatModelSource=view-component-input
66
return React.createElement("div");
7-
}
7+
} // $ reactComponent
88

9-
function Hello3(props) {
9+
function Hello3(props) { // $ threatModelSource=view-component-input
1010
var x = React.createElement("div");
1111
return x;
12-
}
12+
} // $ reactComponent
1313

1414
function NotAComponent(props) {
1515
if (y)
1616
return React.createElement("div");
1717
return g();
1818
}
1919

20-
function SpuriousComponent(props) {
20+
function SpuriousComponent(props) { // $ threatModelSource=view-component-input
2121
if (y)
2222
return React.createElement("div");
2323
return 42;
24-
}
24+
} // $ reactComponent
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
class Hello extends Preact.Component {
2-
render(props, state) {
1+
class Hello extends Preact.Component { // $ threatModelSource=view-component-input
2+
render(props, state) { // $ threatModelSource=view-component-input
33
props.name;
44
state.name;
55
return <div/>;
66
}
7-
}
7+
} // $ reactComponent
88

9-
class Hello extends preact.Component {
9+
class Hello extends preact.Component { // $ threatModelSource=view-component-input
1010

11-
}
11+
} // $ reactComponent
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
class Hello extends Component {
1+
class Hello extends Component { // $ threatModelSource=view-component-input
22
render() {
3-
this.props.name;
3+
this.props.name; // $ threatModelSource=view-component-input
44
return <div/>;
55
}
6-
}
6+
} // $ reactComponent
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
function ES2015() {
2-
class C extends React.Component {
3-
}
2+
class C extends React.Component { // $ threatModelSource=view-component-input
3+
} // $ reactComponent
44

5-
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" };
5+
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" }; // $ getACandidatePropsValue
66

7-
(<C propFromJSX={"propFromJSX"}/>);
7+
(<C propFromJSX={"propFromJSX"}/>); // $ getACandidatePropsValue
88

9-
new C({propFromConstructor: "propFromConstructor"});
9+
new C({propFromConstructor: "propFromConstructor"}); // $ getACandidatePropsValue
1010
}
1111

1212
function ES5() {
1313
var C = React.createClass({
1414
getDefaultProps() {
15-
return { propFromDefaultProps: "propFromDefaultProps" };
15+
return { propFromDefaultProps: "propFromDefaultProps" }; // $ getACandidatePropsValue
1616
}
17-
});
17+
}); // $ reactComponent
1818

19-
(<C propFromJSX={"propFromJSX"}/>);
19+
(<C propFromJSX={"propFromJSX"}/>); // $ getACandidatePropsValue
2020

21-
C({propFromConstructor: "propFromConstructor"});
21+
C({propFromConstructor: "propFromConstructor"}); // $ getACandidatePropsValue
2222

2323
}
2424

2525
function Functional() {
26-
function C(props) {
26+
function C(props) { // $ threatModelSource=view-component-input
2727
return <div/>;
28-
}
28+
} // $ reactComponent
2929

30-
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" };
30+
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" }; // $ getACandidatePropsValue
3131

32-
(<C propFromJSX={"propFromJSX"}/>);
32+
(<C propFromJSX={"propFromJSX"}/>); // $ getACandidatePropsValue
3333

34-
new C({propFromConstructor: "propFromConstructor"});
34+
new C({propFromConstructor: "propFromConstructor"}); // $ getACandidatePropsValue
3535

3636
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class C extends React.Component {
1+
class C extends React.Component { // $ threatModelSource=view-component-input
22
static getDerivedStateFromProps(props, state) {
33
return {};
44
}
@@ -8,4 +8,4 @@ class C extends React.Component {
88
getSnapshotBeforeUpdate(prevProps, prevState) {
99
return {};
1010
}
11-
}
11+
} // $ reactComponent

javascript/ql/test/library-tests/frameworks/ReactJS/react.qll

Lines changed: 0 additions & 3 deletions
This file was deleted.

javascript/ql/test/library-tests/frameworks/ReactJS/statePropertyReads.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ class Reads extends React.Component {
1010
componentDidUpdate(prevProps, prevState) {
1111
prevState.p4;
1212
}
13-
}
13+
} // $ reactComponent

javascript/ql/test/library-tests/frameworks/ReactJS/statePropertyWrites.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ class Writes extends React.Component {
3131
state = {
3232
p7: 42
3333
};
34-
}
34+
} // $ reactComponent
3535

3636
React.createClass({
3737
render: function() {
38-
return <div>Hello {this.props.name}</div>;
38+
return <div>Hello {this.props.name}</div>; // $ threatModelSource=view-component-input
3939
},
4040
getInitialState: function() {
4141
return {
4242
p8: 42
4343
};
4444
}
45-
});
45+
}); // $ reactComponent

0 commit comments

Comments
 (0)