Skip to content

Commit b900d8c

Browse files
committed
Fix 'use after free' error when using reflected user-defined callables #103
1 parent 791e580 commit b900d8c

File tree

6 files changed

+78
-7
lines changed

6 files changed

+78
-7
lines changed

include/jinja2cpp/user_callable.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct UCInvoker
5050

5151
};
5252

53-
const Value& GetParamValue(const UserCallableParams& params, const ArgInfo& info)
53+
inline const Value& GetParamValue(const UserCallableParams& params, const ArgInfo& info)
5454
{
5555
// static Value empty;
5656
auto p = params.args.find(info.paramName);
@@ -112,6 +112,17 @@ auto MakeCallable(Fn&& f, ArgDescr&& ... ad)
112112
{ArgInfo(std::forward<ArgDescr>(ad))...}
113113
};
114114
}
115+
116+
template<typename Fn>
117+
auto MakeCallable(Fn&& f)
118+
{
119+
return UserCallable{
120+
[=, fn = std::forward<Fn>(f)](const UserCallableParams& params) {
121+
return fn();
122+
},
123+
{}
124+
};
125+
}
115126
} // jinja2
116127

117128
#endif // USER_CALLABLE_H

src/filters.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,12 @@ struct PrettyPrinter : visitors::BaseVisitor<InternalValue>
484484
return "none"s;
485485
}
486486

487-
InternalValue operator()(double val) const
487+
InternalValue operator()(const Callable&) const
488+
{
489+
return "<callable>"s;
490+
}
491+
492+
InternalValue operator()(double val) const
488493
{
489494
std::ostringstream os;
490495
os << val;

src/internal_value.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ std::string AsString(const InternalValue& val)
7373
auto* tstr = GetIf<TargetString>(&val);
7474
if (str != nullptr)
7575
return *str;
76-
else
76+
else /* if (tstr != nullptr) */
7777
{
7878
str = GetIf<std::string>(tstr);
7979
if (str != nullptr)
@@ -583,7 +583,7 @@ InputValueConvertor::result_t InputValueConvertor::ConvertUserCallable(const Use
583583
args.emplace_back(pi.paramName, pi.isMandatory, Value2IntValue(pi.defValue));
584584
}
585585

586-
return InternalValue(Callable(Callable::UserCallable, [&val, argsInfo = std::move(args)](const CallParams& params, RenderContext& context) -> InternalValue {
586+
return InternalValue(Callable(Callable::UserCallable, [val, argsInfo = std::move(args)](const CallParams& params, RenderContext& context) -> InternalValue {
587587
auto ucParams = PrepareUserCallableParams(params, context, argsInfo);
588588
return Value2IntValue(val.callable(ucParams));
589589
}));

test/filters_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ INSTANTIATE_TEST_CASE_P(DictSort, FilterGenericTest, ::testing::Values(
377377
InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true) | pprint", "['Value': 'ItemValue', 'key': 'itemName']"},
378378
InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true, reverse=true) | pprint", "['key': 'itemName', 'Value': 'ItemValue']"},
379379
InputOutputPair{"simpleMapValue | dictsort | pprint", "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"},
380-
InputOutputPair{"reflectedVal | dictsort | pprint", "['boolValue': false, 'dblValue': 0, 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '<wchar_string>']"}
380+
InputOutputPair{"reflectedVal | dictsort | pprint", "['basicCallable': <callable>, 'boolValue': false, 'dblValue': 0, 'getInnerStruct': <callable>, 'getInnerStructValue': <callable>, 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '<wchar_string>']"}
381381
));
382382

383383
INSTANTIATE_TEST_CASE_P(UrlEncode, FilterGenericTest, ::testing::Values(

test/test_tools.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <gtest/gtest.h>
55
#include <jinja2cpp/reflected_value.h>
66
#include <jinja2cpp/template.h>
7+
#include <jinja2cpp/user_callable.h>
78

89
struct InputOutputPair
910
{
@@ -182,7 +183,16 @@ struct TypeReflection<TestStruct> : TypeReflected<TestStruct>
182183
vals.push_back(std::make_shared<TestInnerStruct>());
183184
return jinja2::Reflect(list_t(vals.begin(), vals.end()));
184185
}},
185-
};
186+
{"basicCallable",[](const TestStruct& obj) {
187+
return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return obj.intValue; });
188+
}},
189+
{"getInnerStruct",[](const TestStruct& obj) {
190+
return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return jinja2::Reflect(obj.innerStruct); });
191+
}},
192+
{"getInnerStructValue",[](const TestStruct& obj) {
193+
return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return jinja2::Reflect(*obj.innerStruct); });
194+
}},
195+
};
186196

187197
return accessors;
188198
}

test/user_callable_test.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,51 @@ Hello default
109109
EXPECT_EQ(expectedResult, result);
110110
}
111111

112+
TEST(UserCallableTest, ReflectedCallable)
113+
{
114+
std::string source = R"(
115+
{% set callable = reflected.basicCallable %}{{ callable() }}
116+
{{ reflected.basicCallable() }}
117+
{% set inner = reflected.getInnerStruct() %}{{ inner.strValue }}
118+
{% set innerValue = reflected.getInnerStructValue() %}{{ innerValue.strValue }}
119+
{{ innerReflected.strValue }}
120+
)";
121+
122+
Template tpl;
123+
auto parseRes = tpl.Load(source);
124+
EXPECT_TRUE(parseRes.has_value());
125+
if (!parseRes)
126+
{
127+
std::cout << parseRes.error() << std::endl;
128+
return;
129+
}
130+
131+
TestStruct reflected;
132+
auto innerReflected = std::make_shared<TestInnerStruct>();
133+
innerReflected->strValue = "!!Hello World!!";
134+
reflected.intValue = 100500;
135+
reflected.innerStruct = std::make_shared<TestInnerStruct>();
136+
reflected.innerStruct->strValue = "Hello World!";
137+
{
138+
jinja2::ValuesMap params;
139+
params["reflected"] = jinja2::Reflect(reflected);
140+
params["innerReflected"] = jinja2::Reflect(innerReflected);
141+
142+
std::string result = tpl.RenderAsString(params);
143+
std::cout << result << std::endl;
144+
std::string expectedResult = R"(
145+
100500
146+
100500
147+
Hello World!
148+
Hello World!
149+
!!Hello World!!
150+
)";
151+
EXPECT_EQ(expectedResult, result);
152+
}
153+
EXPECT_EQ(1L, innerReflected.use_count());
154+
EXPECT_EQ(1L, reflected.innerStruct.use_count());
155+
}
156+
112157
struct UserCallableParamConvertTestTag;
113158
using UserCallableParamConvertTest = InputOutputPairTest<UserCallableParamConvertTestTag>;
114159

@@ -266,7 +311,7 @@ INSTANTIATE_TEST_CASE_P(MapParamConvert, UserCallableParamConvertTest, ::testing
266311
InputOutputPair{"GMapFn(simpleMapValue) | dictsort",
267312
"['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"},
268313
InputOutputPair{"GMapFn(reflectedVal) | dictsort",
269-
"['boolValue': false, 'dblValue': 0, 'innerStruct': {'strValue': 'Hello World!'}, "
314+
"['basicCallable': <callable>, 'boolValue': false, 'dblValue': 0, 'getInnerStruct': <callable>, 'getInnerStructValue': <callable>, 'innerStruct': {'strValue': 'Hello World!'}, "
270315
"'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, "
271316
"{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, "
272317
"{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, "

0 commit comments

Comments
 (0)