1 /*
2  * nsfError.c --
3  *
4  *      Error reporting functions for the Next Scripting Framework.
5  *
6  * Copyright (C) 1999-2018 Gustaf Neumann (a, b)
7  * Copyright (C) 1999-2007 Uwe Zdun (a, b)
8  * Copyright (C) 2011-2016 Stefan Sobernig (b)
9  *
10  * (a) University of Essen
11  *     Specification of Software Systems
12  *     Altendorferstrasse 97-101
13  *     D-45143 Essen, Germany
14  *
15  * (b) Vienna University of Economics and Business
16  *     Institute of Information Systems and New Media
17  *     A-1020, Welthandelsplatz 1
18  *     Vienna, Austria
19  *
20  * This work is licensed under the MIT License https://www.opensource.org/licenses/MIT
21  *
22  * Copyright:
23  *
24  * Permission is hereby granted, free of charge, to any person obtaining a
25  * copy of this software and associated documentation files (the "Software"),
26  * to deal in the Software without restriction, including without limitation
27  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
28  * and/or sell copies of the Software, and to permit persons to whom the
29  * Software is furnished to do so, subject to the following conditions:
30  *
31  * The above copyright notice and this permission notice shall be included in
32  * all copies or substantial portions of the Software.
33  *
34  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
39  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
40  * DEALINGS IN THE SOFTWARE.
41  *
42  */
43 
44 #include "nsfInt.h"
45 
46 /* function prototypes */
47 Tcl_Obj *NsfParamDefsSyntax(
48     Tcl_Interp *interp, Nsf_Param const *paramsPtr,
49     NsfObject *contextObject, const char *pattern
50 ) nonnull(1) nonnull(2) returns_nonnull;
51 
52 
53 /*
54  *----------------------------------------------------------------------
55  *
56  * NsfDStringVPrintf --
57  *
58  *      Appends a formatted value to a Tcl_DString. This function
59  *      continues until having allocated sufficient memory.
60  *
61  *      Note: The current implementation assumes C99 compliant
62  *      implementations of vs*printf() for all runtimes other than
63  *      MSVC. For MSVC, the pre-C99 vs*printf() implementations are
64  *      explicitly set by Tcl internals (see tclInt.h). For
65  *      MinGW/MinGW-w64, __USE_MINGW_ANSI_STDIO must be set (see
66  *      nsfInt.h).
67  *
68  * Results:
69  *      None.
70  *
71  * Side effects:
72  *      None.
73  *
74  *----------------------------------------------------------------------
75  */
76 
77 void
NsfDStringVPrintf(Tcl_DString * dsPtr,const char * fmt,va_list argPtr)78 NsfDStringVPrintf(Tcl_DString *dsPtr, const char *fmt, va_list argPtr) {
79   int      result, avail, offset;
80   va_list  argPtrCopy;
81   bool     failure;
82 
83   /*
84    * Tcl_DStringLength returns the current length *without* the
85    * terminating character (NTC).
86    */
87   offset = Tcl_DStringLength(dsPtr);
88 
89 #if defined(_MSC_VER)
90   /*
91    * Pre-C99: currently free storage, excluding NTC
92    */
93   avail = dsPtr->spaceAvl - offset - 1;
94 #else
95   /*
96    * C99: currently free storage, including NTC
97    */
98   avail = dsPtr->spaceAvl - offset;
99 #endif
100 
101   /*
102    * 1) Copy va_list so that the caller's copy is untouched.
103    * 2) Run vsnprintf() eagerly.
104    */
105   va_copy(argPtrCopy, argPtr);
106   result = vsnprintf(dsPtr->string + offset, (size_t)avail, fmt, argPtrCopy);
107   va_end(argPtrCopy);
108 
109 #if defined(_MSC_VER)
110   /*
111    *  vs*printf() in pre-C99 runtimes (MSVC up to VS13, VS15 and newer
112    *  in backward-compat mode) return -1 upon overflowing the buffer.
113    *
114    *  Note: Tcl via tclInt.h precludes the use of pre-C99 mode even in
115    *  VS15 and newer (vsnprintf points to backwards compatible, pre-C99
116    *  _vsnprintf).
117    */
118   failure = (result == -1);
119 #else
120   /*
121    *  vs*printf() in C99 compliant runtimes (GCC, CLANG, MSVC in VS15
122    *  and newer, MinGW/MinGW-w64 with __USE_MINGW_ANSI_STDIO) returns
123    *  the number of chars to be written if the buffer would be
124    *  sufficiently large (excluding NTC, the terminating null
125    *  character). A return value of -1 signals an encoding error.
126    */
127   assert(result > -1); /* no encoding error */
128   failure = (result >= (int)avail);
129 #endif
130 
131   if (likely(! failure)) {
132     /*
133      * vsnprintf() copied all content, adjust the Tcl_DString length.
134      */
135     Tcl_DStringSetLength(dsPtr, offset + result);
136 
137   } else {
138     int addedStringLength;
139     /*
140      * vsnprintf() could not copy all content, content was truncated.
141      * Determine the required length (for MSVC), adjust the Tcl_DString
142      * size, and copy again.
143      */
144 
145 #if defined(_MSC_VER)
146     va_copy(argPtrCopy, argPtr);
147     addedStringLength = _vscprintf(fmt, argPtrCopy);
148     va_end(argPtrCopy);
149 #else
150     addedStringLength = result;
151 #endif
152 
153     Tcl_DStringSetLength(dsPtr, offset + addedStringLength);
154 
155 #if defined(_MSC_VER)
156     /*
157      * Pre-C99: currently free storage, excluding NTC
158      */
159     avail = dsPtr->spaceAvl - offset - 1;
160 #else
161     /*
162      * C99: currently free storage, including NTC
163      */
164     avail = dsPtr->spaceAvl - offset;
165 #endif
166 
167     va_copy(argPtrCopy, argPtr);
168     result = vsnprintf(dsPtr->string + offset, (size_t)avail, fmt, argPtrCopy);
169     va_end(argPtrCopy);
170 
171 #if defined(_MSC_VER)
172     failure = (result == -1);
173 #else
174     failure = (result == -1 || result >= avail);
175 #endif
176 
177 #if defined(NDEBUG)
178     if (unlikely(failure)) {
179       Tcl_Panic("writing string-formatting output to a dynamic Tcl string failed");
180     }
181 #endif
182     assert(! failure);
183   }
184 }
185 
186 /*
187  *----------------------------------------------------------------------
188  * Nsf_DStringPrintf --
189  *
190  *      Append a sequence of values using a format string.
191  *
192  * Results:
193  *      Pointer to the current string value.
194  *
195  * Side effects:
196  *      None.
197  *
198  *----------------------------------------------------------------------
199  */
200 
201 void
Nsf_DStringPrintf(Tcl_DString * dsPtr,const char * fmt,...)202 Nsf_DStringPrintf(Tcl_DString *dsPtr, const char *fmt, ...)
203 {
204     va_list ap;
205 
206     nonnull_assert(dsPtr != NULL);
207     nonnull_assert(fmt != NULL);
208 
209     va_start(ap, fmt);
210     NsfDStringVPrintf(dsPtr, fmt, ap);
211     va_end(ap);
212 }
213 
214 /*
215  *----------------------------------------------------------------------
216  *
217  * NsfDStringArgv --
218  *
219  *      Appends argument vector to an initialized Tcl_DString.
220  *
221  * Results:
222  *      None.
223  *
224  * Side effects:
225  *      None.
226  *
227  *----------------------------------------------------------------------
228  */
229 void
NsfDStringArgv(Tcl_DString * dsPtr,int objc,Tcl_Obj * const objv[])230 NsfDStringArgv(
231     Tcl_DString *dsPtr,
232     int objc, Tcl_Obj *const objv[]
233 ) {
234   nonnull_assert(dsPtr != NULL);
235   nonnull_assert(objv != NULL);
236 
237   if (objc > 0) {
238     int i;
239     Tcl_DStringAppendElement(dsPtr, NsfMethodName(objv[0]));
240     for (i = 1; i < objc; i++) {
241       Tcl_DStringAppendElement(dsPtr, ObjStr(objv[i]));
242     }
243   }
244 }
245 
246 
247 /*
248  *----------------------------------------------------------------------
249  *
250  * NsfPrintError --
251  *
252  *      Produce a formatted error message with a printf-like semantics.
253  *
254  * Results:
255  *      TCL_ERROR
256  *
257  * Side effects:
258  *      Sets the result message.
259  *
260  *----------------------------------------------------------------------
261  */
262 int
NsfPrintError(Tcl_Interp * interp,const char * fmt,...)263 NsfPrintError(Tcl_Interp *interp, const char *fmt, ...) {
264   va_list ap;
265   Tcl_DString ds;
266 
267   Tcl_DStringInit(&ds);
268 
269   va_start(ap, fmt);
270   NsfDStringVPrintf(&ds, fmt, ap);
271   va_end(ap);
272 
273   Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)));
274   Tcl_DStringFree(&ds);
275 
276   return TCL_ERROR;
277 }
278 
279 /*
280  *----------------------------------------------------------------------
281  *
282  * NsfErrInProc --
283  *
284  *      Produce a general error message when an error occurs in a
285  *      scripted NSF method.
286  *
287  * Results:
288  *      TCL_ERROR
289  *
290  * Side effects:
291  *      Sets the result message.
292  *
293  *----------------------------------------------------------------------
294  */
295 int
NsfErrInProc(Tcl_Interp * interp,Tcl_Obj * objName,Tcl_Obj * clName,const char * procName)296 NsfErrInProc(
297     Tcl_Interp *interp,
298     Tcl_Obj *objName,
299     Tcl_Obj *clName,
300     const char *procName
301 ) {
302   Tcl_DString errMsg;
303   const char *cName, *space;
304 
305   Tcl_DStringInit(&errMsg);
306   Tcl_DStringAppend(&errMsg, "\n    ", -1);
307   if (clName != NULL) {
308     cName = ObjStr(clName);
309     space = " ";
310   } else {
311     cName = "";
312     space = "";
313   }
314   Tcl_DStringAppend(&errMsg, ObjStr(objName), -1);
315   Tcl_DStringAppend(&errMsg, space, -1);
316   Tcl_DStringAppend(&errMsg, cName, -1);
317   Tcl_DStringAppend(&errMsg, "->", 2);
318   Tcl_DStringAppend(&errMsg, procName, -1);
319   Tcl_AddErrorInfo (interp, Tcl_DStringValue(&errMsg));
320   Tcl_DStringFree(&errMsg);
321   return TCL_ERROR;
322 }
323 
324 /*
325  *----------------------------------------------------------------------
326  *
327  * NsfObjWrongArgs --
328  *
329  *      Produce a general error message when an NSF method is called with
330  *      an invalid argument list (wrong number of arguments).
331  *
332  * Results:
333  *      TCL_ERROR
334  *
335  * Side effects:
336  *      Sets the result message.
337  *
338  *----------------------------------------------------------------------
339  */
340 int
NsfObjWrongArgs(Tcl_Interp * interp,const char * msg,Tcl_Obj * cmdNameObj,Tcl_Obj * methodPathObj,const char * arglist)341 NsfObjWrongArgs(
342     Tcl_Interp *interp,
343     const char *msg, Tcl_Obj *cmdNameObj,
344     Tcl_Obj *methodPathObj, const char *arglist
345 ) {
346   bool        need_space = NSF_FALSE;
347   Tcl_DString ds;
348 
349   nonnull_assert(interp != NULL);
350   nonnull_assert(msg != NULL);
351 
352   Tcl_DStringInit(&ds);
353 
354   Nsf_DStringPrintf(&ds, "%s should be \"", msg);
355   if (cmdNameObj != NULL) {
356     Tcl_DStringAppend(&ds, ObjStr(cmdNameObj), -1);
357     need_space = NSF_TRUE;
358   }
359 
360   if (methodPathObj != NULL) {
361     if (need_space) {
362       Tcl_DStringAppend(&ds, " ", 1);
363     }
364 
365     INCR_REF_COUNT(methodPathObj);
366     Tcl_DStringAppend(&ds, ObjStr(methodPathObj), -1);
367     DECR_REF_COUNT(methodPathObj);
368 
369     need_space = NSF_TRUE;
370   }
371   if (arglist != NULL) {
372     if (need_space) {
373       Tcl_DStringAppend(&ds, " ", 1);
374     }
375     Tcl_DStringAppend(&ds, arglist, -1);
376   }
377   Tcl_DStringAppend(&ds, "\"", 1);
378 
379   Tcl_SetObjResult(interp, Tcl_NewStringObj(ds.string, ds.length));
380   Tcl_DStringFree(&ds);
381 
382   return TCL_ERROR;
383 }
384 
385 
386 /*
387  *----------------------------------------------------------------------
388  *
389  * NsfArgumentError --
390  *
391  *      Produce a wrong-number-of-arguments error based on a parameter
392  *      definition.
393  *
394  * Results:
395  *      TCL_ERROR
396  *
397  * Side effects:
398  *      Sets the result message.
399  *
400  *----------------------------------------------------------------------
401  */
402 int
NsfArgumentError(Tcl_Interp * interp,const char * errorMsg,Nsf_Param const * paramPtr,Tcl_Obj * cmdNameObj,Tcl_Obj * methodPathObj)403 NsfArgumentError(
404     Tcl_Interp *interp,
405     const char *errorMsg, Nsf_Param const *paramPtr,
406     Tcl_Obj *cmdNameObj, Tcl_Obj *methodPathObj
407 ) {
408   Tcl_Obj *argStringObj = NsfParamDefsSyntax(interp, paramPtr, NULL, NULL);
409 
410   nonnull_assert(interp != NULL);
411   nonnull_assert(errorMsg != NULL);
412   nonnull_assert(paramPtr != NULL);
413 
414   NsfObjWrongArgs(interp, errorMsg, cmdNameObj, methodPathObj, ObjStr(argStringObj));
415   DECR_REF_COUNT2("paramDefsObj", argStringObj);
416 
417   return TCL_ERROR;
418 }
419 
420 /*
421  *----------------------------------------------------------------------
422  *
423  * NsfUnexpectedArgumentError --
424  *
425  *      Produce an error message on an unexpected argument (most likely,
426  *      too many arguments)
427  *
428  * Results:
429  *      TCL_ERROR
430  *
431  * Side effects:
432  *      Sets the result message.
433  *
434  *----------------------------------------------------------------------
435  */
436 int
NsfUnexpectedArgumentError(Tcl_Interp * interp,const char * argumentString,Nsf_Object * object,Nsf_Param const * paramPtr,Tcl_Obj * methodPathObj)437 NsfUnexpectedArgumentError(Tcl_Interp *interp, const char *argumentString,
438                            Nsf_Object *object, Nsf_Param const *paramPtr,
439                            Tcl_Obj *methodPathObj) {
440   Tcl_DString ds, *dsPtr = &ds;
441 
442   nonnull_assert(interp != NULL);
443   nonnull_assert(argumentString != NULL);
444   nonnull_assert(paramPtr != NULL);
445   nonnull_assert(methodPathObj != NULL);
446 
447   DSTRING_INIT(dsPtr);
448   Nsf_DStringPrintf(dsPtr, "invalid argument '%s', maybe too many arguments;", argumentString);
449   NsfArgumentError(interp, Tcl_DStringValue(dsPtr), paramPtr, (object != NULL) ? object->cmdName : NULL,
450                    methodPathObj);
451   DSTRING_FREE(dsPtr);
452   return TCL_ERROR;
453 }
454 
455 /*
456  *----------------------------------------------------------------------
457  *
458  * NsfUnexpectedNonposArgumentError --
459  *
460  *      Produce an error message on an invalid non-positional argument.
461  *
462  * Results:
463  *      TCL_ERROR
464  *
465  * Side effects:
466  *      Sets the result message.
467  *
468  *----------------------------------------------------------------------
469  */
470 int
NsfUnexpectedNonposArgumentError(Tcl_Interp * interp,const char * argumentString,Nsf_Object * object,Nsf_Param const * currentParamPtr,Nsf_Param const * paramPtr,Tcl_Obj * methodPathObj)471 NsfUnexpectedNonposArgumentError(
472     Tcl_Interp *interp,
473     const char *argumentString,
474     Nsf_Object *object,
475     Nsf_Param const *currentParamPtr,
476     Nsf_Param const *paramPtr,
477     Tcl_Obj *methodPathObj
478 ) {
479   Tcl_DString ds, *dsPtr = &ds;
480   const Nsf_Param *pPtr;
481 
482   nonnull_assert(interp != NULL);
483   nonnull_assert(argumentString != NULL);
484   nonnull_assert(currentParamPtr != NULL);
485   nonnull_assert(paramPtr != NULL);
486   nonnull_assert(methodPathObj != NULL);
487 
488   DSTRING_INIT(dsPtr);
489   Nsf_DStringPrintf(dsPtr, "invalid non-positional argument '%s', valid are: ", argumentString);
490   for (pPtr = currentParamPtr; (pPtr->name != NULL) && (*pPtr->name == '-'); pPtr ++) {
491     if (pPtr->flags & NSF_ARG_NOCONFIG) {
492       continue;
493     }
494     Tcl_DStringAppend(dsPtr, pPtr->name, -1);
495     Tcl_DStringAppend(dsPtr, ", ", -1);
496   }
497   Tcl_DStringSetLength(dsPtr, Tcl_DStringLength(dsPtr) - 2);
498   Tcl_DStringAppend(dsPtr, ";\n", 2);
499 
500   NsfArgumentError(interp, Tcl_DStringValue(dsPtr), paramPtr, (object != NULL) ? object->cmdName : NULL,
501                    methodPathObj);
502   DSTRING_FREE(dsPtr);
503   return TCL_ERROR;
504 }
505 
506 /*
507  *----------------------------------------------------------------------
508  *
509  * NsfDispatchClientDataError --
510  *
511  *      Produce an error message when a method was not dispatched on an
512  *      object.
513  *
514  * Results:
515  *      TCL_ERROR
516  *
517  * Side effects:
518  *      Sets the result message.
519  *
520  *----------------------------------------------------------------------
521  */
522 int
NsfDispatchClientDataError(Tcl_Interp * interp,ClientData clientData,const char * what,const char * methodName)523 NsfDispatchClientDataError(
524     Tcl_Interp *interp, ClientData clientData,
525     const char *what, const char *methodName
526 ) {
527 
528   nonnull_assert(interp != NULL);
529   nonnull_assert(what != NULL);
530   nonnull_assert(methodName != NULL);
531 
532   if (clientData != NULL) {
533     return NsfPrintError(interp, "method %s not dispatched on valid %s",
534                          methodName, what);
535   } else {
536     return NsfNoCurrentObjectError(interp, methodName);
537   }
538 }
539 
540 /*
541  *----------------------------------------------------------------------
542  *
543  * NsfNoCurrentObjectError --
544  *
545  *      Produce an error message when a method/command was called
546  *      outside the context of an object or a method. The passed in
547  *      methodName is NULL when e.g. "self" is called outside of a NSF
548  *      context.
549  *
550  * Results:
551  *      TCL_ERROR
552  *
553  * Side effects:
554  *      Sets the result message.
555  *
556  *----------------------------------------------------------------------
557  */
558 int
NsfNoCurrentObjectError(Tcl_Interp * interp,const char * methodName)559 NsfNoCurrentObjectError(Tcl_Interp *interp, const char *methodName) {
560 
561   nonnull_assert(interp != NULL);
562 
563   return NsfPrintError(interp, "no current object; %s called outside the context of a Next Scripting method",
564                        (methodName != NULL) ? methodName : "command");
565 }
566 
567 /*
568  *----------------------------------------------------------------------
569  *
570  * NsfObjErrType --
571  *
572  *      Produce a general error message when an NSF method is called with
573  *      an invalid value for some argument.
574  *
575  * Results:
576  *      TCL_ERROR
577  *
578  * Side effects:
579  *      Sets the result message.
580  *
581  *----------------------------------------------------------------------
582  */
583 int
NsfObjErrType(Tcl_Interp * interp,const char * context,Tcl_Obj * value,const char * type,Nsf_Param const * NsfObjErrType)584 NsfObjErrType(
585     Tcl_Interp *interp,
586     const char *context,
587     Tcl_Obj *value,
588     const char *type,
589     Nsf_Param const *NsfObjErrType
590 ) {
591   bool        isNamed     = (NsfObjErrType && (NsfObjErrType->flags & NSF_ARG_UNNAMED) == 0);
592   int         returnValue = !isNamed && NsfObjErrType && (NsfObjErrType->flags & NSF_ARG_IS_RETURNVALUE);
593   int         errMsgLen;
594   const char *prevErrMsg  = Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &errMsgLen);
595   Tcl_DString ds;
596 
597   Tcl_DStringInit(&ds);
598   if (errMsgLen > 0) {
599     Tcl_DStringAppend(&ds, prevErrMsg, errMsgLen);
600     Tcl_DStringAppend(&ds, " 2nd error: ", -1);
601   }
602 
603   if (context != NULL) {
604     Tcl_DStringAppend(&ds, context, -1);
605     Tcl_DStringAppend(&ds, ": ", 2);
606   }
607 
608   Nsf_DStringPrintf(&ds, "expected %s but got \"%s\"", type, ObjStr(value));
609   if (isNamed) {
610     Nsf_DStringPrintf(&ds, " for parameter \"%s\"", NsfObjErrType->name);
611   } else if (returnValue != 0) {
612     Tcl_DStringAppend(&ds, " as return value", -1);
613   }
614 
615   Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)));
616   Tcl_DStringFree(&ds);
617 
618   return TCL_ERROR;
619 }
620 
621 /*
622  * Local Variables:
623  * mode: c
624  * c-basic-offset: 2
625  * fill-column: 72
626  * indent-tabs-mode: nil
627  * eval: (c-guess)
628  * End:
629  */
630