diff --git a/flang/include/flang/ISO_Fortran_binding.h b/flang/include/flang/ISO_Fortran_binding.h --- a/flang/include/flang/ISO_Fortran_binding.h +++ b/flang/include/flang/ISO_Fortran_binding.h @@ -84,18 +84,20 @@ #define CFI_TYPE_LAST CFI_type_char32_t #define CFI_type_other (-1) // must be negative -/* Error code macros */ +/* Error code macros - skip some of the small values to avoid conflicts with + * other status codes mandated by the standard, e.g. those returned by + * GET_ENVIRONMENT_VARIABLE (16.9.84) */ #define CFI_SUCCESS 0 /* must be zero */ -#define CFI_ERROR_BASE_ADDR_NULL 1 -#define CFI_ERROR_BASE_ADDR_NOT_NULL 2 -#define CFI_INVALID_ELEM_LEN 3 -#define CFI_INVALID_RANK 4 -#define CFI_INVALID_TYPE 5 -#define CFI_INVALID_ATTRIBUTE 6 -#define CFI_INVALID_EXTENT 7 -#define CFI_INVALID_DESCRIPTOR 8 -#define CFI_ERROR_MEM_ALLOCATION 9 -#define CFI_ERROR_OUT_OF_BOUNDS 10 +#define CFI_ERROR_BASE_ADDR_NULL 11 +#define CFI_ERROR_BASE_ADDR_NOT_NULL 12 +#define CFI_INVALID_ELEM_LEN 13 +#define CFI_INVALID_RANK 14 +#define CFI_INVALID_TYPE 15 +#define CFI_INVALID_ATTRIBUTE 16 +#define CFI_INVALID_EXTENT 17 +#define CFI_INVALID_DESCRIPTOR 18 +#define CFI_ERROR_MEM_ALLOCATION 19 +#define CFI_ERROR_OUT_OF_BOUNDS 20 /* 18.5.2 per-dimension information */ typedef struct CFI_dim_t { diff --git a/flang/include/flang/Runtime/command.h b/flang/include/flang/Runtime/command.h --- a/flang/include/flang/Runtime/command.h +++ b/flang/include/flang/Runtime/command.h @@ -44,7 +44,8 @@ // Returns a STATUS as described in the standard. std::int32_t RTNAME(EnvVariableValue)(const Descriptor &name, const Descriptor *value = nullptr, bool trim_name = true, - const Descriptor *errmsg = nullptr); + const Descriptor *errmsg = nullptr, const char *sourceFile = nullptr, + int line = 0); // Try to get the significant length of the environment variable specified by // NAME. Returns 0 if it doesn't manage. diff --git a/flang/include/flang/Runtime/magic-numbers.h b/flang/include/flang/Runtime/magic-numbers.h --- a/flang/include/flang/Runtime/magic-numbers.h +++ b/flang/include/flang/Runtime/magic-numbers.h @@ -46,4 +46,10 @@ #define FORTRAN_RUNTIME_STAT_INVALID_ARG_NUMBER 107 #define FORTRAN_RUNTIME_STAT_MISSING_ARG 108 #define FORTRAN_RUNTIME_STAT_VALUE_TOO_SHORT -1 + +#if 0 +Status codes for GET_ENVIRONMENT_VARIABLE. Values mandated by the standard. +#endif +#define FORTRAN_RUNTIME_STAT_MISSING_ENV_VAR 1 +#define FORTRAN_RUNTIME_STAT_ENV_VARS_UNSUPPORTED 2 #endif diff --git a/flang/runtime/command.cpp b/flang/runtime/command.cpp --- a/flang/runtime/command.cpp +++ b/flang/runtime/command.cpp @@ -24,9 +24,9 @@ return 0; } -// Returns the length of the \p n'th argument. Assumes \p n is valid. -static std::int64_t ArgumentLength(std::int32_t n) { - std::size_t length{std::strlen(executionEnvironment.argv[n])}; +// Returns the length of the \p string. Assumes \p string is valid. +static std::int64_t StringLength(const char *string) { + std::size_t length{std::strlen(string)}; if constexpr (sizeof(std::size_t) <= sizeof(std::int64_t)) { return static_cast(length); } else { @@ -37,11 +37,12 @@ } std::int64_t RTNAME(ArgumentLength)(std::int32_t n) { - if (n < 0 || n >= executionEnvironment.argc) { + if (n < 0 || n >= executionEnvironment.argc || + !executionEnvironment.argv[n]) { return 0; } - return ArgumentLength(n); + return StringLength(executionEnvironment.argv[n]); } static bool IsValidCharDescriptor(const Descriptor *value) { @@ -54,6 +55,20 @@ std::memset(value->OffsetElement(), ' ', value->ElementBytes()); } +static std::int32_t CopyToDescriptor(const Descriptor &value, + const char *rawValue, std::int64_t rawValueLength, + const Descriptor *errmsg) { + std::int64_t toCopy{std::min( + rawValueLength, static_cast(value.ElementBytes()))}; + std::memcpy(value.OffsetElement(), rawValue, toCopy); + + if (rawValueLength > toCopy) { + return ToErrmsg(errmsg, StatValueTooShort); + } + + return StatOk; +} + std::int32_t RTNAME(ArgumentValue)( std::int32_t n, const Descriptor *value, const Descriptor *errmsg) { if (IsValidCharDescriptor(value)) { @@ -65,18 +80,13 @@ } if (IsValidCharDescriptor(value)) { - std::int64_t argLen{ArgumentLength(n)}; + const char *arg{executionEnvironment.argv[n]}; + std::int64_t argLen{StringLength(arg)}; if (argLen <= 0) { return ToErrmsg(errmsg, StatMissingArgument); } - std::int64_t toCopy{ - std::min(argLen, static_cast(value->ElementBytes()))}; - std::memcpy(value->OffsetElement(), executionEnvironment.argv[n], toCopy); - - if (argLen > toCopy) { - return ToErrmsg(errmsg, StatValueTooShort); - } + return CopyToDescriptor(*value, arg, argLen, errmsg); } return StatOk; @@ -90,20 +100,45 @@ return s + 1; } -std::int64_t RTNAME(EnvVariableLength)( +static const char *GetEnvVariableValue( const Descriptor &name, bool trim_name, const char *sourceFile, int line) { std::size_t nameLength{ trim_name ? LengthWithoutTrailingSpaces(name) : name.ElementBytes()}; if (nameLength == 0) { - return 0; + return nullptr; } Terminator terminator{sourceFile, line}; const char *value{executionEnvironment.GetEnv( name.OffsetElement(), nameLength, terminator)}; + return value; +} + +std::int32_t RTNAME(EnvVariableValue)(const Descriptor &name, + const Descriptor *value, bool trim_name, const Descriptor *errmsg, + const char *sourceFile, int line) { + if (IsValidCharDescriptor(value)) { + FillWithSpaces(value); + } + + const char *rawValue{GetEnvVariableValue(name, trim_name, sourceFile, line)}; + if (!rawValue) { + return ToErrmsg(errmsg, StatMissingEnvVariable); + } + + if (IsValidCharDescriptor(value)) { + return CopyToDescriptor(*value, rawValue, StringLength(rawValue), errmsg); + } + + return StatOk; +} + +std::int64_t RTNAME(EnvVariableLength)( + const Descriptor &name, bool trim_name, const char *sourceFile, int line) { + const char *value{GetEnvVariableValue(name, trim_name, sourceFile, line)}; if (!value) { return 0; } - return std::strlen(value); + return StringLength(value); } } // namespace Fortran::runtime diff --git a/flang/runtime/stat.h b/flang/runtime/stat.h --- a/flang/runtime/stat.h +++ b/flang/runtime/stat.h @@ -39,6 +39,7 @@ StatFailedImage = FORTRAN_RUNTIME_STAT_FAILED_IMAGE, StatLocked = FORTRAN_RUNTIME_STAT_LOCKED, StatLockedOtherImage = FORTRAN_RUNTIME_STAT_LOCKED_OTHER_IMAGE, + StatMissingEnvVariable = FORTRAN_RUNTIME_STAT_MISSING_ENV_VAR, StatStoppedImage = FORTRAN_RUNTIME_STAT_STOPPED_IMAGE, StatUnlocked = FORTRAN_RUNTIME_STAT_UNLOCKED, StatUnlockedFailedImage = FORTRAN_RUNTIME_STAT_UNLOCKED_FAILED_IMAGE, diff --git a/flang/runtime/stat.cpp b/flang/runtime/stat.cpp --- a/flang/runtime/stat.cpp +++ b/flang/runtime/stat.cpp @@ -57,6 +57,9 @@ case StatValueTooShort: return "Value too short"; + case StatMissingEnvVariable: + return "Missing environment variable"; + default: return nullptr; } diff --git a/flang/unittests/Runtime/CommandTest.cpp b/flang/unittests/Runtime/CommandTest.cpp --- a/flang/unittests/Runtime/CommandTest.cpp +++ b/flang/unittests/Runtime/CommandTest.cpp @@ -53,17 +53,66 @@ const Descriptor *value, const std::string &expected) const { EXPECT_EQ(std::strncmp(value->OffsetElement(), expected.c_str(), value->ElementBytes()), - 0); + 0) + << "expected: " << expected << "\n" + << "value: " + << std::string{value->OffsetElement(), value->ElementBytes()}; } - void CheckArgumentValue(int n, const char *argv) const { + template + void CheckValue(RuntimeCall F, const char *expectedValue, + std::int32_t expectedStatus = 0, + const char *expectedErrMsg = "shouldn't change") const { OwningPtr value{CreateEmptyCharDescriptor()}; ASSERT_NE(value, nullptr); - std::string expected{GetPaddedStr(argv, value->ElementBytes())}; + OwningPtr errmsg{CharDescriptor(expectedErrMsg)}; - EXPECT_EQ(RTNAME(ArgumentValue)(n, value.get(), nullptr), 0); - CheckDescriptorEqStr(value.get(), expected); + std::string expectedValueStr{ + GetPaddedStr(expectedValue, value->ElementBytes())}; + + EXPECT_EQ(F(value.get(), errmsg.get()), expectedStatus); + CheckDescriptorEqStr(value.get(), expectedValueStr); + CheckDescriptorEqStr(errmsg.get(), expectedErrMsg); + } + + void CheckArgumentValue(const char *expectedValue, int n) const { + SCOPED_TRACE(n); + SCOPED_TRACE("Checking argument:"); + CheckValue( + [&](const Descriptor *value, const Descriptor *errmsg) { + return RTNAME(ArgumentValue)(n, value, errmsg); + }, + expectedValue); + } + + void CheckEnvVarValue( + const char *expectedValue, const char *name, bool trimName = true) const { + SCOPED_TRACE(name); + SCOPED_TRACE("Checking environment variable"); + CheckValue( + [&](const Descriptor *value, const Descriptor *errmsg) { + return RTNAME(EnvVariableValue)(*CharDescriptor(name), value, + trimName, errmsg, /*sourceFile=*/nullptr, /*line=*/0); + }, + expectedValue); + } + + void CheckMissingEnvVarValue(const char *name, bool trimName = true) const { + SCOPED_TRACE(name); + SCOPED_TRACE("Checking missing environment variable"); + + ASSERT_EQ(nullptr, std::getenv(name)) + << "Environment variable " << name << " not expected to exist"; + + OwningPtr nameDescriptor{CharDescriptor(name)}; + EXPECT_EQ(0, RTNAME(EnvVariableLength)(*nameDescriptor, trimName)); + CheckValue( + [&](const Descriptor *value, const Descriptor *errmsg) { + return RTNAME(EnvVariableValue)(*nameDescriptor, value, trimName, + errmsg, /*sourceFile=*/nullptr, /*line=*/0); + }, + "", 1, "Missing environment variable"); } void CheckMissingArgumentValue(int n, const char *errStr = nullptr) const { @@ -99,7 +148,7 @@ } TEST_F(ZeroArguments, ArgumentValue) { - CheckArgumentValue(0, commandOnlyArgv[0]); + CheckArgumentValue(commandOnlyArgv[0], 0); } static const char *oneArgArgv[]{"aProgram", "anArgumentOfLength20"}; @@ -118,8 +167,8 @@ } TEST_F(OneArgument, ArgumentValue) { - CheckArgumentValue(0, oneArgArgv[0]); - CheckArgumentValue(1, oneArgArgv[1]); + CheckArgumentValue(oneArgArgv[0], 0); + CheckArgumentValue(oneArgArgv[1], 1); } static const char *severalArgsArgv[]{ @@ -146,10 +195,10 @@ } TEST_F(SeveralArguments, ArgumentValue) { - CheckArgumentValue(0, severalArgsArgv[0]); - CheckArgumentValue(1, severalArgsArgv[1]); - CheckArgumentValue(3, severalArgsArgv[3]); - CheckArgumentValue(4, severalArgsArgv[4]); + CheckArgumentValue(severalArgsArgv[0], 0); + CheckArgumentValue(severalArgsArgv[1], 1); + CheckArgumentValue(severalArgsArgv[3], 3); + CheckArgumentValue(severalArgsArgv[4], 4); } TEST_F(SeveralArguments, NoArgumentValue) { @@ -192,6 +241,7 @@ protected: EnvironmentVariables() : CommandFixture(0, nullptr) { SetEnv("NAME", "VALUE"); + SetEnv("EMPTY", ""); } // If we have access to setenv, we can run some more fine-grained tests. @@ -211,23 +261,79 @@ bool canSetEnv{false}; }; -TEST_F(EnvironmentVariables, Length) { - EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("DOESNT_EXIST"))); +TEST_F(EnvironmentVariables, Nonexistent) { + CheckMissingEnvVarValue("DOESNT_EXIST"); - EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor(" "))); - EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor(""))); + CheckMissingEnvVarValue(" "); + CheckMissingEnvVarValue(""); +} +TEST_F(EnvironmentVariables, Basic) { // Test a variable that's expected to exist in the environment. char *path{std::getenv("PATH")}; auto expectedLen{static_cast(std::strlen(path))}; EXPECT_EQ(expectedLen, RTNAME(EnvVariableLength)(*CharDescriptor("PATH"))); +} +TEST_F(EnvironmentVariables, Trim) { if (EnableFineGrainedTests()) { - EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME"))); + EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME "))); + CheckEnvVarValue("VALUE", "NAME "); + } +} - EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME "))); - EXPECT_EQ(0, - RTNAME(EnvVariableLength)( - *CharDescriptor("NAME "), /*trim_name=*/false)); +TEST_F(EnvironmentVariables, NoTrim) { + if (EnableFineGrainedTests()) { + CheckMissingEnvVarValue("NAME ", /*trim_name=*/false); } } + +TEST_F(EnvironmentVariables, Empty) { + if (EnableFineGrainedTests()) { + EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("EMPTY"))); + CheckEnvVarValue("", "EMPTY"); + } +} + +TEST_F(EnvironmentVariables, NoValueOrErrmsg) { + ASSERT_EQ(std::getenv("DOESNT_EXIST"), nullptr) + << "Environment variable DOESNT_EXIST actually exists"; + EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("DOESNT_EXIST")), 1); + + if (EnableFineGrainedTests()) { + EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("NAME")), 0); + } +} + +TEST_F(EnvironmentVariables, ValueTooShort) { + if (EnableFineGrainedTests()) { + OwningPtr tooShort{CreateEmptyCharDescriptor<2>()}; + ASSERT_NE(tooShort, nullptr); + EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("NAME"), tooShort.get(), + /*trim_name=*/true, nullptr), + -1); + CheckDescriptorEqStr(tooShort.get(), "VALUE"); + + OwningPtr errMsg{CreateEmptyCharDescriptor()}; + ASSERT_NE(errMsg, nullptr); + + EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("NAME"), tooShort.get(), + /*trim_name=*/true, errMsg.get()), + -1); + + std::string expectedErrMsg{ + GetPaddedStr("Value too short", errMsg->ElementBytes())}; + CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); + } +} + +TEST_F(EnvironmentVariables, ErrMsgTooShort) { + ASSERT_EQ(std::getenv("DOESNT_EXIST"), nullptr) + << "Environment variable DOESNT_EXIST actually exists"; + + OwningPtr errMsg{CreateEmptyCharDescriptor<3>()}; + EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("DOESNT_EXIST"), nullptr, + /*trim_name=*/true, errMsg.get()), + 1); + CheckDescriptorEqStr(errMsg.get(), "Mis"); +}