Line data Source code
1 : /* src/native/native.cpp - native library support
2 :
3 : Copyright (C) 1996-2013
4 : CACAOVM - Verein zur Foerderung der freien virtuellen Maschine CACAO
5 :
6 : This file is part of CACAO.
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License as
10 : published by the Free Software Foundation; either version 2, or (at
11 : your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful, but
14 : WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 : 02110-1301, USA.
22 :
23 : */
24 :
25 :
26 : #include "config.h"
27 :
28 : #include <assert.h>
29 : #include <ctype.h>
30 :
31 : #include <stdint.h>
32 :
33 : #include <algorithm>
34 : #include <functional>
35 : #include <map>
36 :
37 : #include "native/jni.hpp"
38 : #include "native/native.hpp"
39 :
40 : #include "threads/mutex.hpp"
41 :
42 : #include "toolbox/logging.hpp"
43 : #include "toolbox/buffer.hpp"
44 :
45 : #include "vm/jit/builtin.hpp"
46 : #include "vm/exceptions.hpp"
47 : #include "vm/global.hpp"
48 : #include "vm/globals.hpp"
49 : #include "vm/hook.hpp"
50 : #include "vm/loader.hpp"
51 : #include "vm/options.hpp"
52 : #include "vm/os.hpp"
53 : #include "vm/resolve.hpp"
54 : #include "vm/string.hpp"
55 : #include "vm/vm.hpp"
56 :
57 : /* native_make_overloaded_function *********************************************
58 :
59 : XXX
60 :
61 : *******************************************************************************/
62 :
63 5832 : static Utf8String native_make_overloaded_function(Utf8String name, Utf8String descriptor)
64 : {
65 5832 : Buffer<> newname;
66 : u2 c;
67 :
68 5832 : newname.write(name).write("__");
69 :
70 5832 : Utf8String::utf16_iterator it = descriptor.utf16_begin();
71 :
72 19045 : for (; (c = *it) != ')'; ++it) {
73 13213 : switch (c) {
74 : case 'Z':
75 : case 'B':
76 : case 'C':
77 : case 'S':
78 : case 'J':
79 : case 'I':
80 : case 'F':
81 : case 'D':
82 2164 : newname.write(c);
83 2164 : break;
84 : case '[':
85 342 : newname.write("_3");
86 342 : break;
87 : case 'L':
88 4875 : newname.write('L');
89 95532 : while ((++it, c = *it) != ';') {
90 161812 : if (((c >= 'a') && (c <= 'z')) ||
91 : ((c >= 'A') && (c <= 'Z')) ||
92 : ((c >= '0') && (c <= '9'))) {
93 76030 : newname.write(c);
94 : } else {
95 9752 : newname.write('_');
96 : }
97 : }
98 4875 : newname.write("_2");
99 4875 : break;
100 : case '(':
101 5832 : break;
102 : default:
103 0 : assert(0);
104 : }
105 : }
106 :
107 : /* make a utf-string */
108 :
109 5832 : return newname.utf8_str();
110 : }
111 :
112 :
113 : /* native_insert_char **********************************************************
114 :
115 : Inserts the passed UTF character into the native method name. If
116 : necessary it is escaped properly.
117 :
118 : *******************************************************************************/
119 :
120 192430 : static inline void native_insert_char(Buffer<>& name, u2 c)
121 : {
122 : char tmp[4];
123 :
124 192430 : switch (c) {
125 : case '/':
126 : case '.':
127 : /* replace '/' or '.' with '_' */
128 13280 : name.write('_');
129 13280 : break;
130 :
131 : case '_':
132 : /* escape sequence for '_' is '_1' */
133 423 : name.write("_1");
134 423 : break;
135 :
136 : case ';':
137 : /* escape sequence for ';' is '_2' */
138 0 : name.write("_2");
139 0 : break;
140 :
141 : case '[':
142 : /* escape sequence for '[' is '_3' */
143 0 : name.write("_3");
144 0 : break;
145 :
146 : default:
147 178727 : if (isalnum(c))
148 178727 : name.write(c);
149 : else {
150 : /* unicode character */
151 0 : name.write("_0");
152 :
153 0 : for (s4 i = 0; i < 4; ++i) {
154 0 : s4 val = (c & 0x0f);
155 0 : tmp[3 - i] = (val > 10) ? ('a' + val - 10) : ('0' + val);
156 0 : c >>= 4;
157 : }
158 :
159 0 : name.write(tmp, 4);
160 : }
161 : break;
162 : }
163 192430 : }
164 :
165 : /* native_method_symbol ********************************************************
166 :
167 : Generate a method-symbol string out of the class name and the
168 : method name.
169 :
170 : *******************************************************************************/
171 :
172 5832 : static Utf8String native_method_symbol(Utf8String classname, Utf8String methodname)
173 : {
174 : Utf8String::byte_iterator begin, end;
175 :
176 : /* Calculate length of native function name. We multiply the
177 : class and method name length by 6 as this is the maxium
178 : escape-sequence that can be generated (unicode). */
179 :
180 : /* allocate memory */
181 :
182 5832 : Buffer<> name;
183 :
184 : /* generate name of native functions */
185 :
186 5832 : name.write("Java_");
187 :
188 5832 : begin = classname.begin();
189 5832 : end = classname.end();
190 :
191 130266 : for (; begin != end; ++begin) {
192 124434 : native_insert_char(name, *begin);
193 : }
194 :
195 : /* seperator between class and method */
196 :
197 5832 : name.write('_');
198 :
199 5832 : begin = methodname.begin();
200 5832 : end = methodname.end();
201 :
202 73828 : for (; begin != end; ++begin) {
203 67996 : native_insert_char(name, *begin);
204 : }
205 :
206 : /* make a utf-string */
207 :
208 5832 : return name.utf8_str();
209 : }
210 :
211 :
212 457303 : bool operator< (const NativeMethod& first, const NativeMethod& second)
213 : {
214 457303 : if (first._classname < second._classname)
215 101929 : return true;
216 355374 : else if (first._classname > second._classname)
217 123090 : return false;
218 :
219 232284 : if (first._name < second._name)
220 81437 : return true;
221 150847 : else if (first._name > second._name)
222 137643 : return false;
223 :
224 13204 : if (first._descriptor < second._descriptor)
225 3581 : return true;
226 9623 : else if (first._descriptor > second._descriptor)
227 1477 : return false;
228 :
229 : // All pointers are equal, we have found the entry.
230 8146 : return false;
231 : }
232 :
233 :
234 : /**
235 : * Register native methods with the VM. This is done by inserting
236 : * them into the native method table.
237 : *
238 : * @param classname
239 : * @param methods Native methods array.
240 : * @param count Number of methods in the array.
241 : */
242 3912 : void NativeMethods::register_methods(Utf8String classname, const JNINativeMethod* methods, size_t count)
243 : {
244 : // Insert all methods passed */
245 43032 : for (size_t i = 0; i < count; i++) {
246 39120 : if (opt_verbosejni) {
247 0 : printf("[Registering JNI native method ");
248 0 : utf_display_printable_ascii_classname(classname);
249 0 : printf(".%s]\n", methods[i].name);
250 : }
251 :
252 : // Generate the UTF8 names.
253 39120 : Utf8String name = Utf8String::from_utf8(methods[i].name);
254 39120 : Utf8String signature = Utf8String::from_utf8(methods[i].signature);
255 :
256 39120 : NativeMethod nm(classname, name, signature, methods[i].fnPtr);
257 :
258 : // Insert the method into the table.
259 39120 : _methods.insert(nm);
260 : }
261 3912 : }
262 :
263 :
264 : /**
265 : * Resolves a native method, maybe from a dynamic library.
266 : *
267 : * @param m Method structure of the native Java method to resolve.
268 : *
269 : * @return Pointer to the resolved method (symbol).
270 : */
271 5832 : void* NativeMethods::resolve_method(methodinfo* m)
272 : {
273 : // Verbose output.
274 5832 : if (opt_verbosejni) {
275 0 : printf("[Dynamic-linking native method ");
276 0 : utf_display_printable_ascii_classname(m->clazz->name);
277 0 : printf(".");
278 0 : utf_display_printable_ascii(m->name);
279 0 : printf(" ... ");
280 : }
281 :
282 : /* generate method symbol string */
283 :
284 5832 : Utf8String name = native_method_symbol(m->clazz->name, m->name);
285 :
286 : /* generate overloaded function (having the types in it's name) */
287 :
288 5832 : Utf8String newname = native_make_overloaded_function(name, m->descriptor);
289 :
290 : // Try to find the symbol.
291 : void* symbol;
292 :
293 : // Try to find the native method symbol in the native methods registered
294 : // with the VM.
295 5832 : symbol = find_registered_method(m);
296 :
297 5832 : if (symbol != NULL)
298 4073 : if (opt_verbosejni)
299 0 : printf("internal ");
300 :
301 : #if defined(ENABLE_DL)
302 : classloader_t* classloader;
303 5832 : if (symbol == NULL) {
304 : // Get the classloader.
305 1759 : classloader = class_get_classloader(m->clazz);
306 :
307 : // Resolve the native method name from the native libraries.
308 1759 : NativeLibraries& libraries = VM::get_current()->get_nativelibraries();
309 :
310 1759 : symbol = libraries.resolve_symbol(name, classloader);
311 :
312 1759 : if (symbol == NULL)
313 284 : symbol = libraries.resolve_symbol(newname, classloader);
314 : }
315 :
316 : # if defined(WITH_JAVA_RUNTIME_LIBRARY_OPENJDK)
317 : if (symbol == NULL) {
318 : /* We can resolve the function directly from
319 : java.lang.ClassLoader as it's a static function. */
320 : /* XXX should be done in native_init */
321 :
322 : methodinfo* method_findNative =
323 : class_resolveclassmethod(class_java_lang_ClassLoader,
324 : utf8::findNative,
325 : utf8::java_lang_ClassLoader_java_lang_String__J,
326 : class_java_lang_ClassLoader,
327 : true);
328 :
329 : if (method_findNative != NULL) {
330 : // Try the normal name.
331 : java_handle_t* s = JavaString::from_utf8(name);
332 : symbol = (void*) vm_call_method_long(method_findNative, NULL, classloader, s);
333 :
334 : // If not found, try the mangled name.
335 : if (symbol == NULL) {
336 : s = JavaString::from_utf8(newname);
337 : symbol = (void*) vm_call_method_long(method_findNative, NULL, classloader, s);
338 : }
339 : }
340 : }
341 : # endif
342 :
343 5832 : if (symbol != NULL)
344 5831 : if (opt_verbosejni)
345 0 : printf("JNI ]\n");
346 : #endif
347 :
348 : // Symbol not found? Throw an exception.
349 5832 : if (symbol == NULL) {
350 1 : if (opt_verbosejni)
351 0 : printf("failed ]\n");
352 :
353 1 : Buffer<> buf;
354 :
355 1 : if (m->clazz)
356 : buf.write(m->clazz->name)
357 1 : .write('.');
358 :
359 : buf.write(m->name)
360 1 : .write(m->descriptor);
361 :
362 1 : exceptions_throw_unsatisfiedlinkerror(buf.utf8_str());
363 : }
364 :
365 : // Hook point just after method resolving finished.
366 5832 : Hook::native_resolved(m, symbol, &symbol);
367 :
368 5832 : return symbol;
369 : }
370 :
371 :
372 : /**
373 : * Try to find the given method in the native methods registered with
374 : * the VM.
375 : *
376 : * @param m Method structure.
377 : *
378 : * @return Pointer to function if found, NULL otherwise.
379 : */
380 5832 : void* NativeMethods::find_registered_method(methodinfo* m)
381 : {
382 5832 : NativeMethod nm(m);
383 5832 : std::set<NativeMethod>::iterator it = _methods.find(nm);
384 :
385 5832 : if (it == _methods.end())
386 1759 : return NULL;
387 :
388 4073 : return (*it).get_function();
389 : }
390 :
391 :
392 : /**
393 : * Open this native library.
394 : *
395 : * @return File handle on success, NULL otherwise.
396 : */
397 : #if defined(ENABLE_DL)
398 432 : void* NativeLibrary::open()
399 : {
400 432 : if (opt_verbosejni) {
401 0 : printf("[Loading native library ");
402 0 : utf_display_printable_ascii(_filename);
403 0 : printf(" ... ");
404 : }
405 :
406 : // Sanity check.
407 432 : assert(_filename != NULL);
408 :
409 : // Try to open the library.
410 432 : _handle = os::dlopen(_filename.begin(), RTLD_LAZY);
411 :
412 432 : if (_handle == NULL) {
413 4 : if (opt_verbosejni)
414 0 : printf("failed ]\n");
415 :
416 4 : if (opt_PrintWarnings)
417 0 : log_println("NativeLibrary::open: os::dlopen failed: %s", os::dlerror());
418 :
419 4 : return NULL;
420 : }
421 :
422 428 : if (opt_verbosejni)
423 0 : printf("OK ]\n");
424 :
425 428 : return _handle;
426 : }
427 : #endif
428 :
429 :
430 : /**
431 : * Close this native library.
432 : */
433 : #if defined(ENABLE_DL)
434 0 : void NativeLibrary::close()
435 : {
436 0 : if (opt_verbosejni) {
437 0 : printf("[Unloading native library ");
438 : /* utf_display_printable_ascii(filename); */
439 0 : printf(" ... ");
440 : }
441 :
442 : // Sanity check.
443 0 : assert(_handle != NULL);
444 :
445 : // Close the library.
446 0 : int result = os::dlclose(_handle);
447 :
448 0 : if (result != 0) {
449 0 : if (opt_verbosejni)
450 0 : printf("failed ]\n");
451 :
452 0 : if (opt_PrintWarnings)
453 0 : log_println("NativeLibrary::close: os::dlclose failed: %s", os::dlerror());
454 : }
455 :
456 0 : if (opt_verbosejni)
457 0 : printf("OK ]\n");
458 0 : }
459 : #endif
460 :
461 :
462 : /**
463 : * Load this native library and initialize it, if possible.
464 : *
465 : * @param env JNI environment.
466 : *
467 : * @return true if library loaded successfully, false otherwise.
468 : */
469 593 : bool NativeLibrary::load(JNIEnv* env)
470 : {
471 : #if defined(ENABLE_DL)
472 593 : if (_filename == NULL) {
473 0 : exceptions_throw_nullpointerexception();
474 0 : return false;
475 : }
476 :
477 : // Is the library already loaded?
478 593 : if (is_loaded())
479 161 : return true;
480 :
481 : // Open the library.
482 432 : open();
483 :
484 432 : if (_handle == NULL)
485 4 : return false;
486 :
487 : # if defined(ENABLE_JNI)
488 : // Resolve JNI_OnLoad function.
489 428 : void* onload = os::dlsym(_handle, "JNI_OnLoad");
490 :
491 428 : if (onload != NULL) {
492 : JNIEXPORT jint (JNICALL *JNI_OnLoad) (JavaVM*, void*);
493 : JavaVM *vm;
494 :
495 423 : JNI_OnLoad = (JNIEXPORT jint (JNICALL *)(JavaVM*, void*)) (uintptr_t) onload;
496 :
497 423 : env->GetJavaVM(&vm);
498 :
499 423 : jint version = JNI_OnLoad(vm, NULL);
500 :
501 : // If the version is not 1.2 and not 1.4 the library cannot be
502 : // loaded.
503 423 : if ((version != JNI_VERSION_1_2) && (version != JNI_VERSION_1_4)) {
504 0 : os::dlclose(_handle);
505 0 : return false;
506 : }
507 : }
508 : # endif
509 :
510 : // Insert the library name into the native library table.
511 428 : NativeLibraries& nativelibraries = VM::get_current()->get_nativelibraries();
512 428 : nativelibraries.add(*this);
513 :
514 428 : return true;
515 : #else
516 : os::abort("NativeLibrary::load: Not available in this configuration.");
517 :
518 : // Keep the compiler happy.
519 : return false;
520 : #endif
521 : }
522 :
523 :
524 : /**
525 : * Checks if this native library is loaded.
526 : *
527 : * @return true if loaded, false otherwise.
528 : */
529 : #if defined(ENABLE_DL)
530 593 : bool NativeLibrary::is_loaded()
531 : {
532 593 : NativeLibraries& libraries = VM::get_current()->get_nativelibraries();
533 593 : return libraries.is_loaded(*this);
534 : }
535 : #endif
536 :
537 :
538 : /**
539 : * Resolve the given symbol in this native library.
540 : *
541 : * @param symbolname Symbol name.
542 : *
543 : * @return Pointer to symbol if found, NULL otherwise.
544 : */
545 3323 : void* NativeLibrary::resolve_symbol(Utf8String symbolname) const
546 : {
547 3323 : return os::dlsym(_handle, symbolname.begin());
548 : }
549 :
550 :
551 : /**
552 : * Add the given native library to the native libraries table.
553 : *
554 : * @param library Native library to insert.
555 : */
556 : #if defined(ENABLE_DL)
557 428 : void NativeLibraries::add(NativeLibrary& library)
558 : {
559 : // Make the container thread-safe.
560 428 : _mutex.lock();
561 :
562 : // XXX Check for double entries.
563 : // Insert the native library.
564 428 : _libraries.insert(std::make_pair(library.get_classloader(), library));
565 :
566 428 : _mutex.unlock();
567 428 : }
568 : #endif
569 :
570 :
571 : /**
572 : * Checks if the given native library is loaded.
573 : *
574 : * @param library Native library.
575 : *
576 : * @return true if loaded, false otherwise.
577 : */
578 593 : bool NativeLibraries::is_loaded(NativeLibrary& library)
579 : {
580 593 : std::pair<MAP::const_iterator, MAP::const_iterator> its = _libraries.equal_range(library.get_classloader());
581 :
582 : // No entry for the classloader was found (the range has length
583 : // zero).
584 593 : if (its.first == its.second)
585 147 : return false;
586 :
587 446 : MAP::const_iterator it = find_if(its.first, its.second, std::bind2nd(comparator(), library.get_filename()));
588 :
589 : // No matching entry in the range found.
590 446 : if (it == its.second)
591 285 : return false;
592 :
593 161 : return true;
594 : }
595 :
596 :
597 : /**
598 : * Try to find a symbol with the given name in all loaded native
599 : * libraries defined by classloader.
600 : *
601 : * @param symbolname Name of the symbol to find.
602 : * @param classloader Defining classloader.
603 : *
604 : * @return Pointer to symbol if found, NULL otherwise.
605 : */
606 2043 : void* NativeLibraries::resolve_symbol(Utf8String symbolname, classloader_t* classloader)
607 : {
608 2043 : std::pair<MAP::const_iterator, MAP::const_iterator> its = _libraries.equal_range(classloader);
609 :
610 : // No entry for the classloader was found (the range has length
611 : // zero).
612 2043 : if (its.first == its.second)
613 2 : return NULL;
614 :
615 3606 : for (MAP::const_iterator it = its.first; it != its.second; it++) {
616 3323 : const NativeLibrary& library = (*it).second;
617 3323 : void* symbol = library.resolve_symbol(symbolname);
618 :
619 3323 : if (symbol != NULL)
620 1758 : return symbol;
621 : }
622 :
623 283 : return NULL;
624 : }
625 :
626 :
627 : /**
628 : * Registers a new native agent by specified by it's library name
629 : * and with an optional options string.
630 : *
631 : * @param library Name of the native agent library.
632 : * @param options The options string or NULL if not specified.
633 : */
634 : #if defined(ENABLE_JVMTI)
635 : void NativeAgents::register_agent_library(char* library, char* options)
636 : {
637 : NativeAgent na(library, options);
638 :
639 : // Insert native agent into list of agents.
640 : _agents.push_back(na);
641 : }
642 : #endif
643 :
644 :
645 : /**
646 : * Registers a new native agent by specified by a path to it's library
647 : * and with an optional options string.
648 : *
649 : * @param path Path of the native agent library.
650 : * @param options The options string or NULL if not specified.
651 : */
652 : #if defined(ENABLE_JVMTI)
653 : void NativeAgents::register_agent_path(char* path, char* options)
654 : {
655 : os::abort("NativeAgents::register_agent_library: Implement me!");
656 : }
657 : #endif
658 :
659 :
660 : /**
661 : * Loads all registered native agents and in turn calls their exported
662 : * start-up functions (Agent_OnLoad). If one of the agents reports an
663 : * error during start-up, the loading is stopped.
664 : *
665 : * @return True if all agents were loaded successfully, false if
666 : * one of them was not found or reported an error.
667 : */
668 : #if defined(ENABLE_JVMTI)
669 : bool NativeAgents::load_agents()
670 : {
671 : // Iterate over all registered agents.
672 : for (std::vector<NativeAgent>::iterator it = _agents.begin(); it != _agents.end(); ++it) {
673 : NativeAgent& na = *(it);
674 :
675 : // Construct agent library name.
676 : Buffer<> buf;
677 :
678 : Utf8String u = buf.write(NATIVE_LIBRARY_PREFIX)
679 : .write(na.get_library())
680 : .write(NATIVE_LIBRARY_SUFFIX)
681 : .utf8_str();
682 :
683 : // Construct new native library.
684 : NativeLibrary nl(u);
685 :
686 : // Try to open the library.
687 : if (nl.open() == NULL)
688 : return false;
689 :
690 : // Resolve Agent_OnLoad function.
691 : void* onload = os::dlsym(nl.get_handle(), "Agent_OnLoad");
692 :
693 : // Call Agent_OnLoad function if present.
694 : if (onload != NULL) {
695 : JNIEXPORT jint (JNICALL *Agent_OnLoad) (JavaVM*, char*, void*);
696 : JavaVM* vm = VM::get_current()->get_javavm();
697 :
698 : Agent_OnLoad = (JNIEXPORT jint (JNICALL *)(JavaVM*, char*, void*)) (uintptr_t) onload;
699 :
700 : jint result = Agent_OnLoad(vm, na.get_options(), NULL);
701 :
702 : // Check for error in Agent_OnLoad.
703 : if (result != 0) {
704 : nl.close();
705 : return false;
706 : }
707 : }
708 :
709 : // According to the interface spec, the library _must_ export
710 : // a start-up function.
711 : else {
712 : log_println("NativeAgents::load_agents: Native agent library does not export Agent_OnLoad");
713 : return false;
714 : }
715 : }
716 :
717 : return true;
718 : }
719 : #endif
720 :
721 :
722 : /* native_new_and_init *********************************************************
723 :
724 : Creates a new object on the heap and calls the initializer.
725 : Returns the object pointer or NULL if memory is exhausted.
726 :
727 : *******************************************************************************/
728 :
729 42832 : java_handle_t *native_new_and_init(classinfo *c)
730 : {
731 : methodinfo *m;
732 : java_handle_t *o;
733 :
734 42832 : if (c == NULL)
735 0 : vm_abort("native_new_and_init: c == NULL");
736 :
737 : /* create object */
738 :
739 42832 : o = builtin_new(c);
740 :
741 42832 : if (o == NULL)
742 0 : return NULL;
743 :
744 : /* try to find the initializer */
745 :
746 42832 : m = class_findmethod(c, utf8::init, utf8::void__void);
747 :
748 : /* ATTENTION: returning the object here is ok, since the class may
749 : not have an initializer */
750 :
751 42832 : if (m == NULL)
752 0 : return o;
753 :
754 : /* call initializer */
755 :
756 42832 : (void) vm_call_method(m, o);
757 :
758 42832 : return o;
759 : }
760 :
761 :
762 873 : java_handle_t *native_new_and_init_string(classinfo *c, java_handle_t *s)
763 : {
764 : methodinfo *m;
765 : java_handle_t *o;
766 :
767 873 : if (c == NULL)
768 0 : vm_abort("native_new_and_init_string: c == NULL");
769 :
770 : /* create object */
771 :
772 873 : o = builtin_new(c);
773 :
774 873 : if (o == NULL)
775 0 : return NULL;
776 :
777 : /* find initializer */
778 :
779 873 : m = class_findmethod(c, utf8::init, utf8::java_lang_String__void);
780 :
781 : /* initializer not found */
782 :
783 873 : if (m == NULL)
784 0 : return NULL;
785 :
786 : /* call initializer */
787 :
788 873 : (void) vm_call_method(m, o, s);
789 :
790 873 : return o;
791 : }
792 :
793 :
794 : /*
795 : * These are local overrides for various environment variables in Emacs.
796 : * Please do not remove this and leave it at the end of the file, where
797 : * Emacs will automagically detect them.
798 : * ---------------------------------------------------------------------
799 : * Local variables:
800 : * mode: c++
801 : * indent-tabs-mode: t
802 : * c-basic-offset: 4
803 : * tab-width: 4
804 : * End:
805 : * vim:noexpandtab:sw=4:ts=4:
806 : */
|