Merge topic 'proper-command-nesting'

12f6e37eb7 cmListFileCache: Enforce proper nesting of flow control statements
67383725bd cm::optional: Add constructor delegation to nullopt_t constructor
0668120398 cm::optional: Fix move assignment

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !5401
This commit is contained in:
Brad King
2020-10-23 13:05:05 +00:00
committed by Kitware Robot
22 changed files with 261 additions and 114 deletions
+118
View File
@@ -30,6 +30,7 @@ struct cmListFileParser
bool ParseFunction(const char* name, long line);
bool AddArgument(cmListFileLexer_Token* token,
cmListFileArgument::Delimiter delim);
cm::optional<cmListFileContext> CheckNesting();
cmListFile* ListFile;
cmListFileBacktrace Backtrace;
cmMessenger* Messenger;
@@ -158,6 +159,17 @@ bool cmListFileParser::Parse()
return false;
}
}
// Check if all functions are nested properly.
if (auto badNesting = this->CheckNesting()) {
this->Messenger->IssueMessage(
MessageType::FATAL_ERROR,
"Flow control statements are not properly nested.",
this->Backtrace.Push(*badNesting));
cmSystemTools::SetFatalErrorOccured();
return false;
}
return true;
}
@@ -317,6 +329,112 @@ bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
return true;
}
namespace {
enum class NestingStateEnum
{
If,
Else,
While,
Foreach,
Function,
Macro,
};
struct NestingState
{
NestingStateEnum State;
cmListFileContext Context;
};
bool TopIs(std::vector<NestingState>& stack, NestingStateEnum state)
{
return !stack.empty() && stack.back().State == state;
}
}
cm::optional<cmListFileContext> cmListFileParser::CheckNesting()
{
std::vector<NestingState> stack;
for (auto const& func : this->ListFile->Functions) {
auto const& name = func.LowerCaseName();
if (name == "if") {
stack.push_back({
NestingStateEnum::If,
cmListFileContext::FromCommandContext(func, this->FileName),
});
} else if (name == "elseif") {
if (!TopIs(stack, NestingStateEnum::If)) {
return cmListFileContext::FromCommandContext(func, this->FileName);
}
stack.back() = {
NestingStateEnum::If,
cmListFileContext::FromCommandContext(func, this->FileName),
};
} else if (name == "else") {
if (!TopIs(stack, NestingStateEnum::If)) {
return cmListFileContext::FromCommandContext(func, this->FileName);
}
stack.back() = {
NestingStateEnum::Else,
cmListFileContext::FromCommandContext(func, this->FileName),
};
} else if (name == "endif") {
if (!TopIs(stack, NestingStateEnum::If) &&
!TopIs(stack, NestingStateEnum::Else)) {
return cmListFileContext::FromCommandContext(func, this->FileName);
}
stack.pop_back();
} else if (name == "while") {
stack.push_back({
NestingStateEnum::While,
cmListFileContext::FromCommandContext(func, this->FileName),
});
} else if (name == "endwhile") {
if (!TopIs(stack, NestingStateEnum::While)) {
return cmListFileContext::FromCommandContext(func, this->FileName);
}
stack.pop_back();
} else if (name == "foreach") {
stack.push_back({
NestingStateEnum::Foreach,
cmListFileContext::FromCommandContext(func, this->FileName),
});
} else if (name == "endforeach") {
if (!TopIs(stack, NestingStateEnum::Foreach)) {
return cmListFileContext::FromCommandContext(func, this->FileName);
}
stack.pop_back();
} else if (name == "function") {
stack.push_back({
NestingStateEnum::Function,
cmListFileContext::FromCommandContext(func, this->FileName),
});
} else if (name == "endfunction") {
if (!TopIs(stack, NestingStateEnum::Function)) {
return cmListFileContext::FromCommandContext(func, this->FileName);
}
stack.pop_back();
} else if (name == "macro") {
stack.push_back({
NestingStateEnum::Macro,
cmListFileContext::FromCommandContext(func, this->FileName),
});
} else if (name == "endmacro") {
if (!TopIs(stack, NestingStateEnum::Macro)) {
return cmListFileContext::FromCommandContext(func, this->FileName);
}
stack.pop_back();
}
}
if (!stack.empty()) {
return stack.back().Context;
}
return cm::nullopt;
}
// We hold either the bottom scope of a directory or a call/file context.
// Discriminate these cases via the parent pointer.
struct cmListFileBacktrace::Entry
+8
View File
@@ -89,6 +89,14 @@ public:
{
}
#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L)
cmListFileContext(const cmListFileContext& /*other*/) = default;
cmListFileContext(cmListFileContext&& /*other*/) = default;
cmListFileContext& operator=(const cmListFileContext& /*other*/) = default;
cmListFileContext& operator=(cmListFileContext&& /*other*/) = delete;
#endif
static cmListFileContext FromCommandContext(
cmCommandContext const& lfcc, std::string const& fileName,
cm::optional<std::string> deferId = {})
+28
View File
@@ -82,6 +82,18 @@ public:
int Value = 0;
};
class NoMoveAssignEventLogger : public EventLogger
{
public:
using EventLogger::EventLogger;
NoMoveAssignEventLogger(const NoMoveAssignEventLogger&) = default;
NoMoveAssignEventLogger(NoMoveAssignEventLogger&&) = default;
NoMoveAssignEventLogger& operator=(const NoMoveAssignEventLogger&) = default;
NoMoveAssignEventLogger& operator=(NoMoveAssignEventLogger&&) = delete;
};
#define ASSERT_TRUE(x) \
do { \
if (!(x)) { \
@@ -328,12 +340,28 @@ static bool testCopyAssign(std::vector<Event>& expected)
o1 = o4; // Intentionally duplicated to test assigning an empty optional to
// an empty optional
cm::optional<NoMoveAssignEventLogger> o5{ 1 };
auto const* v5 = &*o5;
const cm::optional<NoMoveAssignEventLogger> o6{ 2 };
auto const* v6 = &*o6;
o5 = std::move(o6);
const NoMoveAssignEventLogger e7{ 3 };
o5 = std::move(e7);
expected = {
{ Event::VALUE_CONSTRUCT, v2, nullptr, 4 },
{ Event::COPY_CONSTRUCT, v1, v2, 4 },
{ Event::VALUE_CONSTRUCT, v3, nullptr, 5 },
{ Event::COPY_ASSIGN, v1, v3, 5 },
{ Event::DESTRUCT, v1, nullptr, 5 },
{ Event::VALUE_CONSTRUCT, v5, nullptr, 1 },
{ Event::VALUE_CONSTRUCT, v6, nullptr, 2 },
{ Event::COPY_ASSIGN, v5, v6, 2 },
{ Event::VALUE_CONSTRUCT, &e7, nullptr, 3 },
{ Event::COPY_ASSIGN, v5, &e7, 3 },
{ Event::DESTRUCT, &e7, nullptr, 3 },
{ Event::DESTRUCT, v6, nullptr, 2 },
{ Event::DESTRUCT, v5, nullptr, 3 },
{ Event::DESTRUCT, v3, nullptr, 5 },
{ Event::DESTRUCT, v2, nullptr, 4 },
};
+16 -44
View File
@@ -1,68 +1,40 @@
message(STATUS "testname='${testname}'")
if(testname STREQUAL bad_else) # fail
file(WRITE "${dir}/${testname}.cmake"
"else()
")
function(do_end content)
file(WRITE "${dir}/${testname}.cmake" "${content}")
execute_process(COMMAND ${CMAKE_COMMAND} -P "${dir}/${testname}.cmake"
RESULT_VARIABLE rv)
if(NOT rv EQUAL 0)
message(FATAL_ERROR "${testname} failed")
endif()
endfunction()
if(testname STREQUAL bad_else) # fail
do_end("else()\n")
elseif(testname STREQUAL bad_elseif) # fail
file(WRITE "${dir}/${testname}.cmake"
"elseif()
")
execute_process(COMMAND ${CMAKE_COMMAND} -P "${dir}/${testname}.cmake"
RESULT_VARIABLE rv)
if(NOT rv EQUAL 0)
message(FATAL_ERROR "${testname} failed")
endif()
do_end("elseif()\n")
elseif(testname STREQUAL bad_endforeach) # fail
endforeach()
do_end("endforeach()\n")
elseif(testname STREQUAL bad_endfunction) # fail
endfunction()
do_end("endfunction()\n")
elseif(testname STREQUAL bad_endif) # fail
file(WRITE "${dir}/${testname}.cmake"
"cmake_minimum_required(VERSION 2.8)
endif()
")
execute_process(COMMAND ${CMAKE_COMMAND} -P "${dir}/${testname}.cmake"
RESULT_VARIABLE rv)
if(NOT rv EQUAL 0)
message(FATAL_ERROR "${testname} failed")
endif()
do_end("cmake_minimum_required(VERSION 2.8)\nendif()\n")
elseif(testname STREQUAL endif_low_min_version) # pass
file(WRITE "${dir}/${testname}.cmake"
"cmake_minimum_required(VERSION 1.2)
endif()
")
execute_process(COMMAND ${CMAKE_COMMAND} -P "${dir}/${testname}.cmake"
RESULT_VARIABLE rv)
if(NOT rv EQUAL 0)
message(FATAL_ERROR "${testname} failed")
endif()
elseif(testname STREQUAL endif_low_min_version) # fail
do_end("cmake_minimum_required(VERSION 1.2)\nendif()\n")
elseif(testname STREQUAL endif_no_min_version) # pass
file(WRITE "${dir}/${testname}.cmake"
"endif()
")
execute_process(COMMAND ${CMAKE_COMMAND} -P "${dir}/${testname}.cmake"
RESULT_VARIABLE rv)
if(NOT rv EQUAL 0)
message(FATAL_ERROR "${testname} failed")
endif()
elseif(testname STREQUAL endif_no_min_version) # fail
do_end("endif()\n")
elseif(testname STREQUAL bad_endmacro) # fail
endmacro()
do_end("endmacro()\n")
elseif(testname STREQUAL bad_endwhile) # fail
endwhile()
do_end("endwhile()\n")
else() # fail
message(FATAL_ERROR "testname='${testname}' - error: no such test in '${CMAKE_CURRENT_LIST_FILE}'")
@@ -1,8 +1,4 @@
^CMake Error in FunctionUnmatched.cmake:
A logical block opening on the line
.*/Tests/RunCMake/Syntax/FunctionUnmatched.cmake:[0-9]+ \(function\)
is not closed.
^CMake Error at FunctionUnmatched\.cmake:[0-9]+ \(function\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)$
@@ -1,8 +1,4 @@
^CMake Error at FunctionUnmatchedForeach.cmake:[0-9]+ \(f\):
A logical block opening on the line
.*/Tests/RunCMake/Syntax/FunctionUnmatchedForeach.cmake:[0-9]+ \(foreach\)
is not closed.
^CMake Error at FunctionUnmatchedForeach\.cmake:[0-9]+ \(endfunction\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)$
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
^CMake Error at ImproperNesting\.cmake:[0-9]+ \(endforeach\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)$
@@ -0,0 +1,7 @@
message(FATAL_ERROR "This should not happen")
foreach(i 1 2)
if(1)
endforeach()
endif()
endif()
@@ -1,8 +1,4 @@
^CMake Error in MacroUnmatched.cmake:
A logical block opening on the line
.*/Tests/RunCMake/Syntax/MacroUnmatched.cmake:[0-9]+ \(macro\)
is not closed.
^CMake Error at MacroUnmatched\.cmake:[0-9]+ \(macro\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)$
@@ -1,8 +1,4 @@
^CMake Error at MacroUnmatchedForeach.cmake:[0-9]+ \(m\):
A logical block opening on the line
.*/Tests/RunCMake/Syntax/MacroUnmatchedForeach.cmake:[0-9]+ \(foreach\)
is not closed.
^CMake Error at MacroUnmatchedForeach\.cmake:[0-9]+ \(endmacro\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)$
+1
View File
@@ -72,6 +72,7 @@ run_cmake(UnterminatedBrace2)
run_cmake(UnterminatedBracket0)
run_cmake(UnterminatedBracket1)
run_cmake(UnterminatedBracketComment)
run_cmake(ImproperNesting)
# Variable expansion tests
run_cmake(ExpandInAt)
@@ -1,4 +1,4 @@
CMake Error at duplicate-deep-else.cmake:[0-9]+ \(else\):
A duplicate ELSE command was found inside an IF block.
CMake Error at duplicate-deep-else\.cmake:[0-9]+ \(else\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMakeLists\.txt:[0-9]+ \(include\)
@@ -1,4 +1,4 @@
CMake Error at duplicate-else-after-elseif.cmake:[0-9]+ \(else\):
A duplicate ELSE command was found inside an IF block.
CMake Error at duplicate-else-after-elseif\.cmake:[0-9]+ \(else\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMakeLists\.txt:[0-9]+ \(include\)
+3 -3
View File
@@ -1,4 +1,4 @@
CMake Error at duplicate-else.cmake:[0-9]+ \(else\):
A duplicate ELSE command was found inside an IF block.
CMake Error at duplicate-else\.cmake:[0-9]+ \(else\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMakeLists\.txt:[0-9]+ \(include\)
@@ -1,4 +1,4 @@
CMake Error at misplaced-elseif.cmake:[0-9]+ \(elseif\):
An ELSEIF command was found after an ELSE command.
CMake Error at misplaced-elseif\.cmake:[0-9]+ \(elseif\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMakeLists\.txt:[0-9]+ \(include\)
+3 -4
View File
@@ -1,5 +1,4 @@
^CMake Error at EndAlone.cmake:1 \(endwhile\):
endwhile An ENDWHILE command was found outside of a proper WHILE ENDWHILE
structure. Or its arguments did not match the opening WHILE command.
^CMake Error at EndAlone\.cmake:[0-9]+ \(endwhile\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)$
+3 -4
View File
@@ -1,5 +1,4 @@
^CMake Error at EndAloneArgs.cmake:1 \(endwhile\):
endwhile An ENDWHILE command was found outside of a proper WHILE ENDWHILE
structure. Or its arguments did not match the opening WHILE command.
^CMake Error at EndAloneArgs\.cmake:[0-9]+ \(endwhile\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)$
+3 -7
View File
@@ -1,8 +1,4 @@
^CMake Error in EndMissing.cmake:
A logical block opening on the line
.*/Tests/RunCMake/while/EndMissing.cmake:1 \(while\)
is not closed.
^CMake Error at EndMissing\.cmake:[0-9]+ \(while\):
Flow control statements are not properly nested\.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)$
@@ -1,4 +1,11 @@
^CMake Error at MissingArgument.cmake:1 \(while\):
^CMake Error at MissingArgument\.cmake:[0-9]+ \(while\):
while called with incorrect number of arguments
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$
CMakeLists\.txt:[0-9]+ \(include\)
CMake Error at MissingArgument\.cmake:[0-9]+ \(endwhile\):
endwhile An ENDWHILE command was found outside of a proper WHILE ENDWHILE
structure\. Or its arguments did not match the opening WHILE command\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)$
@@ -1 +1,2 @@
while()
endwhile()
+35 -13
View File
@@ -68,16 +68,22 @@ public:
optional& operator=(nullopt_t) noexcept;
optional& operator=(const optional& other);
optional& operator=(optional&& other) noexcept;
template <
typename U = T,
typename = typename std::enable_if<
!std::is_same<typename std::decay<U>::type, cm::optional<T>>::value &&
std::is_constructible<T, U>::value && std::is_assignable<T&, U>::value &&
template <typename U = T>
typename std::enable_if<std::is_constructible<T, U&&>::value &&
std::is_assignable<T&, U&&>::value,
optional&>::type
operator=(optional<U>&& other) noexcept;
template <typename U = T>
typename std::enable_if<
!std::is_same<typename std::decay<U>::type, cm::optional<T>>::value &&
std::is_constructible<T, U&&>::value &&
std::is_assignable<T&, U&&>::value &&
(!std::is_scalar<T>::value ||
!std::is_same<typename std::decay<U>::type, T>::value)>::type>
optional& operator=(U&& v);
!std::is_same<typename std::decay<U>::type, T>::value),
optional&>::type
operator=(U&& v);
const T* operator->() const;
T* operator->();
@@ -134,19 +140,24 @@ optional<T> make_optional(Args&&... args)
template <typename T>
optional<T>::optional(nullopt_t) noexcept
: optional()
{
}
template <typename T>
optional<T>::optional(const optional& other)
{
*this = other;
if (other.has_value()) {
this->emplace(*other);
}
}
template <typename T>
optional<T>::optional(optional&& other) noexcept
{
*this = std::move(other);
if (other.has_value()) {
this->emplace(std::move(*other));
}
}
template <typename T>
@@ -192,7 +203,11 @@ optional<T>& optional<T>::operator=(const optional& other)
}
template <typename T>
optional<T>& optional<T>::operator=(optional&& other) noexcept
template <typename U>
typename std::enable_if<std::is_constructible<T, U&&>::value &&
std::is_assignable<T&, U&&>::value,
optional<T>&>::type
optional<T>::operator=(optional<U>&& other) noexcept
{
if (other.has_value()) {
if (this->has_value()) {
@@ -207,8 +222,15 @@ optional<T>& optional<T>::operator=(optional&& other) noexcept
}
template <typename T>
template <typename U, typename>
optional<T>& optional<T>::operator=(U&& v)
template <typename U>
typename std::enable_if<
!std::is_same<typename std::decay<U>::type, cm::optional<T>>::value &&
std::is_constructible<T, U&&>::value &&
std::is_assignable<T&, U&&>::value &&
(!std::is_scalar<T>::value ||
!std::is_same<typename std::decay<U>::type, T>::value),
optional<T>&>::type
optional<T>::operator=(U&& v)
{
if (this->has_value()) {
this->value() = v;