[BUG] Přetečení v TemplateHelpers::Bytes

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
DocX
Člen | 154
+
0
-

Ahoj,

narazil jsem na chybku v template helperu bytes (TemplateHelpers.php 213 2009–02–13 18:44:46Z). Když předávám číslo, které se nevejde do intu (něco přes 2G) tak to přeteče a výsledkem je něco hnusného :)

U sebe jsem to vyřešil úpravou kódu z

<?php
	public static function bytes($bytes) {
		$bytes = (int) $bytes;
		...
?>

na

<?php
	public static function bytes($bytes) {
		$bytes = round((float) $bytes, 0);
		...
?>

Je to asi o něco náročnější, ale jinak ta funkce nedává smysl (hlavně ty předpony TB a PT) :).

Editoval DocX (2. 5. 2009 11:19)

PetrP
Člen | 587
+
0
-

Hmmm mě to ‚přetejka‘ až při PB.

$b = 1;Debug::timer('bytestest');
for($i=0;$i<10;$i++)
{
	d(TemplateHelpers::bytes($b),$b);
	$b*=1024;
}
d(Debug::timer('bytestest'));
// momentální stav
array(2) {
  0 => string(3) "1 B"
  1 => int(1)
}
array(2) {
  0 => string(4) "1 kB"
  1 => int(1024)
}
array(2) {
  0 => string(4) "1 MB"
  1 => int(1048576)
}
array(2) {
  0 => string(4) "1 GB"
  1 => int(1073741824)
}
array(2) {
  0 => string(4) "1 TB"
  1 => int(1099511627776)
}
array(2) {
  0 => string(4) "1 PB"
  1 => int(1125899906842624)
}
array(2) {
  0 => string(4) "1 PB"
  1 => int(1152921504606846976)
}
array(2) {
  0 => string(3) "0 B"
  1 => float(1.1805916207174E+21)
}
array(2) {
  0 => string(3) "0 B"
  1 => float(1.2089258196146E+24)
}
array(2) {
  0 => string(3) "0 B"
  1 => float(1.2379400392854E+27)
}
float(0.00072598457336426)
// další časy
float(0.00074386596679688)
float(0.00075221061706543)
float(0.00074505805969238)
float(0.00078892707824707)
float(0.00074315071105957)
// po přepsání $bytes = round((float) $bytes, 0);
array(2) {
  0 => string(3) "1 B"
  1 => int(1)
}
array(2) {
  0 => string(4) "1 kB"
  1 => int(1024)
}
array(2) {
  0 => string(4) "1 MB"
  1 => int(1048576)
}
array(2) {
  0 => string(4) "1 GB"
  1 => int(1073741824)
}
array(2) {
  0 => string(4) "1 TB"
  1 => int(1099511627776)
}
array(2) {
  0 => string(4) "1 PB"
  1 => int(1125899906842624)
}
array(2) {
  0 => string(4) "1 PB"
  1 => int(1152921504606846976)
}
array(2) {
  0 => string(7) "1024 PB"
  1 => float(1.1805916207174E+21)
}
array(2) {
  0 => string(10) "1048576 PB"
  1 => float(1.2089258196146E+24)
}
array(2) {
  0 => string(13) "1073741824 PB"
  1 => float(1.2379400392854E+27)
}
float(0.00075197219848633)
// další časy
float(0.00076603889465332)
float(0.00076198577880859)
float(0.00075793266296387)
float(0.00076508522033691)
float(0.00075292587280273)

Takže jestli je nějaké zpomalení tak je nemeřitelné.

Nicméně je tam nějaká chybka ještě protože 1152921504606846976 = 1024PB a ne 1PB

Editoval PetrP (15. 5. 2009 10:17)

DocX
Člen | 154
+
0
-

Na začátek díky za podrobné změření.

PetrP napsal(a):

Hmmm mě to ‚přetejka‘ až při PB.

Většímu intu zřejmě vděčíš 64-bitovému systému.

Nicméně je tam nějaká chybka ještě protože 1152921504606846976 = 1024PB a ne 1PB

Co se týče chyby s PB, tak jde o ten poslední krok, kdy je v $unit PB, kde $bytes zmenší o řád a čeká na další předponu, která to vykompenzuje. Ta už ale není.

Mám tedy takovýto návrh:

<?php
	public static function bytes($bytes)
        {
		// v trunku už je round, ale asi bych tam nechal
		// i přetypování, když by tam někdo vkládal něco jiného
                $bytes = round((float)$bytes);
                $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
                foreach ($units as $unit) {
                        if (abs($bytes) < 1024 || $unit == 'PB') break;
			$bytes = $bytes / 1024;
                }
                return round($bytes, 2) . ' ' . $unit;
        }
?>
David Grudl
Nette Core | 8228
+
0
-

fixed

DocX
Člen | 154
+
0
-

Až teď jsem si všiml, že je tam stále problém s následujícím:

<?php

echo TemplateHelpers::bytes(pow(1024,5)) // 1PB
echo TemplateHelpers::bytes(pow(1024,6)) // 1PB (správně 1024PB)
echo TemplateHelpers::bytes(pow(1024,7)) // 1024PB (správně 1048576PB)

?>

řešení:

<?php
public static function bytes($bytes, $precision = 2)
{
	$bytes = round($bytes);
	$units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
	foreach ($units as $unit) {
-		if (abs($bytes) < 1024) break;
+		if (abs($bytes) < 1024 || $unit == 'PB') break;
		$bytes = $bytes / 1024;
	}
	return round($bytes, $precision) . ' ' . $unit;
}
?>

Je to sice okrajová funkce, ale chyba může způsobit docela velké problémy :)

Editoval DocX (18. 8. 2009 14:08)

David Grudl
Nette Core | 8228
+
0
-

fixed