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/qtqmlglobal_p.h>
8
9#include <QtCore/qtconfigmacros.h>
10#include <QtCore/qregularexpression.h>
11#include <QtCore/qdirlisting.h>
12#include <QtCore/qfile.h>
13
14#if QT_CONFIG(qmlcontextpropertydump)
15# include <QtCore/qsettings.h>
16#endif
17
18#if QT_CONFIG(process)
19# include <QtCore/qprocess.h>
20#endif
21
23
24namespace QQmlJS {
25
26using namespace Qt::StringLiterals;
27
28// There are many ways to set context properties without triggering the regexp in s_pattern,
29// but its supposed to catch most context properties set via "setContextProperty".
30static constexpr QLatin1StringView s_pattern =
31 R"x((\.|->)setContextProperty\s*\‍(\s*(QStringLiteral\s*\‍(|QString\s*\‍(|QLatin1String(View)?\s*\‍(|u)?\s*"([^"]*)")x"_L1;
32static constexpr int s_contextPropertyNameIdxInPattern = 4;
33
34// TODO: use a central list of file extensions that can also be used by qmetatypesjsonprocessor.cpp
35// (that needs header file extensions) and Qt6QmlMacros.cmake.
36static constexpr std::array s_fileFilters = {
37 "*.cpp"_L1, "*.cxx"_L1, "*.cc"_L1, "*.c"_L1, "*.c++"_L1,
38 "*.hpp"_L1, "*.hxx"_L1, "*.hh"_L1, "*.h"_L1, "*.h++"_L1,
39};
40
43
46{
47 const auto it = m_properties.find(name);
48 if (it != m_properties.end())
49 return it.value();
50 return {};
51}
52
53void HeuristicContextProperties::add(const QString &name, const HeuristicContextProperty &property)
54{
55 if (const auto it = m_properties.find(name); it != m_properties.end()) {
56 it.value().append(property);
57 return;
58 }
59 m_properties.insert(name, { property });
60}
61
62void HeuristicContextProperties::collectFromFile(const QString &filePath)
63{
64 QFile file(filePath);
65 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
66 return;
67
68 const QString fileContent = QString::fromUtf8(file.readAll());
69 for (const auto &match : s_matchSetContextProperty.globalMatch(fileContent)) {
70 const quint32 offset = match.capturedStart(s_contextPropertyNameIdxInPattern);
71 const quint32 length = match.capturedLength(s_contextPropertyNameIdxInPattern);
72 const auto [row, column] = QQmlJS::SourceLocation::rowAndColumnFrom(fileContent, offset);
73 const QQmlJS::SourceLocation sourceLocation{ offset, length, row, column };
74
75 add(match.captured(s_contextPropertyNameIdxInPattern),
76 HeuristicContextProperty{ filePath, sourceLocation });
77 }
78}
79
80void HeuristicContextProperties::grepFallback(const QList<QString> &rootUrls)
81{
82 const QStringList fileFilters{ s_fileFilters.begin(), s_fileFilters.end() };
83
84 for (const QString &url : rootUrls) {
85 for (const auto &dirEntry : QDirListing{ url, fileFilters,
86 QDirListing::IteratorFlag::Recursive
87 | QDirListing::IteratorFlag::FilesOnly }) {
88
89 const QString filePath = dirEntry.filePath();
90 collectFromFile(filePath);
91 }
92 }
93}
94
95#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
97{
98 for (const auto line : QStringTokenizer{ output, "\n"_L1, Qt::SkipEmptyParts })
100}
101#endif
102
104HeuristicContextProperties::collectFromCppSourceDirs(const QList<QString> &cppSourceDirs)
105{
107 result.collectFromDirs(cppSourceDirs);
108 return result;
109}
110
111/*!
112 \internal
113 Uses grep to find files that have setContextProperty()-calls, and then search matching files
114 with QRegularExpression to extract the location and name of the found context properties.
115*/
116void HeuristicContextProperties::collectFromDirs(const QList<QString> &dirs)
117{
118 if (dirs.isEmpty())
119 return;
120
121#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
122 if (qEnvironmentVariableIsSet("QT_QML_NO_GREP")) {
123 grepFallback(dirs);
124 return;
125 }
126
127 QProcess grep;
128 QStringList arguments{ "--recursive"_L1,
129 "--null-data"_L1, // match multiline patterns
130 "--files-with-matches"_L1,
131 "--extended-regexp"_L1, // the pattern is "extended"
132 "-e"_L1,
133 s_pattern };
134
135 // don't search non-cpp files
136 for (const auto fileFilter : s_fileFilters)
137 arguments << "--include"_L1 << fileFilter;
138
139 arguments.append(dirs);
140 grep.start("grep"_L1, arguments);
141 grep.waitForFinished();
142 if (grep.exitStatus() == QProcess::NormalExit) {
143 switch (grep.exitCode()) {
144 case 0: { // success
145 const QString output = QString::fromUtf8(grep.readAllStandardOutput());
146 parseGrepOutput(output);
147 return;
148 }
149 case 1: // success but no context properties found
150 return;
151 default: // grep error
152 break;
153 }
154 }
155#endif
156 grepFallback(dirs);
157}
158
159#if QT_CONFIG(qmlcontextpropertydump)
161{
162 constexpr int size = 4;
164 if (bits.length() != size)
165 return SourceLocation{};
166
170
171 bool everythingOk = true;
172 for (int i = 0; i < size; ++i) {
173 bool ok = false;
174 *destination[i] = bits[i].toInt(&ok);
175 everythingOk &= ok;
176 }
177
178 if (everythingOk)
179 return result;
180 return SourceLocation{};
181}
182
184{
190 return result;
191}
192#endif
193
194static constexpr auto cachedHeuristicListKey = "cachedHeuristicList"_L1;
195
197{
198#if QT_CONFIG(qmlcontextpropertydump)
199 HeuristicContextProperties result;
200 std::vector<QString> names;
201
202 const int size = settings->beginReadArray(cachedHeuristicListKey);
203 for (int i = 0; i < size; ++i) {
204 settings->setArrayIndex(i);
205 names.push_back(settings->value("name").toString());
206 }
207 settings->endArray();
208
209 for (const auto &name : names) {
210 const int size = settings->beginReadArray(u"property_"_s.append(name));
211 for (int i = 0; i < size; ++i) {
212 settings->setArrayIndex(i);
213 result.add(
214 name,
215 HeuristicContextProperty{
216 settings->value("fileName").toString(),
217 deserializeSourceLocation(settings->value("sourceLocation").toString()),
218 });
219 }
220 settings->endArray();
221 }
222 return result;
223#else
224 Q_UNUSED(settings);
225 return HeuristicContextProperties{};
226#endif
227}
228
229void HeuristicContextProperties::writeCache(const QString &folder) const
230{
231#if QT_CONFIG(qmlcontextpropertydump)
232 QSettings settings(folder + "/.qt/contextPropertyDump.ini"_L1, QSettings::IniFormat);
233 settings.beginWriteArray(cachedHeuristicListKey);
234 int index = 0;
235 for (const auto &[name, _] : m_properties) {
236 settings.setArrayIndex(index++);
237 settings.setValue("name", name);
238 }
239 settings.endArray();
240
241 for (const auto &[name, definitions] : m_properties) {
242 settings.beginWriteArray(u"property_"_s.append(name));
243 for (int i = 0; i < definitions.size(); ++i) {
244 settings.setArrayIndex(i);
245 settings.setValue("fileName", definitions[i].filename);
246 settings.setValue("sourceLocation", serializeSourceLocation(definitions[i].location));
247 }
248 settings.endArray();
249 }
250#else
251 Q_UNUSED(folder);
252#endif
253}
254} // namespace QQmlJS
255
256QT_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 auto cachedHeuristicListKey
static constexpr QLatin1StringView s_pattern
static constexpr int s_contextPropertyNameIdxInPattern
static constexpr std::array s_fileFilters
Combined button and popup list for selecting options.