blob: 956454b629d31c314e25b4713695f90c0e2f1f35 [file] [log] [blame] [edit]
// Protocol Buffers - Google's data interchange format
// Copyright 2025 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "google/protobuf/symbol_checker.h"
#include <utility>
#include "google/protobuf/descriptor.pb.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor_test_utils.h"
#include "google/protobuf/feature_resolver.h"
// Must be included last.
#include "google/protobuf/port_def.inc"
using ::testing::NotNull;
namespace {
absl::Status GetStatus(const absl::Status& s) { return s; }
template <typename T>
absl::Status GetStatus(const absl::StatusOr<T>& s) {
return s.status();
}
MATCHER_P2(StatusIs, status, message,
absl::StrCat(".status() is ", testing::PrintToString(status))) {
return GetStatus(arg).code() == status &&
testing::ExplainMatchResult(message, GetStatus(arg).message(),
result_listener);
}
#define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk, testing::_))
#define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk, testing::_))
} // namespace
namespace google {
namespace protobuf {
namespace descriptor_unittest {
class SymbolCheckerTest : public ValidationErrorTest {
protected:
void SetUp() override {
ValidationErrorTest::SetUp();
auto default_spec = FeatureResolver::CompileDefaults(
FeatureSet::descriptor(), {}, EDITION_PROTO2, EDITION_99999_TEST_ONLY);
ASSERT_OK(default_spec);
ASSERT_OK(pool_.SetFeatureSetDefaults(std::move(default_spec).value()));
}
};
TEST_F(SymbolCheckerTest, VisibilityFromSame) {
pool_.EnforceSymbolVisibility(true);
EXPECT_THAT(ParseAndBuildFile("vis.proto", R"schema(
edition = "2024";
package vis.test;
local message LocalMessage {
}
export message ExportMessage {
LocalMessage foo = 1;
}
)schema"),
NotNull());
}
TEST_F(SymbolCheckerTest, ExplicitVisibilityFromOther) {
pool_.EnforceSymbolVisibility(true);
ASSERT_THAT(ParseAndBuildFile("vis.proto", R"schema(
edition = "2024";
package vis.test;
local message LocalMessage {
}
export message ExportMessage {
}
)schema"),
NotNull());
ParseAndBuildFileWithErrorSubstr(
"importer.proto",
R"schema(
edition = "2024";
import "vis.proto";
message BadImport {
vis.test.LocalMessage foo = 1;
}
)schema",
"importer.proto: BadImport.foo: TYPE: Symbol \"vis.test.LocalMessage\", "
"defined in \"vis.proto\" is not visible from \"importer.proto\". It is "
"explicitly marked 'local' and cannot be accessed outside its own "
"file\n");
}
TEST_F(SymbolCheckerTest, ExplicitVisibilityFromOtherDisabled) {
pool_.EnforceSymbolVisibility(false);
ASSERT_THAT(ParseAndBuildFile("vis.proto", R"schema(
edition = "2024";
package vis.test;
local message LocalMessage {
}
export message ExportMessage {
}
)schema"),
NotNull());
EXPECT_THAT(ParseAndBuildFile("importer.proto",
R"schema(
edition = "2024";
import "vis.proto";
message BadImport {
vis.test.LocalMessage foo = 1;
}
)schema"),
NotNull());
}
TEST_F(SymbolCheckerTest, Edition2024DefaultVisibilityFromOther) {
pool_.EnforceSymbolVisibility(true);
ASSERT_THAT(ParseAndBuildFile("vis.proto", R"schema(
edition = "2024";
package vis.test;
message TopLevelMessage {
message NestedMessage {
}
}
)schema"),
NotNull());
ASSERT_THAT(ParseAndBuildFile("good_importer.proto", R"schema(
edition = "2024";
import "vis.proto";
message GoodImport {
vis.test.TopLevelMessage foo = 1;
}
)schema"),
NotNull());
ParseAndBuildFileWithErrorSubstr(
"bad_importer.proto", R"schema(
edition = "2024";
import "vis.proto";
message BadImport {
vis.test.TopLevelMessage.NestedMessage foo = 1;
}
)schema",
"bad_importer.proto: BadImport.foo: TYPE: Symbol "
"\"vis.test.TopLevelMessage.NestedMessage\", defined in \"vis.proto\" "
"is not visible from \"bad_importer.proto\". It defaulted to local from "
"file-level 'option features.default_symbol_visibility = "
"'EXPORT_TOP_LEVEL'; and cannot be accessed outside its own file\n");
}
TEST_F(SymbolCheckerTest, VisibilityFromLocalExtender) {
pool_.EnforceSymbolVisibility(true);
ASSERT_THAT(ParseAndBuildFile("vis.proto", R"schema(
edition = "2024";
package vis.test;
local message LocalExtendee {
extensions 1 to 100;
}
)schema"),
NotNull());
ParseAndBuildFileWithErrorSubstr(
"bad_importer.proto", R"schema(
edition = "2024";
import "vis.proto";
extend vis.test.LocalExtendee {
string bar = 1;
}
)schema",
"bad_importer.proto: bar: EXTENDEE: Symbol \"vis.test.LocalExtendee\", "
"defined in \"vis.proto\" target of extend is not visible from "
"\"bad_importer.proto\". It is explicitly marked 'local' and cannot be "
"accessed outside its own file\n");
}
TEST_F(SymbolCheckerTest, IsEnumNamespaceMessage) {
pool_.EnforceSymbolVisibility(true);
const FileDescriptor* file = ParseAndBuildFile("vis.proto", R"schema(
edition = "2024";
package vis.test;
option features.default_symbol_visibility = STRICT;
local message EnumNamespaceMessage {
export enum Enum {
FOO = 0;
}
reserved 1 to max;
}
enum TopLevelEnum {
BAR = 0;
}
)schema");
ASSERT_THAT(file, NotNull());
const Descriptor* namespace_message =
file->FindMessageTypeByName("EnumNamespaceMessage");
ASSERT_THAT(namespace_message, NotNull());
ASSERT_TRUE(SymbolChecker::IsEnumNamespaceMessage(*namespace_message));
const EnumDescriptor* enum_descriptor =
namespace_message->FindEnumTypeByName("Enum");
ASSERT_THAT(enum_descriptor, NotNull());
ASSERT_TRUE(SymbolChecker::IsNamespacedEnum(*enum_descriptor));
// Make sure it can be imported.
ASSERT_THAT(ParseAndBuildFile("other.proto", R"schema(
edition = "2024";
package vis.test;
import "vis.proto";
message OtherMessage {
vis.test.EnumNamespaceMessage.Enum foo = 1;
}
)schema"),
NotNull());
const EnumDescriptor* top_enum_descriptor =
file->FindEnumTypeByName("TopLevelEnum");
ASSERT_THAT(top_enum_descriptor, NotNull());
ASSERT_FALSE(SymbolChecker::IsNamespacedEnum(*top_enum_descriptor));
}
TEST_F(SymbolCheckerTest, IsNotEnumNamespaceMessage) {
pool_.EnforceSymbolVisibility(true);
// message is not local
ParseAndBuildFileWithErrorSubstr(
"vis.proto", R"schema(
edition = "2024";
package vis.test;
option features.default_symbol_visibility = STRICT;
export message EnumNamespaceMessage {
export enum Enum {
FOO = 0;
}
reserved 1 to max;
}
)schema",
"is a nested enum and cannot be marked `export` with STRICT");
// message doesn't have reserved range.
ParseAndBuildFileWithErrorSubstr(
"vis2.proto", R"schema(
edition = "2024";
package vis.test;
option features.default_symbol_visibility = STRICT;
local message EnumNamespaceMessage {
export enum Enum {
FOO = 0;
}
}
)schema",
"is a nested enum and cannot be marked `export` with STRICT");
// message doesn't have full reserved range.
ParseAndBuildFileWithErrorSubstr(
"vis2.proto", R"schema(
edition = "2024";
package vis.test;
option features.default_symbol_visibility = STRICT;
local message EnumNamespaceMessage {
export enum Enum {
FOO = 0;
}
reserved 1 to 10;
}
)schema",
"is a nested enum and cannot be marked `export` with STRICT");
}
} // namespace descriptor_unittest
} // namespace protobuf
} // namespace google
#include "google/protobuf/port_undef.inc"