Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljscontextproperties.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <private/qqmljsutils_p.h>
8#include <private/qtqmlglobal_p.h>
9
10#include <QtCore/qtconfigmacros.h>
11#include <QtCore/qregularexpression.h>
12#include <QtCore/qdirlisting.h>
13#include <QtCore/qfile.h>
14
15#if QT_CONFIG(qmlcontextpropertydump)
16# include <QtCore/qsettings.h>
17#endif
18
19#if QT_CONFIG(process)
20# include <QtCore/qprocess.h>
21#endif
22
24
25namespace QQmlJS {
26
27using namespace Qt::StringLiterals;
28
29// There are many ways to set context properties without triggering the regexp in s_pattern,
30// but its supposed to catch most context properties set via "setContextProperty".
31static constexpr QLatin1StringView s_pattern =
32 R"x((\.|->)setContextProperty\s*\‍(\s*(QStringLiteral\s*\‍(|QString\s*\‍(|QLatin1String(View)?\s*\‍(|u)?\s*"([^"]*)")x"_L1;
33static constexpr int s_contextPropertyNameIdxInPattern = 4;
34
37
40{
41 const auto it = m_properties.find(name);
42 if (it != m_properties.end())
43 return it.value();
44 return {};
45}
46
47void HeuristicContextProperties::add(const QString &name, const HeuristicContextProperty &property)
48{
49 if (const auto it = m_properties.find(name); it != m_properties.end()) {
50 it.value().append(property);
51 return;
52 }
53 m_properties.insert(name, { property });
54}
55
56void HeuristicContextProperties::collectFromFile(const QString &filePath)
57{
58 QFile file(filePath);
59 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
60 return;
61
62 const QString fileContent = QString::fromUtf8(file.readAll());
63 for (const auto &match : s_matchSetContextProperty.globalMatch(fileContent)) {
64 const quint32 offset = match.capturedStart(s_contextPropertyNameIdxInPattern);
65 const quint32 length = match.capturedLength(s_contextPropertyNameIdxInPattern);
66 const auto [row, column] = QQmlJS::SourceLocation::rowAndColumnFrom(fileContent, offset);
67 const QQmlJS::SourceLocation sourceLocation{ offset, length, row, column };
68
69 add(match.captured(s_contextPropertyNameIdxInPattern),
70 HeuristicContextProperty{ filePath, sourceLocation });
71 }
72}
73
74void HeuristicContextProperties::grepFallback(const QList<QString> &rootUrls)
75{
76 const QStringList fileFilters{ QQmlJSUtils::cppFileFilters.begin(),
77 QQmlJSUtils::cppFileFilters.end() };
78
79 for (const QString &url : rootUrls) {
80 for (const auto &dirEntry : QDirListing{ url, fileFilters,
81 QDirListing::IteratorFlag::Recursive
82 | QDirListing::IteratorFlag::FilesOnly }) {
83
84 const QString filePath = dirEntry.filePath();
85 collectFromFile(filePath);
86 }
87 }
88}
89
90#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
92{
93 for (const auto line : QStringTokenizer{ output, "\n"_L1, Qt::SkipEmptyParts })
95}
96#endif
97
99HeuristicContextProperties::collectFromCppSourceDirs(const QList<QString> &cppSourceDirs)
100{
102 result.collectFromDirs(cppSourceDirs);
103 return result;
104}
105
106/*!
107 \internal
108 Uses grep to find files that have setContextProperty()-calls, and then search matching files
109 with QRegularExpression to extract the location and name of the found context properties.
110*/
111void HeuristicContextProperties::collectFromDirs(const QList<QString> &dirs)
112{
113 if (dirs.isEmpty())
114 return;
115
116#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
117 if (qEnvironmentVariableIsSet("QT_QML_NO_GREP")) {
118 grepFallback(dirs);
119 return;
120 }
121
122 QProcess grep;
123 QStringList arguments{ "--recursive"_L1,
124 "--null-data"_L1, // match multiline patterns
125 "--files-with-matches"_L1,
126 "--extended-regexp"_L1, // the pattern is "extended"
127 "-e"_L1,
128 s_pattern };
129
130 // don't search non-cpp files
131 for (const auto &fileFilter : QQmlJSUtils::cppFileFilters)
132 arguments << "--include"_L1 << fileFilter;
133
134 arguments.append(dirs);
135 grep.start("grep"_L1, arguments);
136 grep.waitForFinished();
137 if (grep.exitStatus() == QProcess::NormalExit) {
138 switch (grep.exitCode()) {
139 case 0: { // success
140 const QString output = QString::fromUtf8(grep.readAllStandardOutput());
141 parseGrepOutput(output);
142 return;
143 }
144 case 1: // success but no context properties found
145 return;
146 default: // grep error
147 break;
148 }
149 }
150#endif
151 grepFallback(dirs);
152}
153
154#if QT_CONFIG(qmlcontextpropertydump)
156{
157 constexpr int size = 4;
159 if (bits.length() != size)
160 return SourceLocation{};
161
165
166 bool everythingOk = true;
167 for (int i = 0; i < size; ++i) {
168 bool ok = false;
169 *destination[i] = bits[i].toInt(&ok);
170 everythingOk &= ok;
171 }
172
173 if (everythingOk)
174 return result;
175 return SourceLocation{};
176}
177
179{
185 return result;
186}
187#endif
188
189#if QT_CONFIG(qmlcontextpropertydump)
190static constexpr auto cachedHeuristicListKey = "cachedHeuristicList"_L1;
191#endif
192
194{
195#if QT_CONFIG(qmlcontextpropertydump)
196 HeuristicContextProperties result;
197 std::vector<QString> names;
198
199 const int size = settings->beginReadArray(cachedHeuristicListKey);
200 for (int i = 0; i < size; ++i) {
201 settings->setArrayIndex(i);
202 names.push_back(settings->value("name").toString());
203 }
204 settings->endArray();
205
206 for (const auto &name : names) {
207 const int size = settings->beginReadArray(u"property_"_s.append(name));
208 for (int i = 0; i < size; ++i) {
209 settings->setArrayIndex(i);
210 result.add(
211 name,
212 HeuristicContextProperty{
213 settings->value("fileName").toString(),
214 deserializeSourceLocation(settings->value("sourceLocation").toString()),
215 });
216 }
217 settings->endArray();
218 }
219 return result;
220#else
221 Q_UNUSED(settings);
222 return HeuristicContextProperties{};
223#endif
224}
225
226void HeuristicContextProperties::writeCache(const QString &folder) const
227{
228#if QT_CONFIG(qmlcontextpropertydump)
229 QSettings settings(folder + "/.qt/contextPropertyDump.ini"_L1, QSettings::IniFormat);
230 settings.beginWriteArray(cachedHeuristicListKey);
231 int index = 0;
232 for (const auto &[name, _] : m_properties) {
233 settings.setArrayIndex(index++);
234 settings.setValue("name", name);
235 }
236 settings.endArray();
237
238 for (const auto &[name, definitions] : m_properties) {
239 settings.beginWriteArray(u"property_"_s.append(name));
240 for (int i = 0; i < definitions.size(); ++i) {
241 settings.setArrayIndex(i);
242 settings.setValue("fileName", definitions[i].filename);
243 settings.setValue("sourceLocation", serializeSourceLocation(definitions[i].location));
244 }
245 settings.endArray();
246 }
247#else
248 Q_UNUSED(folder);
249#endif
250}
251} // namespace QQmlJS
252
253QT_END_NAMESPACE
void add(const QString &name, const HeuristicContextProperty &property)
static HeuristicContextProperties collectFrom(QSettings *settings)
void writeCache(const QString &folder) const
QList< HeuristicContextProperty > definitionsForName(const QString &name) const
static const QRegularExpression s_matchSetContextProperty
static constexpr QLatin1StringView s_pattern
static constexpr int s_contextPropertyNameIdxInPattern
Combined button and popup list for selecting options.