Branch data Line data Source code
1 : : // $Id: ID.cc 6724 2009-06-07 09:23:03Z vern $
2 : : //
3 : : // See the file "COPYING" in the main distribution directory for copyright.
4 : :
5 : : #include "config.h"
6 : :
7 : : #include "ID.h"
8 : : #include "Expr.h"
9 : : #include "Dict.h"
10 : : #include "EventRegistry.h"
11 : : #include "Func.h"
12 : : #include "Scope.h"
13 : : #include "File.h"
14 : : #include "Serializer.h"
15 : : #include "RemoteSerializer.h"
16 : : #include "PersistenceSerializer.h"
17 : : #include "Scope.h"
18 : : #include "Traverse.h"
19 : :
20 : 8952 : ID::ID(const char* arg_name, IDScope arg_scope, bool arg_is_export)
21 : : {
22 : 8952 : name = copy_string(arg_name);
23 : 8952 : scope = arg_scope;
24 : 8952 : is_export = arg_is_export;
25 : 8952 : type = 0;
26 : 8952 : val = 0;
27 : 8952 : attrs = 0;
28 : 8952 : is_const = 0;
29 : 8952 : is_enum_const = 0;
30 : 8952 : is_type = 0;
31 : 8952 : offset = 0;
32 : :
33 : 8952 : infer_return_type = false;
34 : 8952 : weak_ref = false;
35 : :
36 : 8952 : SetLocationInfo(&start_location, &end_location);
37 : 8952 : }
38 : :
39 : 0 : ID::~ID()
40 : : {
41 [ # # ][ # # ]: 0 : delete [] name;
[ # # ]
42 : 0 : Unref(type);
43 : 0 : Unref(attrs);
44 : :
45 [ # # # # # : 0 : if ( ! weak_ref )
# ]
46 : 0 : Unref(val);
47 [ # # ][ # # ]: 0 : }
[ # # ]
48 : :
49 : 911 : string ID::ModuleName() const
50 : : {
51 : 911 : return extract_module_name(name);
52 : : }
53 : :
54 : 0 : void ID::ClearVal()
55 : : {
56 [ # # ]: 0 : if ( ! weak_ref )
57 : 0 : Unref(val);
58 : :
59 : 0 : val = 0;
60 : 0 : }
61 : :
62 : 5607 : void ID::SetVal(Val* v, Opcode op, bool arg_weak_ref)
63 : : {
64 [ + + ]: 5607 : if ( op != OP_NONE )
65 : : {
66 [ + + ][ + + ]: 5589 : if ( type && val && type->Tag() == TYPE_TABLE &&
[ + + ][ - + ]
[ # # ][ - + ]
67 : : val->AsTableVal()->FindAttr(ATTR_MERGEABLE) &&
68 : : v->AsTableVal()->FindAttr(ATTR_MERGEABLE) )
69 : : {
70 : : StateAccess::Log(new StateAccess(OP_ASSIGN, this,
71 : 0 : v, val));
72 : 0 : v->AsTableVal()->AddTo(val->AsTableVal(), 0, false);
73 : 0 : return;
74 : : }
75 : :
76 : 5589 : MutableVal::Properties props = 0;
77 : :
78 [ + + ][ + + ]: 5589 : if ( attrs && attrs->FindAttr(ATTR_SYNCHRONIZED) )
[ + + ]
79 : 2 : props |= MutableVal::SYNCHRONIZED;
80 : :
81 [ + + ][ + + ]: 5589 : if ( attrs && attrs->FindAttr(ATTR_PERSISTENT) )
[ + + ]
82 : 14 : props |= MutableVal::PERSISTENT;
83 : :
84 [ + + ][ - + ]: 5589 : if ( attrs && attrs->FindAttr(ATTR_TRACKED) )
[ - + ]
85 : 0 : props |= MutableVal::TRACKED;
86 : :
87 [ + + ]: 5589 : if ( props )
88 : : {
89 [ + - ]: 16 : if ( v->IsMutableVal() )
90 : 16 : v->AsMutableVal()->AddProperties(props);
91 : : }
92 : :
93 : : #ifndef DEBUG
94 : : if ( props )
95 : : #else
96 [ + - ][ + + ]: 5589 : if ( debug_logger.IsVerbose() || props )
[ + + ]
97 : : #endif
98 : 16 : StateAccess::Log(new StateAccess(op, this, v, val));
99 : : }
100 : :
101 [ + - ]: 5607 : if ( ! weak_ref )
102 : 5607 : Unref(val);
103 : :
104 : 5607 : val = v;
105 : 5607 : weak_ref = arg_weak_ref;
106 : :
107 : : #ifdef DEBUG
108 : 5607 : UpdateValID();
109 : : #endif
110 : :
111 [ + + + - ]: 5607 : if ( type && val &&
[ + + ][ + + ]
[ + + ]
112 : : type->Tag() == TYPE_FUNC && type->AsFuncType()->IsEvent() )
113 : : {
114 : 399 : EventHandler* handler = event_registry->Lookup(name);
115 [ + + ]: 399 : if ( ! handler )
116 : : {
117 : 62 : handler = new EventHandler(name);
118 : 62 : handler->SetLocalHandler(val->AsFunc());
119 : 62 : event_registry->Register(handler);
120 : : }
121 : : else
122 : : {
123 : : // Otherwise, internally defined events cannot
124 : : // have local handler.
125 : 5607 : handler->SetLocalHandler(val->AsFunc());
126 : : }
127 : : }
128 : : }
129 : :
130 : 1976 : void ID::SetVal(Val* v, init_class c)
131 : : {
132 [ + + ][ + + ]: 1976 : if ( c == INIT_NONE || c == INIT_FULL )
133 : : {
134 : 1880 : SetVal(v);
135 : 1880 : return;
136 : : }
137 : :
138 [ + + ][ + - ]: 96 : if ( type->Tag() != TYPE_TABLE &&
[ - + ][ - + ]
139 : : (type->Tag() != TYPE_PATTERN || c == INIT_REMOVE) )
140 : : {
141 [ # # ]: 0 : if ( c == INIT_EXTRA )
142 : 0 : Error("+= initializer only applies to tables, sets and patterns", v);
143 : : else
144 : 0 : Error("-= initializer only applies to tables and sets", v);
145 : : }
146 : :
147 : : else
148 : : {
149 [ + - ]: 96 : if ( c == INIT_EXTRA )
150 : : {
151 [ - + ]: 96 : if ( ! val )
152 : : {
153 : 0 : SetVal(v);
154 : 0 : return;
155 : : }
156 : : else
157 : 96 : v->AddTo(val, 0);
158 : : }
159 : : else
160 : : {
161 [ # # ]: 0 : if ( val )
162 : 0 : v->RemoveFrom(val);
163 : : }
164 : : }
165 : :
166 : 1976 : Unref(v);
167 : : }
168 : :
169 : 2 : void ID::SetVal(Expr* ev, init_class c)
170 : : {
171 : : Attr* a = attrs->FindAttr(c == INIT_EXTRA ?
172 [ + - ]: 2 : ATTR_ADD_FUNC : ATTR_DEL_FUNC);
173 : :
174 [ - + ]: 2 : if ( ! a )
175 : 0 : Internal("no add/delete function in ID::SetVal");
176 : :
177 : 2 : EvalFunc(a->AttrExpr(), ev);
178 : 2 : }
179 : :
180 : 0 : void ID::SetAttrs(Attributes* a)
181 : : {
182 : 0 : Unref(attrs);
183 : 0 : attrs = 0;
184 : 0 : AddAttrs(a);
185 : 0 : }
186 : :
187 : 6542 : void ID::UpdateValAttrs()
188 : : {
189 [ + + ]: 6542 : if ( ! attrs )
190 : 3812 : return;
191 : :
192 : 2730 : MutableVal::Properties props = 0;
193 : :
194 [ + + ][ + + ]: 2730 : if ( val && val->IsMutableVal() )
[ + + ]
195 : : {
196 [ + + ]: 603 : if ( attrs->FindAttr(ATTR_SYNCHRONIZED) )
197 : 2 : props |= MutableVal::SYNCHRONIZED;
198 : :
199 [ + + ]: 603 : if ( attrs->FindAttr(ATTR_PERSISTENT) )
200 : 18 : props |= MutableVal::PERSISTENT;
201 : :
202 [ - + ]: 603 : if ( attrs->FindAttr(ATTR_TRACKED) )
203 : 0 : props |= MutableVal::TRACKED;
204 : :
205 : 603 : val->AsMutableVal()->AddProperties(props);
206 : : }
207 : :
208 [ + - ]: 2730 : if ( ! IsInternalGlobal() )
209 : : {
210 [ + + ]: 2730 : if ( attrs->FindAttr(ATTR_SYNCHRONIZED) )
211 : 4 : remote_serializer->Register(this);
212 : :
213 [ + + ]: 2730 : if ( attrs->FindAttr(ATTR_PERSISTENT) )
214 : 32 : persistence_serializer->Register(this);
215 : : }
216 : :
217 [ + + ][ + + ]: 2730 : if ( val && val->Type()->Tag() == TYPE_TABLE )
[ + + ]
218 : 590 : val->AsTableVal()->SetAttrs(attrs);
219 : :
220 [ + + ][ + + ]: 2730 : if ( val && val->Type()->Tag() == TYPE_FILE )
[ + + ]
221 : 38 : val->AsFile()->SetAttrs(attrs);
222 : :
223 [ + + ]: 2730 : if ( Type()->Tag() == TYPE_FUNC )
224 : : {
225 : 244 : Attr* attr = attrs->FindAttr(ATTR_GROUP);
226 [ + + ]: 244 : if ( attr )
227 : : {
228 : 216 : Val* group = attr->AttrExpr()->ExprVal();
229 [ + - ]: 216 : if ( group )
230 : : {
231 [ + - ]: 216 : if ( group->Type()->Tag() == TYPE_STRING )
232 : 216 : event_registry->SetGroup(Name(), group->AsString()->CheckString());
233 : : else
234 : 6542 : Error("&group attribute takes string");
235 : : }
236 : : }
237 : : }
238 : : }
239 : :
240 : 1280 : void ID::AddAttrs(Attributes* a)
241 : : {
242 [ + + ]: 1280 : if ( attrs )
243 : 16 : attrs->AddAttrs(a);
244 : : else
245 : 1264 : attrs = a;
246 : :
247 : 1280 : UpdateValAttrs();
248 : 1280 : }
249 : :
250 : 0 : void ID::RemoveAttr(attr_tag a)
251 : : {
252 [ # # ]: 0 : if ( attrs )
253 : 0 : attrs->RemoveAttr(a);
254 : :
255 [ # # ][ # # ]: 0 : if ( val && val->IsMutableVal() )
[ # # ]
256 : : {
257 : 0 : MutableVal::Properties props = 0;
258 : :
259 [ # # ]: 0 : if ( a == ATTR_SYNCHRONIZED )
260 : 0 : props |= MutableVal::SYNCHRONIZED;
261 : :
262 [ # # ]: 0 : if ( a == ATTR_PERSISTENT )
263 : 0 : props |= MutableVal::PERSISTENT;
264 : :
265 [ # # ]: 0 : if ( a == ATTR_TRACKED )
266 : 0 : props |= MutableVal::TRACKED;
267 : :
268 : 0 : val->AsMutableVal()->RemoveProperties(props);
269 : : }
270 : 0 : }
271 : :
272 : 2 : void ID::EvalFunc(Expr* ef, Expr* ev)
273 : : {
274 : 2 : Expr* arg1 = new ConstExpr(val->Ref());
275 : 2 : ListExpr* args = new ListExpr();
276 : 2 : args->Append(arg1);
277 : 2 : args->Append(ev->Ref());
278 : :
279 : 2 : CallExpr* ce = new CallExpr(ef->Ref(), args);
280 : :
281 : 2 : SetVal(ce->Eval(0));
282 : 2 : Unref(ce);
283 : 2 : }
284 : :
285 : 1 : bool ID::Serialize(SerialInfo* info) const
286 : : {
287 : 1 : return (ID*) SerialObj::Serialize(info);
288 : : }
289 : :
290 : : #if 0
291 : : void ID::CopyFrom(const ID* id)
292 : : {
293 : : is_export = id->is_export;
294 : : is_const = id->is_const;
295 : : is_enum_const = id->is_enum_const;
296 : : is_type = id->is_type;
297 : : offset = id->offset ;
298 : : infer_return_type = id->infer_return_type;
299 : :
300 : : if ( FindAttr(ATTR_PERSISTENT) )
301 : : persistence_serializer->Unregister(this);
302 : :
303 : : if ( id->type )
304 : : Ref(id->type);
305 : : if ( id->val && ! id->weak_ref )
306 : : Ref(id->val);
307 : : if ( id->attrs )
308 : : Ref(id->attrs);
309 : :
310 : : Unref(type);
311 : : Unref(attrs);
312 : : if ( ! weak_ref )
313 : : Unref(val);
314 : :
315 : : type = id->type;
316 : : val = id->val;
317 : : attrs = id->attrs;
318 : : weak_ref = id->weak_ref;
319 : :
320 : : #ifdef DEBUG
321 : : UpdateValID();
322 : : #endif
323 : :
324 : : if ( FindAttr(ATTR_PERSISTENT) )
325 : : persistence_serializer->Unregister(this);
326 : : }
327 : : #endif
328 : :
329 : 0 : ID* ID::Unserialize(UnserialInfo* info)
330 : : {
331 : 0 : ID* id = (ID*) SerialObj::Unserialize(info, SER_ID);
332 [ # # ]: 0 : if ( ! id )
333 : 0 : return 0;
334 : :
335 [ # # ]: 0 : if ( ! id->IsGlobal() )
336 : 0 : return id;
337 : :
338 : : // Globals.
339 : 0 : ID* current = global_scope()->Lookup(id->name);
340 : :
341 [ # # ]: 0 : if ( ! current )
342 : : {
343 [ # # ]: 0 : if ( ! info->install_globals )
344 : : {
345 : 0 : info->s->Error("undefined");
346 : 0 : Unref(id);
347 : 0 : return 0;
348 : : }
349 : :
350 : 0 : Ref(id);
351 : 0 : global_scope()->Insert(id->Name(), id);
352 : : #ifdef USE_PERFTOOLS
353 : : heap_checker->IgnoreObject(id);
354 : : #endif
355 : : }
356 : :
357 : : else
358 : : {
359 [ # # ]: 0 : if ( info->id_policy != UnserialInfo::InstantiateNew )
360 : : {
361 : 0 : persistence_serializer->Unregister(current);
362 : 0 : remote_serializer->Unregister(current);
363 : : }
364 : :
365 [ # # # # # : 0 : switch ( info->id_policy ) {
# ]
366 : :
367 : : case UnserialInfo::Keep:
368 : 0 : Unref(id);
369 : 0 : Ref(current);
370 : 0 : id = current;
371 : 0 : break;
372 : :
373 : : case UnserialInfo::Replace:
374 : 0 : Unref(current);
375 : 0 : Ref(id);
376 : 0 : global_scope()->Insert(id->Name(), id);
377 : 0 : break;
378 : :
379 : : case UnserialInfo::CopyNewToCurrent:
380 [ # # ]: 0 : if ( ! same_type(current->type, id->type) )
381 : : {
382 : 0 : info->s->Error("type mismatch");
383 : 0 : Unref(id);
384 : 0 : return 0;
385 : : }
386 : :
387 [ # # ]: 0 : if ( ! current->weak_ref )
388 : 0 : Unref(current->val);
389 : :
390 : 0 : current->val = id->val;
391 : 0 : current->weak_ref = id->weak_ref;
392 [ # # ][ # # ]: 0 : if ( current->val && ! current->weak_ref )
393 : 0 : Ref(current->val);
394 : :
395 : : #ifdef DEBUG
396 : 0 : current->UpdateValID();
397 : : #endif
398 : :
399 : 0 : Unref(id);
400 : 0 : Ref(current);
401 : 0 : id = current;
402 : :
403 : 0 : break;
404 : :
405 : : case UnserialInfo::CopyCurrentToNew:
406 [ # # ]: 0 : if ( ! same_type(current->type, id->type) )
407 : : {
408 : 0 : info->s->Error("type mismatch");
409 : 0 : return 0;
410 : : }
411 [ # # ]: 0 : if ( ! id->weak_ref )
412 : 0 : Unref(id->val);
413 : 0 : id->val = current->val;
414 : 0 : id->weak_ref = current->weak_ref;
415 [ # # ][ # # ]: 0 : if ( id->val && ! id->weak_ref )
416 : 0 : Ref(id->val);
417 : :
418 : : #ifdef DEBUG
419 : 0 : id->UpdateValID();
420 : : #endif
421 : :
422 : 0 : Unref(current);
423 : 0 : Ref(id);
424 : 0 : global_scope()->Insert(id->Name(), id);
425 : 0 : break;
426 : :
427 : : case UnserialInfo::InstantiateNew:
428 : : // Do nothing.
429 : 0 : break;
430 : :
431 : : default:
432 : 0 : internal_error("unknown type for UnserialInfo::id_policy");
433 : :
434 : : }
435 : : }
436 : :
437 [ # # ]: 0 : if ( id->FindAttr(ATTR_PERSISTENT) )
438 : 0 : persistence_serializer->Register(id);
439 : :
440 [ # # ]: 0 : if ( id->FindAttr(ATTR_SYNCHRONIZED) )
441 : 0 : remote_serializer->Register(id);
442 : :
443 : 0 : return id;
444 : :
445 : : }
446 : :
447 : 5 : IMPLEMENT_SERIAL(ID, SER_ID);
448 : :
449 : 1 : bool ID::DoSerialize(SerialInfo* info) const
450 : : {
451 [ + - ][ - + ]: 1 : DO_SERIALIZE_WITH_SUSPEND(SER_ID, BroObj);
[ # # ][ + - ]
[ - + ][ - + ]
452 : :
453 [ + - ]: 1 : if ( info->cont.NewInstance() )
454 : : {
455 : 1 : DisableSuspend suspend(info);
456 : :
457 : 1 : info->s->WriteOpenTag("ID");
458 : :
459 [ + - + - ]: 1 : if ( ! (SERIALIZE(name) &&
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ - + ]
[ - + ]
460 : : SERIALIZE(char(scope)) &&
461 : : SERIALIZE(is_export) &&
462 : : SERIALIZE(is_const) &&
463 : : SERIALIZE(is_enum_const) &&
464 : : SERIALIZE(is_type) &&
465 : : SERIALIZE(offset) &&
466 : : SERIALIZE(infer_return_type) &&
467 : : SERIALIZE(weak_ref) &&
468 : : type->Serialize(info)) )
469 : 0 : return false;
470 : :
471 [ + - ][ + - ]: 1 : SERIALIZE_OPTIONAL(attrs);
[ - + ][ - + ]
[ - + ][ # # ]
[ - + ]
472 : : }
473 : :
474 [ + - ][ + - ]: 1 : SERIALIZE_OPTIONAL(val);
[ - + ][ - + ]
[ - + ][ # # ]
475 : :
476 : 1 : return true;
477 : : }
478 : :
479 : 0 : bool ID::DoUnserialize(UnserialInfo* info)
480 : : {
481 : 0 : bool installed_tmp = false;
482 : :
483 [ # # ]: 0 : DO_UNSERIALIZE(BroObj);
484 : :
485 : : char id_scope;
486 : :
487 [ # # ][ # # ]: 0 : if ( ! (UNSERIALIZE_STR(&name, 0) &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
488 : : UNSERIALIZE(&id_scope) &&
489 : : UNSERIALIZE(&is_export) &&
490 : : UNSERIALIZE(&is_const) &&
491 : : UNSERIALIZE(&is_enum_const) &&
492 : : UNSERIALIZE(&is_type) &&
493 : : UNSERIALIZE(&offset) &&
494 : : UNSERIALIZE(&infer_return_type) &&
495 : : UNSERIALIZE(&weak_ref)
496 : : ) )
497 : 0 : return false;
498 : :
499 : 0 : scope = IDScope(id_scope);
500 : :
501 : 0 : info->s->SetErrorDescr(fmt("unserializing ID %s", name));
502 : :
503 : 0 : type = BroType::Unserialize(info);
504 [ # # ]: 0 : if ( ! type )
505 : 0 : return false;
506 : :
507 [ # # ][ # # ]: 0 : UNSERIALIZE_OPTIONAL(attrs, Attributes::Unserialize(info));
[ # # ]
508 : :
509 : : // If it's a global function not currently known,
510 : : // we temporarily install it in global scope.
511 : : // This is necessary for recursive functions.
512 [ # # ][ # # ]: 0 : if ( IsGlobal() && Type()->Tag() == TYPE_FUNC )
[ # # ]
513 : : {
514 : 0 : ID* current = global_scope()->Lookup(name);
515 [ # # ]: 0 : if ( ! current )
516 : : {
517 : 0 : installed_tmp = true;
518 : 0 : global_scope()->Insert(Name(), this);
519 : : }
520 : : }
521 : :
522 [ # # ][ # # ]: 0 : UNSERIALIZE_OPTIONAL(val, Val::Unserialize(info));
[ # # ]
523 : : #ifdef DEBUG
524 : 0 : UpdateValID();
525 : : #endif
526 : :
527 [ # # ]: 0 : if ( weak_ref )
528 : : {
529 : : // At this point at least the serialization cache will hold a
530 : : // reference so this will not delete the val.
531 [ # # ]: 0 : assert(val->RefCnt() > 1);
532 : 0 : Unref(val);
533 : : }
534 : :
535 [ # # ][ # # ]: 0 : if ( installed_tmp && ! global_scope()->Remove(name) )
[ # # ]
536 : 0 : internal_error("tmp id missing");
537 : :
538 : 0 : return true;
539 : : }
540 : :
541 : 0 : TraversalCode ID::Traverse(TraversalCallback* cb) const
542 : : {
543 : 0 : TraversalCode tc = cb->PreID(this);
544 [ # # # # ]: 0 : HANDLE_TC_STMT_PRE(tc);
545 : :
546 [ # # ]: 0 : if ( is_type )
547 : : {
548 : 0 : tc = cb->PreTypedef(this);
549 [ # # # # ]: 0 : HANDLE_TC_STMT_PRE(tc);
550 : :
551 : 0 : tc = cb->PostTypedef(this);
552 [ # # # # ]: 0 : HANDLE_TC_STMT_PRE(tc);
553 : : }
554 : :
555 : : // FIXME: Perhaps we should be checking at other than global scope.
556 [ # # ][ # # ]: 0 : else if ( val && IsFunc(val->Type()->Tag()) &&
[ # # ][ # # ]
557 : : cb->current_scope == global_scope() )
558 : : {
559 : 0 : tc = val->AsFunc()->Traverse(cb);
560 [ # # # # ]: 0 : HANDLE_TC_STMT_PRE(tc);
561 : : }
562 : :
563 [ # # ]: 0 : else if ( ! is_enum_const )
564 : : {
565 : 0 : tc = cb->PreDecl(this);
566 [ # # # # ]: 0 : HANDLE_TC_STMT_PRE(tc);
567 : :
568 : 0 : tc = cb->PostDecl(this);
569 [ # # # # ]: 0 : HANDLE_TC_STMT_PRE(tc);
570 : : }
571 : :
572 : 0 : tc = cb->PostID(this);
573 : 0 : HANDLE_TC_EXPR_POST(tc);
574 : : }
575 : :
576 : 0 : void ID::Error(const char* msg, const BroObj* o2)
577 : : {
578 : 0 : BroObj::Error(msg, o2, 1);
579 : 0 : SetType(error_type());
580 : 0 : }
581 : :
582 : 0 : void ID::Describe(ODesc* d) const
583 : : {
584 : 0 : d->Add(name);
585 : 0 : }
586 : :
587 : 0 : void ID::DescribeExtended(ODesc* d) const
588 : : {
589 : 0 : d->Add(name);
590 : :
591 [ # # ]: 0 : if ( type )
592 : : {
593 : 0 : d->Add(" : ");
594 : 0 : type->Describe(d);
595 : : }
596 : :
597 [ # # ]: 0 : if ( val )
598 : : {
599 : 0 : d->Add(" = ");
600 : 0 : val->Describe(d);
601 : : }
602 : :
603 [ # # ]: 0 : if ( attrs )
604 : : {
605 : 0 : d->Add(" ");
606 : 0 : attrs->Describe(d);
607 : : }
608 : 0 : }
609 : :
610 : : #ifdef DEBUG
611 : 5607 : void ID::UpdateValID()
612 : : {
613 [ + + ][ + - ]: 5607 : if ( IsGlobal() && val && name && name[0] != '#' )
[ + - ][ + + ]
[ + + ]
614 : 5569 : val->SetID(this);
615 [ + - ][ + - ]: 5613 : }
616 : 3 : #endif
617 : :
|