Skip to content

Commit a16fab4

Browse files
Protocol Buffer TeamLogofile
authored andcommitted
This change includes the following modifications:
* Recommended downcast method topic added * Clarification about when extension declarations are used in proto2 * Deletes the duplicate topic, effective-protos.md * Adds a link to the Editions features topic to the overview topic PiperOrigin-RevId: 574225280 Change-Id: I6357e609930d9c0d3dfed9dd06e4b1da019cef29
1 parent 1990bfb commit a16fab4

File tree

4 files changed

+167
-137
lines changed

4 files changed

+167
-137
lines changed

content/editions/overview.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ as `edition = "2024"`, to specify the default behaviors your file will have.
1313
Editions enable the language to evolve incrementally over time.
1414

1515
Instead of the hardcoded behaviors that older versions have had, editions
16-
represent a collection of features with a default value (behavior) per feature.
17-
Features are options on a file, message, field, enum, and so on, that specify
18-
the behavior of protoc, the code generators, and protobuf runtimes. You can
19-
explicitly override a behavior at those different levels (file, message, field,
20-
...) when your needs don't match the default behavior for the edition you've
21-
selected. You can also override your overrides. The
22-
[section later in this topic on inheritance](#inheritance) goes into more detail
23-
on that.
16+
represent a collection of [features](/editions/features)
17+
with a default value (behavior) per feature. Features are options on a file,
18+
message, field, enum, and so on, that specify the behavior of protoc, the code
19+
generators, and protobuf runtimes. You can explicitly override a behavior at
20+
those different levels (file, message, field, ...) when your needs don't match
21+
the default behavior for the edition you've selected. You can also override your
22+
overrides. The [section later in this topic on inheritance](#inheritance) goes
23+
into more detail on that.
2424

2525
## Lifecycle of a Feature {#lifecycles}
2626

content/programming-guides/effective-protos.md

Lines changed: 0 additions & 127 deletions
This file was deleted.

content/programming-guides/extension_declarations.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ freshness: { owner: 'shaod' reviewed: '2023-09-06' }
1515
This page describes in detail what extension declarations are, why we need them,
1616
and how we use them.
1717

18-
**NOTE:** Extension declarations are used in proto2 only, as proto3 does not
19-
support extensions at this time.
18+
**NOTE:** Extension declarations are mostly used in proto2, as proto3 does not
19+
support extensions at this time (except for
20+
[declaring custom options](/programming-guides/proto3/#customoptions)).
2021

2122
If you need an introduction to extensions, read this
2223
[extensions guide](https://protobuf.dev/programming-guides/proto2/#extensions)
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Recommended Way to Downcast `proto2::Message`
2+
3+
go/protobuf-downcast-recommendation
4+
5+
<!--
6+
# Document freshness: For more information, see go/fresh-source.
7+
freshness: { owner: 'debadribasak' reviewed: '2023-10-10' }
8+
*-->
9+
10+
TL;DR: While downcasting `proto2::Message` to a generated type, use
11+
[`proto2::DynamicCastToGenerated`](https://source.corp.google.com/piper///depot/google3/third_party/protobuf/message.h;rcl=571241355;l=1530)
12+
where `dynamic_cast` is required and use
13+
[`proto2::DownCastToGenerated`](https://source.corp.google.com/piper///depot/google3/third_party/protobuf/message.h;rcl=571241355;l=1577)
14+
for all other types of cast (`static_cast`, C-style cast and
15+
[`down_cast`](https://source.corp.google.com/piper///depot/google3/base/casts.h;rcl=570699316;l=59))
16+
17+
## Problem {#problem}
18+
19+
If there are multiple `message` definitions inside a `.proto` file and only few
20+
of them are referenced, code is still generated and will contribute towards the
21+
final binary size. Code generated for unused Protocol Buffer messages is
22+
stripped in an effort to reduce the size of the final binary.
23+
24+
Implementing
25+
[weak-reflection-based messages](http://go/protobuf-weak-speed-messages)
26+
modifies code generation to define the `default_instance` of every generated
27+
type as a weak symbol, and defines each of them in a separate section. This
28+
makes the linker drop the unused sections from the final binary, resulting in a
29+
reduction in total size.
30+
31+
There can be false positives, such as when one `default_instance` gets dropped
32+
even if it is getting used inside code. This happens in the cases where a
33+
generated type is getting created using reflection but is used outside of
34+
reflection. This can lead to undefined behavior when a base `proto2::Message`
35+
that holds an object of a generated type is downcast to the corresponding
36+
generated type. This will make the type appear in the code, without its
37+
`default_instance` being present. This situation will arise when some built-in
38+
cast operators and functions are used, such as `static_cast`, `dynamic_cast`,
39+
C-style cast, and
40+
[`down_cast`](https://source.corp.google.com/piper///depot/google3/base/casts.h;rcl=570699316;l=59).
41+
42+
### Example of Undefined Behavior {#example-undefined}
43+
44+
Consider this proto definition:
45+
46+
```proto
47+
message MessageA {
48+
optional int32 value = 1;
49+
}
50+
51+
message MessageB {
52+
optional int32 value1 = 1;
53+
optional int32 value2 = 2 [default = 5];
54+
}
55+
```
56+
57+
Which is used in this C++ code:
58+
59+
```cpp {.bad}
60+
// Bad example
61+
proto2_unittest::protobuf_stripping::MessageA messageA;
62+
messageA.set_value(12345);
63+
const proto2::DescriptorPool* generated_pool =
64+
proto2::DescriptorPool::generated_pool();
65+
if (generated_pool == nullptr) return 1;
66+
proto2::Message* message =
67+
proto2::MessageFactory::generated_factory()
68+
->GetPrototype(
69+
proto2::DescriptorPool::generated_pool()->FindMessageTypeByName(
70+
"proto2_unittest.protobuf_stripping.MessageB"))
71+
->New();
72+
const proto2::Reflection* reflection = message->GetReflection();
73+
const proto2::FieldDescriptor* field =
74+
message->GetDescriptor()->FindFieldByName("value1");
75+
reflection->SetInt32(message, field, 123);
76+
const auto* messageB =
77+
static_cast<proto2_unittest::protobuf_stripping::MessageB*>(message);
78+
std::cout << messageB->value2();
79+
```
80+
81+
In this example, `MessageA` is getting directly used, so its `default_instance`
82+
would not get dropped. But `MessageB` is getting generated using its descriptor
83+
and the type is never used inside the code. There is no way for the linker to
84+
know about the usage and its `default_instance` will get dropped. The code sets
85+
`value1` using reflection, but `value2` is never set. `value2` has a default
86+
value of 5 which would have been the case if `default_instance` was pinned. But
87+
in this example the output statement where we print the `value2` field will have
88+
some default value (like 0).
89+
90+
## Solution {#solution}
91+
92+
To prevent undefined behavior related to downcasting, use two functions:
93+
[`proto2::DynamicCastToGenerated`](https://source.corp.google.com/piper///depot/google3/third_party/protobuf/message.h;rcl=571241355;l=1530)
94+
and
95+
[`proto2::DownCastToGenerated`](https://source.corp.google.com/piper///depot/google3/third_party/protobuf/message.h;rcl=571241355;l=1577).
96+
97+
These functions perform type-pinning, which ensures that the `default_instance`
98+
of the destination type is not dropped from the final binary.
99+
100+
### Usage Instructions {#usage}
101+
102+
#### `proto2::DynamicCastToGenerated` {#DynamicCastToGenerated}
103+
104+
Use `proto2::DynamicCastToGenerated` only when it is necessary to check whether
105+
the destination type is a derived type, such as a child class of
106+
`proto2::Message`. As it uses `dynamic_cast` in its implementation, it can have
107+
performance impact where there is no necessity of type-checking. Therefore, it
108+
is recommended to use it in the places where `dynamic_cast` should be used.
109+
110+
#### `proto2::DownCastToGenerated` {#DownCastToGenerated}
111+
112+
Use `proto2::DownCastToGenerated` when type-checking is not necessary.
113+
Internally, this function performs `static_cast` with additional type-pinning.
114+
Therefore, it is recommended to use in places where the caller is certain that
115+
the destination type is a derived type. It should be used where `static_cast`,
116+
C-style cast or `down_cast` is being used currently for downcasting
117+
`proto2::Message` to a derived type.
118+
119+
### Examples {#examples}
120+
121+
#### Case 1: Type-checking is not Necessary {#case-1}
122+
123+
```c++ {.bad}
124+
// Bad example
125+
Example* DowncastToExample(proto2::Message* message) {
126+
return static_cast<Example*>(message);
127+
}
128+
```
129+
130+
```c++ {.good}
131+
// Good example
132+
Example* DowncastToExample(proto2::Message* message) {
133+
return proto2::DownCastToGenerated<Example>(message);
134+
}
135+
```
136+
137+
#### Case 2: Type-checking is Required {#case-2}
138+
139+
Here the template parameter `T` can be any type. The function will compile only
140+
if it is a type derived from `proto2::Message`
141+
142+
```c++ {.bad}
143+
// Bad example
144+
template<typename T>
145+
T* DowncastToExample(proto2::Message* message) {
146+
return dynamic_cast<T*>(message);
147+
}
148+
```
149+
150+
```c++ {.good}
151+
// Good example
152+
template<typename T>
153+
T* DowncastToExample(proto2::Message* message) {
154+
return proto2::DynamicCastToGenerated<T>(message);
155+
}
156+
```

0 commit comments

Comments
 (0)