Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Capture d'erreurs et protection

11 views
Skip to first unread message

Yliur

unread,
Dec 18, 2011, 9:24:53 PM12/18/11
to

Bonjour

J'ai un traitement qui peut planter et je voudrais :
- Afficher un message d'erreur gentil à l'utilisateur.
- Éviter que la fonction soit interrompue (par exemple parce
qu'elle a autre chose à faire après).

Pour l'instant j'ai écrit quelque chose comme ça :

(handler-case
(là des trucs à faire...)
(serious-condition (e)
(format t "Erreur blablabla")
(print e)))

Les erreurs assez grave pour interrompre l'exécution devraient être
capturées par la clause serious-condition (e)
(du moins si ces erreurs héritent bien de serious-condition).

Et d'éventuelles conditions moins graves (en fait, n'héritant pas de
serious-condition, des avertissements par exemple) ne seraient pas
concernés, ce qui évite de tout casser si une fonction signale un simple
avertissement.

Protection du code :
- Je n'ai pas dit de bêtises ?
- Le code ne me paraît pas très bien protégé : comment éviter
qu'une condition qui n'hériterait pas de serious-condition ne
traverse ? Ou encore des branchements non dus à des conditions ?
Il y a bien unwind-protect qui permet de tout attraper, mais une
fois son "nettoyage" terminé le code ne continue pas à la suite du
bloc unwind-protect.

