Vi è mai capitato di dover eseguire in una vostra applicazione codice javascript introdotto dall’utente o comunque generato a runtime in qualche modo?
O di dover calcolare il risultato di un’espressione aritmetica contenuta in una stringa?
Per questi casi javascript dispone della funzione eval(), a cui basta passare una stringa contenente codice javascript per eseguirlo e ricevere poi l’eventuale output.
Il problema è che l’uso di eval() è molto rischioso e va utilizzato con grande attenzione.
In generale, usarlo per eseguire codice definito dall’utente o comunque non noto al programmatore, amplifica enormemente questi rischi e costituisce un serio problema alla sicurezza delle applicazioni.
Certo, ci sono diverse accortezze da utilizzare per rendere l’impiego di eval() il più sicuro possibile, a cominciare dalla chiamata indiretta (che sembra essere la modalità usata in IDC), ma renderlo completamente sicuro è praticamente impossibile.
A me è capitato in due occasioni di dover implementare in un software una caratteristica del genere e non vi nascondo che il pensiero di dover usare eval() non mi rendeva affatto tranquillo.
Ho quindi iniziato a cercare un’alternativa che fosse in grado di azzerare i rischi connessi a questo tipo di attività e cercando sul web ho trovato un componente nodejs che per me ha rappresentato la soluzione perfetta del problema.
Si chiama “sval” e permette di eseguire codice javascript in una maniera completamente sicura, isolandolo totalmente dall’applicazione e dal browser in cui è eseguito.
Lo potete trovare qui: sval - npm
In breve, è un interprete javascript scritto in javascript stesso, in grado di eseguire in maniera autonoma (ossia non ricorrendo all’engine che sta facendo girare l’applicazione da cui lo richiamiamo), provvedendo internamente al parsing ed al run del codice che gli viene passato.
Nessuna possibilità di accedere agli oggetti del contesto in cui gira l’applicazione, nemmeno quello globale, pur mantenendo la possibilità di implementare un “dialogo” in entrambi i sensi tra l’applicazione principale e quella fatta eseguire a sval, in una maniera completamente controllata dal programmatore.
L’uso è estremamente semplice; il componente ha pochissimi metodi e un costruttore a cui passare una serie di opzioni per configurarlo.
Per utilizzarlo in un progetto IDC, è innanzitutto necessario importarlo nel nostro server al solito modo (trovate tutte le istruzioni del caso nella documentazione di ProGamma) e poi crearne un’istanza nella nostra applicazione:
let SVAL = require(“sval”);
a questo punto, possiamo configurarlo secondo le nostre esigenze, ad esempio:
let JS = new SVAL({
ecmaVer : ‘latest’,
sourceType : ‘script’,
sandBox : true
});
in questo caso, viene impostato per lavorare completamente in modalità sandbox (senza alcun collegamento con l’ambiente in cui è eseguito), utilizzando la versione di javascript più recente di cui il componente dispone.
A questo punto non resta che fargli eseguire uno script:
JS.run(‘let x = 2 + 2;’);
per recuperare l’eventuale output, è possibile passare oggetti o variabili dallo script all’applicazione principale tramite l’oggetto export dell’interprete:
JS.run(‘let x = 2 + 2; exports.x = x;’);
console.log(JS.exports.x);
É inoltre possibile pre-compilare uno script ed eseguirlo poi saltando questa fase, velocizzando le operazioni in alcuni casi.
Davvero molto semplice da usare e completamente sicuro.
Occorre però tener presente un paio considerazioni per poterlo impiegare correttamente.
La prima, com’è facilmente immaginabile, è che la velocità di esecuzione non è particolarmente alta.
Essendo un interprete a sé stante, sval non beneficia di alcun vantaggio prestazionale offerto dall’engine di nodejs, il V8 JavaScript Engine.
In più, l’interprete è scritto in javascript puro, il che ovviamente si riflette (in negativo) sulla velocità di esecuzione.
Per piccole porzioni di codice o per brevi cicli va più che bene, ma fargli eseguire più di qualche decina di righe di codice, magari in loop, non è un’idea particolarmente brillante.
In quest’ultimo caso, un piccolo aiuto può venire dall’uso del metodo parse, che effettua il pre-parsing di uno script così da evitare che questo avvenga al run.
Negli scenari in cui il run viene richiamato all’interno di un ciclo, questo può dare una mano a ridurre i tempi di esecuzione.
Inoltre, occorre notare che il contesto di esecuzione viene creato quando si instanzia un oggetto sval e mantenuto finché l’oggetto persiste nel suo scope, non quando si esegue il run.
Questo vuol dire che se richiamiamo il metodo run più volte, ciò che viene definito tra le esecuzioni si aggiunge al contesto dell’oggetto su cui il run è chiamato.
In pratica, se in una run definiamo una variabile, un’oggetto, una funzione o una classe, questi poi ce li ritroviamo nelle run successive nello stesso scope e se per caso (o per uso troppo disinvolto) tentiamo di ri-definirli, ci beccheremo un errore di ridefinizione non permessa.
Per evitare ciò possiamo ricorrere a diverse strategie; utilizzare più istanze dell’interprete (una per run), scrivere gli snippet in maniera tale da evitare ridefinizioni ecc.
La scelta migliore dipende ovviamente dal contesto specifico in cui utilizzate sval e va valutata caso per caso.
Per ulteriori esempi e una stringata (ma completa) panoramica dei (pochissimi) metodi di sval, potete fare riferimento alla documentazione presente all’indirizzo di cui sopra.
Per vederlo in azione in un progetto IDC, potete lanciare il progetto condiviso “sval-calc”, che realizza una piccola calcolatrice programmabile, in grado di calcolare il risultato di formule caricate in una datamap.