QmlDesigner: Detect alias cycle

An exception is thrown if an alias cycle is detected. This prevents that
the designer would get in an endless loop.

Task-number: QDS-4682
Change-Id: Iffbfb42d7334aa2a5490fe2830b5cfab8c8e627a
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Marco Bubke
2021-07-15 10:21:56 +02:00
parent 15f44db84c
commit 3591735293
3 changed files with 64 additions and 3 deletions

View File

@@ -712,7 +712,7 @@ private:
void linkAliasPropertyDeclarationAliasIds(const std::vector<AliasPropertyDeclaration> &aliasDeclarations)
{
for (auto &aliasDeclaration : aliasDeclarations) {
for (const auto &aliasDeclaration : aliasDeclarations) {
auto [aliasTypeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(aliasDeclaration.aliasTypeName,
aliasDeclaration.sourceId);
@@ -729,7 +729,7 @@ private:
void updateAliasPropertyDeclarationValues(const std::vector<AliasPropertyDeclaration> &aliasDeclarations)
{
for (auto &aliasDeclaration : aliasDeclarations) {
for (const auto &aliasDeclaration : aliasDeclarations) {
updatetPropertiesDeclarationValuesOfAliasStatement.write(
&aliasDeclaration.propertyDeclarationId);
updatePropertyAliasDeclarationRecursivelyStatement.write(
@@ -737,12 +737,21 @@ private:
}
}
void checkAliasPropertyDeclarationCycles(const std::vector<AliasPropertyDeclaration> &aliasDeclarations)
{
for (const auto &aliasDeclaration : aliasDeclarations)
checkForAliasChainCycle(aliasDeclaration.propertyDeclarationId);
}
void linkAliases(const std::vector<AliasPropertyDeclaration> &insertedAliasPropertyDeclarations,
const std::vector<AliasPropertyDeclaration> &updatedAliasPropertyDeclarations)
{
linkAliasPropertyDeclarationAliasIds(insertedAliasPropertyDeclarations);
linkAliasPropertyDeclarationAliasIds(updatedAliasPropertyDeclarations);
checkAliasPropertyDeclarationCycles(insertedAliasPropertyDeclarations);
checkAliasPropertyDeclarationCycles(updatedAliasPropertyDeclarations);
updateAliasPropertyDeclarationValues(insertedAliasPropertyDeclarations);
updateAliasPropertyDeclarationValues(updatedAliasPropertyDeclarations);
}
@@ -1183,7 +1192,7 @@ private:
synchronizeEnumerationDeclarations(typeId, type.enumerationDeclarations);
}
void checkForPrototypeChainCycle(TypeId typeId)
void checkForPrototypeChainCycle(TypeId typeId) const
{
auto callback = [=](long long currentTypeId) {
if (typeId == TypeId{currentTypeId})
@@ -1195,6 +1204,19 @@ private:
selectTypeIdsForPrototypeChainIdStatement.readCallback(callback, &typeId);
}
void checkForAliasChainCycle(PropertyDeclarationId propertyDeclarationId) const
{
auto callback = [=](long long currentPropertyDeclarationId) {
if (propertyDeclarationId == PropertyDeclarationId{currentPropertyDeclarationId})
throw AliasChainCycle{};
return Sqlite::CallbackControl::Continue;
};
selectPropertyDeclarationIdsForAliasChainStatement.readCallback(callback,
&propertyDeclarationId);
}
void syncPrototypes(Storage::Type &type)
{
if (Utils::visit([](auto &&typeName) -> bool { return typeName.name.isEmpty(); },
@@ -2015,6 +2037,16 @@ public:
"UPDATE propertyDeclarations SET aliasPropertyDeclarationId=NULL WHERE "
"propertyDeclarationId=?1",
database};
mutable ReadStatement<1> selectPropertyDeclarationIdsForAliasChainStatement{
"WITH RECURSIVE "
" properties(propertyDeclarationId) AS ( "
" SELECT aliasPropertyDeclarationId FROM propertyDeclarations WHERE "
" propertyDeclarationId=?1 "
" UNION ALL "
" SELECT aliasPropertyDeclarationId FROM propertyDeclarations JOIN properties "
" USING(propertyDeclarationId)) "
"SELECT propertyDeclarationId FROM properties",
database};
};
} // namespace QmlDesigner

View File

@@ -95,4 +95,10 @@ public:
const char *what() const noexcept override { return "There is a prototype chain cycle!"; }
};
class AliasChainCycle : std::exception
{
public:
const char *what() const noexcept override { return "There is a prototype chain cycle!"; }
};
} // namespace QmlDesigner

View File

@@ -3271,4 +3271,27 @@ TEST_F(ProjectStorageSlowTest, UpdateAliasesAfterChangePropertyToAlias)
"objects"))))));
}
TEST_F(ProjectStorageSlowTest, CheckForProtoTypeCycle)
{
Storage::Types types{createTypesWithRecursiveAliases()};
types[1].propertyDeclarations.clear();
types[1].propertyDeclarations.push_back(
Storage::PropertyDeclaration{"objects", Storage::ExportedType{"AliasItem2"}, "objects"});
ASSERT_THROW(storage.synchronizeTypes(types,
{sourceId1, sourceId2, sourceId3, sourceId4, sourceId5}),
QmlDesigner::AliasChainCycle);
}
TEST_F(ProjectStorageSlowTest, CheckForProtoTypeCycleAfterUpdate)
{
Storage::Types types{createTypesWithRecursiveAliases()};
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4, sourceId5});
types[1].propertyDeclarations.clear();
types[1].propertyDeclarations.push_back(
Storage::PropertyDeclaration{"objects", Storage::ExportedType{"AliasItem2"}, "objects"});
ASSERT_THROW(storage.synchronizeTypes({types[1]}, {sourceId2}), QmlDesigner::AliasChainCycle);
}
} // namespace