Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cel-c/ast_traverse.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ CEL_ATTRIBUTE_NOTHROW
CEL_EXTERN void cel_AstTraverser_StepOver(
cel_AstTraverser* cel_nonnull ast_traverser);

// Not `CEL_ATTRIBUTE_NOTHROW`: visitor callbacks may abort the traversal with
// `longjmp`, which on some platforms (e.g. Windows) unwinds through this frame
// like an exception and would call `std::terminate` if it were `noexcept`.
CEL_ATTRIBUTE_NODISCARD
CEL_ATTRIBUTE_NOTHROW
CEL_EXTERN bool cel_AstTraverser_Traverse(CEL_NONNULL(cel_AstTraverser*)
ast_traverser,
CEL_NONNULL(cel_Status*) status);
Expand Down
2 changes: 2 additions & 0 deletions cel-c/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,8 @@ cc_test(
"@abseil-cpp//absl/container:flat_hash_map",
"@abseil-cpp//absl/log:absl_check",
"@abseil-cpp//absl/log:die_if_null",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:status_matchers",
"@abseil-cpp//absl/strings:string_view",
"//cel-c:alloc",
"//cel-c:arena",
Expand Down
9 changes: 9 additions & 0 deletions cel-c/internal/runtime/interpreter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,15 @@ static _cel_InstrKind _cel_Interpreter_TimeAccessorKind(
static void _cel_Interpreter_PostVisitCallExpr(
cel_AstVisitor* cel_nonnull visitor, const cel_CallExpr* cel_nonnull expr) {
_cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor);
if (cel_CallExpr_Target(expr) == cel_nullptr &&
cel_CallExpr_Args(expr, cel_nullptr, cel_nullptr) == 1 &&
cel_StringView_Equals(cel_CallExpr_Function(expr),
cel_StringView_From("dyn"))) {
// `dyn(x)` only affects type checking and is the identity function at
// runtime. The value of its argument is already on the top of the value
// stack, so no instruction is needed.
return;
}
uint32_t index =
_cel_Interpreter_InternString(interp, cel_CallExpr_Function(expr));
_cel_Instr* instr = _cel_Interpreter_AppendInstr(interp);
Expand Down
147 changes: 147 additions & 0 deletions cel-c/internal/runtime_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include "absl/container/flat_hash_map.h"
#include "absl/log/absl_check.h"
#include "absl/log/die_if_null.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/strings/string_view.h"
#include "cel-c/activation.h"
#include "cel-c/alloc.h"
Expand Down Expand Up @@ -60,7 +62,9 @@

namespace {

using ::absl_testing::StatusIs;
using ::testing::_;
using ::testing::HasSubstr;
using ::testing::Invoke;

struct _cel_AstDeleter {
Expand Down Expand Up @@ -245,6 +249,20 @@ class RuntimeHarness {
return prog;
}

// Like `Parse`, but returns the planning status instead of crashing when
// planning fails.
absl::Status ParseStatus(absl::string_view content) {
_cel_AstPtr ast(_cel_TestingParser_Parse(ABSL_DIE_IF_NULL(parser_.get()),
cel_StringView_FromAbsl(content),
arena()));
_cel_ProgramPtr prog(
cel_Runtime_Compile(rt_.get(), ast.get(), nullptr, status()));
absl::Status result = cel_Status_ToAbsl(status());
ABSL_CHECK_EQ(prog == nullptr, !result.ok());
cel_Status_Clear(status());
return result;
}

_cel_ProgramPtr Compile(absl::string_view content) {
_cel_AstPtr ast(_cel_TestingCompiler_Compile(
ABSL_DIE_IF_NULL(compiler_.get()), cel_StringView_FromAbsl(content),
Expand Down Expand Up @@ -2097,6 +2115,135 @@ TEST_F(RuntimeTest, CallDateAccessorsPropagatesError) {
cel_StringView_From("cel: divide by zero"));
}

TEST_F(RuntimeTest, CallDyn) {
_cel_ProgramPtr prog = Parse(R"cel(dyn(1))cel");
_cel_TestingVariableResolver var_resolver({{"foo", IntValue(42)}});
_cel_ActivationPtr act(Activate(prog.get(), &var_resolver));
cel_Value value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 1);

prog = Parse(R"cel(dyn(2u))cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsUint(&value));
EXPECT_EQ(cel_Value_GetUint(&value), 2u);

prog = Parse(R"cel(dyn(null))cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
EXPECT_TRUE(cel_Value_IsNull(&value));

prog = Parse(R"cel(dyn("foo") + "bar")cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsString(&value));
EXPECT_EQ(cel_Value_GetString(&value), cel_StringView_From("foobar"));

prog = Parse(R"cel(dyn(dyn(true)))cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsBool(&value));
EXPECT_TRUE(cel_Value_GetBool(&value));

prog = Parse(R"cel(dyn(foo) + 1)cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 43);

prog = Parse(R"cel(dyn([1, 2]) == [1, 2])cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsBool(&value));
EXPECT_TRUE(cel_Value_GetBool(&value));

prog = Parse(R"cel(dyn(false) ? 1 : dyn(2))cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 2);
}

TEST_F(RuntimeTest, CallDynInControlFlow) {
_cel_ProgramPtr prog = Parse(R"cel(dyn(false) && (1 / 0 == 1))cel");
_cel_TestingVariableResolver var_resolver;
_cel_ActivationPtr act(Activate(prog.get(), &var_resolver));
cel_Value value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsBool(&value));
EXPECT_FALSE(cel_Value_GetBool(&value));

prog = Parse(R"cel(dyn(true) || (1 / 0 == 1))cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsBool(&value));
EXPECT_TRUE(cel_Value_GetBool(&value));

prog = Parse(R"cel(true ? dyn(1) : 2)cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 1);

prog = Parse(R"cel({dyn("k"): dyn(1)}["k"])cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 1);

prog = Parse(R"cel([dyn(1), 2][0])cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 1);

prog = Parse(R"cel(cel.bind(x, dyn(2), x * x))cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 4);

prog = Parse(R"cel(cel.bind(x, 2, dyn(x) * dyn(x)))cel");
act = Activate(prog.get(), &var_resolver);
value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 4);
}

TEST_F(RuntimeTest, CallDynRequiresSingleGlobalArg) {
EXPECT_THAT(ParseStatus(R"cel(dyn())cel"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("function with name dyn")));
EXPECT_THAT(ParseStatus(R"cel(dyn(1, 2))cel"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("function with name dyn")));
EXPECT_THAT(ParseStatus(R"cel(1.dyn())cel"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("function with name dyn")));
EXPECT_THAT(ParseStatus(R"cel(1.dyn(2))cel"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("function with name dyn")));
}

TEST_F(RuntimeTest, CallDynPropagatesError) {
_cel_ProgramPtr prog = Parse(R"cel(dyn(1 / 0))cel");
_cel_TestingVariableResolver var_resolver;
_cel_ActivationPtr act(Activate(prog.get(), &var_resolver));
cel_Value value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsError(&value));
EXPECT_EQ(cel_Error_Message(cel_Value_GetError(&value)),
cel_StringView_From("cel: divide by zero"));
}

TEST_F(RuntimeTest, CallDynChecked) {
_cel_ProgramPtr prog = Compile(R"cel(dyn(1) + 2)cel");
_cel_TestingVariableResolver var_resolver;
_cel_ActivationPtr act(Activate(prog.get(), &var_resolver));
cel_Value value = Execute(act.get());
ASSERT_TRUE(cel_Value_IsInt(&value));
EXPECT_EQ(cel_Value_GetInt(&value), 3);
}

// TODO: Add addition tests for duration/timestamp

TEST_F(RuntimeTest, IntSub) {
Expand Down
Loading