iemejia commented on code in PR #3863:
URL: https://github.com/apache/avro/pull/3863#discussion_r3565200040
##########
lang/php/lib/Datum/AvroIOBinaryDecoder.php:
##########
@@ -65,9 +73,37 @@ public function readBoolean(): bool
*/
public function read(int $len): string
{
+ if ($len < 0) {
+ // AvroStringIO::read() accepts a negative length and moves the
+ // pointer backwards; reject it before delegating.
+ throw new AvroException("Cannot read a negative number of bytes:
{$len}");
+ }
+ if ($len > self::MAX_UNCHECKED_READ) {
+ $remaining = $this->bytesRemaining();
+ if ($len > $remaining) {
+ throw new AvroException("Cannot read {$len} bytes, only
{$remaining} remaining.");
+ }
+ }
+
return $this->io->read($len);
}
+ /**
+ * Number of bytes still available to read, determined by seeking to the
end
+ * and restoring the position (which both the string and file IO
+ * implementations support). Used to reject a declared length or collection
+ * block count that exceeds the data actually available before allocating.
+ */
+ public function bytesRemaining(): int
+ {
+ $current = $this->io->tell();
+ $this->io->seek(0, AvroIO::SEEK_END);
+ $end = $this->io->tell();
+ $this->io->seek($current, AvroIO::SEEK_SET);
+
+ return $end - $current;
+ }
Review Comment:
Fixed in f3a4764: bytesRemaining() clamps to 0 via max(0, ...), so a
position past EOF no longer yields a negative remaining count.
##########
lang/php/lib/Datum/AvroIODatumReader.php:
##########
@@ -538,4 +541,80 @@ private function readDecimal(string $bytes, int $scale):
string
return (string) ($scale > 0 ? ($int / (10 ** $scale)) : $int);
}
+
+ /**
+ * Minimum number of bytes a single value of the given schema can occupy on
+ * the wire. Used to reject an array/map block count that could not be
backed
+ * by the bytes remaining. A type that can encode to zero bytes (null)
+ * returns 0, which disables the collection check for it (so an array of
+ * nulls is not falsely rejected). Types that cannot be resolved cheaply
+ * default to 1, which is safe because the only zero-byte primitive is
null.
+ *
+ * @param array<int, bool> $visited
+ */
+ private static function minBytesPerElement(mixed $schema, array $visited =
[]): int
+ {
+ $type = $schema instanceof AvroSchema ? $schema->type() : $schema;
+ // Named/complex field types may nest a schema object; unwrap one
level.
+ if ($type instanceof AvroSchema) {
+ return self::minBytesPerElement($type, $visited);
+ }
+ if (!is_string($type)) {
+ return 1;
+ }
+ switch ($type) {
+ case AvroSchema::NULL_TYPE:
+ return 0;
+ case AvroSchema::FLOAT_TYPE:
+ return 4;
+ case AvroSchema::DOUBLE_TYPE:
+ return 8;
+ case AvroSchema::FIXED_SCHEMA:
+ return $schema instanceof AvroFixedSchema ? $schema->size() :
1;
+ case AvroSchema::RECORD_SCHEMA:
+ case AvroSchema::ERROR_SCHEMA:
+ if (!$schema instanceof AvroRecordSchema) {
+ return 1;
+ }
+ $id = spl_object_id($schema);
+ if (isset($visited[$id])) {
+ return 0; // break recursion for self-referencing schemas
+ }
Review Comment:
Fixed in f3a4764: minBytesPerElement() returns 1 (not 0) on a
self-referencing cycle, keeping the guard conservative for arrays/maps of
recursive records.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]