Fix slow handling of protobuf comments (#955)
This syncs cl/613879585 and cl/614589429.
Closes #935.
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index 1876f2a..06488bb 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -13,6 +13,8 @@
and values are now handled to generate Dart `@deprecated` annotations.
([#900], [#908])
* `protoc_plugin` and generated files now require Dart 3.3.0. (#953)
+* Fix performance issues when handling documentation comments in protobufs.
+ ([#935], [#955])
[#738]: https://github.com/google/protobuf.dart/issues/738
[#903]: https://github.com/google/protobuf.dart/pull/903
@@ -22,6 +24,8 @@
[#909]: https://github.com/google/protobuf.dart/pull/909
[#908]: https://github.com/google/protobuf.dart/pull/908
[#953]: https://github.com/google/protobuf.dart/pull/953
+[#935]: https://github.com/google/protobuf.dart/pull/935
+[#955]: https://github.com/google/protobuf.dart/pull/955
## 21.1.2
diff --git a/protoc_plugin/lib/src/shared.dart b/protoc_plugin/lib/src/shared.dart
index 8fe66bb..7ecdb01 100644
--- a/protoc_plugin/lib/src/shared.dart
+++ b/protoc_plugin/lib/src/shared.dart
@@ -6,6 +6,7 @@
import 'dart:io';
import '../protoc.dart';
+import '../src/generated/descriptor.pb.dart';
const protobufImportPrefix = r'$pb';
const asyncImportPrefix = r'$async';
@@ -14,6 +15,20 @@
const mixinImportPrefix = r'$mixin';
extension FileDescriptorProtoExt on FileGenerator {
+ bool _listEquals(List<int> a, List<int> b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ // Note: paths are much likely to share common prefixes than to share common
+ // suffixes, so it's probably faster to run this loop backwards ;)
+ for (var i = a.length - 1; i >= 0; i--) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/// Convert leading comments of a definition at [path] to Dart doc comment
/// syntax.
///
@@ -23,18 +38,22 @@
/// The output can contain multiple lines. None of the lines will have
/// trailing whitespace.
String? commentBlock(List<int> path) {
- final bits = descriptor.sourceCodeInfo.location
- .where((element) => element.path.toString() == path.toString())
- .toList();
-
- if (bits.length == 1) {
- final match = bits.single;
- return toDartComment(match.leadingComments);
+ SourceCodeInfo_Location? singleMatch;
+ for (final location in descriptor.sourceCodeInfo.location) {
+ if (_listEquals(location.path, path)) {
+ if (singleMatch == null) {
+ singleMatch = location;
+ } else {
+ // TODO: evaluate if we should just concatenate all of the matching
+ // entries.
+ stderr.writeln('Too many source code locations. Skipping.');
+ return null;
+ }
+ }
}
- if (bits.length > 1) {
- // TODO: evaluate if we should just concatenate all of the entries.
- stderr.writeln('Too many source code locations. Skipping.');
+ if (singleMatch != null) {
+ return toDartComment(singleMatch.leadingComments);
}
return null;