diff --git a/apps/components_tests/fx/lexer.cpp b/apps/components_tests/fx/lexer.cpp index 976f88d7a3..f8996118da 100644 --- a/apps/components_tests/fx/lexer.cpp +++ b/apps/components_tests/fx/lexer.cpp @@ -135,6 +135,17 @@ namespace EXPECT_EQ(std::get(token).value, 123); } + TEST(LexerTest, float_suffix_should_be_float) + { + Lexer lexer(R"( + 123f + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_FLOAT_EQ(std::get(token).value, 123.f); + } + TEST(LexerTest, simple_string) { Lexer lexer(R"( diff --git a/apps/components_tests/fx/technique.cpp b/apps/components_tests/fx/technique.cpp index b04ff5c52a..63d65ef95f 100644 --- a/apps/components_tests/fx/technique.cpp +++ b/apps/components_tests/fx/technique.cpp @@ -90,6 +90,56 @@ namespace technique { passes = main; } )" }; + constexpr VFS::Path::NormalizedView invalidNumberInfPath("shaders/invalid_number_inf.omwfx"); + + TestingOpenMW::VFSTestFile invalidNumberInf{ R"( + uniform_vec4 uVec4 { + step = inf; + } + fragment main { } + technique { passes = main; } +)" }; + + constexpr VFS::Path::NormalizedView invalidNumberNegativeInfPath("shaders/invalid_number_negative_inf.omwfx"); + + TestingOpenMW::VFSTestFile invalidNumberNegativeInf{ R"( + uniform_vec4 uVec4 { + step = -inf; + } + fragment main { } + technique { passes = main; } +)" }; + + constexpr VFS::Path::NormalizedView invalidNumberUnsignedLongPath("shaders/invalid_number_ulong.omwfx"); + + TestingOpenMW::VFSTestFile invalidNumberUnsignedLong{ R"( + uniform_vec4 uVec4 { + step = 18446744073709551615; + } + fragment main { } + technique { passes = main; } +)" }; + + constexpr VFS::Path::NormalizedView invalidNumberHexFloatPath("shaders/invalid_number_hex.omwfx"); + + TestingOpenMW::VFSTestFile invalidNumberHexFloat{ R"( + uniform_vec4 uVec4 { + step = 0x1.fffffep+12; + } + fragment main { } + technique { passes = main; } +)" }; + + constexpr VFS::Path::NormalizedView invalidNumberDoublePath("shaders/invalid_number_double.omwfx"); + + TestingOpenMW::VFSTestFile invalidNumberDouble{ R"( + uniform_vec4 uVec4 { + step = 1.79769e+50; + } + fragment main { } + technique { passes = main; } +)" }; + using namespace testing; using namespace Fx; @@ -106,6 +156,11 @@ namespace { uniformPropertiesPath, &uniformProperties }, { missingSamplerSourcePath, &missingSamplerSource }, { repeatedSharedBlockPath, &repeatedSharedBlock }, + { invalidNumberInfPath, &invalidNumberInf }, + { invalidNumberNegativeInfPath, &invalidNumberNegativeInf }, + { invalidNumberUnsignedLongPath, &invalidNumberUnsignedLong }, + { invalidNumberHexFloatPath, &invalidNumberHexFloat }, + { invalidNumberDoublePath, &invalidNumberDouble }, })) , mImageManager(mVFS.get(), 0) { @@ -117,6 +172,17 @@ namespace *mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true); mTechnique->compile(); } + + void expectFailure(const std::string& name, std::string_view errorString) + { + internal::CaptureStdout(); + + compile(name); + + std::string output = internal::GetCapturedStdout(); + Log(Debug::Error) << output; + EXPECT_THAT(output, HasSubstr(errorString)); + } }; TEST_F(TechniqueTest, technique_properties) @@ -183,23 +249,24 @@ namespace TEST_F(TechniqueTest, fail_with_missing_source_for_sampler) { - internal::CaptureStdout(); - - compile("missing_sampler_source"); - - std::string output = internal::GetCapturedStdout(); - Log(Debug::Error) << output; - EXPECT_THAT(output, HasSubstr("sampler_1d 'mysampler1d' requires a filename")); + expectFailure("missing_sampler_source", "sampler_1d 'mysampler1d' requires a filename"); } TEST_F(TechniqueTest, fail_with_repeated_shared_block) { - internal::CaptureStdout(); + expectFailure("repeated_shared_block", "repeated 'shared' block"); + } - compile("repeated_shared_block"); + TEST_F(TechniqueTest, fail_with_invalid_float) + { + expectFailure("invalid_number_inf", "expected float value"); + expectFailure("invalid_number_negative_inf", "expected float value"); + expectFailure("invalid_number_hex", "expected float value"); + } - std::string output = internal::GetCapturedStdout(); - Log(Debug::Error) << output; - EXPECT_THAT(output, HasSubstr("repeated 'shared' block")); + TEST_F(TechniqueTest, fail_with_out_of_range) + { + expectFailure("invalid_number_ulong", "number out of range"); + expectFailure("invalid_number_double", "number out of range"); } } diff --git a/components/fx/lexer.cpp b/components/fx/lexer.cpp index 07d7f060a4..43ce17717f 100644 --- a/components/fx/lexer.cpp +++ b/components/fx/lexer.cpp @@ -1,13 +1,11 @@ #include "lexer.hpp" #include +#include +#include #include -#ifndef __cpp_lib_to_chars -#include -#else -#include -#endif +#include namespace Fx { @@ -294,23 +292,42 @@ namespace Fx Token Lexer::scanNumber() { double buffer; -#ifndef __cpp_lib_to_chars char* endPtr = nullptr; buffer = std::strtod(mHead, &endPtr); if (endPtr == nullptr || endPtr == mHead) -#else - const auto [endPtr, ec] = std::from_chars(mHead, mTail, buffer); - if (ec != std::errc()) -#endif error("critical error while parsing number"); + bool isDefinitelyAFloat = false; + // GLSL allows floats to end on f/F + if (endPtr != mTail && Misc::StringUtils::toLower(*endPtr) == 'f') + { + isDefinitelyAFloat = true; + ++endPtr; + } + std::string_view literal(mHead, endPtr); mHead = endPtr; - if (literal.find('.') != std::string_view::npos) - return Float{ static_cast(buffer) }; + // Disallow -inf, -nan, and values that cannot be represented as doubles + if (!std::isfinite(buffer)) + return Literal{ literal }; + // Disallow hex notation (not allowed in GLSL so confusing to partially allow) + if (Misc::StringUtils::ciStartsWith(literal, "0x") || Misc::StringUtils::ciStartsWith(literal, "-0x")) + return Literal{ literal }; - return Integer{ static_cast(buffer) }; + constexpr std::string_view floatCharacters = ".eE"; + if (!isDefinitelyAFloat && literal.find_first_of(floatCharacters) == std::string_view::npos) + { + // This is supposed to be an int, but that doesn't mean it can fit in one + int intValue = static_cast(buffer); + if (intValue != buffer) + error("number out of range"); + return Integer{ intValue }; + } + float floatValue = static_cast(buffer); + if (!std::isfinite(floatValue)) + error("number out of range"); + return Float{ floatValue }; } } } diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index ac55a16b6f..945a3cf1b1 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -758,7 +758,7 @@ namespace Fx if (named) { - expect("name is required for preceeding block decleration"); + expect("name is required for preceeding block declaration"); mBlockName = std::get(mToken).value;