Skip to content

Commit 4694024

Browse files
authored
C++: Fix polymorphic lookup and add tests (#576)
The previous iteration does not make ASAN happy because of the implicit static cast between Message and concrete type. This PR reverts that change, adds a new abstract validation registry, and explicitly performs a dynamic cast when asked to do so. It also checks to make sure a validator exists for the passed in message. Also add a .clang-format so at least manual formatting can be done. Signed-off-by: Matt Klein <[email protected]>
1 parent d4f85cd commit 4694024

File tree

7 files changed

+119
-46
lines changed

7 files changed

+119
-46
lines changed

.clang-format

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
Language: Cpp
3+
AccessModifierOffset: -2
4+
ColumnLimit: 100
5+
DerivePointerAlignment: false
6+
PointerAlignment: Left
7+
SortIncludes: false
8+
...
9+
10+
---
11+
Language: Proto
12+
ColumnLimit: 100
13+
SpacesInContainerLiterals: false
14+
AllowShortFunctionsOnASingleLine: false
15+
ReflowComments: false
16+
...

templates/cc/message.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const messageTpl = `
88
{{ else }}
99
{
1010
pgv::ValidationMsg inner_err;
11-
if ({{ hasAccessor .}} && !pgv::Validator<{{ ctype $f.Type }}>::CheckMessage({{ accessor . }}, &inner_err)) {
11+
if ({{ hasAccessor .}} && !pgv::BaseValidator::AbstractCheckMessage({{ accessor . }}, &inner_err)) {
1212
{{ errCause . "inner_err" "embedded message failed validation" }}
1313
}
1414
}

tests/harness/cc/BUILD

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
1+
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_proto_library", "cc_test")
2+
load("@rules_proto//proto:defs.bzl", "proto_library")
23

34
MSVC_C_OPTS = [
45
"-WX",
@@ -36,6 +37,29 @@ cc_binary(
3637
],
3738
)
3839

40+
proto_library(
41+
name = "other_proto",
42+
srcs = ["other.proto"],
43+
)
44+
45+
cc_proto_library(
46+
name = "other_cc_proto",
47+
deps = ["other_proto"],
48+
)
49+
50+
cc_test(
51+
name = "polymorphic_test",
52+
srcs = ["polymorphic_test.cc"],
53+
copts = select({
54+
":windows_x86_64": MSVC_C_OPTS,
55+
"//conditions:default": POSIX_C_OPTS,
56+
}),
57+
deps = [
58+
":other_cc_proto",
59+
"//tests/harness/cases:cc",
60+
],
61+
)
62+
3963
# Ensure that if the headers are included in multiple libraries, those libraries
4064
# can be linked without conflicts.
4165
cc_test(

tests/harness/cc/diamond_test.cc

+1-6
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,4 @@
2525
#include "tests/harness/cases/wkt_wrappers.pb.h"
2626
#include "tests/harness/cases/wkt_wrappers.pb.validate.h"
2727

28-
int main(int argc, char **argv) {
29-
(void)argc;
30-
(void)argv;
31-
32-
return EXIT_SUCCESS;
33-
}
28+
int main() { return EXIT_SUCCESS; }

tests/harness/cc/other.proto

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
syntax = "proto3";
2+
3+
package tests.harness.cc;
4+
5+
message Foo {}

tests/harness/cc/polymorphic_test.cc

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include "tests/harness/cases/bool.pb.h"
2+
#include "tests/harness/cc/other.pb.h"
3+
#include "validate/validate.h"
4+
5+
int main() {
6+
tests::harness::cc::Foo foo;
7+
8+
// This does not have an associated validator but should still pass.
9+
std::string err;
10+
if (!pgv::BaseValidator::AbstractCheckMessage(foo, &err)) {
11+
return EXIT_FAILURE;
12+
}
13+
14+
tests::harness::cases::BoolConstTrue bool_const_true;
15+
bool_const_true.set_val(false);
16+
if (pgv::BaseValidator::AbstractCheckMessage(bool_const_true, &err)) {
17+
return EXIT_FAILURE;
18+
}
19+
20+
return EXIT_SUCCESS;
21+
}

validate/validate.h

+50-38
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
#include <regex>
66
#include <stdexcept>
77
#include <string>
8-
#include <typeinfo>
98
#include <typeindex>
9+
#include <typeinfo>
1010
#include <unordered_map>
1111

1212
#if !defined(_WIN32)
@@ -27,6 +27,7 @@
2727

2828
#endif
2929

30+
#include "google/protobuf/message.h"
3031
#include "google/protobuf/stubs/strutil.h" // for UTF8Len
3132

3233
namespace pgv {
@@ -42,68 +43,78 @@ class UnimplementedException : public std::runtime_error {
4243
using ValidationMsg = std::string;
4344

4445
class BaseValidator {
46+
public:
47+
/**
48+
* Validate/check a generic message object with a registered validator for the concrete message
49+
* type.
50+
* @param m supplies the message to check.
51+
* @param err supplies the place to return error information.
52+
* @return true if the validation passes OR there is no registered validator for the concrete
53+
* message type. false is returned if validation explicitly fails.
54+
*/
55+
static bool AbstractCheckMessage(const google::protobuf::Message& m, ValidationMsg* err) {
56+
// Polymorphic lookup is used to see if there is a matching concrete validator. If so, call it.
57+
// Otherwise return success.
58+
auto it = abstractValidators().find(std::type_index(typeid(m)));
59+
if (it == abstractValidators().end()) {
60+
return true;
61+
}
62+
return it->second(m, err);
63+
}
64+
4565
protected:
46-
static std::unordered_map<std::type_index, BaseValidator*>& validators() {
47-
static auto* validator_map = new std::unordered_map<std::type_index, BaseValidator*>();
66+
// Used to implement AbstractCheckMessage() above. Every message that is linked into the binary
67+
// will register itself by type_index, allowing for polymorphic lookup later.
68+
static std::unordered_map<std::type_index,
69+
std::function<bool(const google::protobuf::Message&, ValidationMsg*)>>&
70+
abstractValidators() {
71+
static auto* validator_map = new std::unordered_map<
72+
std::type_index, std::function<bool(const google::protobuf::Message&, ValidationMsg*)>>();
4873
return *validator_map;
4974
}
5075
};
5176

52-
template <typename T>
53-
class Validator : public BaseValidator {
77+
template <typename T> class Validator : public BaseValidator {
5478
public:
55-
Validator(std::function<bool(const T&, ValidationMsg*)> check) : check_(check)
56-
{
57-
validators()[std::type_index(typeid(T))] = this;
58-
}
59-
60-
static bool CheckMessage(const T& m, ValidationMsg* err)
61-
{
62-
// Run typeid() on the variable vs. the type to allow polymorphic lookup of the validator.
63-
auto val = static_cast<Validator<T>*>(validators()[std::type_index(typeid(m))]);
64-
if (val) {
65-
return val->check_(m, err);
66-
}
67-
return true;
79+
Validator(std::function<bool(const T&, ValidationMsg*)> check) : check_(check) {
80+
abstractValidators()[std::type_index(typeid(T))] = [this](const google::protobuf::Message& m,
81+
ValidationMsg* err) -> bool {
82+
return check_(dynamic_cast<const T&>(m), err);
83+
};
6884
}
6985

7086
private:
7187
std::function<bool(const T&, ValidationMsg*)> check_;
7288
};
7389

74-
static inline std::string String(const ValidationMsg& msg)
75-
{
76-
return std::string(msg);
77-
}
90+
static inline std::string String(const ValidationMsg& msg) { return std::string(msg); }
7891

79-
static inline bool IsPrefix(const string& maybe_prefix, const string& search_in)
80-
{
92+
static inline bool IsPrefix(const string& maybe_prefix, const string& search_in) {
8193
return search_in.compare(0, maybe_prefix.size(), maybe_prefix) == 0;
8294
}
8395

84-
static inline bool IsSuffix(const string& maybe_suffix, const string& search_in)
85-
{
86-
return maybe_suffix.size() <= search_in.size() && search_in.compare(search_in.size() - maybe_suffix.size(), maybe_suffix.size(), maybe_suffix) == 0;
96+
static inline bool IsSuffix(const string& maybe_suffix, const string& search_in) {
97+
return maybe_suffix.size() <= search_in.size() &&
98+
search_in.compare(search_in.size() - maybe_suffix.size(), maybe_suffix.size(),
99+
maybe_suffix) == 0;
87100
}
88101

89-
static inline bool Contains(const string& search_in, const string& to_find)
90-
{
102+
static inline bool Contains(const string& search_in, const string& to_find) {
91103
return search_in.find(to_find) != string::npos;
92104
}
93105

94-
static inline bool NotContains(const string& search_in, const string& to_find)
95-
{
106+
static inline bool NotContains(const string& search_in, const string& to_find) {
96107
return !Contains(search_in, to_find);
97108
}
98109

99110
static inline bool IsIpv4(const string& to_validate) {
100-
struct sockaddr_in sa;
101-
return !(inet_pton(AF_INET, to_validate.c_str(), &sa.sin_addr) < 1);
111+
struct sockaddr_in sa;
112+
return !(inet_pton(AF_INET, to_validate.c_str(), &sa.sin_addr) < 1);
102113
}
103114

104115
static inline bool IsIpv6(const string& to_validate) {
105116
struct sockaddr_in6 sa_six;
106-
return !(inet_pton(AF_INET6, to_validate.c_str(), &sa_six.sin6_addr) < 1);
117+
return !(inet_pton(AF_INET6, to_validate.c_str(), &sa_six.sin6_addr) < 1);
107118
}
108119

109120
static inline bool IsIp(const string& to_validate) {
@@ -119,7 +130,7 @@ static inline bool IsHostname(const string& to_validate) {
119130
const auto iter_end = std::sregex_token_iterator();
120131
auto iter = std::sregex_token_iterator(to_validate.begin(), to_validate.end(), dot_regex, -1);
121132
for (; iter != iter_end; ++iter) {
122-
const std::string &part = *iter;
133+
const std::string& part = *iter;
123134
if (part.empty() || part.length() > 63) {
124135
return false;
125136
}
@@ -129,8 +140,9 @@ static inline bool IsHostname(const string& to_validate) {
129140
if (part.at(part.length() - 1) == '-') {
130141
return false;
131142
}
132-
for (const auto &character : part) {
133-
if ((character < 'A' || character > 'Z') && (character < 'a' || character > 'z') && (character < '0' || character > '9') && character != '-') {
143+
for (const auto& character : part) {
144+
if ((character < 'A' || character > 'Z') && (character < 'a' || character > 'z') &&
145+
(character < '0' || character > '9') && character != '-') {
134146
return false;
135147
}
136148
}
@@ -140,7 +152,7 @@ static inline bool IsHostname(const string& to_validate) {
140152
}
141153

142154
static inline size_t Utf8Len(const string& narrow_string) {
143-
const char *str_char = narrow_string.c_str();
155+
const char* str_char = narrow_string.c_str();
144156
ptrdiff_t byte_len = narrow_string.length();
145157
size_t unicode_len = 0;
146158
int char_len = 1;

0 commit comments

Comments
 (0)