diff --git a/flang/runtime/command.cpp b/flang/runtime/command.cpp --- a/flang/runtime/command.cpp +++ b/flang/runtime/command.cpp @@ -8,6 +8,7 @@ #include "flang/Runtime/command.h" #include "environment.h" +#include "flang/Runtime/descriptor.h" #include namespace Fortran::runtime { @@ -20,11 +21,8 @@ return 0; } -std::int64_t RTNAME(ArgumentLength)(std::int32_t n) { - if (n < 0 || n >= executionEnvironment.argc) { - 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])}; if constexpr (sizeof(std::size_t) <= sizeof(std::int64_t)) { return static_cast(length); @@ -34,4 +32,49 @@ : static_cast(length); } } + +std::int64_t RTNAME(ArgumentLength)(std::int32_t n) { + if (n < 0 || n >= executionEnvironment.argc) { + return 0; + } + + return ArgumentLength(n); +} + +static bool IsValidCharDescriptor(const Descriptor *value) { + return value && value->IsAllocated() && + value->type() == TypeCode(TypeCategory::Character, 1) && + value->rank() == 0; +} + +static void FillWithSpaces(const Descriptor *value) { + std::memset(value->OffsetElement(), ' ', value->ElementBytes()); +} + +std::int32_t RTNAME(ArgumentValue)( + std::int32_t n, const Descriptor *value, const Descriptor *errmsg) { + if (IsValidCharDescriptor(value)) { + FillWithSpaces(value); + } + + if (n < 0 || n >= executionEnvironment.argc) { + return 1; + } + + if (IsValidCharDescriptor(value)) { + std::int64_t argLen{ArgumentLength(n)}; + if (argLen <= 0) { + return 2; + } + + std::int64_t toCopy{ + std::min(argLen, static_cast(value->ElementBytes()))}; + std::strncpy(value->OffsetElement(), executionEnvironment.argv[n], toCopy); + + if (argLen > toCopy) { + return -1; + } + } + return 0; +} } // namespace Fortran::runtime diff --git a/flang/test/Runtime/no-cpp-dep.c b/flang/test/Runtime/no-cpp-dep.c --- a/flang/test/Runtime/no-cpp-dep.c +++ b/flang/test/Runtime/no-cpp-dep.c @@ -22,12 +22,15 @@ void RTNAME(ProgramStart)(int, const char *[], const char *[]); int32_t RTNAME(ArgumentCount)(); +int32_t RTNAME(ArgumentValue)( + int32_t, const struct Descriptor *, const struct Descriptor *); int64_t RTNAME(ArgumentLength)(int32_t); int main() { double x = RTNAME(CpuTime)(); RTNAME(ProgramStart)(0, 0, 0); int32_t c = RTNAME(ArgumentCount)(); + int32_t v = RTNAME(ArgumentValue)(0, 0, 0); int32_t l = RTNAME(ArgumentLength)(0); return x + c + l; } 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 @@ -13,11 +13,55 @@ using namespace Fortran::runtime; +template +static OwningPtr CreateEmptyCharDescriptor() { + OwningPtr descriptor{Descriptor::Create( + sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)}; + if (descriptor->Allocate() != 0) { + return nullptr; + } + return descriptor; +} + class CommandFixture : public ::testing::Test { protected: CommandFixture(int argc, const char *argv[]) { RTNAME(ProgramStart)(argc, argv, {}); } + + std::string GetPaddedStr(const char *text, std::size_t len) const { + std::string res{text}; + assert(res.length() <= len && "No room to pad"); + res.append(len - res.length(), ' '); + return res; + } + + void CheckDescriptorEqStr( + const Descriptor *value, const std::string &expected) const { + EXPECT_EQ(std::strncmp(value->OffsetElement(), expected.c_str(), + value->ElementBytes()), + 0); + } + + void CheckArgumentValue(int n, const char *argv) const { + OwningPtr value{CreateEmptyCharDescriptor()}; + ASSERT_NE(value, nullptr); + + std::string expected{GetPaddedStr(argv, value->ElementBytes())}; + + EXPECT_EQ(RTNAME(ArgumentValue)(n, value.get(), nullptr), 0); + CheckDescriptorEqStr(value.get(), expected); + } + + void CheckMissingArgumentValue(int n) const { + OwningPtr value{CreateEmptyCharDescriptor()}; + ASSERT_NE(value, nullptr); + + EXPECT_GT(RTNAME(ArgumentValue)(n, value.get(), nullptr), 0); + + std::string spaces(value->ElementBytes(), ' '); + CheckDescriptorEqStr(value.get(), spaces); + } }; static const char *commandOnlyArgv[]{"aProgram"}; @@ -34,6 +78,10 @@ EXPECT_EQ(0, RTNAME(ArgumentLength)(1)); } +TEST_F(ZeroArguments, ArgumentValue) { + CheckArgumentValue(0, commandOnlyArgv[0]); +} + static const char *oneArgArgv[]{"aProgram", "anArgumentOfLength20"}; class OneArgument : public CommandFixture { protected: @@ -49,6 +97,11 @@ EXPECT_EQ(0, RTNAME(ArgumentLength)(2)); } +TEST_F(OneArgument, ArgumentValue) { + CheckArgumentValue(0, oneArgArgv[0]); + CheckArgumentValue(1, oneArgArgv[1]); +} + static const char *severalArgsArgv[]{ "aProgram", "16-char-long-arg", "", "-22-character-long-arg", "o"}; class SeveralArguments : public CommandFixture { @@ -71,3 +124,28 @@ EXPECT_EQ(1, RTNAME(ArgumentLength)(4)); EXPECT_EQ(0, RTNAME(ArgumentLength)(5)); } + +TEST_F(SeveralArguments, ArgumentValue) { + CheckArgumentValue(0, severalArgsArgv[0]); + CheckArgumentValue(1, severalArgsArgv[1]); + CheckMissingArgumentValue(2); + CheckArgumentValue(3, severalArgsArgv[3]); + CheckArgumentValue(4, severalArgsArgv[4]); +} + +TEST_F(SeveralArguments, NoArgumentValue) { + // Make sure we don't crash if the 'value' and 'error' parameters aren't + // passed. + EXPECT_EQ(RTNAME(ArgumentValue)(2, nullptr, nullptr), 0); + EXPECT_GT(RTNAME(ArgumentValue)(-1, nullptr, nullptr), 0); +} + +TEST_F(SeveralArguments, ArgumentValueErrors) { + CheckMissingArgumentValue(-1); + CheckMissingArgumentValue(5); + + OwningPtr tooShort{CreateEmptyCharDescriptor<15>()}; + ASSERT_NE(tooShort, nullptr); + EXPECT_EQ(RTNAME(ArgumentValue)(1, tooShort.get(), nullptr), -1); + CheckDescriptorEqStr(tooShort.get(), severalArgsArgv[1]); +}