1 /* src/vm/jit/stacktrace.c - machine independet stacktrace system
3 Copyright (C) 1996-2005 R. Grafl, A. Krall, C. Kruegel, C. Oates,
4 R. Obermaisser, M. Platter, M. Probst, S. Ring, E. Steiner,
5 C. Thalinger, D. Thuernbeck, P. Tomsich, C. Ullrich, J. Wenninger,
6 Institut f. Computersprachen - TU Wien
8 This file is part of CACAO.
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License as
12 published by the Free Software Foundation; either version 2, or (at
13 your option) any later version.
15 This program is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
25 Contact: cacao@complang.tuwien.ac.at
27 Authors: Joseph Wenninger
29 Changes: Christian Thalinger
31 $Id: stacktrace.c 3524 2005-11-01 12:36:30Z twisti $
43 #include "native/native.h"
45 #include "vm/global.h" /* required here for native includes */
46 #include "native/include/java_lang_ClassLoader.h"
48 #include "toolbox/logging.h"
49 #include "vm/builtin.h"
51 #include "vm/exceptions.h"
52 #include "vm/loader.h"
53 #include "vm/options.h"
54 #include "vm/stringlocal.h"
55 #include "vm/tables.h"
56 #include "vm/jit/asmpart.h"
57 #include "vm/jit/codegen.inc.h"
58 #include "vm/jit/methodheader.h"
61 /* lineNumberTableEntry *******************************************************/
63 /* Keep the type of line the same as the pointer type, otherwise we run into */
64 /* alignment troubles (like on MIPS64). */
66 typedef struct lineNumberTableEntry {
69 } lineNumberTableEntry;
72 typedef struct lineNumberTableEntryInlineBegin {
73 /* this should have the same layout and size as the lineNumberTableEntry */
76 } lineNumberTableEntryInlineBegin;
79 typedef bool(*CacaoStackTraceCollector)(void **, stackTraceBuffer*);
81 #define BLOCK_INITIALSIZE 40
82 #define BLOCK_SIZEINCREMENT 40
85 /* global variables ***********************************************************/
87 #if defined(USE_THREADS)
88 #define STACKFRAMEINFO (stackframeinfo **) (&THREADINFO->_stackframeinfo)
90 THREADSPECIFIC stackframeinfo *_no_threads_stackframeinfo = NULL;
92 #define STACKFRAMEINFO (&_no_threads_stackframeinfo)
96 /* stacktrace_create_stackframeinfo ********************************************
98 Creates an stackframe info structure for inline code in the
101 *******************************************************************************/
103 #if defined(ENABLE_INTRP)
104 void stacktrace_create_stackframeinfo(stackframeinfo *sfi, u1 *pv,
105 u1 *sp, functionptr ra)
107 stackframeinfo **psfi;
110 /* get current stackframe info pointer */
112 psfi = STACKFRAMEINFO;
114 /* if we don't have pv handy */
117 pv = (u1 *) (ptrint) codegen_findmethod(ra);
119 /* get methodinfo pointer from data segment */
121 m = *((methodinfo **) (pv + MethodPointer));
123 /* fill new stackframe info structure */
131 /* xpc is the same as ra, but is required in fillInStackTrace */
135 /* store new stackframe info pointer */
139 #endif /* defined(ENABLE_INTRP) */
141 /* stacktrace_create_inline_stackframeinfo *************************************
143 Creates an stackframe info structure for an inline exception stub.
145 *******************************************************************************/
147 void stacktrace_create_inline_stackframeinfo(stackframeinfo *sfi, u1 *pv,
148 u1 *sp, functionptr ra,
151 stackframeinfo **psfi;
153 /* get current stackframe info pointer */
155 psfi = STACKFRAMEINFO;
157 #if defined(ENABLE_INTRP)
159 /* if we don't have pv handy */
162 pv = (u1 *) (ptrint) codegen_findmethod(ra);
167 /* fill new stackframe info structure */
176 /* store new stackframe info pointer */
182 /* stacktrace_create_extern_stackframeinfo *************************************
184 Creates an stackframe info structure for an extern exception
185 (hardware or assembler).
187 *******************************************************************************/
189 void stacktrace_create_extern_stackframeinfo(stackframeinfo *sfi, u1 *pv,
190 u1 *sp, functionptr ra,
193 stackframeinfo **psfi;
194 #if !defined(__I386__) && !defined(__X86_64__)
199 /* get current stackframe info pointer */
201 psfi = STACKFRAMEINFO;
203 /* sometimes we don't have pv handy (e.g. in asmpart.S:
204 L_asm_call_jit_compiler_exception or in the interpreter). */
207 pv = (u1 *) (ptrint) codegen_findmethod(ra);
209 #if defined(ENABLE_INTRP)
210 /* When using the interpreter, we pass RA to the function. */
214 # if defined(__I386__) || defined(__X86_64__)
215 /* On i386 and x86_64 we always have to get the return address
218 framesize = *((u4 *) (pv + FrameSize));
220 ra = md_stacktrace_get_returnaddress(sp, framesize);
222 /* If the method is a non-leaf function, we need to get the return
223 address from the stack. For leaf functions the return address
224 is set correctly. This makes the assembler and the signal
225 handler code simpler. */
227 isleafmethod = *((s4 *) (pv + IsLeaf));
230 framesize = *((u4 *) (pv + FrameSize));
232 ra = md_stacktrace_get_returnaddress(sp, framesize);
235 #if defined(ENABLE_INTRP)
239 /* fill new stackframe info structure */
248 /* store new stackframe info pointer */
254 /* stacktrace_create_native_stackframeinfo *************************************
256 Creates a stackframe info structure for a native stub.
258 *******************************************************************************/
260 void stacktrace_create_native_stackframeinfo(stackframeinfo *sfi, u1 *pv,
261 u1 *sp, functionptr ra)
263 stackframeinfo **psfi;
266 /* get methodinfo pointer from data segment */
268 m = *((methodinfo **) (pv + MethodPointer));
270 /* get current stackframe info pointer */
272 psfi = STACKFRAMEINFO;
274 /* fill new stackframe info structure */
283 /* store new stackframe info pointer */
289 /* stacktrace_remove_stackframeinfo ********************************************
293 *******************************************************************************/
295 void stacktrace_remove_stackframeinfo(stackframeinfo *sfi)
297 stackframeinfo **psfi;
299 /* get current stackframe info pointer */
301 psfi = STACKFRAMEINFO;
303 /* restore the old pointer */
309 /* stacktrace_inline_arithmeticexception ***************************************
311 Creates an ArithemticException for inline stub.
313 *******************************************************************************/
315 java_objectheader *stacktrace_inline_arithmeticexception(u1 *pv, u1 *sp,
320 java_objectheader *o;
322 /* create stackframeinfo */
324 stacktrace_create_inline_stackframeinfo(&sfi, pv, sp, ra, xpc);
326 /* create exception */
328 o = new_arithmeticexception();
330 /* remove stackframeinfo */
332 stacktrace_remove_stackframeinfo(&sfi);
338 /* stacktrace_inline_arrayindexoutofboundsexception ****************************
340 Creates an ArrayIndexOutOfBoundsException for inline stub.
342 *******************************************************************************/
344 java_objectheader *stacktrace_inline_arrayindexoutofboundsexception(u1 *pv,
351 java_objectheader *o;
353 /* create stackframeinfo */
355 stacktrace_create_inline_stackframeinfo(&sfi, pv, sp, ra, xpc);
357 /* create exception */
359 o = new_arrayindexoutofboundsexception(index);
361 /* remove stackframeinfo */
363 stacktrace_remove_stackframeinfo(&sfi);
369 /* stacktrace_inline_arraystoreexception ***************************************
371 Creates an ArrayStoreException for inline stub.
373 *******************************************************************************/
375 java_objectheader *stacktrace_inline_arraystoreexception(u1 *pv, u1 *sp,
380 java_objectheader *o;
382 /* create stackframeinfo */
384 stacktrace_create_inline_stackframeinfo(&sfi, pv, sp, ra, xpc);
386 /* create exception */
388 o = new_arraystoreexception();
390 /* remove stackframeinfo */
392 stacktrace_remove_stackframeinfo(&sfi);
398 /* stacktrace_inline_classcastexception ****************************************
400 Creates an ClassCastException for inline stub.
402 *******************************************************************************/
404 java_objectheader *stacktrace_inline_classcastexception(u1 *pv, u1 *sp,
409 java_objectheader *o;
411 /* create stackframeinfo */
413 stacktrace_create_inline_stackframeinfo(&sfi, pv, sp, ra, xpc);
415 /* create exception */
417 o = new_classcastexception();
419 /* remove stackframeinfo */
421 stacktrace_remove_stackframeinfo(&sfi);
427 /* stacktrace_inline_nullpointerexception **************************************
429 Creates an NullPointerException for inline stub.
431 *******************************************************************************/
433 java_objectheader *stacktrace_inline_nullpointerexception(u1 *pv, u1 *sp,
438 java_objectheader *o;
440 /* create stackframeinfo */
442 stacktrace_create_inline_stackframeinfo(&sfi, pv, sp, ra, xpc);
444 /* create exception */
446 o = new_nullpointerexception();
448 /* remove stackframeinfo */
450 stacktrace_remove_stackframeinfo(&sfi);
456 /* stacktrace_hardware_arithmeticexception *************************************
458 Creates an ArithemticException for inline stub.
460 *******************************************************************************/
462 java_objectheader *stacktrace_hardware_arithmeticexception(u1 *pv, u1 *sp,
467 java_objectheader *o;
469 /* create stackframeinfo */
471 stacktrace_create_extern_stackframeinfo(&sfi, pv, sp, ra, xpc);
473 /* create exception */
475 o = new_arithmeticexception();
477 /* remove stackframeinfo */
479 stacktrace_remove_stackframeinfo(&sfi);
485 /* stacktrace_hardware_nullpointerexception ************************************
487 Creates an NullPointerException for the SIGSEGV signal handler.
489 *******************************************************************************/
491 java_objectheader *stacktrace_hardware_nullpointerexception(u1 *pv, u1 *sp,
496 java_objectheader *o;
498 /* create stackframeinfo */
500 stacktrace_create_extern_stackframeinfo(&sfi, pv, sp, ra, xpc);
502 /* create exception */
504 o = new_nullpointerexception();
506 /* remove stackframeinfo */
508 stacktrace_remove_stackframeinfo(&sfi);
514 /* stacktrace_inline_fillInStackTrace ******************************************
516 Fills in the correct stacktrace into an existing exception object
517 (this one is for inline exception stubs).
519 *******************************************************************************/
521 java_objectheader *stacktrace_inline_fillInStackTrace(u1 *pv, u1 *sp,
526 java_objectheader *o;
529 /* create stackframeinfo */
531 stacktrace_create_inline_stackframeinfo(&sfi, pv, sp, ra, xpc);
537 /* clear exception */
539 *exceptionptr = NULL;
541 /* resolve methodinfo pointer from exception object */
543 m = class_resolvemethod(o->vftbl->class,
544 utf_fillInStackTrace,
545 utf_void__java_lang_Throwable);
549 asm_calljavafunction(m, o, NULL, NULL, NULL);
551 /* remove stackframeinfo */
553 stacktrace_remove_stackframeinfo(&sfi);
559 /* addEntry ********************************************************************
563 *******************************************************************************/
565 static void addEntry(stackTraceBuffer *buffer, methodinfo *method, u2 line)
567 if (buffer->size > buffer->full) {
568 stacktraceelement *tmp = &(buffer->start[buffer->full]);
570 tmp->method = method;
571 tmp->linenumber = line;
572 buffer->full = buffer->full + 1;
575 stacktraceelement *newBuffer;
578 (stacktraceelement *) malloc((buffer->size + BLOCK_SIZEINCREMENT) *
579 sizeof(stacktraceelement));
581 if (newBuffer == 0) {
582 log_text("OOM during stacktrace creation");
586 memcpy(newBuffer, buffer->start, buffer->size * sizeof(stacktraceelement));
587 if (buffer->needsFree)
590 buffer->start = newBuffer;
591 buffer->size = buffer->size + BLOCK_SIZEINCREMENT;
592 buffer->needsFree = 1;
594 addEntry(buffer, method, line);
599 /* stacktrace_fillInStackTrace_methodRecursive *********************************
603 *******************************************************************************/
605 static bool stacktrace_fillInStackTrace_methodRecursive(stackTraceBuffer *buffer,
607 lineNumberTableEntry *lntentry,
612 lineNumberTableEntryInlineBegin *lntinline;
615 /* find the line number for the specified pc (going backwards) */
617 for (; lntsize > 0; lntsize--, lntentry--) {
618 /* did we reach the current line? */
620 if (pc >= lntentry->pc) {
621 /* check for special inline entries */
623 switch (lntentry->line) {
625 /* XXX TWISTI we have to think about this inline stuff again */
627 case -1: /* begin of inlined method */
628 lntinline = (lineNumberTableEntryInlineBegin *) (--lntentry);
630 lntsize--; lntsize--;
631 if (stacktrace_fillInStackTrace_methodRecursive(buffer,
637 addEntry(buffer, m, ilStart->lineNrOuter);
643 case -2: /* end of inlined method */
645 *entriesAhead = ahead;
651 addEntry(buffer, m, lntentry->line);
657 /* check if we are before the actual JIT code */
659 if ((ptrint) pc < (ptrint) m->entrypoint) {
660 dolog("Current pc before start of code: %p < %p", pc, m->entrypoint);
664 /* otherwise just add line 0 */
666 addEntry(buffer, m, 0);
672 /* stacktrace_fillInStackTrace_method ******************************************
676 *******************************************************************************/
678 static void stacktrace_fillInStackTrace_method(stackTraceBuffer *buffer,
679 methodinfo *method, u1 *pv,
682 ptrint lntsize; /* size of line number table */
683 u1 *lntstart; /* start of line number table */
684 lineNumberTableEntry *lntentry; /* points to last entry in the table */
686 /* get size of line number table */
688 lntsize = *((ptrint *) (pv + LineNumberTableSize));
689 lntstart = *((u1 **) (pv + LineNumberTableStart));
691 /* subtract the size of the line number entry of the structure, since the */
692 /* line number table start points to the pc */
694 lntentry = (lineNumberTableEntry *) (lntstart - SIZEOF_VOID_P);
697 /* this happens when an exception is thrown in the native stub */
699 addEntry(buffer, method, 0);
702 if (!stacktrace_fillInStackTrace_methodRecursive(buffer,
707 log_text("Trace point not found in suspected method");
714 /* cacao_stacktrace_fillInStackTrace *******************************************
718 *******************************************************************************/
720 static bool cacao_stacktrace_fillInStackTrace(void **target,
721 CacaoStackTraceCollector coll)
723 stacktraceelement primaryBlock[BLOCK_INITIALSIZE*sizeof(stacktraceelement)];
724 stackTraceBuffer buffer;
734 /* prevent compiler warnings */
740 /* In most cases this should be enough -> one malloc less. I don't think */
741 /* temporary data should be allocated with the GC, only the result. */
743 buffer.needsFree = 0;
744 buffer.start = primaryBlock;
745 buffer.size = BLOCK_INITIALSIZE; /* *sizeof(stacktraceelement); */
748 /* the first element in the stackframe chain must always be a native */
749 /* stackframeinfo (VMThrowable.fillInStackTrace is a native function) */
751 sfi = *STACKFRAMEINFO;
758 #define PRINTMETHODS 0
761 printf("\n\nfillInStackTrace start:\n");
765 /* loop while we have a method pointer (asm_calljavafunction has NULL) or */
766 /* there is a stackframeinfo in the chain */
771 /* m == NULL should only happen for the first time and inline */
772 /* stackframe infos, like from the exception stubs or the patcher */
776 /* for native stub stackframe infos, pv is always NULL */
778 if (sfi->pv == NULL) {
779 /* get methodinfo, sp and ra from the current stackframe info */
782 sp = sfi->sp; /* sp of parent Java function */
786 addEntry(&buffer, m, 0);
789 utf_display_classname(m->class->name);
791 utf_display(m->name);
792 utf_display(m->descriptor);
793 printf(": native stub\n");
796 /* this is an native stub stackframe info, so we can get the */
797 /* parent pv from the return address (ICMD_INVOKE*) */
799 pv = (u1 *) (ptrint) codegen_findmethod(ra);
801 /* get methodinfo pointer from parent data segment */
803 m = *((methodinfo **) (pv + MethodPointer));
806 /* Inline stackframe infos are special: they have a xpc of */
807 /* the actual exception position and the return address saved */
808 /* since an inline stackframe info can also be in a leaf */
809 /* method (no return address saved on stack!!!). */
810 /* ATTENTION: This one is also for hardware exceptions!!! */
812 /* get methodinfo, sp and ra from the current stackframe info */
814 m = sfi->method; /* m == NULL */
815 pv = sfi->pv; /* pv of parent Java function */
816 sp = sfi->sp; /* sp of parent Java function */
817 ra = sfi->ra; /* ra of parent Java function */
818 xpc = sfi->xpc; /* actual exception position */
821 printf("NULL: inline stub\n");
825 /* get methodinfo from current Java method */
827 m = *((methodinfo **) (pv + MethodPointer));
829 /* if m == NULL, this is a asm_calljavafunction call */
833 utf_display_classname(m->class->name);
835 utf_display(m->name);
836 utf_display(m->descriptor);
837 printf(": inline stub parent\n");
841 #if defined(ENABLE_INTRP)
845 /* add it to the stacktrace */
847 stacktrace_fillInStackTrace_method(&buffer, m, pv,
848 (u1 *) ((ptrint) xpc));
850 /* get the current stack frame size */
852 framesize = *((u4 *) (pv + FrameSize));
854 /* set stack pointer to stackframe of parent Java */
855 /* function of the current Java function */
857 #if defined(__I386__) || defined (__X86_64__)
858 sp += framesize + SIZEOF_VOID_P;
863 /* get data segment and methodinfo pointer from parent */
866 pv = (u1 *) (ptrint) codegen_findmethod(ra);
867 m = *((methodinfo **) (pv + MethodPointer));
869 #if defined(ENABLE_INTRP)
875 printf("asm_calljavafunction\n");
881 /* get previous stackframeinfo in the chain */
887 utf_display_classname(m->class->name);
889 utf_display(m->name);
890 utf_display(m->descriptor);
894 /* JIT method found, add it to the stacktrace (we subtract 1 from */
895 /* the return address since it points the the instruction after */
898 stacktrace_fillInStackTrace_method(&buffer, m, pv,
899 (u1 *) ((ptrint) ra) - 1);
901 /* get the current stack frame size */
903 framesize = *((u4 *) (pv + FrameSize));
905 /* get return address of current stack frame */
907 ra = md_stacktrace_get_returnaddress(sp, framesize);
909 /* get data segment and methodinfo pointer from parent method */
911 pv = (u1 *) (ptrint) codegen_findmethod(ra);
912 m = *((methodinfo **) (pv + MethodPointer));
916 #if defined(ENABLE_INTRP)
918 sp = *(u1 **)(sp - framesize);
922 #if defined(__I386__) || defined (__X86_64__)
923 sp += framesize + SIZEOF_VOID_P;
932 result = coll(target, &buffer);
934 if (buffer.needsFree)
941 /* stackTraceCollector *********************************************************
945 *******************************************************************************/
947 static bool stackTraceCollector(void **target, stackTraceBuffer *buffer)
949 stackTraceBuffer *dest;
951 dest = *target = heap_allocate(sizeof(stackTraceBuffer) + buffer->full * sizeof(stacktraceelement), true, 0);
956 memcpy(*target, buffer, sizeof(stackTraceBuffer));
957 memcpy(dest + 1, buffer->start, buffer->full * sizeof(stacktraceelement));
960 dest->size = dest->full;
961 dest->start = (stacktraceelement *) (dest + 1);
967 bool cacao_stacktrace_NormalTrace(void **target)
969 return cacao_stacktrace_fillInStackTrace(target, &stackTraceCollector);
974 static bool classContextCollector(void **target, stackTraceBuffer *buffer)
976 java_objectarray *oa;
977 stacktraceelement *current;
978 stacktraceelement *start;
986 for (i = 0; i < size; i++)
987 if (buffer->start[i].method != 0)
990 start = buffer->start;
994 if (targetSize > 0) {
996 (start->method->class == class_java_lang_SecurityManager)) {
1002 oa = builtin_anewarray(targetSize, class_java_lang_Class);
1007 for(i = 0, current = start; i < targetSize; i++, current++) {
1008 if (!current->method) {
1013 use_class_as_object(current->method->class);
1015 oa->data[i] = (java_objectheader *) current->method->class;
1025 java_objectarray *cacao_createClassContextArray(void)
1027 java_objectarray *array = NULL;
1029 if (!cacao_stacktrace_fillInStackTrace((void **) &array,
1030 &classContextCollector))
1037 /* stacktrace_classLoaderCollector *********************************************
1041 *******************************************************************************/
1043 static bool stacktrace_classLoaderCollector(void **target,
1044 stackTraceBuffer *buffer)
1046 stacktraceelement *current;
1047 stacktraceelement *start;
1052 size = buffer->full;
1053 start = &(buffer->start[0]);
1055 for(i = 0, current = start; i < size; i++, current++) {
1056 m = current->method;
1061 if (m->class == class_java_security_PrivilegedAction) {
1066 if (m->class->classloader) {
1067 *target = (java_lang_ClassLoader *) m->class->classloader;
1078 /* cacao_currentClassLoader ****************************************************
1082 *******************************************************************************/
1084 java_objectheader *cacao_currentClassLoader(void)
1086 java_objectheader *header = NULL;
1088 if (!cacao_stacktrace_fillInStackTrace((void**)&header,
1089 &stacktrace_classLoaderCollector))
1096 static bool getStackCollector(void **target, stackTraceBuffer *buffer)
1098 java_objectarray *oa;
1099 java_objectarray *classes;
1100 java_objectarray *methodnames;
1101 java_lang_String *str;
1103 stacktraceelement *current;
1106 /* *result = (java_objectarray **) target; */
1108 size = buffer->full;
1110 oa = builtin_anewarray(2, arrayclass_java_lang_Object);
1115 classes = builtin_anewarray(size, class_java_lang_Class);
1120 methodnames = builtin_anewarray(size, class_java_lang_String);
1125 oa->data[0] = (java_objectheader *) classes;
1126 oa->data[1] = (java_objectheader *) methodnames;
1128 for (i = 0, current = &(buffer->start[0]); i < size; i++, current++) {
1129 c = current->method->class;
1131 use_class_as_object(c);
1133 classes->data[i] = (java_objectheader *) c;
1134 str = javastring_new(current->method->name);
1139 methodnames->data[i] = (java_objectheader *) str;
1148 java_objectarray *cacao_getStackForVMAccessController(void)
1150 java_objectarray *result = NULL;
1152 if (!cacao_stacktrace_fillInStackTrace((void **) &result,
1153 &getStackCollector))
1160 /* stacktrace_dump_trace *******************************************************
1162 This method is call from signal_handler_sigusr1 to dump the
1163 stacktrace of the current thread to stdout.
1165 *******************************************************************************/
1167 void stacktrace_dump_trace(void)
1169 stackTraceBuffer *buffer;
1172 /* get thread stackframeinfo */
1174 info = &THREADINFO->_stackframeinfo;
1176 /* fill stackframeinfo structure */
1178 tmp.oldThreadspecificHeadValue = *info;
1179 tmp.addressOfThreadspecificHead = info;
1182 tmp.ra = _mc->gregs[REG_RIP];
1187 /* generate stacktrace */
1189 cacao_stacktrace_NormalTrace((void **) &buffer);
1191 /* print stacktrace */
1194 stacktrace_print_trace(buffer);
1198 /* stacktrace_print_trace ******************************************************
1200 Print the stacktrace of a given stackTraceBuffer with CACAO intern
1201 methods (no Java help). This method is used by
1202 stacktrace_dump_trace and builtin_trace_exception.
1204 *******************************************************************************/
1206 void stacktrace_print_trace(stackTraceBuffer *stb)
1208 stacktraceelement *ste;
1214 for (i = 0; i < stb->size; i++, ste++) {
1218 utf_display_classname(m->class->name);
1220 utf_display(m->name);
1221 utf_display(m->descriptor);
1223 if (m->flags & ACC_NATIVE) {
1224 printf("(Native Method)\n");
1228 utf_display(m->class->sourcefile);
1229 printf(":%d)\n", (u4) ste->linenumber);
1233 /* just to be sure */
1240 * These are local overrides for various environment variables in Emacs.
1241 * Please do not remove this and leave it at the end of the file, where
1242 * Emacs will automagically detect them.
1243 * ---------------------------------------------------------------------
1246 * indent-tabs-mode: t