1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "pluginmanager.h"
27 #include "pluginmanager_p.h"
28 #include "pluginspec.h"
29 #include "pluginspec_p.h"
30 #include "optionsparser.h"
31 #include "iplugin.h"
32 
33 #include <QCoreApplication>
34 #include <QCryptographicHash>
35 #include <QDateTime>
36 #include <QDebug>
37 #include <QDir>
38 #include <QEventLoop>
39 #include <QFile>
40 #include <QGuiApplication>
41 #include <QLibrary>
42 #include <QLibraryInfo>
43 #include <QMessageBox>
44 #include <QMetaProperty>
45 #include <QPushButton>
46 #include <QSysInfo>
47 #include <QTextStream>
48 #include <QTimer>
49 #include <QWriteLocker>
50 
51 #include <utils/algorithm.h>
52 #include <utils/benchmarker.h>
53 #include <utils/executeondestruction.h>
54 #include <utils/fileutils.h>
55 #include <utils/hostosinfo.h>
56 #include <utils/mimetypes/mimedatabase.h>
57 #include <utils/qtcassert.h>
58 #include <utils/qtcprocess.h>
59 #include <utils/qtcsettings.h>
60 
61 #ifdef WITH_TESTS
62 #include <utils/hostosinfo.h>
63 #include <QTest>
64 #include <QThread>
65 #endif
66 
67 #include <functional>
68 #include <memory>
69 
70 Q_LOGGING_CATEGORY(pluginLog, "qtc.extensionsystem", QtWarningMsg)
71 
72 const char C_IGNORED_PLUGINS[] = "Plugins/Ignored";
73 const char C_FORCEENABLED_PLUGINS[] = "Plugins/ForceEnabled";
74 const int DELAYED_INITIALIZE_INTERVAL = 20; // ms
75 
76 enum { debugLeaks = 0 };
77 
78 /*!
79     \namespace ExtensionSystem
80     \inmodule QtCreator
81     \brief The ExtensionSystem namespace provides classes that belong to the
82            core plugin system.
83 
84     The basic extension system contains the plugin manager and its supporting classes,
85     and the IPlugin interface that must be implemented by plugin providers.
86 */
87 
88 /*!
89     \namespace ExtensionSystem::Internal
90     \internal
91 */
92 
93 /*!
94     \class ExtensionSystem::PluginManager
95     \inheaderfile extensionsystem/pluginmanager.h
96     \inmodule QtCreator
97     \ingroup mainclasses
98 
99     \brief The PluginManager class implements the core plugin system that
100     manages the plugins, their life cycle, and their registered objects.
101 
102     The plugin manager is used for the following tasks:
103     \list
104     \li Manage plugins and their state
105     \li Manipulate a \e {common object pool}
106     \endlist
107 
108     \section1 Plugins
109     Plugins must derive from the IPlugin class and have the IID
110     \c "org.qt-project.Qt.QtCreatorPlugin".
111 
112     The plugin manager is used to set a list of file system directories to search for
113     plugins, retrieve information about the state of these plugins, and to load them.
114 
115     Usually, the application creates a PluginManager instance and initiates the
116     loading.
117     \code
118         // 'plugins' and subdirs will be searched for plugins
119         PluginManager::setPluginPaths(QStringList("plugins"));
120         PluginManager::loadPlugins(); // try to load all the plugins
121     \endcode
122     Additionally, it is possible to directly access plugin meta data, instances,
123     and state.
124 
125     \section1 Object Pool
126     Plugins (and everybody else) can add objects to a common \e pool that is located in
127     the plugin manager. Objects in the pool must derive from QObject, there are no other
128     prerequisites. Objects can be retrieved from the object pool via the getObject()
129     and getObjectByName() functions.
130 
131     Whenever the state of the object pool changes, a corresponding signal is
132     emitted by the plugin manager.
133 
134     A common usecase for the object pool is that a plugin (or the application) provides
135     an \e {extension point} for other plugins, which is a class or interface that can
136     be implemented and added to the object pool. The plugin that provides the
137     extension point looks for implementations of the class or interface in the object pool.
138     \code
139         // Plugin A provides a "MimeTypeHandler" extension point
140         // in plugin B:
141         MyMimeTypeHandler *handler = new MyMimeTypeHandler();
142         PluginManager::instance()->addObject(handler);
143         // In plugin A:
144         MimeTypeHandler *mimeHandler =
145             PluginManager::getObject<MimeTypeHandler>();
146     \endcode
147 
148 
149     The ExtensionSystem::Invoker class template provides \e {syntactic sugar}
150     for using \e soft extension points that may or may not be provided by an
151     object in the pool. This approach neither requires the \e user plugin being
152     linked against the \e provider plugin nor a common shared
153     header file. The exposed interface is implicitly given by the
154     invokable functions of the provider object in the object pool.
155 
156     The ExtensionSystem::invoke() function template encapsulates
157     ExtensionSystem::Invoker construction for the common case where
158     the success of the call is not checked.
159 
160     \code
161         // In the "provide" plugin A:
162         namespace PluginA {
163         class SomeProvider : public QObject
164         {
165             Q_OBJECT
166 
167         public:
168             Q_INVOKABLE QString doit(const QString &msg, int n) {
169             {
170                 qDebug() << "I AM DOING IT " << msg;
171                 return QString::number(n);
172             }
173         };
174         } // namespace PluginA
175 
176 
177         // In the "user" plugin B:
178         int someFuntionUsingPluginA()
179         {
180             using namespace ExtensionSystem;
181 
182             QObject *target = PluginManager::getObjectByClassName("PluginA::SomeProvider");
183 
184             if (target) {
185                 // Some random argument.
186                 QString msg = "REALLY.";
187 
188                 // Plain function call, no return value.
189                 invoke<void>(target, "doit", msg, 2);
190 
191                 // Plain function with no return value.
192                 qDebug() << "Result: " << invoke<QString>(target, "doit", msg, 21);
193 
194                 // Record success of function call with return value.
195                 Invoker<QString> in1(target, "doit", msg, 21);
196                 qDebug() << "Success: (expected)" << in1.wasSuccessful();
197 
198                 // Try to invoke a non-existing function.
199                 Invoker<QString> in2(target, "doitWrong", msg, 22);
200                 qDebug() << "Success (not expected):" << in2.wasSuccessful();
201 
202             } else {
203 
204                 // We have to cope with plugin A's absence.
205             }
206         };
207     \endcode
208 
209     \note The type of the parameters passed to the \c{invoke()} calls
210     is deduced from the parameters themselves and must match the type of
211     the arguments of the called functions \e{exactly}. No conversion or even
212     integer promotions are applicable, so to invoke a function with a \c{long}
213     parameter explicitly, use \c{long(43)} or such.
214 
215     \note The object pool manipulating functions are thread-safe.
216 */
217 
218 /*!
219     \fn template <typename T> *ExtensionSystem::PluginManager::getObject()
220 
221     Retrieves the object of a given type from the object pool.
222 
223     This function uses \c qobject_cast to determine the type of an object.
224     If there are more than one objects of the given type in
225     the object pool, this function will arbitrarily choose one of them.
226 
227     \sa addObject()
228 */
229 
230 /*!
231     \fn template <typename T, typename Predicate> *ExtensionSystem::PluginManager::getObject(Predicate predicate)
232 
233     Retrieves the object of a given type from the object pool that matches
234     the \a predicate.
235 
236     This function uses \c qobject_cast to determine the type of an object.
237     The predicate must be a function taking T * and returning a bool.
238     If there is more than one object matching the type and predicate,
239     this function will arbitrarily choose one of them.
240 
241     \sa addObject()
242 */
243 
244 
245 using namespace Utils;
246 
247 namespace ExtensionSystem {
248 
249 using namespace Internal;
250 
251 static Internal::PluginManagerPrivate *d = nullptr;
252 static PluginManager *m_instance = nullptr;
253 
254 /*!
255     Gets the unique plugin manager instance.
256 */
instance()257 PluginManager *PluginManager::instance()
258 {
259     return m_instance;
260 }
261 
262 /*!
263     Creates a plugin manager. Should be done only once per application.
264 */
PluginManager()265 PluginManager::PluginManager()
266 {
267     m_instance = this;
268     d = new PluginManagerPrivate(this);
269 }
270 
271 /*!
272     \internal
273 */
~PluginManager()274 PluginManager::~PluginManager()
275 {
276     delete d;
277     d = nullptr;
278 }
279 
280 /*!
281     Adds the object \a obj to the object pool, so it can be retrieved
282     again from the pool by type.
283 
284     The plugin manager does not do any memory management. Added objects
285     must be removed from the pool and deleted manually by whoever is responsible for the object.
286 
287     Emits the \c objectAdded() signal.
288 
289     \sa PluginManager::removeObject()
290     \sa PluginManager::getObject()
291     \sa PluginManager::getObjectByName()
292 */
addObject(QObject * obj)293 void PluginManager::addObject(QObject *obj)
294 {
295     d->addObject(obj);
296 }
297 
298 /*!
299     Emits the \c aboutToRemoveObject() signal and removes the object \a obj
300     from the object pool.
301     \sa PluginManager::addObject()
302 */
removeObject(QObject * obj)303 void PluginManager::removeObject(QObject *obj)
304 {
305     d->removeObject(obj);
306 }
307 
308 /*!
309     Retrieves the list of all objects in the pool, unfiltered.
310 
311     Usually, clients do not need to call this function.
312 
313     \sa PluginManager::getObject()
314 */
allObjects()315 QVector<QObject *> PluginManager::allObjects()
316 {
317     return d->allObjects;
318 }
319 
320 /*!
321     \internal
322 */
listLock()323 QReadWriteLock *PluginManager::listLock()
324 {
325     return &d->m_lock;
326 }
327 
328 /*!
329     Tries to load all the plugins that were previously found when
330     setting the plugin search paths. The plugin specs of the plugins
331     can be used to retrieve error and state information about individual plugins.
332 
333     \sa setPluginPaths()
334     \sa plugins()
335 */
loadPlugins()336 void PluginManager::loadPlugins()
337 {
338     d->loadPlugins();
339 }
340 
341 /*!
342     Returns \c true if any plugin has errors even though it is enabled.
343     Most useful to call after loadPlugins().
344 */
hasError()345 bool PluginManager::hasError()
346 {
347     return Utils::anyOf(plugins(), [](PluginSpec *spec) {
348         // only show errors on startup if plugin is enabled.
349         return spec->hasError() && spec->isEffectivelyEnabled();
350     });
351 }
352 
allErrors()353 const QStringList PluginManager::allErrors()
354 {
355     return Utils::transform<QStringList>(Utils::filtered(plugins(), [](const PluginSpec *spec) {
356         return spec->hasError() && spec->isEffectivelyEnabled();
357     }), [](const PluginSpec *spec) {
358         return spec->name().append(": ").append(spec->errorString());
359     });
360 }
361 
362 /*!
363     Returns all plugins that require \a spec to be loaded. Recurses into dependencies.
364  */
pluginsRequiringPlugin(PluginSpec * spec)365 const QSet<PluginSpec *> PluginManager::pluginsRequiringPlugin(PluginSpec *spec)
366 {
367     QSet<PluginSpec *> dependingPlugins({spec});
368     // recursively add plugins that depend on plugins that.... that depend on spec
369     for (PluginSpec *spec : d->loadQueue()) {
370         if (spec->requiresAny(dependingPlugins))
371             dependingPlugins.insert(spec);
372     }
373     dependingPlugins.remove(spec);
374     return dependingPlugins;
375 }
376 
377 /*!
378     Returns all plugins that \a spec requires to be loaded. Recurses into dependencies.
379  */
pluginsRequiredByPlugin(PluginSpec * spec)380 const QSet<PluginSpec *> PluginManager::pluginsRequiredByPlugin(PluginSpec *spec)
381 {
382     QSet<PluginSpec *> recursiveDependencies;
383     recursiveDependencies.insert(spec);
384     std::queue<PluginSpec *> queue;
385     queue.push(spec);
386     while (!queue.empty()) {
387         PluginSpec *checkSpec = queue.front();
388         queue.pop();
389         const QHash<PluginDependency, PluginSpec *> deps = checkSpec->dependencySpecs();
390         for (auto depIt = deps.cbegin(), end = deps.cend(); depIt != end; ++depIt) {
391             if (depIt.key().type != PluginDependency::Required)
392                 continue;
393             PluginSpec *depSpec = depIt.value();
394             if (!recursiveDependencies.contains(depSpec)) {
395                 recursiveDependencies.insert(depSpec);
396                 queue.push(depSpec);
397             }
398         }
399     }
400     recursiveDependencies.remove(spec);
401     return recursiveDependencies;
402 }
403 
404 /*!
405     Shuts down and deletes all plugins.
406 */
shutdown()407 void PluginManager::shutdown()
408 {
409     d->shutdown();
410 }
411 
filled(const QString & s,int min)412 static QString filled(const QString &s, int min)
413 {
414     return s + QString(qMax(0, min - s.size()), ' ');
415 }
416 
systemInformation()417 QString PluginManager::systemInformation()
418 {
419     QString result;
420     CommandLine qtDiag(HostOsInfo::withExecutableSuffix(
421                 QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qtdiag"));
422     QtcProcess qtDiagProc;
423     qtDiagProc.setCommand(qtDiag);
424     qtDiagProc.runBlocking();
425     if (qtDiagProc.result() == QtcProcess::FinishedWithSuccess)
426         result += qtDiagProc.allOutput() + "\n";
427     result += "Plugin information:\n\n";
428     auto longestSpec = std::max_element(d->pluginSpecs.cbegin(), d->pluginSpecs.cend(),
429                                         [](const PluginSpec *left, const PluginSpec *right) {
430                                             return left->name().size() < right->name().size();
431                                         });
432     int size = (*longestSpec)->name().size();
433     for (const PluginSpec *spec : plugins()) {
434         result += QLatin1String(spec->isEffectivelyEnabled() ? "+ " : "  ") + filled(spec->name(), size) +
435                   " " + spec->version() + "\n";
436     }
437     QString settingspath = QFileInfo(settings()->fileName()).path();
438     if (settingspath.startsWith(QDir::homePath()))
439         settingspath.replace(QDir::homePath(), "~");
440     result += "\nUsed settingspath: " + settingspath + "\n";
441     return result;
442 }
443 
444 /*!
445     The list of paths were the plugin manager searches for plugins.
446 
447     \sa setPluginPaths()
448 */
pluginPaths()449 QStringList PluginManager::pluginPaths()
450 {
451     return d->pluginPaths;
452 }
453 
454 /*!
455     Sets the plugin paths. All the specified \a paths and their subdirectory
456     trees are searched for plugins.
457 
458     \sa pluginPaths()
459     \sa loadPlugins()
460 */
setPluginPaths(const QStringList & paths)461 void PluginManager::setPluginPaths(const QStringList &paths)
462 {
463     d->setPluginPaths(paths);
464 }
465 
466 /*!
467     The IID that valid plugins must have.
468 
469     \sa setPluginIID()
470 */
pluginIID()471 QString PluginManager::pluginIID()
472 {
473     return d->pluginIID;
474 }
475 
476 /*!
477     Sets the IID that valid plugins must have to \a iid. Only plugins with this
478     IID are loaded, others are silently ignored.
479 
480     At the moment this must be called before setPluginPaths() is called.
481 
482     \omit
483     // ### TODO let this + setPluginPaths read the plugin meta data lazyly whenever loadPlugins() or plugins() is called.
484     \endomit
485 */
setPluginIID(const QString & iid)486 void PluginManager::setPluginIID(const QString &iid)
487 {
488     d->pluginIID = iid;
489 }
490 
491 /*!
492     Defines the user specific \a settings to use for information about enabled and
493     disabled plugins.
494     Needs to be set before the plugin search path is set with setPluginPaths().
495 */
setSettings(QtcSettings * settings)496 void PluginManager::setSettings(QtcSettings *settings)
497 {
498     d->setSettings(settings);
499 }
500 
501 /*!
502     Defines the global (user-independent) \a settings to use for information about
503     default disabled plugins.
504     Needs to be set before the plugin search path is set with setPluginPaths().
505 */
setGlobalSettings(QtcSettings * settings)506 void PluginManager::setGlobalSettings(QtcSettings *settings)
507 {
508     d->setGlobalSettings(settings);
509 }
510 
511 /*!
512     Returns the user specific settings used for information about enabled and
513     disabled plugins.
514 */
settings()515 QtcSettings *PluginManager::settings()
516 {
517     return d->settings;
518 }
519 
520 /*!
521     Returns the global (user-independent) settings used for information about default disabled plugins.
522 */
globalSettings()523 QtcSettings *PluginManager::globalSettings()
524 {
525     return d->globalSettings;
526 }
527 
writeSettings()528 void PluginManager::writeSettings()
529 {
530     d->writeSettings();
531 }
532 
533 /*!
534     The arguments left over after parsing (that were neither startup nor plugin
535     arguments). Typically, this will be the list of files to open.
536 */
arguments()537 QStringList PluginManager::arguments()
538 {
539     return d->arguments;
540 }
541 
542 /*!
543     The arguments that should be used when automatically restarting the application.
544     This includes plugin manager related options for enabling or disabling plugins,
545     but excludes others, like the arguments returned by arguments() and the appOptions
546     passed to the parseOptions() method.
547 */
argumentsForRestart()548 QStringList PluginManager::argumentsForRestart()
549 {
550     return d->argumentsForRestart;
551 }
552 
553 /*!
554     List of all plugins that have been found in the plugin search paths.
555     This list is valid directly after the setPluginPaths() call.
556     The plugin specifications contain plugin metadata and the current state
557     of the plugins. If a plugin's library has been already successfully loaded,
558     the plugin specification has a reference to the created plugin instance as well.
559 
560     \sa setPluginPaths()
561 */
plugins()562 const QVector<PluginSpec *> PluginManager::plugins()
563 {
564     return d->pluginSpecs;
565 }
566 
pluginCollections()567 QHash<QString, QVector<PluginSpec *>> PluginManager::pluginCollections()
568 {
569     return d->pluginCategories;
570 }
571 
572 static const char argumentKeywordC[] = ":arguments";
573 static const char pwdKeywordC[] = ":pwd";
574 
575 /*!
576     Serializes plugin options and arguments for sending in a single string
577     via QtSingleApplication:
578     ":myplugin|-option1|-option2|:arguments|argument1|argument2",
579     as a list of lists started by a keyword with a colon. Arguments are last.
580 
581     \sa setPluginPaths()
582 */
serializedArguments()583 QString PluginManager::serializedArguments()
584 {
585     const QChar separator = QLatin1Char('|');
586     QString rc;
587     for (const PluginSpec *ps : plugins()) {
588         if (!ps->arguments().isEmpty()) {
589             if (!rc.isEmpty())
590                 rc += separator;
591             rc += QLatin1Char(':');
592             rc += ps->name();
593             rc += separator;
594             rc +=  ps->arguments().join(separator);
595         }
596     }
597     if (!rc.isEmpty())
598         rc += separator;
599     rc += QLatin1String(pwdKeywordC) + separator + QDir::currentPath();
600     if (!d->arguments.isEmpty()) {
601         if (!rc.isEmpty())
602             rc += separator;
603         rc += QLatin1String(argumentKeywordC);
604         for (const QString &argument : qAsConst(d->arguments))
605             rc += separator + argument;
606     }
607     return rc;
608 }
609 
610 /* Extract a sublist from the serialized arguments
611  * indicated by a keyword starting with a colon indicator:
612  * ":a,i1,i2,:b:i3,i4" with ":a" -> "i1,i2"
613  */
subList(const QStringList & in,const QString & key)614 static QStringList subList(const QStringList &in, const QString &key)
615 {
616     QStringList rc;
617     // Find keyword and copy arguments until end or next keyword
618     const QStringList::const_iterator inEnd = in.constEnd();
619     QStringList::const_iterator it = std::find(in.constBegin(), inEnd, key);
620     if (it != inEnd) {
621         const QChar nextIndicator = QLatin1Char(':');
622         for (++it; it != inEnd && !it->startsWith(nextIndicator); ++it)
623             rc.append(*it);
624     }
625     return rc;
626 }
627 
628 /*!
629     Parses the options encoded in \a serializedArgument
630     and passes them on to the respective plugins along with the arguments.
631 
632     \a socket is passed for disconnecting the peer when the operation is done (for example,
633     document is closed) for supporting the \c -block flag.
634 */
635 
remoteArguments(const QString & serializedArgument,QObject * socket)636 void PluginManager::remoteArguments(const QString &serializedArgument, QObject *socket)
637 {
638     if (serializedArgument.isEmpty())
639         return;
640     QStringList serializedArguments = serializedArgument.split(QLatin1Char('|'));
641     const QStringList pwdValue = subList(serializedArguments, QLatin1String(pwdKeywordC));
642     const QString workingDirectory = pwdValue.isEmpty() ? QString() : pwdValue.first();
643     const QStringList arguments = subList(serializedArguments, QLatin1String(argumentKeywordC));
644     for (const PluginSpec *ps : plugins()) {
645         if (ps->state() == PluginSpec::Running) {
646             const QStringList pluginOptions = subList(serializedArguments, QLatin1Char(':') + ps->name());
647             QObject *socketParent = ps->plugin()->remoteCommand(pluginOptions, workingDirectory,
648                                                                 arguments);
649             if (socketParent && socket) {
650                 socket->setParent(socketParent);
651                 socket = nullptr;
652             }
653         }
654     }
655     if (socket)
656         delete socket;
657 }
658 
659 /*!
660     Takes the list of command line options in \a args and parses them.
661     The plugin manager itself might process some options itself directly
662     (\c {-noload <plugin>}), and adds options that are registered by
663     plugins to their plugin specs.
664 
665     The caller (the application) may register itself for options via the
666     \a appOptions list, containing pairs of \e {option string} and a bool
667     that indicates whether the option requires an argument.
668     Application options always override any plugin's options.
669 
670     \a foundAppOptions is set to pairs of (\e {option string}, \e argument)
671     for any application options that were found.
672     The command line options that were not processed can be retrieved via the arguments() function.
673     If an error occurred (like missing argument for an option that requires one), \a errorString contains
674     a descriptive message of the error.
675 
676     Returns if there was an error.
677  */
parseOptions(const QStringList & args,const QMap<QString,bool> & appOptions,QMap<QString,QString> * foundAppOptions,QString * errorString)678 bool PluginManager::parseOptions(const QStringList &args,
679     const QMap<QString, bool> &appOptions,
680     QMap<QString, QString> *foundAppOptions,
681     QString *errorString)
682 {
683     OptionsParser options(args, appOptions, foundAppOptions, errorString, d);
684     return options.parse();
685 }
686 
687 
688 
indent(QTextStream & str,int indent)689 static inline void indent(QTextStream &str, int indent)
690 {
691     str << QString(indent, ' ');
692 }
693 
formatOption(QTextStream & str,const QString & opt,const QString & parm,const QString & description,int optionIndentation,int descriptionIndentation)694 static inline void formatOption(QTextStream &str,
695                                 const QString &opt, const QString &parm, const QString &description,
696                                 int optionIndentation, int descriptionIndentation)
697 {
698     int remainingIndent = descriptionIndentation - optionIndentation - opt.size();
699     indent(str, optionIndentation);
700     str << opt;
701     if (!parm.isEmpty()) {
702         str << " <" << parm << '>';
703         remainingIndent -= 3 + parm.size();
704     }
705     if (remainingIndent >= 1) {
706         indent(str, remainingIndent);
707     } else {
708         str << '\n';
709         indent(str, descriptionIndentation);
710     }
711     str << description << '\n';
712 }
713 
714 /*!
715     Formats the startup options of the plugin manager for command line help with the specified
716     \a optionIndentation and \a descriptionIndentation.
717     Adds the result to \a str.
718 */
719 
formatOptions(QTextStream & str,int optionIndentation,int descriptionIndentation)720 void PluginManager::formatOptions(QTextStream &str, int optionIndentation, int descriptionIndentation)
721 {
722     formatOption(str, QLatin1String(OptionsParser::LOAD_OPTION),
723                  QLatin1String("plugin"), QLatin1String("Load <plugin> and all plugins that it requires"),
724                  optionIndentation, descriptionIndentation);
725     formatOption(str, QLatin1String(OptionsParser::LOAD_OPTION) + QLatin1String(" all"),
726                  QString(), QLatin1String("Load all available plugins"),
727                  optionIndentation, descriptionIndentation);
728     formatOption(str, QLatin1String(OptionsParser::NO_LOAD_OPTION),
729                  QLatin1String("plugin"), QLatin1String("Do not load <plugin> and all plugins that require it"),
730                  optionIndentation, descriptionIndentation);
731     formatOption(str, QLatin1String(OptionsParser::NO_LOAD_OPTION) + QLatin1String(" all"),
732                  QString(), QString::fromLatin1("Do not load any plugin (useful when "
733                                                 "followed by one or more \"%1\" arguments)")
734                  .arg(QLatin1String(OptionsParser::LOAD_OPTION)),
735                  optionIndentation, descriptionIndentation);
736     formatOption(str, QLatin1String(OptionsParser::PROFILE_OPTION),
737                  QString(), QLatin1String("Profile plugin loading"),
738                  optionIndentation, descriptionIndentation);
739     formatOption(str,
740                  QLatin1String(OptionsParser::NO_CRASHCHECK_OPTION),
741                  QString(),
742                  QLatin1String("Disable startup check for previously crashed instance"),
743                  optionIndentation,
744                  descriptionIndentation);
745 #ifdef WITH_TESTS
746     formatOption(str, QString::fromLatin1(OptionsParser::TEST_OPTION)
747                  + QLatin1String(" <plugin>[,testfunction[:testdata]]..."), QString(),
748                  QLatin1String("Run plugin's tests (by default a separate settings path is used)"),
749                  optionIndentation, descriptionIndentation);
750     formatOption(str, QString::fromLatin1(OptionsParser::TEST_OPTION) + QLatin1String(" all"),
751                  QString(), QLatin1String("Run tests from all plugins"),
752                  optionIndentation, descriptionIndentation);
753     formatOption(str, QString::fromLatin1(OptionsParser::NOTEST_OPTION),
754                  QLatin1String("plugin"), QLatin1String("Exclude all of the plugin's tests from the test run"),
755                  optionIndentation, descriptionIndentation);
756     formatOption(str, QString::fromLatin1(OptionsParser::SCENARIO_OPTION),
757                  QString("scenarioname"), QLatin1String("Run given scenario"),
758                  optionIndentation, descriptionIndentation);
759 #endif
760 }
761 
762 /*!
763     Formats the plugin options of the plugin specs for command line help with the specified
764     \a optionIndentation and \a descriptionIndentation.
765     Adds the result to \a str.
766 */
767 
formatPluginOptions(QTextStream & str,int optionIndentation,int descriptionIndentation)768 void PluginManager::formatPluginOptions(QTextStream &str, int optionIndentation, int descriptionIndentation)
769 {
770     // Check plugins for options
771     for (PluginSpec *ps : qAsConst(d->pluginSpecs)) {
772         const PluginSpec::PluginArgumentDescriptions pargs = ps->argumentDescriptions();
773         if (!pargs.empty()) {
774             str << "\nPlugin: " <<  ps->name() << '\n';
775             for (const PluginArgumentDescription &pad : pargs)
776                 formatOption(str, pad.name, pad.parameter, pad.description, optionIndentation, descriptionIndentation);
777         }
778     }
779 }
780 
781 /*!
782     Formats the version of the plugin specs for command line help and adds it to \a str.
783 */
formatPluginVersions(QTextStream & str)784 void PluginManager::formatPluginVersions(QTextStream &str)
785 {
786     for (PluginSpec *ps : qAsConst(d->pluginSpecs))
787         str << "  " << ps->name() << ' ' << ps->version() << ' ' << ps->description() <<  '\n';
788 }
789 
790 /*!
791     \internal
792  */
testRunRequested()793 bool PluginManager::testRunRequested()
794 {
795     return !d->testSpecs.empty();
796 }
797 
798 #ifdef WITH_TESTS
799 // Called in plugin initialization, the scenario function will be called later, from main
registerScenario(const QString & scenarioId,std::function<bool ()> scenarioStarter)800 bool PluginManager::registerScenario(const QString &scenarioId, std::function<bool()> scenarioStarter)
801 {
802     if (d->m_scenarios.contains(scenarioId)) {
803         const QString warning = QString("Can't register scenario \"%1\" as the other scenario was "
804                     "already registered with this name.").arg(scenarioId);
805         qWarning("%s", qPrintable(warning));
806         return false;
807     }
808 
809     d->m_scenarios.insert(scenarioId, scenarioStarter);
810     return true;
811 }
812 
813 // Called from main
isScenarioRequested()814 bool PluginManager::isScenarioRequested()
815 {
816     return !d->m_requestedScenario.isEmpty();
817 }
818 
819 // Called from main (may be squashed with the isScenarioRequested: runScenarioIfRequested).
820 // Returns false if scenario couldn't run (e.g. no Qt version set)
runScenario()821 bool PluginManager::runScenario()
822 {
823     if (d->m_isScenarioRunning) {
824         qWarning("Scenario is already running. Can't run scenario recursively.");
825         return false;
826     }
827 
828     if (d->m_requestedScenario.isEmpty()) {
829         qWarning("Can't run any scenario since no scenario was requested.");
830         return false;
831     }
832 
833     if (!d->m_scenarios.contains(d->m_requestedScenario)) {
834         const QString warning = QString("Requested scenario \"%1\" was not registered.").arg(d->m_requestedScenario);
835         qWarning("%s", qPrintable(warning));
836         return false;
837     }
838 
839     d->m_isScenarioRunning = true;
840     // The return value comes now from scenarioStarted() function. It may fail e.g. when
841     // no Qt version is set. Initializing the scenario may take some time, that's why
842     // waitForScenarioFullyInitialized() was added.
843     bool ret = d->m_scenarios[d->m_requestedScenario]();
844 
845     QMutexLocker locker(&d->m_scenarioMutex);
846     d->m_scenarioFullyInitialized = true;
847     d->m_scenarioWaitCondition.wakeAll();
848 
849     return ret;
850 }
851 
852 // Called from scenario point (and also from runScenario - don't run scenarios recursively).
853 // This may be called from non-main thread. We assume that m_requestedScenario
854 // may only be changed from the main thread.
isScenarioRunning(const QString & scenarioId)855 bool PluginManager::isScenarioRunning(const QString &scenarioId)
856 {
857     return d->m_isScenarioRunning && d->m_requestedScenario == scenarioId;
858 }
859 
860 // This may be called from non-main thread.
finishScenario()861 bool PluginManager::finishScenario()
862 {
863     if (!d->m_isScenarioRunning)
864         return false; // Can't finish not running scenario
865 
866     if (d->m_isScenarioFinished.exchange(true))
867         return false; // Finish was already called before. We return false, as we didn't finish it right now.
868 
869     QMetaObject::invokeMethod(d, []() { emit m_instance->scenarioFinished(0); });
870     return true; // Finished successfully.
871 }
872 
873 // Waits until the running scenario is fully initialized
waitForScenarioFullyInitialized()874 void PluginManager::waitForScenarioFullyInitialized()
875 {
876     if (QThread::currentThread() == qApp->thread()) {
877         qWarning("The waitForScenarioFullyInitialized() function can't be called from main thread.");
878         return;
879     }
880     QMutexLocker locker(&d->m_scenarioMutex);
881     if (d->m_scenarioFullyInitialized)
882         return;
883 
884     d->m_scenarioWaitCondition.wait(&d->m_scenarioMutex);
885 }
886 #endif
887 
setCreatorProcessData(const PluginManager::ProcessData & data)888 void PluginManager::setCreatorProcessData(const PluginManager::ProcessData &data)
889 {
890     d->m_creatorProcessData = data;
891 }
892 
creatorProcessData()893 PluginManager::ProcessData PluginManager::creatorProcessData()
894 {
895     return d->m_creatorProcessData;
896 }
897 
898 /*!
899     \internal
900 */
901 
profilingReport(const char * what,const PluginSpec * spec)902 void PluginManager::profilingReport(const char *what, const PluginSpec *spec)
903 {
904     d->profilingReport(what, spec);
905 }
906 
907 
908 /*!
909     Returns a list of plugins in load order.
910 */
loadQueue()911 QVector<PluginSpec *> PluginManager::loadQueue()
912 {
913     return d->loadQueue();
914 }
915 
916 //============PluginManagerPrivate===========
917 
918 /*!
919     \internal
920 */
createSpec()921 PluginSpec *PluginManagerPrivate::createSpec()
922 {
923     return new PluginSpec();
924 }
925 
926 /*!
927     \internal
928 */
setSettings(QtcSettings * s)929 void PluginManagerPrivate::setSettings(QtcSettings *s)
930 {
931     if (settings)
932         delete settings;
933     settings = s;
934     if (settings)
935         settings->setParent(this);
936 }
937 
938 /*!
939     \internal
940 */
setGlobalSettings(QtcSettings * s)941 void PluginManagerPrivate::setGlobalSettings(QtcSettings *s)
942 {
943     if (globalSettings)
944         delete globalSettings;
945     globalSettings = s;
946     if (globalSettings)
947         globalSettings->setParent(this);
948 }
949 
950 /*!
951     \internal
952 */
privateSpec(PluginSpec * spec)953 PluginSpecPrivate *PluginManagerPrivate::privateSpec(PluginSpec *spec)
954 {
955     return spec->d;
956 }
957 
nextDelayedInitialize()958 void PluginManagerPrivate::nextDelayedInitialize()
959 {
960     while (!delayedInitializeQueue.empty()) {
961         PluginSpec *spec = delayedInitializeQueue.front();
962         delayedInitializeQueue.pop();
963         profilingReport(">delayedInitialize", spec);
964         bool delay = spec->d->delayedInitialize();
965         profilingReport("<delayedInitialize", spec);
966         if (delay)
967             break; // do next delayedInitialize after a delay
968     }
969     if (delayedInitializeQueue.empty()) {
970         m_isInitializationDone = true;
971         delete delayedInitializeTimer;
972         delayedInitializeTimer = nullptr;
973         profilingSummary();
974         emit q->initializationDone();
975 #ifdef WITH_TESTS
976         if (PluginManager::testRunRequested())
977             startTests();
978         else if (PluginManager::isScenarioRequested()) {
979             if (PluginManager::runScenario()) {
980                 const QString info = QString("Successfully started scenario \"%1\"...").arg(d->m_requestedScenario);
981                 qInfo("%s", qPrintable(info));
982             } else {
983                 QMetaObject::invokeMethod(this, []() { emit m_instance->scenarioFinished(1); });
984             }
985         }
986 #endif
987     } else {
988         delayedInitializeTimer->start();
989     }
990 }
991 
992 /*!
993     \internal
994 */
PluginManagerPrivate(PluginManager * pluginManager)995 PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager) :
996     q(pluginManager)
997 {
998 }
999 
1000 
1001 /*!
1002     \internal
1003 */
~PluginManagerPrivate()1004 PluginManagerPrivate::~PluginManagerPrivate()
1005 {
1006     qDeleteAll(pluginSpecs);
1007 }
1008 
1009 /*!
1010     \internal
1011 */
writeSettings()1012 void PluginManagerPrivate::writeSettings()
1013 {
1014     if (!settings)
1015         return;
1016     QStringList tempDisabledPlugins;
1017     QStringList tempForceEnabledPlugins;
1018     for (PluginSpec *spec : qAsConst(pluginSpecs)) {
1019         if (spec->isEnabledByDefault() && !spec->isEnabledBySettings())
1020             tempDisabledPlugins.append(spec->name());
1021         if (!spec->isEnabledByDefault() && spec->isEnabledBySettings())
1022             tempForceEnabledPlugins.append(spec->name());
1023     }
1024 
1025     settings->setValueWithDefault(C_IGNORED_PLUGINS, tempDisabledPlugins);
1026     settings->setValueWithDefault(C_FORCEENABLED_PLUGINS, tempForceEnabledPlugins);
1027 }
1028 
1029 /*!
1030     \internal
1031 */
readSettings()1032 void PluginManagerPrivate::readSettings()
1033 {
1034     if (globalSettings) {
1035         defaultDisabledPlugins = globalSettings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
1036         defaultEnabledPlugins = globalSettings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
1037     }
1038     if (settings) {
1039         disabledPlugins = settings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
1040         forceEnabledPlugins = settings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
1041     }
1042 }
1043 
1044 /*!
1045     \internal
1046 */
stopAll()1047 void PluginManagerPrivate::stopAll()
1048 {
1049     if (delayedInitializeTimer && delayedInitializeTimer->isActive()) {
1050         delayedInitializeTimer->stop();
1051         delete delayedInitializeTimer;
1052         delayedInitializeTimer = nullptr;
1053     }
1054 
1055     const QVector<PluginSpec *> queue = loadQueue();
1056     for (PluginSpec *spec : queue)
1057         loadPlugin(spec, PluginSpec::Stopped);
1058 }
1059 
1060 /*!
1061     \internal
1062 */
deleteAll()1063 void PluginManagerPrivate::deleteAll()
1064 {
1065     Utils::reverseForeach(loadQueue(), [this](PluginSpec *spec) {
1066         loadPlugin(spec, PluginSpec::Deleted);
1067     });
1068 }
1069 
1070 #ifdef WITH_TESTS
1071 
1072 using TestPlan = QMap<QObject *, QStringList>; // Object -> selected test functions
1073 
isTestFunction(const QMetaMethod & metaMethod)1074 static bool isTestFunction(const QMetaMethod &metaMethod)
1075 {
1076     static const QVector<QByteArray> blackList = {"initTestCase()",
1077                                                   "cleanupTestCase()",
1078                                                   "init()",
1079                                                   "cleanup()"};
1080 
1081     if (metaMethod.methodType() != QMetaMethod::Slot)
1082         return false;
1083 
1084     if (metaMethod.access() != QMetaMethod::Private)
1085         return false;
1086 
1087     const QByteArray signature = metaMethod.methodSignature();
1088     if (blackList.contains(signature))
1089         return false;
1090 
1091     if (!signature.startsWith("test"))
1092         return false;
1093 
1094     if (signature.endsWith("_data()"))
1095         return false;
1096 
1097     return true;
1098 }
1099 
testFunctions(const QMetaObject * metaObject)1100 static QStringList testFunctions(const QMetaObject *metaObject)
1101 {
1102 
1103     QStringList functions;
1104 
1105     for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) {
1106         const QMetaMethod metaMethod = metaObject->method(i);
1107         if (isTestFunction(metaMethod)) {
1108             const QByteArray signature = metaMethod.methodSignature();
1109             const QString method = QString::fromLatin1(signature);
1110             const QString methodName = method.left(method.size() - 2);
1111             functions.append(methodName);
1112         }
1113     }
1114 
1115     return functions;
1116 }
1117 
matchingTestFunctions(const QStringList & testFunctions,const QString & matchText)1118 static QStringList matchingTestFunctions(const QStringList &testFunctions,
1119                                          const QString &matchText)
1120 {
1121     // There might be a test data suffix like in "testfunction:testdata1".
1122     QString testFunctionName = matchText;
1123     QString testDataSuffix;
1124     const int index = testFunctionName.indexOf(QLatin1Char(':'));
1125     if (index != -1) {
1126         testDataSuffix = testFunctionName.mid(index);
1127         testFunctionName = testFunctionName.left(index);
1128     }
1129 
1130     const QRegularExpression regExp(
1131                 QRegularExpression::wildcardToRegularExpression(testFunctionName));
1132     QStringList matchingFunctions;
1133     for (const QString &testFunction : testFunctions) {
1134         if (regExp.match(testFunction).hasMatch()) {
1135             // If the specified test data is invalid, the QTest framework will
1136             // print a reasonable error message for us.
1137             matchingFunctions.append(testFunction + testDataSuffix);
1138         }
1139     }
1140 
1141     return matchingFunctions;
1142 }
1143 
objectWithClassName(const QVector<QObject * > & objects,const QString & className)1144 static QObject *objectWithClassName(const QVector<QObject *> &objects, const QString &className)
1145 {
1146     return Utils::findOr(objects, nullptr, [className] (QObject *object) -> bool {
1147         QString candidate = QString::fromUtf8(object->metaObject()->className());
1148         const int colonIndex = candidate.lastIndexOf(QLatin1Char(':'));
1149         if (colonIndex != -1 && colonIndex < candidate.size() - 1)
1150             candidate = candidate.mid(colonIndex + 1);
1151         return candidate == className;
1152     });
1153 }
1154 
executeTestPlan(const TestPlan & testPlan)1155 static int executeTestPlan(const TestPlan &testPlan)
1156 {
1157     int failedTests = 0;
1158 
1159     for (auto it = testPlan.cbegin(), end = testPlan.cend(); it != end; ++it) {
1160         QObject *testObject = it.key();
1161         QStringList functions = it.value();
1162 
1163         // Don't run QTest::qExec without any test functions, that'd run *all* slots as tests.
1164         if (functions.isEmpty())
1165             continue;
1166 
1167         functions.removeDuplicates();
1168 
1169         // QTest::qExec() expects basically QCoreApplication::arguments(),
1170         QStringList qExecArguments = QStringList()
1171                 << QLatin1String("arg0") // fake application name
1172                 << QLatin1String("-maxwarnings") << QLatin1String("0"); // unlimit output
1173         qExecArguments << functions;
1174         // avoid being stuck in QTBUG-24925
1175         if (!HostOsInfo::isWindowsHost())
1176             qExecArguments << "-nocrashhandler";
1177         failedTests += QTest::qExec(testObject, qExecArguments);
1178     }
1179 
1180     return failedTests;
1181 }
1182 
1183 /// Resulting plan consists of all test functions of the plugin object and
1184 /// all test functions of all test objects of the plugin.
generateCompleteTestPlan(IPlugin * plugin,const QVector<QObject * > & testObjects)1185 static TestPlan generateCompleteTestPlan(IPlugin *plugin, const QVector<QObject *> &testObjects)
1186 {
1187     TestPlan testPlan;
1188 
1189     testPlan.insert(plugin, testFunctions(plugin->metaObject()));
1190     for (QObject *testObject : testObjects) {
1191         const QStringList allFunctions = testFunctions(testObject->metaObject());
1192         testPlan.insert(testObject, allFunctions);
1193     }
1194 
1195     return testPlan;
1196 }
1197 
1198 /// Resulting plan consists of all matching test functions of the plugin object
1199 /// and all matching functions of all test objects of the plugin. However, if a
1200 /// match text denotes a test class, all test functions of that will be
1201 /// included and the class will not be considered further.
1202 ///
1203 /// Since multiple match texts can match the same function, a test function might
1204 /// be included multiple times for a test object.
generateCustomTestPlan(IPlugin * plugin,const QVector<QObject * > & testObjects,const QStringList & matchTexts)1205 static TestPlan generateCustomTestPlan(IPlugin *plugin,
1206                                        const QVector<QObject *> &testObjects,
1207                                        const QStringList &matchTexts)
1208 {
1209     TestPlan testPlan;
1210 
1211     const QStringList testFunctionsOfPluginObject = testFunctions(plugin->metaObject());
1212     QStringList matchedTestFunctionsOfPluginObject;
1213     QStringList remainingMatchTexts = matchTexts;
1214     QVector<QObject *> remainingTestObjectsOfPlugin = testObjects;
1215 
1216     while (!remainingMatchTexts.isEmpty()) {
1217         const QString matchText = remainingMatchTexts.takeFirst();
1218         bool matched = false;
1219 
1220         if (QObject *testObject = objectWithClassName(remainingTestObjectsOfPlugin, matchText)) {
1221             // Add all functions of the matching test object
1222             matched = true;
1223             testPlan.insert(testObject, testFunctions(testObject->metaObject()));
1224             remainingTestObjectsOfPlugin.removeAll(testObject);
1225 
1226         } else {
1227             // Add all matching test functions of all remaining test objects
1228             for (QObject *testObject : qAsConst(remainingTestObjectsOfPlugin)) {
1229                 const QStringList allFunctions = testFunctions(testObject->metaObject());
1230                 const QStringList matchingFunctions = matchingTestFunctions(allFunctions,
1231                                                                             matchText);
1232                 if (!matchingFunctions.isEmpty()) {
1233                     matched = true;
1234                     testPlan[testObject] += matchingFunctions;
1235                 }
1236             }
1237         }
1238 
1239         const QStringList currentMatchedTestFunctionsOfPluginObject
1240             = matchingTestFunctions(testFunctionsOfPluginObject, matchText);
1241         if (!currentMatchedTestFunctionsOfPluginObject.isEmpty()) {
1242             matched = true;
1243             matchedTestFunctionsOfPluginObject += currentMatchedTestFunctionsOfPluginObject;
1244         }
1245 
1246         if (!matched) {
1247             QTextStream out(stdout);
1248             out << "No test function or class matches \"" << matchText
1249                 << "\" in plugin \"" << plugin->metaObject()->className()
1250                 << "\".\nAvailable functions:\n";
1251             for (const QString &f : testFunctionsOfPluginObject)
1252                 out << "  " << f << '\n';
1253             out << '\n';
1254         }
1255     }
1256 
1257     // Add all matching test functions of plugin
1258     if (!matchedTestFunctionsOfPluginObject.isEmpty())
1259         testPlan.insert(plugin, matchedTestFunctionsOfPluginObject);
1260 
1261     return testPlan;
1262 }
1263 
startTests()1264 void PluginManagerPrivate::startTests()
1265 {
1266     if (PluginManager::hasError()) {
1267         qWarning("Errors occurred while loading plugins, skipping test run.");
1268         for (const QString &pluginError : PluginManager::allErrors())
1269             qWarning("%s", qPrintable(pluginError));
1270         QTimer::singleShot(1, QCoreApplication::instance(), &QCoreApplication::quit);
1271         return;
1272     }
1273 
1274     int failedTests = 0;
1275     for (const TestSpec &testSpec : qAsConst(testSpecs)) {
1276         IPlugin *plugin = testSpec.pluginSpec->plugin();
1277         if (!plugin)
1278             continue; // plugin not loaded
1279 
1280         const QVector<QObject *> testObjects = plugin->createTestObjects();
1281         ExecuteOnDestruction deleteTestObjects([&]() { qDeleteAll(testObjects); });
1282         Q_UNUSED(deleteTestObjects)
1283 
1284         const bool hasDuplicateTestObjects = testObjects.size()
1285                                              != Utils::filteredUnique(testObjects).size();
1286         QTC_ASSERT(!hasDuplicateTestObjects, continue);
1287         QTC_ASSERT(!testObjects.contains(plugin), continue);
1288 
1289         const TestPlan testPlan = testSpec.testFunctionsOrObjects.isEmpty()
1290                 ? generateCompleteTestPlan(plugin, testObjects)
1291                 : generateCustomTestPlan(plugin, testObjects, testSpec.testFunctionsOrObjects);
1292 
1293         failedTests += executeTestPlan(testPlan);
1294     }
1295 
1296     QTimer::singleShot(0, this, [failedTests]() { emit m_instance->testsFinished(failedTests); });
1297 }
1298 #endif
1299 
1300 /*!
1301     \internal
1302 */
addObject(QObject * obj)1303 void PluginManagerPrivate::addObject(QObject *obj)
1304 {
1305     {
1306         QWriteLocker lock(&m_lock);
1307         if (obj == nullptr) {
1308             qWarning() << "PluginManagerPrivate::addObject(): trying to add null object";
1309             return;
1310         }
1311         if (allObjects.contains(obj)) {
1312             qWarning() << "PluginManagerPrivate::addObject(): trying to add duplicate object";
1313             return;
1314         }
1315 
1316         if (debugLeaks)
1317             qDebug() << "PluginManagerPrivate::addObject" << obj << obj->objectName();
1318 
1319         if (m_profilingVerbosity && !m_profileTimer.isNull()) {
1320             // Report a timestamp when adding an object. Useful for profiling
1321             // its initialization time.
1322             const int absoluteElapsedMS = int(m_profileTimer->elapsed());
1323             qDebug("  %-43s %8dms", obj->metaObject()->className(), absoluteElapsedMS);
1324         }
1325 
1326         allObjects.append(obj);
1327     }
1328     emit q->objectAdded(obj);
1329 }
1330 
1331 /*!
1332     \internal
1333 */
removeObject(QObject * obj)1334 void PluginManagerPrivate::removeObject(QObject *obj)
1335 {
1336     if (obj == nullptr) {
1337         qWarning() << "PluginManagerPrivate::removeObject(): trying to remove null object";
1338         return;
1339     }
1340 
1341     if (!allObjects.contains(obj)) {
1342         qWarning() << "PluginManagerPrivate::removeObject(): object not in list:"
1343             << obj << obj->objectName();
1344         return;
1345     }
1346     if (debugLeaks)
1347         qDebug() << "PluginManagerPrivate::removeObject" << obj << obj->objectName();
1348 
1349     emit q->aboutToRemoveObject(obj);
1350     QWriteLocker lock(&m_lock);
1351     allObjects.removeAll(obj);
1352 }
1353 
1354 /*!
1355     \internal
1356 */
loadPlugins()1357 void PluginManagerPrivate::loadPlugins()
1358 {
1359     const QVector<PluginSpec *> queue = loadQueue();
1360     Utils::setMimeStartupPhase(MimeStartupPhase::PluginsLoading);
1361     for (PluginSpec *spec : queue)
1362         loadPlugin(spec, PluginSpec::Loaded);
1363 
1364     Utils::setMimeStartupPhase(MimeStartupPhase::PluginsInitializing);
1365     for (PluginSpec *spec : queue)
1366         loadPlugin(spec, PluginSpec::Initialized);
1367 
1368     Utils::setMimeStartupPhase(MimeStartupPhase::PluginsDelayedInitializing);
1369     Utils::reverseForeach(queue, [this](PluginSpec *spec) {
1370         loadPlugin(spec, PluginSpec::Running);
1371         if (spec->state() == PluginSpec::Running) {
1372             delayedInitializeQueue.push(spec);
1373         } else {
1374             // Plugin initialization failed, so cleanup after it
1375             spec->d->kill();
1376         }
1377     });
1378     emit q->pluginsChanged();
1379     Utils::setMimeStartupPhase(MimeStartupPhase::UpAndRunning);
1380 
1381     delayedInitializeTimer = new QTimer;
1382     delayedInitializeTimer->setInterval(DELAYED_INITIALIZE_INTERVAL);
1383     delayedInitializeTimer->setSingleShot(true);
1384     connect(delayedInitializeTimer, &QTimer::timeout,
1385             this, &PluginManagerPrivate::nextDelayedInitialize);
1386     delayedInitializeTimer->start();
1387 }
1388 
1389 /*!
1390     \internal
1391 */
shutdown()1392 void PluginManagerPrivate::shutdown()
1393 {
1394     stopAll();
1395     if (!asynchronousPlugins.isEmpty()) {
1396         shutdownEventLoop = new QEventLoop;
1397         shutdownEventLoop->exec();
1398     }
1399     deleteAll();
1400 #ifdef WITH_TESTS
1401     if (PluginManager::isScenarioRunning("TestModelManagerInterface")) {
1402         qDebug() << "Point 2: Expect the next call to Point 3 triggers a crash";
1403         QThread::currentThread()->sleep(5);
1404     }
1405 #endif
1406     if (!allObjects.isEmpty()) {
1407         qDebug() << "There are" << allObjects.size() << "objects left in the plugin manager pool.";
1408         // Intentionally split debug info here, since in case the list contains
1409         // already deleted object we get at least the info about the number of objects;
1410         qDebug() << "The following objects left in the plugin manager pool:" << allObjects;
1411     }
1412 }
1413 
1414 /*!
1415     \internal
1416 */
asyncShutdownFinished()1417 void PluginManagerPrivate::asyncShutdownFinished()
1418 {
1419     auto *plugin = qobject_cast<IPlugin *>(sender());
1420     Q_ASSERT(plugin);
1421     asynchronousPlugins.remove(plugin->pluginSpec());
1422     if (asynchronousPlugins.isEmpty())
1423         shutdownEventLoop->exit();
1424 }
1425 
1426 /*!
1427     \internal
1428 */
loadQueue()1429 const QVector<PluginSpec *> PluginManagerPrivate::loadQueue()
1430 {
1431     QVector<PluginSpec *> queue;
1432     for (PluginSpec *spec : qAsConst(pluginSpecs)) {
1433         QVector<PluginSpec *> circularityCheckQueue;
1434         loadQueue(spec, queue, circularityCheckQueue);
1435     }
1436     return queue;
1437 }
1438 
1439 /*!
1440     \internal
1441 */
loadQueue(PluginSpec * spec,QVector<PluginSpec * > & queue,QVector<PluginSpec * > & circularityCheckQueue)1442 bool PluginManagerPrivate::loadQueue(PluginSpec *spec,
1443                                      QVector<PluginSpec *> &queue,
1444                                      QVector<PluginSpec *> &circularityCheckQueue)
1445 {
1446     if (queue.contains(spec))
1447         return true;
1448     // check for circular dependencies
1449     if (circularityCheckQueue.contains(spec)) {
1450         spec->d->hasError = true;
1451         spec->d->errorString = PluginManager::tr("Circular dependency detected:");
1452         spec->d->errorString += QLatin1Char('\n');
1453         int index = circularityCheckQueue.indexOf(spec);
1454         for (int i = index; i < circularityCheckQueue.size(); ++i) {
1455             spec->d->errorString.append(PluginManager::tr("%1 (%2) depends on")
1456                 .arg(circularityCheckQueue.at(i)->name()).arg(circularityCheckQueue.at(i)->version()));
1457             spec->d->errorString += QLatin1Char('\n');
1458         }
1459         spec->d->errorString.append(PluginManager::tr("%1 (%2)").arg(spec->name()).arg(spec->version()));
1460         return false;
1461     }
1462     circularityCheckQueue.append(spec);
1463     // check if we have the dependencies
1464     if (spec->state() == PluginSpec::Invalid || spec->state() == PluginSpec::Read) {
1465         queue.append(spec);
1466         return false;
1467     }
1468 
1469     // add dependencies
1470     const QHash<PluginDependency, PluginSpec *> deps = spec->dependencySpecs();
1471     for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) {
1472         // Skip test dependencies since they are not real dependencies but just force-loaded
1473         // plugins when running tests
1474         if (it.key().type == PluginDependency::Test)
1475             continue;
1476         PluginSpec *depSpec = it.value();
1477         if (!loadQueue(depSpec, queue, circularityCheckQueue)) {
1478             spec->d->hasError = true;
1479             spec->d->errorString =
1480                 PluginManager::tr("Cannot load plugin because dependency failed to load: %1 (%2)\nReason: %3")
1481                     .arg(depSpec->name()).arg(depSpec->version()).arg(depSpec->errorString());
1482             return false;
1483         }
1484     }
1485     // add self
1486     queue.append(spec);
1487     return true;
1488 }
1489 
1490 class LockFile
1491 {
1492 public:
filePath(PluginManagerPrivate * pm)1493     static QString filePath(PluginManagerPrivate *pm)
1494     {
1495         return QFileInfo(pm->settings->fileName()).absolutePath() + '/'
1496                + QCoreApplication::applicationName() + '.'
1497                + QCryptographicHash::hash(QCoreApplication::applicationDirPath().toUtf8(),
1498                                           QCryptographicHash::Sha1)
1499                      .left(8)
1500                      .toHex()
1501                + ".lock";
1502     }
1503 
lockedPluginName(PluginManagerPrivate * pm)1504     static Utils::optional<QString> lockedPluginName(PluginManagerPrivate *pm)
1505     {
1506         const QString lockFilePath = LockFile::filePath(pm);
1507         if (QFile::exists(lockFilePath)) {
1508             QFile f(lockFilePath);
1509             if (f.open(QIODevice::ReadOnly)) {
1510                 const auto pluginName = QString::fromUtf8(f.readLine()).trimmed();
1511                 f.close();
1512                 return pluginName;
1513             } else {
1514                 qCDebug(pluginLog) << "Lock file" << lockFilePath << "exists but is not readable";
1515             }
1516         }
1517         return {};
1518     }
1519 
LockFile(PluginManagerPrivate * pm,PluginSpec * spec)1520     LockFile(PluginManagerPrivate *pm, PluginSpec *spec)
1521         : m_filePath(filePath(pm))
1522     {
1523         QDir().mkpath(QFileInfo(m_filePath).absolutePath());
1524         QFile f(m_filePath);
1525         if (f.open(QIODevice::WriteOnly)) {
1526             f.write(spec->name().toUtf8());
1527             f.write("\n");
1528             f.close();
1529         } else {
1530             qCDebug(pluginLog) << "Cannot write lock file" << m_filePath;
1531         }
1532     }
1533 
~LockFile()1534     ~LockFile() { QFile::remove(m_filePath); }
1535 
1536 private:
1537     QString m_filePath;
1538 };
1539 
checkForProblematicPlugins()1540 void PluginManagerPrivate::checkForProblematicPlugins()
1541 {
1542     if (!enableCrashCheck)
1543         return;
1544     const Utils::optional<QString> pluginName = LockFile::lockedPluginName(this);
1545     if (pluginName) {
1546         PluginSpec *spec = pluginByName(*pluginName);
1547         if (spec && !spec->isRequired()) {
1548             const QSet<PluginSpec *> dependents = PluginManager::pluginsRequiringPlugin(spec);
1549             auto dependentsNames = Utils::transform<QStringList>(dependents, &PluginSpec::name);
1550             std::sort(dependentsNames.begin(), dependentsNames.end());
1551             const QString dependentsList = dependentsNames.join(", ");
1552             const QString pluginsMenu = HostOsInfo::isMacHost()
1553                                             ? tr("%1 > About Plugins")
1554                                                   .arg(QGuiApplication::applicationDisplayName())
1555                                             : tr("Help > About Plugins");
1556             const QString otherPluginsText = tr("The following plugins depend on "
1557                                                 "%1 and are also disabled: %2.\n\n")
1558                                                  .arg(spec->name(), dependentsList);
1559             const QString detailsText = (dependents.isEmpty() ? QString() : otherPluginsText)
1560                                         + tr("Disable plugins permanently in %1.").arg(pluginsMenu);
1561             const QString text = tr("It looks like %1 closed because of a problem with the \"%2\" "
1562                                     "plugin. Temporarily disable the plugin?")
1563                                      .arg(QGuiApplication::applicationDisplayName(), spec->name());
1564             QMessageBox dialog;
1565             dialog.setIcon(QMessageBox::Question);
1566             dialog.setText(text);
1567             dialog.setDetailedText(detailsText);
1568             QPushButton *disableButton = dialog.addButton(tr("Disable Plugin"),
1569                                                           QMessageBox::AcceptRole);
1570             dialog.addButton(tr("Continue"), QMessageBox::RejectRole);
1571             dialog.exec();
1572             if (dialog.clickedButton() == disableButton) {
1573                 spec->d->setForceDisabled(true);
1574                 for (PluginSpec *other : dependents)
1575                     other->d->setForceDisabled(true);
1576                 enableDependenciesIndirectly();
1577             }
1578         }
1579     }
1580 }
1581 
checkForProblematicPlugins()1582 void PluginManager::checkForProblematicPlugins()
1583 {
1584     d->checkForProblematicPlugins();
1585 }
1586 
1587 /*!
1588     \internal
1589 */
loadPlugin(PluginSpec * spec,PluginSpec::State destState)1590 void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destState)
1591 {
1592     if (spec->hasError() || spec->state() != destState-1)
1593         return;
1594 
1595     // don't load disabled plugins.
1596     if (!spec->isEffectivelyEnabled() && destState == PluginSpec::Loaded)
1597         return;
1598 
1599     std::unique_ptr<LockFile> lockFile;
1600     if (enableCrashCheck && destState < PluginSpec::Stopped)
1601         lockFile.reset(new LockFile(this, spec));
1602 
1603     switch (destState) {
1604     case PluginSpec::Running:
1605         profilingReport(">initializeExtensions", spec);
1606         spec->d->initializeExtensions();
1607         profilingReport("<initializeExtensions", spec);
1608         return;
1609     case PluginSpec::Deleted:
1610         profilingReport(">delete", spec);
1611         spec->d->kill();
1612         profilingReport("<delete", spec);
1613         return;
1614     default:
1615         break;
1616     }
1617     // check if dependencies have loaded without error
1618     const QHash<PluginDependency, PluginSpec *> deps = spec->dependencySpecs();
1619     for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) {
1620         if (it.key().type != PluginDependency::Required)
1621             continue;
1622         PluginSpec *depSpec = it.value();
1623         if (depSpec->state() != destState) {
1624             spec->d->hasError = true;
1625             spec->d->errorString =
1626                 PluginManager::tr("Cannot load plugin because dependency failed to load: %1(%2)\nReason: %3")
1627                     .arg(depSpec->name()).arg(depSpec->version()).arg(depSpec->errorString());
1628             return;
1629         }
1630     }
1631     switch (destState) {
1632     case PluginSpec::Loaded:
1633         profilingReport(">loadLibrary", spec);
1634         spec->d->loadLibrary();
1635         profilingReport("<loadLibrary", spec);
1636         break;
1637     case PluginSpec::Initialized:
1638         profilingReport(">initializePlugin", spec);
1639         spec->d->initializePlugin();
1640         profilingReport("<initializePlugin", spec);
1641         break;
1642     case PluginSpec::Stopped:
1643         profilingReport(">stop", spec);
1644         if (spec->d->stop() == IPlugin::AsynchronousShutdown) {
1645             asynchronousPlugins << spec;
1646             connect(spec->plugin(), &IPlugin::asynchronousShutdownFinished,
1647                     this, &PluginManagerPrivate::asyncShutdownFinished);
1648         }
1649         profilingReport("<stop", spec);
1650         break;
1651     default:
1652         break;
1653     }
1654 }
1655 
1656 /*!
1657     \internal
1658 */
setPluginPaths(const QStringList & paths)1659 void PluginManagerPrivate::setPluginPaths(const QStringList &paths)
1660 {
1661     qCDebug(pluginLog) << "Plugin search paths:" << paths;
1662     qCDebug(pluginLog) << "Required IID:" << pluginIID;
1663     pluginPaths = paths;
1664     readSettings();
1665     readPluginPaths();
1666 }
1667 
pluginFiles(const QStringList & pluginPaths)1668 static const QStringList pluginFiles(const QStringList &pluginPaths)
1669 {
1670     QStringList pluginFiles;
1671     QStringList searchPaths = pluginPaths;
1672     while (!searchPaths.isEmpty()) {
1673         const QDir dir(searchPaths.takeFirst());
1674         const QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::NoSymLinks);
1675         const QStringList absoluteFilePaths = Utils::transform(files, &QFileInfo::absoluteFilePath);
1676         pluginFiles += Utils::filtered(absoluteFilePaths, [](const QString &path) { return QLibrary::isLibrary(path); });
1677         const QFileInfoList dirs = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
1678         searchPaths += Utils::transform(dirs, &QFileInfo::absoluteFilePath);
1679     }
1680     return pluginFiles;
1681 }
1682 
1683 /*!
1684     \internal
1685 */
readPluginPaths()1686 void PluginManagerPrivate::readPluginPaths()
1687 {
1688     qDeleteAll(pluginSpecs);
1689     pluginSpecs.clear();
1690     pluginCategories.clear();
1691 
1692     // default
1693     pluginCategories.insert(QString(), QVector<PluginSpec *>());
1694 
1695     for (const QString &pluginFile : pluginFiles(pluginPaths)) {
1696         PluginSpec *spec = PluginSpec::read(pluginFile);
1697         if (!spec) // not a Qt Creator plugin
1698             continue;
1699 
1700         // defaultDisabledPlugins and defaultEnabledPlugins from install settings
1701         // is used to override the defaults read from the plugin spec
1702         if (spec->isEnabledByDefault() && defaultDisabledPlugins.contains(spec->name())) {
1703             spec->d->setEnabledByDefault(false);
1704             spec->d->setEnabledBySettings(false);
1705         } else if (!spec->isEnabledByDefault() && defaultEnabledPlugins.contains(spec->name())) {
1706             spec->d->setEnabledByDefault(true);
1707             spec->d->setEnabledBySettings(true);
1708         }
1709         if (!spec->isEnabledByDefault() && forceEnabledPlugins.contains(spec->name()))
1710             spec->d->setEnabledBySettings(true);
1711         if (spec->isEnabledByDefault() && disabledPlugins.contains(spec->name()))
1712             spec->d->setEnabledBySettings(false);
1713 
1714         pluginCategories[spec->category()].append(spec);
1715         pluginSpecs.append(spec);
1716     }
1717     resolveDependencies();
1718     enableDependenciesIndirectly();
1719     // ensure deterministic plugin load order by sorting
1720     Utils::sort(pluginSpecs, &PluginSpec::name);
1721     emit q->pluginsChanged();
1722 }
1723 
resolveDependencies()1724 void PluginManagerPrivate::resolveDependencies()
1725 {
1726     for (PluginSpec *spec : qAsConst(pluginSpecs))
1727         spec->d->resolveDependencies(pluginSpecs);
1728 }
1729 
enableDependenciesIndirectly()1730 void PluginManagerPrivate::enableDependenciesIndirectly()
1731 {
1732     for (PluginSpec *spec : qAsConst(pluginSpecs))
1733         spec->d->enabledIndirectly = false;
1734     // cannot use reverse loadQueue here, because test dependencies can introduce circles
1735     QVector<PluginSpec *> queue = Utils::filtered(pluginSpecs, &PluginSpec::isEffectivelyEnabled);
1736     while (!queue.isEmpty()) {
1737         PluginSpec *spec = queue.takeFirst();
1738         queue += spec->d->enableDependenciesIndirectly(containsTestSpec(spec));
1739     }
1740 }
1741 
1742 // Look in argument descriptions of the specs for the option.
pluginForOption(const QString & option,bool * requiresArgument) const1743 PluginSpec *PluginManagerPrivate::pluginForOption(const QString &option, bool *requiresArgument) const
1744 {
1745     // Look in the plugins for an option
1746     *requiresArgument = false;
1747     for (PluginSpec *spec : qAsConst(pluginSpecs)) {
1748         PluginArgumentDescription match = Utils::findOrDefault(spec->argumentDescriptions(),
1749                                                                [option](PluginArgumentDescription pad) {
1750                                                                    return pad.name == option;
1751                                                                });
1752         if (!match.name.isEmpty()) {
1753             *requiresArgument = !match.parameter.isEmpty();
1754             return spec;
1755         }
1756     }
1757     return nullptr;
1758 }
1759 
pluginByName(const QString & name) const1760 PluginSpec *PluginManagerPrivate::pluginByName(const QString &name) const
1761 {
1762     return Utils::findOrDefault(pluginSpecs, [name](PluginSpec *spec) { return spec->name() == name; });
1763 }
1764 
initProfiling()1765 void PluginManagerPrivate::initProfiling()
1766 {
1767     if (m_profileTimer.isNull()) {
1768         m_profileTimer.reset(new QElapsedTimer);
1769         m_profileTimer->start();
1770         m_profileElapsedMS = 0;
1771         qDebug("Profiling started");
1772     } else {
1773         m_profilingVerbosity++;
1774     }
1775 }
1776 
profilingReport(const char * what,const PluginSpec * spec)1777 void PluginManagerPrivate::profilingReport(const char *what, const PluginSpec *spec /* = 0 */)
1778 {
1779     if (!m_profileTimer.isNull()) {
1780         const int absoluteElapsedMS = int(m_profileTimer->elapsed());
1781         const int elapsedMS = absoluteElapsedMS - m_profileElapsedMS;
1782         m_profileElapsedMS = absoluteElapsedMS;
1783         if (spec)
1784             qDebug("%-22s %-22s %8dms (%8dms)", what, qPrintable(spec->name()), absoluteElapsedMS, elapsedMS);
1785         else
1786             qDebug("%-45s %8dms (%8dms)", what, absoluteElapsedMS, elapsedMS);
1787         if (what && *what == '<') {
1788             QString tc;
1789             if (spec) {
1790                 m_profileTotal[spec] += elapsedMS;
1791                 tc = spec->name() + '_';
1792             }
1793             tc += QString::fromUtf8(QByteArray(what + 1));
1794             Utils::Benchmarker::report("loadPlugins", tc, elapsedMS);
1795         }
1796     }
1797 }
1798 
profilingSummary() const1799 void PluginManagerPrivate::profilingSummary() const
1800 {
1801     if (!m_profileTimer.isNull()) {
1802         QMultiMap<int, const PluginSpec *> sorter;
1803         int total = 0;
1804 
1805         auto totalEnd = m_profileTotal.constEnd();
1806         for (auto it = m_profileTotal.constBegin(); it != totalEnd; ++it) {
1807             sorter.insert(it.value(), it.key());
1808             total += it.value();
1809         }
1810 
1811         auto sorterEnd = sorter.constEnd();
1812         for (auto it = sorter.constBegin(); it != sorterEnd; ++it)
1813             qDebug("%-22s %8dms   ( %5.2f%% )", qPrintable(it.value()->name()),
1814                 it.key(), 100.0 * it.key() / total);
1815          qDebug("Total: %8dms", total);
1816          Utils::Benchmarker::report("loadPlugins", "Total", total);
1817     }
1818 }
1819 
getPlatformName()1820 static inline QString getPlatformName()
1821 {
1822     if (HostOsInfo::isMacHost())
1823         return QLatin1String("OS X");
1824     else if (HostOsInfo::isAnyUnixHost())
1825         return QLatin1String(HostOsInfo::isLinuxHost() ? "Linux" : "Unix");
1826     else if (HostOsInfo::isWindowsHost())
1827         return QLatin1String("Windows");
1828     return QLatin1String("Unknown");
1829 }
1830 
platformName()1831 QString PluginManager::platformName()
1832 {
1833     static const QString result = getPlatformName() + " (" + QSysInfo::prettyProductName() + ')';
1834     return result;
1835 }
1836 
isInitializationDone()1837 bool PluginManager::isInitializationDone()
1838 {
1839     return d->m_isInitializationDone;
1840 }
1841 
1842 /*!
1843     Retrieves one object with \a name from the object pool.
1844     \sa addObject()
1845 */
1846 
getObjectByName(const QString & name)1847 QObject *PluginManager::getObjectByName(const QString &name)
1848 {
1849     QReadLocker lock(&d->m_lock);
1850     return Utils::findOrDefault(allObjects(), [&name](const QObject *obj) {
1851         return obj->objectName() == name;
1852     });
1853 }
1854 
1855 } // ExtensionSystem
1856