Informations sur l'erreur :
- Pour les erreurs standards de Common Lisp, y a-t-il un moyen de
récupérer à l'intérieur de l'objet représentant la condition un
texte un peu moins moche que celui obtenu par print par exemple
("#<SYSTEM::SIMPLE-DIVISION-BY-ZERO#x21B02226>") ? Le débogueur
affiche un message un peu plus joli, mais je ne sais pas s'il le
tire de l'objet lui-même ou si c'est lui qui le produit (il l'a
écrit en français, donc sans doute l'option 2).
- Y a-t-il un moyen de récupérer une pile d'appels, pas dans le
débogueur mais dans le programme, sous forme de chaîne ou de
liste, ... quelque chose qui pourrait par exemple être stocké
dans un journal ?

Merci

Yliur

Pascal J. Bourguignon

unread,
Dec 19, 2011, 10:18:57 AM12/19/11
to
Yliur <yl...@free.fr> writes:

> Bonjour
>
> J'ai un traitement qui peut planter et je voudrais :
> - Afficher un message d'erreur gentil à l'utilisateur.
> - Éviter que la fonction soit interrompue (par exemple parce
> qu'elle a autre chose à faire après).

Lire:

http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
http://chaitanyagupta.com/lisp/restarts.html
et bien sur le chapitre 9 de CLHS.



> Pour l'instant j'ai écrit quelque chose comme ça :
>
> (handler-case
> (là des trucs à faire...)
> (serious-condition (e)
> (format t "Erreur blablabla")
> (print e)))
>
> Les erreurs assez grave pour interrompre l'exécution devraient être
> capturées par la clause serious-condition (e)
> (du moins si ces erreurs héritent bien de serious-condition).

Une SERIOUS-CONDITION, ce n'est pas une erreur!


> Et d'éventuelles conditions moins graves (en fait, n'héritant pas de
> serious-condition, des avertissements par exemple) ne seraient pas
> concernés, ce qui évite de tout casser si une fonction signale un simple
> avertissement.

Signaler n'importe quelle condition qui n'est pas traitée ne casse rien,
l'exécution continue:

(handler-case
(progn (signal 'division-by-zero)
42)
(file-error () 'ha!))
--> 42


(handler-case
(progn (signal 'division-by-zero)
42)
(division-by-zero () 'ha!))
--> HA!


> Protection du code :
> - Je n'ai pas dit de bêtises ?
> - Le code ne me paraît pas très bien protégé : comment éviter
> qu'une condition qui n'hériterait pas de serious-condition ne
> traverse ?

On n'évite pas. Il ne faut pas éviter. Les conditions ne sont pas
toutes des erreurs. Il vaut mieux attraper au maximum les ERREUR, et
pas les SERIOUS-CONDITION. Par exemple, il est possible que lorsqu'il
n'y a plus de mémoire, une SERIOUS-CONDITION soit signalée, et attrapée
par une boucle de haut niveau, qui ferait appel au ramasse-miette. Si
tu t'intercale là, tu pourrais avoir des problèmes.

De toutes façons, une SERIOUS-CONDITION, ce n'est pas une erreur!



> Ou encore des branchements non dus à des conditions ?
> Il y a bien unwind-protect qui permet de tout attraper, mais une
> fois son "nettoyage" terminé le code ne continue pas à la suite du
> bloc unwind-protect.

Il faut le dire. Comment veux tu faire la différence entre:

- tout attraper et arrêter
- tout attraper et continuer

?

Et d'ailleurs, continuer où? On ne peut pas deviner, il faut que tu le
programme!

(loop
with done = nil
do (unwind-protect
(progn
(do-something)
(setf done t))
(clean-something))
until done)



> Informations sur l'erreur :
> - Pour les erreurs standards de Common Lisp, y a-t-il un moyen de
> récupérer à l'intérieur de l'objet représentant la condition un
> texte un peu moins moche que celui obtenu par print par exemple
> ("#<SYSTEM::SIMPLE-DIVISION-BY-ZERO#x21B02226>") ?

Utiliser ~A au lieu de ~S, princ au lieu de prin1.


> Le débogueur
> affiche un message un peu plus joli, mais je ne sais pas s'il le
> tire de l'objet lui-même ou si c'est lui qui le produit (il l'a
> écrit en français, donc sans doute l'option 2).

Regarder la documentation de la condition normalisé (ou spécifique à
l'implémentation). Ne pas oublier les super-classes!

Par exemple, DIVISION-BY-ZERO est une ARITHMETIC-ERROR, et une
ARITHMETIC-ERROR a deux attributs:
ARITHMETIC-ERROR-OPERATION et ARITHMETIC-ERROR-OPERANDS.

Cependant, l'erreur peut survenir dans des couches internes ou après
moultes optiomisation, alors ces attributs ne sont pas forcément en
relation avec les arguments et opérateurs donnés dans le programme:

CL-USER> (handler-case
(if (zerop (random 2))
(/ 0)
(/ 3 2 1 0))
(division-by-zero (err)
(format t "division by zero, operation: ~S, operands: ~S~%"
(arithmetic-error-operation err)
(arithmetic-error-operands err))))
division by zero, operation: /, operands: (3/2 0) ; on peut supposer la deuxième branche.
NIL
CL-USER> (handler-case
(if (zerop (random 2))
(/ 0)
(/ 3 2 1 0))
(division-by-zero (err)
(format t "division by zero, operation: ~S, operands: ~S~%"
(arithmetic-error-operation err)
(arithmetic-error-operands err))))
division by zero, operation: CCL::UNKNOWN, operands: NIL ; pas d'information,
NIL ; l'erreur est probablement optimisée directement dans la
; première branche.




> - Y a-t-il un moyen de récupérer une pile d'appels, pas dans le
> débogueur mais dans le programme, sous forme de chaîne ou de
> liste, ... quelque chose qui pourrait par exemple être stocké
> dans un journal ?

C'est possible, mais en utilisant des fonctions spécifiques à chaque
implémentation. Voir les sources de swank (slime), il y aurait peut
être moyen d'en extraire une bibliothèque de portabilité.





Finalement, bien que le standard donne quelques indications sur les
conditions qui peuvent ou doivent être signalées par les operateurs
standardisés, il y a beaucoup de flou, et peu de restart sont
standardisés.


Par exemple, il n'est pas dit que dans le cas d'un appel à cl:/ tel que:

(cl:/ 1 2 3 0 4 5 6)

on ait des restarts tels que:

IGNORE-ZERO
GIVE-NEW-VALUE-FOR-ARGUMENT
GIVE-DIVISION-RESULT

(notament, car le standard veut laisser des possibilités
d'optimisation; cl:/ peut changer l'ordre des diviseurs, peut calculer
la division à la compilation si les operandes sont des constantes, etc).



Donc on obtient des listes de restarts complètement divergentes:

[pjb@kuiper :0 ~]$ clall -r '(block :divide (handler-bind ((division-by-zero (lambda (err) (return-from :divide (mapcar (function restart-name) (compute-restarts err)))))) (/ 1 2 3 0 4 5 6)))'

International Allegro CL Free Express Edition --> (EXCL::RETRY EXCL::SKIP EXCL::RECOMPILE-DUE-TO-INCOMPATIBLE-FASL ABORT)
Clozure Common Lisp --> (CCL::RETRY-LOAD CCL::SKIP-LOAD CCL::LOAD-OTHER CONTINUE ABORT ABORT-BREAK ABORT)
CLISP --> (SYSTEM::SKIP RETRY SYSTEM::STOP)
CMU Common Lisp --> (CONTINUE ABORT)
ECL --> (CONTINUE ABORT)
SBCL --> (CONTINUE ABORT ABORT QUIT)

========================================================================


On ne peut pas traiter les erreurs automatiquement directement au niveau
des opérateurs standardisés. Il faut le faire dans ses propres
fonctions. On peut écrire des emballages pour le faire. Par exemple:


(defun my-/ (numerator &rest denominators)
(let ((division-result))
(restart-case
(loop
:with new-denominator
:for denominator :in denominators
:for result = numerator
:then (restart-case
(/ result denominator)
(ignore-zero ()
:report (lambda (stream) (format stream "Ignore zero denominator"))
result)
(give-new-value-for-argument (new-denominator)
:report (lambda (stream) (format stream "Give new value for zero denominator"))
:interactive (lambda ()
(format *query-io* "Enter the new denominator: ")
(finish-output *query-io*)
(list (read *query-io*)))
(/ result new-denominator)))
:finally (return result))
(give-division-result (division-result)
:report (lambda (stream) (format stream "Give division result"))
:interactive (lambda ()
(format *query-io* "Enter the division result: ")
(finish-output *query-io*)
(list (read *query-io*)))
division-result))))



On peut alors traiter les erreurs de division par zéro en invoquant le
restart que l'on veut, ce qui fera faire à l'emballage le traitement
prévu dans ces différents cas:


(handler-bind
((division-by-zero (lambda (err)
(declare (ignore err))
(invoke-restart (find-restart 'ignore-zero)))))
(my-/ 1 2 3 0 4 5 6))
--> 1/360


(handler-bind
((division-by-zero (lambda (err)
(declare (ignore err))
(invoke-restart (find-restart 'give-new-value-for-argument) 1/1000000))))
(my-/ 1 2 3 0 4 5 6))
--> 25000/9


(handler-bind
((division-by-zero (lambda (err)
(declare (ignore err))
(invoke-restart (find-restart 'give-division-result) 'infinity))))
(my-/ 1 2 3 0 4 5 6))
--> INFINITY


Encore une fois, le standard ne spécifie pas de cas de restart pour les
operateurs standard en général (les conditions et restarts sont un ajout
tardif), d'où la nécessité de ces emballages, ou en pratique, de gérer
les erreurs au niveau du code utilisateur, pas au niveau des operateurs
standards.

Si une fonction n'est pas conçue pour être restartable, elle ne peut
pas l'être.

On pourrait définir un paquetage exportant des emballages pour tous les
operateurs standard, et l'utiliser au lieu de CL, mais ça ne rendrait
pas les fonctions les utilisant restartable, seulement, les emballages.
La difficulté, c'est de décider quel restart et quelle valeurs
utiliser. Si une fonction fait une division par zéro, le restart à
choisir, ou le résultat à retourner, dépend de la fonction. Sans la
connaitre, on ne peut pas choisir. Veut on substituer 1 pour 0? Veut
on retourner un grand nombre? Un symbole INFINITY?



Finalement, il y a aussi: IGNORE-ERRORS ;-)

--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.

Yliur

unread,
Dec 19, 2011, 9:56:45 PM12/19/11
to
Le Mon, 19 Dec 2011 16:18:57 +0100
"Pascal J. Bourguignon" <p...@informatimago.com> a écrit :

> Yliur <yl...@free.fr> writes:
>
> > Bonjour
> >
> > J'ai un traitement qui peut planter et je voudrais :
> > - Afficher un message d'erreur gentil à l'utilisateur.
> > - Éviter que la fonction soit interrompue (par exemple parce
> > qu'elle a autre chose à faire après).
>
> Lire:
>
> http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
> http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
> http://chaitanyagupta.com/lisp/restarts.html
> et bien sur le chapitre 9 de CLHS.

Je venais de relire le premier. Mais c'est assez difficile de tout
saisir juste en lisant plein de doc. Et de passer à la pratique. Je
viens de lire les autres. Merci pour les liens.
D'accord, donc c'est uniquement le protocole suivi qui compte
(notamment le fait d'utiliser signal ou error, qui ne font pas la même
chose quand la condition n'est pas gérée).


> > Protection du code :
> > - Je n'ai pas dit de bêtises ?
> > - Le code ne me paraît pas très bien protégé : comment éviter
> > qu'une condition qui n'hériterait pas de serious-condition ne
> > traverse ?
>
> On n'évite pas. Il ne faut pas éviter. Les conditions ne sont pas
> toutes des erreurs. Il vaut mieux attraper au maximum les ERREUR, et
> pas les SERIOUS-CONDITION.

Donc j'attrape les erreurs, sans savoir si elles allaient tout
casser ou si d'autres conditions qui ne sont pas des erreurs vont
causer des dégâts. En faisant la supposition raisonnable que le code
appelant ne fait pas trop n'importe quoi et que les erreurs qui
interrompent le fil du programme sont de type error (sous-types
compris). Ça me permet d'attraper les erreurs pour les afficher
calmement ailleurs.


> Par exemple, il est possible que lorsqu'il
> n'y a plus de mémoire, une SERIOUS-CONDITION soit signalée, et
> attrapée par une boucle de haut niveau, qui ferait appel au
> ramasse-miette. Si tu t'intercale là, tu pourrais avoir des
> problèmes.
>
> De toutes façons, une SERIOUS-CONDITION, ce n'est pas une erreur!

C'est en cherchant dans la spécification que j'ai trouvé les parents de
error et j'ai vu cette phrase dans la description de serious-condition :
"All conditions serious enough to require interactive intervention if
not handled should inherit from the type serious-condition."
(http://www.lispworks.com/documentation/HyperSpec/Body/e_seriou.htm#serious-condition)

Et la note plus bas :
"Signaling a serious condition does not itself force entry into the
debugger. However, except in the unusual situation where the programmer
can assure that no harm will come from failing to handle a serious
condition, such a condition is usually signaled with error rather than
signal in order to assure that the program does not continue without
handling the condition. (And conversely, it is conventional to use
signal rather than error to signal conditions which are not serious
conditions, since normally the failure to handle a non-serious
condition is not reason enough for the debugger to be entered.) "

J'avais compris que les serious-condition étaient en principe signalées
avec error et qu'elles risquaient de tout casser, donc qu'il fallait
les capturer comme des erreurs.


Et dans un des liens plus haut on trouve cet exemple :
(http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html)
"And since any condition representing a stack overflow is going to be a
kind of SERIOUS-CONDITION, but not a kind of ERROR, the use of
IGNORE-ERRORS will succeed in trapping a file error but not a stack
overflow. If one wanted to catch serious conditions as well, one would
write instead:

(handler-case (open "some.file")
(serious-condition (c)
(values nil c)))
"

Est-ce que si la pile déborde je vais le manquer en ne capturant que
les erreurs ou est-ce qu'il y a un gestionnaire qui va s'occuper de
lever une erreur ? C'est le genre de choses dont je voudrais me
prémunir, si possible en récupérant le message associé.


> > Ou encore des branchements non dus à des conditions ?
> > Il y a bien unwind-protect qui permet de tout attraper, mais
> > une fois son "nettoyage" terminé le code ne continue pas à la suite
> > du bloc unwind-protect.
>
> Il faut le dire. Comment veux tu faire la différence entre:
>
> - tout attraper et arrêter
> - tout attraper et continuer
>
> ?
>
> Et d'ailleurs, continuer où? On ne peut pas deviner, il faut que tu
> le programme!
>
> (loop
> with done = nil
> do (unwind-protect
> (progn
> (do-something)
> (setf done t))
> (clean-something))
> until done)

C'est ce que je voudrais faire :) . Mais je ne sais pas comment. Le
mécanisme de unwind-protect capture tout bien comme il faut, mais
ensuite il laisse traverser le problème en remontant la pile. Et comme
ce n'est pas une macro mais un opérateur spécial, je n'ai as pu
regarder à l'intérieur comment il faisait pour tout capturer. A moins
que dans la clause de nettoyage je fasse un bond de côté en sortant de
unwind-protect (avec go ?) ? Comme je ne connais pas bien cette partie,
je ne sais pas si ça interromprait la redescente de la pile d'appels.

Le code ci-dessus ne semble pas empêcher la sortie de loop par exemple.
C'est ce que je cherche : comment arrêter la machine infernale dans
tous les cas.

(essaie-ça-mais-si-ça-rate-tant-pis-la-vie-continue-quoi-qui-puisse-arriver
(...))


> > Informations sur l'erreur :
> > - Pour les erreurs standards de Common Lisp, y a-t-il un moyen
> > de récupérer à l'intérieur de l'objet représentant la condition un
> > texte un peu moins moche que celui obtenu par print par
> > exemple ("#<SYSTEM::SIMPLE-DIVISION-BY-ZERO#x21B02226>") ?
>
> Utiliser ~A au lieu de ~S, princ au lieu de prin1.

D'accord, c'est beaucoup mieux, merci. J'ai vu passer dans la
spécification quelques indications sur la manière dont les messages
d'erreur sont stockés dans la condition.
"Optimisée directement" : le compilateur aurait remplacé l'opération
qui mène forcément à une erreur pas un simple signalement de l'erreur,
sans appeler du tout la fonction ?


> > - Y a-t-il un moyen de récupérer une pile d'appels, pas dans le
> > débogueur mais dans le programme, sous forme de chaîne ou de
> > liste, ... quelque chose qui pourrait par exemple être stocké
> > dans un journal ?
>
> C'est possible, mais en utilisant des fonctions spécifiques à chaque
> implémentation. Voir les sources de swank (slime), il y aurait peut
> être moyen d'en extraire une bibliothèque de portabilité.

D'accord, je note ça pour plus tard alors :) .
Pour l'instant je ne vais pas tâcher de redémarrer ce qui vient
d'échouer, juste éviter que ça ne mette le programme par terre. Et
attraper les messages d'erreur pour indiquer ce qui s'est passé. Mais
je note tout ça.

Juste une petite question annexe sur le code ci-dessus : pourquoi
appeler finish-output avant de lire la réponse de l'utilisateur ? Pour
être sûr qu'il a bien la question ? J'ai écrit du code qui ressemblait
à ça dans un projet, mais je ne suis pas tombé sur le problème. Par
contre il me revient à l'esprit que parfois mes traces avec format ne
s'affichaient pas complètement et je ne comprenais pas ce qu'il passait
(dans des cas d'erreur notamment). Jusqu'à ce que je mette des retours
à la ligne, qui vident probablement le tampon. Est-ce que certaines
implémentations vident automatiquement le tampon d'écriture sur la
sortie standard avant d'appeler read (ou dans la fonction read), sans
que ce soit spécifié par la norme ? Mes tests qui fonctionnent sans
(finish-output) tournaient sur CLisp je pense.


> Finalement, il y a aussi: IGNORE-ERRORS ;-)

Ça fait en gros la même chose que mon handler-case d'origine, avec error
plutôt que serious-condition, et ça gère le problème un peu
différemment. Donc ça ne gère pas tous les cas de retour comme peut le
faire unwind-protect. Mais peut-être que je peux raisonnablement
supposer que le code appelé ne va pas sauter de manière plus ou moins
aléatoire vers l'extérieur de cette capture d'erreur... Aux questions
un peu plus haut sur serious-condition près.

Pascal J. Bourguignon

unread,
Dec 20, 2011, 7:40:53 AM12/20/11
to
Yliur <yl...@free.fr> writes:

> J'avais compris que les serious-condition étaient en principe signalées
> avec error et qu'elles risquaient de tout casser, donc qu'il fallait
> les capturer comme des erreurs.
>
>
> Et dans un des liens plus haut on trouve cet exemple :
> (http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html)
> "And since any condition representing a stack overflow is going to be a
> kind of SERIOUS-CONDITION, but not a kind of ERROR, the use of
> IGNORE-ERRORS will succeed in trapping a file error but not a stack
> overflow. If one wanted to catch serious conditions as well, one would
> write instead:
>
> (handler-case (open "some.file")
> (serious-condition (c)
> (values nil c)))
> "
>
> Est-ce que si la pile déborde je vais le manquer en ne capturant que
> les erreurs ou est-ce qu'il y a un gestionnaire qui va s'occuper de
> lever une erreur ? C'est le genre de choses dont je voudrais me
> prémunir, si possible en récupérant le message associé.

Oui, l'implémentation va normalement installer un handler pour ses
SERIOUS-CONDITIONS et autres non-ERROR CONDITIONS.

Donc, en cas de débordement de pile, c'est le handler de
l'implémentation qui devrait s'activer (et effectivement il sera
probablement interactif).

La question c'est de savoir si tu es capable d'écrire un handler pour
une telle condition, qui n'utilise pas la pile? (question importante si
tu utilises HANDLER-BIND).

Plus généralement, on est là dans le domaine dépendant de
l'implémentation: Quelles conditions peuvent survenir? Sais tu comment
les traiter? Que se passera-t'il sur une autre implémentation?
La sortie de la boucle ci-dessus est controlée par le drapeau DONE. Il
n'a rien à voir avec le UNWIND-PROTECT (sinon que si DO-SOMETHING
effectue une sortie non locale, le SETF DONE ne sera pas exécuté).

Il faut bien sur ajouter un handler-case si tu veux attraper les
erreurs. UNWIND-PROTECT est juste utiliser pour faire le ménage quand
on le traverse.

CL-USER> (flet ((do-something ()
(unless (zerop (random 10))
(error "Cannot do something.")))
(clean-something ()
#|clean clean clean|#))
(loop
with done = nil
do (handler-case
(unwind-protect
(progn
(do-something)
(setf done t))
(clean-something))
(error (err)
(format t "Got error: ~A~%~:[Continuing~;Aborting~]...~%" err done)))
until done))
Got error: Cannot do something.
Continuing...
Got error: Cannot do something.
Continuing...
Got error: Cannot do something.
Continuing...
Got error: Cannot do something.
Continuing...
NIL
CL-USER>

> "Optimisée directement" : le compilateur aurait remplacé l'opération
> qui mène forcément à une erreur pas un simple signalement de l'erreur,
> sans appeler du tout la fonction ?

Oui, c'est possible.





> Juste une petite question annexe sur le code ci-dessus : pourquoi
> appeler finish-output avant de lire la réponse de l'utilisateur ? Pour
> être sûr qu'il a bien la question ?

Oui.


> J'ai écrit du code qui ressemblait
> à ça dans un projet, mais je ne suis pas tombé sur le problème. Par
> contre il me revient à l'esprit que parfois mes traces avec format ne
> s'affichaient pas complètement et je ne comprenais pas ce qu'il passait
> (dans des cas d'erreur notamment). Jusqu'à ce que je mette des retours
> à la ligne, qui vident probablement le tampon. Est-ce que certaines
> implémentations vident automatiquement le tampon d'écriture sur la
> sortie standard avant d'appeler read (ou dans la fonction read), sans
> que ce soit spécifié par la norme ? Mes tests qui fonctionnent sans
> (finish-output) tournaient sur CLisp je pense.

Ça dépend d'un tas de chose. La seule façon conforme de faire c'est
d'appeler explicitement FINISH-OUTPUT ou FORCE-OUTPUT.

Yliur

unread,
Dec 20, 2011, 9:34:28 PM12/20/11
to
Le Tue, 20 Dec 2011 13:40:53 +0100
"Pascal J. Bourguignon" <p...@informatimago.com> a écrit :

> Yliur <yl...@free.fr> writes:
>
> > J'avais compris que les serious-condition étaient en principe
> > signalées avec error et qu'elles risquaient de tout casser, donc
> > qu'il fallait les capturer comme des erreurs.
> >
> >
> > Et dans un des liens plus haut on trouve cet exemple :
> > (http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html)
> > "And since any condition representing a stack overflow is going to
> > be a kind of SERIOUS-CONDITION, but not a kind of ERROR, the use of
> > IGNORE-ERRORS will succeed in trapping a file error but not a stack
> > overflow. If one wanted to catch serious conditions as well, one
> > would write instead:
> >
> > (handler-case (open "some.file")
> > (serious-condition (c)
> > (values nil c)))
> > "
> >
> > Est-ce que si la pile déborde je vais le manquer en ne capturant que
> > les erreurs ou est-ce qu'il y a un gestionnaire qui va s'occuper de
> > lever une erreur ? C'est le genre de choses dont je voudrais me
> > prémunir, si possible en récupérant le message associé.
>
> Oui, l'implémentation va normalement installer un handler pour ses
> SERIOUS-CONDITIONS et autres non-ERROR CONDITIONS.
>
> Donc, en cas de débordement de pile, c'est le handler de
> l'implémentation qui devrait s'activer (et effectivement il sera
> probablement interactif).

C'est ennuyeux ça quand même : si la pile déborde c'est un problème qui
empêche le traitement de continuer, mais pas tout le programme. Après
tout, j'ai lancé une fonction récursive qui a malencontreusement
explosé la pile parce qu'il y avait beaucoup de données à traiter par
exemple, ça n'empêche pas le programme de poursuivre (afficher une
erreur à l'utilisateur et passer à autre chose ["autre chose" ne
dépendant pas du traitement échoué, évidemment]).


> La question c'est de savoir si tu es capable d'écrire un handler pour
> une telle condition, qui n'utilise pas la pile? (question importante
> si tu utilises HANDLER-BIND).
>
> Plus généralement, on est là dans le domaine dépendant de
> l'implémentation: Quelles conditions peuvent survenir? Sais tu comment
> les traiter? Que se passera-t'il sur une autre implémentation?

Je ne veux pas réellement les traiter, je veux juste éviter que ça
n'interrompe le programme. Le débogueur c'est bien pour le
développement mais si le programme est distribué à des gens je préfère
afficher un message d'erreur sans tout planter plutôt qu'afficher le
débogueur à l'utilisateur.

Je ne tiens pas absolument à écrire un gestionnaire pour ces conditions
et si la pile explose je ne vois pas trop quoi y faire dans mon cas, je
voudrais juste que le programme continue (plus loin, tant pis pour le
traitement courant).

Est-il possible d'éviter simplement l'entrée dans une partie
interactive ? Et qu'une erreur soit signalée à la place par exemple ?
Après les traitements que l'implémentation a décidé d'associer à cette
condition, si elle veut tâcher d'y faire quelque chose.


> Il faut bien sur ajouter un handler-case si tu veux attraper les
> erreurs. UNWIND-PROTECT est juste utiliser pour faire le ménage
> quand on le traverse.

Par exemple je peux écrire ça :
(block barriere
(unwind-protect
(handler-case
(...)
(error (e)
(...)))
(return-from barriere)))

Ça permet à la fois d'attraper les erreurs et d'éviter toute
possibilité de traverser la barrière en redescendant la pile grâce à la
combinaison unwind-protect/return-form/block. Mais je ne sais pas si
c'est le moyen le plus simple ni si j'ai encore cassé quelque
chose :) . Et ça ne résout pas le problème mentionné plus haut des
conditions qui peuvent interrompre l'exécution du programme sans être
des erreurs.

Détail en passant : je ne vois pas de grande différence entre
return-from/block et throw/catch, à part l'évaluation du nom du bloc.


> > J'ai écrit du code qui ressemblait
> > à ça dans un projet, mais je ne suis pas tombé sur le problème. Par
> > contre il me revient à l'esprit que parfois mes traces avec format
> > ne s'affichaient pas complètement et je ne comprenais pas ce qu'il
> > passait (dans des cas d'erreur notamment). Jusqu'à ce que je mette
> > des retours à la ligne, qui vident probablement le tampon. Est-ce
> > que certaines implémentations vident automatiquement le tampon
> > d'écriture sur la sortie standard avant d'appeler read (ou dans la
> > fonction read), sans que ce soit spécifié par la norme ? Mes tests
> > qui fonctionnent sans (finish-output) tournaient sur CLisp je pense.
>
> Ça dépend d'un tas de chose. La seule façon conforme de faire c'est
> d'appeler explicitement FINISH-OUTPUT ou FORCE-OUTPUT.

D'accord.

Yliur

unread,
Dec 20, 2011, 9:39:32 PM12/20/11
to
Le Tue, 20 Dec 2011 13:40:53 +0100
"Pascal J. Bourguignon" <p...@informatimago.com> a écrit :

> > J'ai écrit du code qui ressemblait
> > à ça dans un projet, mais je ne suis pas tombé sur le problème. Par
> > contre il me revient à l'esprit que parfois mes traces avec format
> > ne s'affichaient pas complètement et je ne comprenais pas ce qu'il
> > passait (dans des cas d'erreur notamment). Jusqu'à ce que je mette
> > des retours à la ligne, qui vident probablement le tampon. Est-ce
> > que certaines implémentations vident automatiquement le tampon
> > d'écriture sur la sortie standard avant d'appeler read (ou dans la
> > fonction read), sans que ce soit spécifié par la norme ? Mes tests
> > qui fonctionnent sans (finish-output) tournaient sur CLisp je pense.
>
> Ça dépend d'un tas de chose. La seule façon conforme de faire c'est
> d'appeler explicitement FINISH-OUTPUT ou FORCE-OUTPUT.

Est-ce que le fait d'écrire (format t "...~%") garantit que le tampon a
bien été vidé, avec le retour à la ligne ?

Yliur

unread,
Dec 20, 2011, 11:46:43 PM12/20/11
to
Le Mon, 19 Dec 2011 16:18:57 +0100
"Pascal J. Bourguignon" <p...@informatimago.com> a écrit :

> > Informations sur l'erreur :
> > - Pour les erreurs standards de Common Lisp, y a-t-il un moyen
> > de récupérer à l'intérieur de l'objet représentant la condition un
> > texte un peu moins moche que celui obtenu par print par
> > exemple ("#<SYSTEM::SIMPLE-DIVISION-BY-ZERO#x21B02226>") ?
>
> Utiliser ~A au lieu de ~S, princ au lieu de prin1.

Comment ça marche ? Manifestement si j'appelle (print-object) sur
l'objet ça ne renvoie pas quelque chose d'aussi joli.

Est-ce que c'est format qui gère les conditions de manière
particulière quand elles héritent de simple-condition ?

Pascal J. Bourguignon

unread,
Dec 21, 2011, 7:05:37 AM12/21/11
to
Yliur <yl...@free.fr> writes:

> Détail en passant : je ne vois pas de grande différence entre
> return-from/block et throw/catch, à part l'évaluation du nom du bloc.

block/return-from est lexical, tandis que catch/throw est dynamique.

(defun f () (return-from b nil)) ; erreur, pas de block b dans le
(defun g () ; contexte lexical de f.
(block b
(f)))

(defun f () (throw 'petite-gazongue nil)) ; ça marche, si un catch
(defun g () ; petite-gazongue est
(catch 'petite-gazongue ; defini quand f est
(f))) ; appelée ce qui est le
; cas ici.

Ceci dit, on peut implémenter block/return-from avec catch/throw et
vice-versa. Voir: http://www.pipeline.com/~hbaker1/MetaCircular.html

Pascal J. Bourguignon

unread,
Dec 21, 2011, 7:09:24 AM12/21/11
to
En aucun cas.

Par exemple, si on écrit sur une bande avec des blocs de 512 octets avec
un facteur de blocage de 10, le tampon sera de 5120 octets, et écrire
quatre caractères "..." et newline ne remplira pas le tampon en général
(sauf si on était à la fin du tampon), donc rien ne sera écrit sur le
support et le tampon ne sera pas vidé.

Pascal J. Bourguignon

unread,
Dec 21, 2011, 7:13:39 AM12/21/11
to
Yliur <yl...@free.fr> writes:

> Le Mon, 19 Dec 2011 16:18:57 +0100
> "Pascal J. Bourguignon" <p...@informatimago.com> a écrit :
>
>> > Informations sur l'erreur :
>> > - Pour les erreurs standards de Common Lisp, y a-t-il un moyen
>> > de récupérer à l'intérieur de l'objet représentant la condition un
>> > texte un peu moins moche que celui obtenu par print par
>> > exemple ("#<SYSTEM::SIMPLE-DIVISION-BY-ZERO#x21B02226>") ?
>>
>> Utiliser ~A au lieu de ~S, princ au lieu de prin1.
>
> Comment ça marche ? Manifestement si j'appelle (print-object) sur
> l'objet ça ne renvoie pas quelque chose d'aussi joli.

clhs princ
clhs prin1
clhs write

printt appelle prin1 et terpri. prin1 et princ appellent write en liant
les variables speciales *print-readably* et autres. write peut appeler
print-object. print-object doit regarder la valeur de *print-readably*
et autres.

Normalement, tu ne dois pas appeler print-object directement. Ces
méthodes sont des crochets pour write.

> Est-ce que c'est format qui gère les conditions de manière
> particulière quand elles héritent de simple-condition ?

format ne gère aucune condition.

Yliur

unread,
Jan 3, 2012, 9:56:17 PM1/3/12
to

Bon, du coup ça ressemble à ça :

(let ((resultats-collecte '()))
(dolist (...)
(let ((avertissements '())
(erreurs '()))
;; Collecte
(block barriere
(unwind-protect
(handler-bind
((avertissement (lambda (e)
(push (format nil "~a" e) avertissements)))
(error (lambda (e)
(push (format nil "~a" e) erreurs)
(return-from barriere))))
(collecter ...))
(return-from barriere)))
;; Noter le résultat de la collecte
(push (list ...
:avertissements (nreverse avertissements)
:erreurs (nreverse erreurs))
resultats-collecte)))
;; Renvoyer la liste de résultats
resultats-collecte)
0 new messages