@ -30,10 +30,14 @@
# include "google/protobuf/compiler/retention.h"
# include <algorithm>
# include <memory>
# include <string>
# include <utility>
# include <vector>
# include "absl/container/flat_hash_set.h"
# include "absl/types/span.h"
# include "google/protobuf/descriptor.h"
# include "google/protobuf/dynamic_message.h"
@ -43,24 +47,38 @@ namespace compiler {
namespace {
// Recursively strips any options with source retention from the message.
void StripMessage ( Message & m ) {
// Recursively strips any options with source retention from the message. If
// stripped_paths is not null, then this function will populate it with the
// paths that were stripped, using the path format from
// SourceCodeInfo.Location. The path parameter is used as a stack tracking the
// path to the current location.
void StripMessage ( Message & m , std : : vector < int > & path ,
std : : vector < std : : vector < int > > * stripped_paths ) {
const Reflection * reflection = m . GetReflection ( ) ;
std : : vector < const FieldDescriptor * > fields ;
reflection - > ListFields ( m , & fields ) ;
for ( const FieldDescriptor * field : fields ) {
path . push_back ( field - > number ( ) ) ;
if ( field - > options ( ) . retention ( ) = = FieldOptions : : RETENTION_SOURCE ) {
reflection - > ClearField ( & m , field ) ;
if ( stripped_paths ! = nullptr ) {
stripped_paths - > push_back ( path ) ;
}
} else if ( field - > type ( ) = = FieldDescriptor : : TYPE_MESSAGE ) {
if ( field - > is_repeated ( ) ) {
int field_size = reflection - > FieldSize ( m , field ) ;
for ( int i = 0 ; i < field_size ; + + i ) {
StripMessage ( * reflection - > MutableRepeatedMessage ( & m , field , i ) ) ;
path . push_back ( i ) ;
StripMessage ( * reflection - > MutableRepeatedMessage ( & m , field , i ) , path ,
stripped_paths ) ;
path . pop_back ( ) ;
}
} else {
StripMessage ( * reflection - > MutableMessage ( & m , field ) ) ;
StripMessage ( * reflection - > MutableMessage ( & m , field ) , path ,
stripped_paths ) ;
}
}
path . pop_back ( ) ;
}
}
@ -72,18 +90,23 @@ void StripMessage(Message& m) {
// Using a dynamic message allows us to see these custom options. To convert
// back and forth between the generated type and the dynamic message, we have
// to serialize one and parse that into the other.
void ConvertToDynamicMessageAndStripOptions ( Message & m ,
const DescriptorPool & pool ) {
//
// If stripped_paths is not null, it will be populated with the paths that were
// stripped, using the path format from SourceCodeInfo.Location.
void ConvertToDynamicMessageAndStripOptions (
Message & m , const DescriptorPool & pool ,
std : : vector < std : : vector < int > > * stripped_paths = nullptr ) {
// We need to look up the descriptor in the pool so that we can get a
// descriptor which knows about any custom options that were used in the
// .proto file.
const Descriptor * descriptor = pool . FindMessageTypeByName ( m . GetTypeName ( ) ) ;
std : : vector < int > path ;
if ( descriptor = = nullptr ) {
// If the pool does not contain the descriptor, then this proto file does
// not transitively depend on descriptor.proto, in which case we know there
// are no custom options to worry about.
StripMessage ( m ) ;
StripMessage ( m , path , stripped_paths ) ;
} else {
DynamicMessageFactory factory ;
std : : unique_ptr < Message > dynamic_message (
@ -91,7 +114,7 @@ void ConvertToDynamicMessageAndStripOptions(Message& m,
std : : string serialized ;
ABSL_CHECK ( m . SerializeToString ( & serialized ) ) ;
ABSL_CHECK ( dynamic_message - > ParseFromString ( serialized ) ) ;
StripMessage ( * dynamic_message ) ;
StripMessage ( * dynamic_message , path , stripped_paths ) ;
ABSL_CHECK ( dynamic_message - > SerializeToString ( & serialized ) ) ;
ABSL_CHECK ( m . ParseFromString ( serialized ) ) ;
}
@ -118,12 +141,72 @@ auto StripLocalOptions(const DescriptorType& descriptor) {
return options ;
}
// Returns true if x is a prefix of y.
bool IsPrefix ( absl : : Span < const int > x , absl : : Span < const int > y ) {
return x = = y . subspan ( 0 , x . size ( ) ) ;
}
// Strips the paths in stripped_paths from the SourceCodeInfo.
void StripSourceCodeInfo ( std : : vector < std : : vector < int > > & stripped_paths ,
SourceCodeInfo & source_code_info ) {
RepeatedPtrField < SourceCodeInfo : : Location > * locations =
source_code_info . mutable_location ( ) ;
// We sort the locations lexicographically by their paths and include an
// index pointing back to the original location.
std : : vector < std : : pair < absl : : Span < const int > , int > > sorted_locations ;
sorted_locations . reserve ( locations - > size ( ) ) ;
for ( int i = 0 ; i < locations - > size ( ) ; + + i ) {
sorted_locations . emplace_back ( ( * locations ) [ i ] . path ( ) , i ) ;
}
absl : : c_sort ( sorted_locations ) ;
absl : : c_sort ( stripped_paths ) ;
// With both arrays sorted, we can efficiently step through them in tandem.
// If a stripped path is a prefix of any location, then that is a location
// we need to delete from the SourceCodeInfo.
absl : : flat_hash_set < int > indices_to_delete ;
auto i = stripped_paths . cbegin ( ) ;
auto j = sorted_locations . cbegin ( ) ;
while ( i ! = stripped_paths . cend ( ) & & j ! = sorted_locations . cend ( ) ) {
if ( IsPrefix ( * i , j - > first ) ) {
indices_to_delete . insert ( j - > second ) ;
+ + j ;
} else if ( * i < j - > first ) {
+ + i ;
} else {
+ + j ;
}
}
// We delete the locations in descending order to avoid invalidating
// indices.
std : : vector < SourceCodeInfo : : Location * > old_locations ;
old_locations . resize ( locations - > size ( ) ) ;
locations - > ExtractSubrange ( 0 , locations - > size ( ) , old_locations . data ( ) ) ;
locations - > Reserve ( old_locations . size ( ) - indices_to_delete . size ( ) ) ;
for ( int i = 0 ; i < old_locations . size ( ) ; + + i ) {
if ( indices_to_delete . contains ( i ) ) {
delete old_locations [ i ] ;
} else {
locations - > AddAllocated ( old_locations [ i ] ) ;
}
}
}
} // namespace
FileDescriptorProto StripSourceRetentionOptions ( const FileDescriptor & file ) {
FileDescriptorProto StripSourceRetentionOptions ( const FileDescriptor & file ,
bool include_source_code_info ) {
FileDescriptorProto file_proto ;
file . CopyTo ( & file_proto ) ;
ConvertToDynamicMessageAndStripOptions ( file_proto , * file . pool ( ) ) ;
std : : vector < std : : vector < int > > stripped_paths ;
ConvertToDynamicMessageAndStripOptions ( file_proto , * file . pool ( ) ,
& stripped_paths ) ;
if ( include_source_code_info ) {
file . CopySourceCodeInfoTo ( & file_proto ) ;
StripSourceCodeInfo ( stripped_paths , * file_proto . mutable_source_code_info ( ) ) ;
}
return file_proto ;
}