#include "environment.h"
#include "stat.h"
#include "terminator.h"
+#include "tools.h"
#include "flang/Runtime/descriptor.h"
#include <cstdlib>
#include <limits>
value->rank() == 0;
}
-static void FillWithSpaces(const Descriptor *value) {
- std::memset(value->OffsetElement(), ' ', value->ElementBytes());
+static bool IsValidIntDescriptor(const Descriptor *length) {
+ auto typeCode{length->type().GetCategoryAndKind()};
+ // Check that our descriptor is allocated and is a scalar integer with
+ // kind != 1 (i.e. with a large enough decimal exponent range).
+ return length->IsAllocated() && length->rank() == 0 &&
+ length->type().IsInteger() && typeCode && typeCode->second != 1;
+}
+
+static void FillWithSpaces(const Descriptor &value, std::size_t offset = 0) {
+ if (offset < value.ElementBytes()) {
+ std::memset(
+ value.OffsetElement(offset), ' ', value.ElementBytes() - offset);
+ }
}
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<std::int64_t>(value.ElementBytes()))};
- std::memcpy(value.OffsetElement(), rawValue, toCopy);
+ const char *rawValue, std::int64_t rawValueLength, const Descriptor *errmsg,
+ std::size_t offset = 0) {
+
+ std::int64_t toCopy{std::min(rawValueLength,
+ static_cast<std::int64_t>(value.ElementBytes() - offset))};
+ if (toCopy < 0) {
+ return ToErrmsg(errmsg, StatValueTooShort);
+ }
+
+ std::memcpy(value.OffsetElement(offset), rawValue, toCopy);
if (rawValueLength > toCopy) {
return ToErrmsg(errmsg, StatValueTooShort);
return StatOk;
}
+static std::int32_t CheckAndCopyToDescriptor(const Descriptor *value,
+ const char *rawValue, const Descriptor *errmsg, std::size_t &offset) {
+ bool haveValue{IsValidCharDescriptor(value)};
+
+ std::int64_t len{StringLength(rawValue)};
+ if (len <= 0) {
+ if (haveValue) {
+ FillWithSpaces(*value);
+ }
+ return ToErrmsg(errmsg, StatMissingArgument);
+ }
+
+ std::int32_t stat{StatOk};
+ if (haveValue) {
+ stat = CopyToDescriptor(*value, rawValue, len, errmsg, offset);
+ }
+
+ offset += len;
+ return stat;
+}
+
std::int32_t RTNAME(ArgumentValue)(
std::int32_t n, const Descriptor *value, const Descriptor *errmsg) {
if (IsValidCharDescriptor(value)) {
- FillWithSpaces(value);
+ FillWithSpaces(*value);
}
if (n < 0 || n >= executionEnvironment.argc) {
return StatOk;
}
+template <int KIND> struct FitsInIntegerKind {
+ bool operator()(std::int64_t value) {
+ return value <= std::numeric_limits<Fortran::runtime::CppTypeFor<
+ Fortran::common::TypeCategory::Integer, KIND>>::max();
+ }
+};
+
+std::int32_t RTNAME(GetCommand)(const Descriptor *value,
+ const Descriptor *length, const Descriptor *errmsg, const char *sourceFile,
+ int line) {
+ Terminator terminator{sourceFile, line};
+
+ auto storeLength = [&](std::int64_t value) {
+ auto typeCode{length->type().GetCategoryAndKind()};
+ int kind{typeCode->second};
+ Fortran::runtime::ApplyIntegerKind<Fortran::runtime::StoreIntegerAt, void>(
+ kind, terminator, *length, /* atIndex = */ 0, value);
+ };
+
+ if (value) {
+ RUNTIME_CHECK(terminator, IsValidCharDescriptor(value));
+ }
+
+ // Store 0 in case we error out later on.
+ if (length) {
+ RUNTIME_CHECK(terminator, IsValidIntDescriptor(length));
+ storeLength(0);
+ }
+
+ auto shouldContinue = [&](std::int32_t stat) -> bool {
+ // We continue as long as everything is ok OR the value descriptor is
+ // too short, but we still need to compute the length.
+ return stat == StatOk || (length && stat == StatValueTooShort);
+ };
+
+ std::size_t offset{0};
+
+ if (executionEnvironment.argc == 0) {
+ return CheckAndCopyToDescriptor(value, "", errmsg, offset);
+ }
+
+ // value = argv[0]
+ std::int32_t stat{CheckAndCopyToDescriptor(
+ value, executionEnvironment.argv[0], errmsg, offset)};
+ if (!shouldContinue(stat)) {
+ return stat;
+ }
+
+ // value += " " + argv[1:n]
+ for (std::int32_t i{1}; i < executionEnvironment.argc; ++i) {
+ stat = CheckAndCopyToDescriptor(value, " ", errmsg, offset);
+ if (!shouldContinue(stat)) {
+ return stat;
+ }
+
+ stat = CheckAndCopyToDescriptor(
+ value, executionEnvironment.argv[i], errmsg, offset);
+ if (!shouldContinue(stat)) {
+ return stat;
+ }
+ }
+
+ auto fitsInLength = [&](std::int64_t value) -> bool {
+ auto typeCode{length->type().GetCategoryAndKind()};
+ int kind{typeCode->second};
+ return Fortran::runtime::ApplyIntegerKind<FitsInIntegerKind, bool>(
+ kind, terminator, value);
+ };
+
+ if (length && fitsInLength(offset)) {
+ storeLength(offset);
+ }
+
+ // value += spaces for padding
+ if (value) {
+ FillWithSpaces(*value, offset);
+ }
+
+ return stat;
+}
+
static std::size_t LengthWithoutTrailingSpaces(const Descriptor &d) {
std::size_t s{d.ElementBytes() - 1};
while (*d.OffsetElement(s) == ' ') {
const Descriptor *value, bool trim_name, const Descriptor *errmsg,
const char *sourceFile, int line) {
if (IsValidCharDescriptor(value)) {
- FillWithSpaces(value);
+ FillWithSpaces(*value);
}
const char *rawValue{GetEnvVariableValue(name, trim_name, sourceFile, line)};
return descriptor;
}
+template <int kind = sizeof(std::int64_t)>
+static OwningPtr<Descriptor> EmptyIntDescriptor() {
+ OwningPtr<Descriptor> descriptor{Descriptor::Create(TypeCategory::Integer,
+ kind, 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[]) {
void CheckDescriptorEqStr(
const Descriptor *value, const std::string &expected) const {
+ ASSERT_NE(value, nullptr);
EXPECT_EQ(std::strncmp(value->OffsetElement(), expected.c_str(),
value->ElementBytes()),
0)
<< std::string{value->OffsetElement(), value->ElementBytes()};
}
+ template <typename INT_T = std::int64_t>
+ void CheckDescriptorEqInt(
+ const Descriptor *value, const INT_T expected) const {
+ if (expected != -1) {
+ ASSERT_NE(value, nullptr);
+ EXPECT_EQ(*value->OffsetElement<INT_T>(), expected);
+ }
+ }
+
template <typename RuntimeCall>
void CheckValue(RuntimeCall F, const char *expectedValue,
- std::int32_t expectedStatus = 0,
+ std::int64_t expectedLength = -1, std::int32_t expectedStatus = 0,
const char *expectedErrMsg = "shouldn't change") const {
OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()};
ASSERT_NE(value, nullptr);
+ OwningPtr<Descriptor> length{
+ expectedLength == -1 ? nullptr : EmptyIntDescriptor()};
+
OwningPtr<Descriptor> errmsg{CharDescriptor(expectedErrMsg)};
+ ASSERT_NE(errmsg, nullptr);
std::string expectedValueStr{
GetPaddedStr(expectedValue, value->ElementBytes())};
- EXPECT_EQ(F(value.get(), errmsg.get()), expectedStatus);
+ EXPECT_EQ(F(value.get(), length.get(), errmsg.get()), expectedStatus);
CheckDescriptorEqStr(value.get(), expectedValueStr);
+ CheckDescriptorEqInt(length.get(), expectedLength);
CheckDescriptorEqStr(errmsg.get(), expectedErrMsg);
}
SCOPED_TRACE(n);
SCOPED_TRACE("Checking argument:");
CheckValue(
- [&](const Descriptor *value, const Descriptor *errmsg) {
+ [&](const Descriptor *value, const Descriptor *,
+ const Descriptor *errmsg) {
return RTNAME(ArgumentValue)(n, value, errmsg);
},
expectedValue);
}
+ void CheckCommandValue(const char *args[], int n) const {
+ SCOPED_TRACE("Checking command:");
+ ASSERT_GE(n, 1);
+ std::string expectedValue{args[0]};
+ for (int i = 1; i < n; i++) {
+ expectedValue += " " + std::string{args[i]};
+ }
+ CheckValue(
+ [&](const Descriptor *value, const Descriptor *length,
+ const Descriptor *errmsg) {
+ return RTNAME(GetCommand)(value, length, errmsg);
+ },
+ expectedValue.c_str(), expectedValue.size());
+ }
+
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) {
+ [&](const Descriptor *value, const Descriptor *,
+ const Descriptor *errmsg) {
return RTNAME(EnvVariableValue)(*CharDescriptor(name), value,
trimName, errmsg, /*sourceFile=*/nullptr, /*line=*/0);
},
OwningPtr<Descriptor> nameDescriptor{CharDescriptor(name)};
EXPECT_EQ(0, RTNAME(EnvVariableLength)(*nameDescriptor, trimName));
CheckValue(
- [&](const Descriptor *value, const Descriptor *errmsg) {
+ [&](const Descriptor *value, const Descriptor *,
+ const Descriptor *errmsg) {
return RTNAME(EnvVariableValue)(*nameDescriptor, value, trimName,
errmsg, /*sourceFile=*/nullptr, /*line=*/0);
},
- "", 1, "Missing environment variable");
+ "", -1, 1, "Missing environment variable");
}
void CheckMissingArgumentValue(int n, const char *errStr = nullptr) const {
CheckDescriptorEqStr(err.get(), paddedErrStr);
}
}
+
+ void CheckMissingCommandValue(const char *errStr = nullptr) const {
+ OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()};
+ ASSERT_NE(value, nullptr);
+
+ OwningPtr<Descriptor> length{EmptyIntDescriptor()};
+ ASSERT_NE(length, nullptr);
+
+ OwningPtr<Descriptor> err{errStr ? CreateEmptyCharDescriptor() : nullptr};
+
+ EXPECT_GT(RTNAME(GetCommand)(value.get(), length.get(), err.get()), 0);
+
+ std::string spaces(value->ElementBytes(), ' ');
+ CheckDescriptorEqStr(value.get(), spaces);
+
+ CheckDescriptorEqInt(length.get(), 0);
+
+ if (errStr) {
+ std::string paddedErrStr(GetPaddedStr(errStr, err->ElementBytes()));
+ CheckDescriptorEqStr(err.get(), paddedErrStr);
+ }
+ }
};
+class NoArgv : public CommandFixture {
+protected:
+ NoArgv() : CommandFixture(0, nullptr) {}
+};
+
+// TODO: Test other intrinsics with this fixture.
+
+TEST_F(NoArgv, GetCommand) { CheckMissingCommandValue(); }
+
static const char *commandOnlyArgv[]{"aProgram"};
class ZeroArguments : public CommandFixture {
protected:
CheckArgumentValue(commandOnlyArgv[0], 0);
}
+TEST_F(ZeroArguments, GetCommand) { CheckCommandValue(commandOnlyArgv, 1); }
+
static const char *oneArgArgv[]{"aProgram", "anArgumentOfLength20"};
class OneArgument : public CommandFixture {
protected:
CheckArgumentValue(oneArgArgv[1], 1);
}
+TEST_F(OneArgument, GetCommand) { CheckCommandValue(oneArgArgv, 2); }
+
static const char *severalArgsArgv[]{
"aProgram", "16-char-long-arg", "", "-22-character-long-arg", "o"};
class SeveralArguments : public CommandFixture {
CheckMissingArgumentValue(5);
}
-TEST_F(SeveralArguments, ValueTooShort) {
+TEST_F(SeveralArguments, ArgValueTooShort) {
OwningPtr<Descriptor> tooShort{CreateEmptyCharDescriptor<15>()};
ASSERT_NE(tooShort, nullptr);
EXPECT_EQ(RTNAME(ArgumentValue)(1, tooShort.get(), nullptr), -1);
CheckDescriptorEqStr(errMsg.get(), expectedErrMsg);
}
-TEST_F(SeveralArguments, ErrMsgTooShort) {
+TEST_F(SeveralArguments, ArgErrMsgTooShort) {
OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor<3>()};
EXPECT_GT(RTNAME(ArgumentValue)(-1, nullptr, errMsg.get()), 0);
CheckDescriptorEqStr(errMsg.get(), "Inv");
}
+TEST_F(SeveralArguments, GetCommand) {
+ CheckMissingCommandValue();
+ CheckMissingCommandValue("Missing argument");
+}
+
+TEST_F(SeveralArguments, CommandErrMsgTooShort) {
+ OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()};
+ OwningPtr<Descriptor> length{EmptyIntDescriptor()};
+ OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor<3>()};
+
+ EXPECT_GT(RTNAME(GetCommand)(value.get(), length.get(), errMsg.get()), 0);
+
+ std::string spaces(value->ElementBytes(), ' ');
+ CheckDescriptorEqStr(value.get(), spaces);
+ CheckDescriptorEqInt(length.get(), 0);
+ CheckDescriptorEqStr(errMsg.get(), "Mis");
+}
+
+TEST_F(SeveralArguments, GetCommandCanTakeNull) {
+ EXPECT_GT(RTNAME(GetCommand)(nullptr, nullptr, nullptr), 0);
+}
+
+static const char *onlyValidArgsArgv[]{
+ "aProgram", "-f", "has/a/few/slashes", "has\\a\\few\\backslashes"};
+class OnlyValidArguments : public CommandFixture {
+protected:
+ OnlyValidArguments()
+ : CommandFixture(sizeof(onlyValidArgsArgv) / sizeof(*onlyValidArgsArgv),
+ onlyValidArgsArgv) {}
+};
+
+TEST_F(OnlyValidArguments, GetCommand) {
+ CheckCommandValue(onlyValidArgsArgv, 4);
+}
+
+TEST_F(OnlyValidArguments, CommandValueTooShort) {
+ OwningPtr<Descriptor> tooShort{CreateEmptyCharDescriptor<50>()};
+ ASSERT_NE(tooShort, nullptr);
+ OwningPtr<Descriptor> length{EmptyIntDescriptor()};
+ ASSERT_NE(length, nullptr);
+
+ EXPECT_EQ(RTNAME(GetCommand)(tooShort.get(), length.get(), nullptr), -1);
+
+ CheckDescriptorEqStr(
+ tooShort.get(), "aProgram -f has/a/few/slashes has\\a\\few\\backslashe");
+ CheckDescriptorEqInt(length.get(), 51);
+
+ OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor()};
+ ASSERT_NE(errMsg, nullptr);
+
+ EXPECT_EQ(-1, RTNAME(GetCommand)(tooShort.get(), nullptr, errMsg.get()));
+
+ std::string expectedErrMsg{
+ GetPaddedStr("Value too short", errMsg->ElementBytes())};
+ CheckDescriptorEqStr(errMsg.get(), expectedErrMsg);
+}
+
+TEST_F(OnlyValidArguments, GetCommandCanTakeNull) {
+ EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, nullptr, nullptr));
+
+ OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()};
+ ASSERT_NE(value, nullptr);
+ OwningPtr<Descriptor> length{EmptyIntDescriptor()};
+ ASSERT_NE(length, nullptr);
+
+ EXPECT_EQ(0, RTNAME(GetCommand)(value.get(), nullptr, nullptr));
+ CheckDescriptorEqStr(value.get(),
+ GetPaddedStr("aProgram -f has/a/few/slashes has\\a\\few\\backslashes",
+ value->ElementBytes()));
+
+ EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, length.get(), nullptr));
+ CheckDescriptorEqInt(length.get(), 51);
+}
+
+TEST_F(OnlyValidArguments, GetCommandShortLength) {
+ OwningPtr<Descriptor> length{EmptyIntDescriptor<sizeof(short)>()};
+ ASSERT_NE(length, nullptr);
+
+ EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, length.get(), nullptr));
+ CheckDescriptorEqInt<short>(length.get(), 51);
+}
+
class EnvironmentVariables : public CommandFixture {
protected:
EnvironmentVariables() : CommandFixture(0, nullptr) {