diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 4d6cb62394..cb2bb1ad22 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -30,6 +30,7 @@ Generate( ) target_link_libraries(backend_utils_autogen nxtcpp) target_include_directories(backend_utils_autogen PUBLIC ${GENERATED_DIR}) +target_include_directories(backend_utils_autogen PRIVATE ${SRC_DIR}) function(GenerateProcTable backend) Generate( @@ -342,6 +343,10 @@ list(APPEND BACKEND_SOURCES ${BACKEND_DIR}/CommandBufferStateTracker.h ${BACKEND_DIR}/Device.cpp ${BACKEND_DIR}/Device.h + ${BACKEND_DIR}/Error.cpp + ${BACKEND_DIR}/Error.h + ${BACKEND_DIR}/ErrorData.cpp + ${BACKEND_DIR}/ErrorData.h ${BACKEND_DIR}/Forward.h ${BACKEND_DIR}/InputState.cpp ${BACKEND_DIR}/InputState.h diff --git a/src/backend/Error.cpp b/src/backend/Error.cpp new file mode 100644 index 0000000000..150f5c70c3 --- /dev/null +++ b/src/backend/Error.cpp @@ -0,0 +1,31 @@ +// Copyright 2018 The NXT Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "backend/Error.h" + +#include "backend/ErrorData.h" + +namespace backend { + + ErrorData* MakeError(const char* message, const char* file, const char* function, int line) { + ErrorData* error = new ErrorData(message); + error->AppendBacktrace(file, function, line); + return error; + } + + void AppendBacktrace(ErrorData* error, const char* file, const char* function, int line) { + error->AppendBacktrace(file, function, line); + } + +} // namespace backend diff --git a/src/backend/Error.h b/src/backend/Error.h new file mode 100644 index 0000000000..9db8358c93 --- /dev/null +++ b/src/backend/Error.h @@ -0,0 +1,83 @@ +// Copyright 2018 The NXT Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BACKEND_ERROR_H_ +#define BACKEND_ERROR_H_ + +#include "common/Result.h" + +namespace backend { + + // This is the content of an error value for MaybeError or ResultOrError, split off to its own + // file to avoid having all files including headers like and + class ErrorData; + + // MaybeError and ResultOrError are meant to be used as return value for function that are not + // expected to, but might fail. The handling of error is potentially much slower than successes. + using MaybeError = Result; + + template + using ResultOrError = Result; + + // Returning a success is done like so: + // return {}; // for Error + // return SomethingOfTypeT; // for ResultOrError + // + // Returning an error is done via: + // NXT_RETURN_ERROR("My error message"); +#define NXT_RETURN_ERROR(EXPR) return MakeError(EXPR, __FILE__, __func__, __LINE__) + +#define NXT_CONCAT1(x, y) x##y +#define NXT_CONCAT2(x, y) NXT_CONCAT1(x, y) +#define NXT_LOCAL_VAR NXT_CONCAT2(_localVar, __LINE__) + + // When Errors aren't handled explicitly, calls to functions returning errors should be + // wrapped in an NXT_TRY. It will return the error if any, otherwise keep executing + // the current function. +#define NXT_TRY(EXPR) \ + { \ + auto NXT_LOCAL_VAR = EXPR; \ + if (NXT_UNLIKELY(NXT_LOCAL_VAR.IsError())) { \ + ErrorData* error = NXT_LOCAL_VAR.AcquireError(); \ + AppendBacktrace(error, __FILE__, __func__, __LINE__); \ + return {error}; \ + } \ + } \ + for (;;) \ + break + + // NXT_TRY_ASSIGN is the same as NXT_TRY for ResultOrError and assigns the success value, if + // any, to VAR. +#define NXT_TRY_ASSIGN(VAR, EXPR) \ + { \ + auto NXT_LOCAL_VAR = EXPR; \ + if (NXT_UNLIKELY(NXT_LOCAL_VAR.IsError())) { \ + ErrorData* error = NXT_LOCAL_VAR.AcquireError(); \ + AppendBacktrace(error, __FILE__, __func__, __LINE__); \ + return {error}; \ + } \ + VAR = NXT_LOCAL_VAR.AcquireSuccess(); \ + } \ + for (;;) \ + break + + // Implementation detail of NXT_TRY and NXT_TRY_ASSIGN's adding to the Error's backtrace. + void AppendBacktrace(ErrorData* error, const char* file, const char* function, int line); + + // Implementation detail of NXT_RETURN_ERROR + ErrorData* MakeError(const char* message, const char* file, const char* function, int line); + +} // namespace backend + +#endif // BACKEND_ERROR_H_ diff --git a/src/backend/ErrorData.cpp b/src/backend/ErrorData.cpp new file mode 100644 index 0000000000..e39cb75e9b --- /dev/null +++ b/src/backend/ErrorData.cpp @@ -0,0 +1,41 @@ +// Copyright 2018 The NXT Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "backend/ErrorData.h" + +namespace backend { + + ErrorData::ErrorData() = default; + + ErrorData::ErrorData(std::string message) : mMessage(std::move(message)) { + } + + void ErrorData::AppendBacktrace(const char* file, const char* function, int line) { + BacktraceRecord record; + record.file = file; + record.function = function; + record.line = line; + + mBacktrace.push_back(std::move(record)); + } + + const std::string& ErrorData::GetMessage() const { + return mMessage; + } + + const std::vector& ErrorData::GetBacktrace() const { + return mBacktrace; + } + +} // namespace backend diff --git a/src/backend/ErrorData.h b/src/backend/ErrorData.h new file mode 100644 index 0000000000..ca91587da3 --- /dev/null +++ b/src/backend/ErrorData.h @@ -0,0 +1,45 @@ +// Copyright 2018 The NXT Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BACKEND_ERRORDATA_H_ +#define BACKEND_ERRORDATA_H_ + +#include +#include + +namespace backend { + + class ErrorData { + public: + ErrorData(); + ErrorData(std::string message); + + struct BacktraceRecord { + const char* file; + const char* function; + int line; + }; + void AppendBacktrace(const char* file, const char* function, int line); + + const std::string& GetMessage() const; + const std::vector& GetBacktrace() const; + + private: + std::string mMessage; + std::vector mBacktrace; + }; + +} // namespace backend + +#endif // BACKEND_ERRORDATA_H_ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 4a17398997..1e2dace566 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -33,6 +33,7 @@ list(APPEND UNITTEST_SOURCES ${UNITTESTS_DIR}/BitSetIteratorTests.cpp ${UNITTESTS_DIR}/CommandAllocatorTests.cpp ${UNITTESTS_DIR}/EnumClassBitmasksTests.cpp + ${UNITTESTS_DIR}/ErrorTests.cpp ${UNITTESTS_DIR}/MathTests.cpp ${UNITTESTS_DIR}/ObjectBaseTests.cpp ${UNITTESTS_DIR}/PerStageTests.cpp diff --git a/src/tests/unittests/ErrorTests.cpp b/src/tests/unittests/ErrorTests.cpp new file mode 100644 index 0000000000..7320ad9192 --- /dev/null +++ b/src/tests/unittests/ErrorTests.cpp @@ -0,0 +1,269 @@ +// Copyright 2018 The NXT Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "backend/Error.h" +#include "backend/ErrorData.h" + +using namespace backend; + +namespace { + +int dummySuccess = 0xbeef; +const char* dummyErrorMessage = "I am an error message :3"; + +// Check returning a success MaybeError with {}; +TEST(ErrorTests, Error_Success) { + auto ReturnSuccess = []() -> MaybeError { + return {}; + }; + + MaybeError result = ReturnSuccess(); + ASSERT_TRUE(result.IsSuccess()); +} + +// Check returning an error MaybeError with NXT_RETURN_ERROR +TEST(ErrorTests, Error_Error) { + auto ReturnError = []() -> MaybeError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + MaybeError result = ReturnError(); + ASSERT_TRUE(result.IsError()); + + ErrorData* errorData = result.AcquireError(); + ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage); + delete errorData; +} + +// Check returning a success ResultOrError with an implicit conversion +TEST(ErrorTests, ResultOrError_Success) { + auto ReturnSuccess = []() -> ResultOrError { + return &dummySuccess; + }; + + ResultOrError result = ReturnSuccess(); + ASSERT_TRUE(result.IsSuccess()); + ASSERT_EQ(result.AcquireSuccess(), &dummySuccess); +} + +// Check returning an error ResultOrError with NXT_RETURN_ERROR +TEST(ErrorTests, ResultOrError_Error) { + auto ReturnError = []() -> ResultOrError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + ResultOrError result = ReturnError(); + ASSERT_TRUE(result.IsError()); + + ErrorData* errorData = result.AcquireError(); + ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage); + delete errorData; +} + +// Check NXT_TRY handles successes correctly. +TEST(ErrorTests, TRY_Success) { + auto ReturnSuccess = []() -> MaybeError { + return {}; + }; + + // We need to check that NXT_TRY doesn't return on successes + bool tryReturned = true; + + auto Try = [ReturnSuccess, &tryReturned]() -> MaybeError { + NXT_TRY(ReturnSuccess()); + tryReturned = false; + return {}; + }; + + MaybeError result = Try(); + ASSERT_TRUE(result.IsSuccess()); + ASSERT_FALSE(tryReturned); +} + +// Check NXT_TRY handles errors correctly. +TEST(ErrorTests, TRY_Error) { + auto ReturnError = []() -> MaybeError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + auto Try = [ReturnError]() -> MaybeError { + NXT_TRY(ReturnError()); + // NXT_TRY should return before this point + EXPECT_FALSE(true); + return {}; + }; + + MaybeError result = Try(); + ASSERT_TRUE(result.IsError()); + + ErrorData* errorData = result.AcquireError(); + ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage); + delete errorData; +} + +// Check NXT_TRY adds to the backtrace. +TEST(ErrorTests, TRY_AddsToBacktrace) { + auto ReturnError = []() -> MaybeError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + auto SingleTry = [ReturnError]() -> MaybeError { + NXT_TRY(ReturnError()); + return {}; + }; + + auto DoubleTry = [SingleTry]() -> MaybeError { + NXT_TRY(SingleTry()); + return {}; + }; + + MaybeError singleResult = SingleTry(); + ASSERT_TRUE(singleResult.IsError()); + + MaybeError doubleResult = DoubleTry(); + ASSERT_TRUE(doubleResult.IsError()); + + ErrorData* singleData = singleResult.AcquireError(); + ErrorData* doubleData = doubleResult.AcquireError(); + + ASSERT_EQ(singleData->GetBacktrace().size() + 1, doubleData->GetBacktrace().size()); + + delete singleData; + delete doubleData; +} + +// Check NXT_TRY_ASSIGN handles successes correctly. +TEST(ErrorTests, TRY_RESULT_Success) { + auto ReturnSuccess = []() -> ResultOrError { + return &dummySuccess; + }; + + // We need to check that NXT_TRY doesn't return on successes + bool tryReturned = true; + + auto Try = [ReturnSuccess, &tryReturned]() -> ResultOrError { + int* result = nullptr; + NXT_TRY_ASSIGN(result, ReturnSuccess()); + tryReturned = false; + + EXPECT_EQ(result, &dummySuccess); + return result; + }; + + ResultOrError result = Try(); + ASSERT_TRUE(result.IsSuccess()); + ASSERT_FALSE(tryReturned); + ASSERT_EQ(result.AcquireSuccess(), &dummySuccess); +} + +// Check NXT_TRY_ASSIGN handles errors correctly. +TEST(ErrorTests, TRY_RESULT_Error) { + auto ReturnError = []() -> ResultOrError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + auto Try = [ReturnError]() -> ResultOrError { + int* result = nullptr; + NXT_TRY_ASSIGN(result, ReturnError()); + (void) result; + + // NXT_TRY should return before this point + EXPECT_FALSE(true); + return &dummySuccess; + }; + + ResultOrError result = Try(); + ASSERT_TRUE(result.IsError()); + + ErrorData* errorData = result.AcquireError(); + ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage); + delete errorData; +} + +// Check NXT_TRY_ASSIGN adds to the backtrace. +TEST(ErrorTests, TRY_RESULT_AddsToBacktrace) { + auto ReturnError = []() -> ResultOrError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + auto SingleTry = [ReturnError]() -> ResultOrError { + NXT_TRY(ReturnError()); + return &dummySuccess; + }; + + auto DoubleTry = [SingleTry]() -> ResultOrError { + NXT_TRY(SingleTry()); + return &dummySuccess; + }; + + ResultOrError singleResult = SingleTry(); + ASSERT_TRUE(singleResult.IsError()); + + ResultOrError doubleResult = DoubleTry(); + ASSERT_TRUE(doubleResult.IsError()); + + ErrorData* singleData = singleResult.AcquireError(); + ErrorData* doubleData = doubleResult.AcquireError(); + + ASSERT_EQ(singleData->GetBacktrace().size() + 1, doubleData->GetBacktrace().size()); + + delete singleData; + delete doubleData; +} + +// Check a ResultOrError can be NXT_TRY_ASSIGNED in a function that returns an Error +TEST(ErrorTests, TRY_RESULT_ConversionToError) { + auto ReturnError = []() -> ResultOrError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + auto Try = [ReturnError]() -> MaybeError { + int* result = nullptr; + NXT_TRY_ASSIGN(result, ReturnError()); + (void) result; + + return {}; + }; + + MaybeError result = Try(); + ASSERT_TRUE(result.IsError()); + + ErrorData* errorData = result.AcquireError(); + ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage); + delete errorData; +} + +// Check a MaybeError can be NXT_TRIED in a function that returns an ResultOrError +// Check NXT_TRY handles errors correctly. +TEST(ErrorTests, TRY_ConversionToErrorOrResult) { + auto ReturnError = []() -> MaybeError { + NXT_RETURN_ERROR(dummyErrorMessage); + }; + + auto Try = [ReturnError]() -> ResultOrError{ + NXT_TRY(ReturnError()); + return &dummySuccess; + }; + + ResultOrError result = Try(); + ASSERT_TRUE(result.IsError()); + + ErrorData* errorData = result.AcquireError(); + ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage); + delete errorData; +} + +} // anonymous namespace