Legitimní užití evalu

Školení, která pořádám

Při odstraňování užití funkce eval jsem narazil na zajímavý kód:

function objectConverter(columns) {
	return new Function("d", "return {" + columns.map(function(name, i) {
		return JSON.stringify(name) + ": d[" + i + "]";
	}).join(",") + "}");
}

Funkce se používá pro vytváření objektů při importu CSV. V prvním řádku CSV jsou názvy sloupců, např. a, b, c, tak tuto funkci zavoláme jako objectConverter(['a', 'b', 'c']). Ona vrátí takovouto funkci:

function(d) {
	return {"a": d[0], "b": d[1], "c": d[2]};
}

Tuto funkci pak voláme s jednotlivými řádky CSV a ona nám vytváří objekty mapující název sloupce na jeho hodnotu.

Přepsat funkci objectConverter tak, aby nepoužívala eval je jednoduché:

function objectConverter(columns) {
	return function(d) {
		return columns.reduce(function(result, name, i) {
			result[name] = d[i];
			return result;
		}, {});
	};
}

Bohužel je tato funkce dramaticky pomalejší než originální implementace: 1462 ms místo 376 ms na testovacích datech v Chrome. Problém je nejspíš v tom, že jednotlivé řádky se vytváří vždycky postupně z prázdného objektu. Nepřišel jsem na to, jak funkci udělat stejně efektivní jako originální implementaci.

Jakub Vrána, Řešení problému, 10.5.2017, diskuse: 9 (nové: 0)

Diskuse

Jan Tojnar:

Atom je stále rozbitý kvůli neuzavřenému tagu img v předchozím článku:

https://validator.w3.org/feed/check.cgi?url=…%2Fatom.php

ikona Jakub Vrána OpenID:

Díky za upozornění, opravil jsem to. Nějak jsem si odvykl psát XHTML.

v6ak:

Obecně jde o generování kódu, pak jsou podobné techniky přínosné. Vidím tu ale takový rule of thumb: Jak často je to potřeba? Pokud jen jednou při startu aplikace, pak je to nejspíš OK. (Nicméně by to pak šlo generovat staticky bez evalu…) Pokud častěji, stojí za zvážení, jestli tu není něco špatně.

ikona Jakub Vrána OpenID:

Tahle konkrétní funkce se volá jednou při parsování každého CSV. Z prvního řádku, kde jsou nadpisy sloupců, se vytvoří funkce, která pak generuje objekty pro ostatní řádky.

v6ak:

Což už není úplně ideální. V některých případech se to do mého rule of thumb vleze (zpracování jednoho souboru), při opakovaném použití ne. Tam by se mělo (aspoň teoreticky) vyplatit mít funkci vyššího řádu, která vytvoří parsovací funkci.

Dá se tu aplikovat ještě jeden bezpečnostní rule of thumb: Jak velký vliv má na vstup potenciální útočník? Což se ale na druhou stranu bez kontextu okolního kódu dá spíše odhadovat.

ikona Jakub Vrána OpenID:

Tohle právě je funkce vyššího řádu vytvářející konverzní funkci. Nejrychlejší je ovšem s evalem.

Tato funkce je očividně bezpečná. Žádný vstup nemůže vést ke spuštění jakéhokoliv kódu.

Problém je, že když z CSP vyhodíme 'unsafe-eval', tak žádný eval prostě nefunguje, i když bychom chtěli.

ikona Ondra:

Zajímalo by mne, jestli by se nějak měřitelně změnil čas, pokud by se namísto funkcionální redukce použil cyklus.

ikona Jakub Vrána OpenID:

Zkoušel jsem to taky, výsledek byl nepatrně lepší, ale pořád na míle daleko od evalu.

ikona Ondra:

Díky za info!

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2017 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.