diff --git a/javascript/ql/lib/change-notes/2025-10-21-react-precallgraph-step.md b/javascript/ql/lib/change-notes/2025-10-21-react-precallgraph-step.md new file mode 100644 index 000000000000..e28a900e8d9c --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-10-21-react-precallgraph-step.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Added `PreCallGraphStep` flow model for React's `useRef` hook. +* Added a `DomValueSource` that uses the `current` property off the object returned by React's `useRef` hook. \ No newline at end of file diff --git a/javascript/ql/lib/semmle/javascript/frameworks/React.qll b/javascript/ql/lib/semmle/javascript/frameworks/React.qll index d55ace8636df..42885f6b9cb4 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/React.qll @@ -612,6 +612,25 @@ private class UseStateStep extends PreCallGraphStep { } } +/** + * Step through a `useRef` call. + * + * It returns an object with a single property (`current`) initialized to the initial value. + * + * For example: + * ```js + * const inputRef1 = useRef(initialValue); + * ``` + */ +private class UseRefStep extends PreCallGraphStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::CallNode call | call = react().getAMemberCall("useRef") | + pred = call.getArgument(0) and // initial state + succ = call.getAPropertyRead("current") + ) + } +} + /** * A step through a React context object. * @@ -785,6 +804,17 @@ private class ReactRouterLocationSource extends DOM::LocationSource::Range { } } +private class UseRefDomValueSource extends DOM::DomValueSource::Range { + UseRefDomValueSource() { + this = + any(JsxAttribute attrib | attrib.getName() = "ref") + .getValue() + .flow() + .getALocalSource() + .getAPropertyRead("current") + } +} + /** * 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.