1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "Config.h"
6 #include "RecordInfo.h"
7 #include "clang/Sema/Sema.h"
8
9 using namespace clang;
10 using std::string;
11
RecordInfo(CXXRecordDecl * record,RecordCache * cache)12 RecordInfo::RecordInfo(CXXRecordDecl* record, RecordCache* cache)
13 : cache_(cache),
14 record_(record),
15 name_(record->getName()),
16 fields_need_tracing_(TracingStatus::Unknown()),
17 bases_(0),
18 fields_(0),
19 is_stack_allocated_(kNotComputed),
20 is_non_newable_(kNotComputed),
21 is_only_placement_newable_(kNotComputed),
22 does_need_finalization_(kNotComputed),
23 has_gc_mixin_methods_(kNotComputed),
24 is_declaring_local_trace_(kNotComputed),
25 is_eagerly_finalized_(kNotComputed),
26 determined_trace_methods_(false),
27 trace_method_(0),
28 trace_dispatch_method_(0),
29 finalize_dispatch_method_(0),
30 is_gc_derived_(false) {}
31
~RecordInfo()32 RecordInfo::~RecordInfo() {
33 delete fields_;
34 delete bases_;
35 }
36
37 // Get |count| number of template arguments. Returns false if there
38 // are fewer than |count| arguments or any of the arguments are not
39 // of a valid Type structure. If |count| is non-positive, all
40 // arguments are collected.
GetTemplateArgs(size_t count,TemplateArgs * output_args)41 bool RecordInfo::GetTemplateArgs(size_t count, TemplateArgs* output_args) {
42 ClassTemplateSpecializationDecl* tmpl =
43 dyn_cast<ClassTemplateSpecializationDecl>(record_);
44 if (!tmpl)
45 return false;
46 const TemplateArgumentList& args = tmpl->getTemplateArgs();
47 if (args.size() < count)
48 return false;
49 if (count <= 0)
50 count = args.size();
51 for (unsigned i = 0; i < count; ++i) {
52 TemplateArgument arg = args[i];
53 if (arg.getKind() == TemplateArgument::Type && !arg.getAsType().isNull()) {
54 output_args->push_back(arg.getAsType().getTypePtr());
55 } else {
56 return false;
57 }
58 }
59 return true;
60 }
61
62 // Test if a record is a HeapAllocated collection.
IsHeapAllocatedCollection()63 bool RecordInfo::IsHeapAllocatedCollection() {
64 if (!Config::IsGCCollection(name_) && !Config::IsWTFCollection(name_))
65 return false;
66
67 TemplateArgs args;
68 if (GetTemplateArgs(0, &args)) {
69 for (TemplateArgs::iterator it = args.begin(); it != args.end(); ++it) {
70 if (CXXRecordDecl* decl = (*it)->getAsCXXRecordDecl())
71 if (decl->getName() == kHeapAllocatorName)
72 return true;
73 }
74 }
75
76 return Config::IsGCCollection(name_);
77 }
78
79 // Test if a record is derived from a garbage collected base.
IsGCDerived()80 bool RecordInfo::IsGCDerived() {
81 // If already computed, return the known result.
82 if (gc_base_names_.size())
83 return is_gc_derived_;
84
85 if (!record_->hasDefinition())
86 return false;
87
88 // The base classes are not themselves considered garbage collected objects.
89 if (Config::IsGCBase(name_))
90 return false;
91
92 // Walk the inheritance tree to find GC base classes.
93 walkBases();
94 return is_gc_derived_;
95 }
96
GetDependentTemplatedDecl(const Type & type)97 CXXRecordDecl* RecordInfo::GetDependentTemplatedDecl(const Type& type) {
98 const TemplateSpecializationType* tmpl_type =
99 type.getAs<TemplateSpecializationType>();
100 if (!tmpl_type)
101 return 0;
102
103 TemplateDecl* tmpl_decl = tmpl_type->getTemplateName().getAsTemplateDecl();
104 if (!tmpl_decl)
105 return 0;
106
107 return dyn_cast_or_null<CXXRecordDecl>(tmpl_decl->getTemplatedDecl());
108 }
109
walkBases()110 void RecordInfo::walkBases() {
111 // This traversal is akin to CXXRecordDecl::forallBases()'s,
112 // but without stepping over dependent bases -- these might also
113 // have a "GC base name", so are to be included and considered.
114 SmallVector<const CXXRecordDecl*, 8> queue;
115
116 const CXXRecordDecl *base_record = record();
117 while (true) {
118 for (const auto& it : base_record->bases()) {
119 const RecordType *type = it.getType()->getAs<RecordType>();
120 CXXRecordDecl* base;
121 if (!type)
122 base = GetDependentTemplatedDecl(*it.getType());
123 else {
124 base = cast_or_null<CXXRecordDecl>(type->getDecl()->getDefinition());
125 if (base)
126 queue.push_back(base);
127 }
128 if (!base)
129 continue;
130
131 const std::string& name = base->getName();
132 if (Config::IsGCBase(name)) {
133 gc_base_names_.push_back(name);
134 is_gc_derived_ = true;
135 }
136 }
137
138 if (queue.empty())
139 break;
140 base_record = queue.pop_back_val(); // not actually a queue.
141 }
142 }
143
IsGCFinalized()144 bool RecordInfo::IsGCFinalized() {
145 if (!IsGCDerived())
146 return false;
147 for (const auto& gc_base : gc_base_names_) {
148 if (Config::IsGCFinalizedBase(gc_base))
149 return true;
150 }
151 return false;
152 }
153
154 // A GC mixin is a class that inherits from a GC mixin base and has
155 // not yet been "mixed in" with another GC base class.
IsGCMixin()156 bool RecordInfo::IsGCMixin() {
157 if (!IsGCDerived() || !gc_base_names_.size())
158 return false;
159 for (const auto& gc_base : gc_base_names_) {
160 // If it is not a mixin base we are done.
161 if (!Config::IsGCMixinBase(gc_base))
162 return false;
163 }
164 // This is a mixin if all GC bases are mixins.
165 return true;
166 }
167
168 // Test if a record is allocated on the managed heap.
IsGCAllocated()169 bool RecordInfo::IsGCAllocated() {
170 return IsGCDerived() || IsHeapAllocatedCollection();
171 }
172
IsEagerlyFinalized()173 bool RecordInfo::IsEagerlyFinalized() {
174 if (is_eagerly_finalized_ == kNotComputed) {
175 is_eagerly_finalized_ = kFalse;
176 if (IsGCFinalized()) {
177 for (Decl* decl : record_->decls()) {
178 if (TypedefDecl* typedef_decl = dyn_cast<TypedefDecl>(decl)) {
179 if (typedef_decl->getNameAsString() == kIsEagerlyFinalizedName) {
180 is_eagerly_finalized_ = kTrue;
181 break;
182 }
183 }
184 }
185 }
186 }
187 return is_eagerly_finalized_;
188 }
189
HasDefinition()190 bool RecordInfo::HasDefinition() {
191 return record_->hasDefinition();
192 }
193
Lookup(CXXRecordDecl * record)194 RecordInfo* RecordCache::Lookup(CXXRecordDecl* record) {
195 // Ignore classes annotated with the GC_PLUGIN_IGNORE macro.
196 if (!record || Config::IsIgnoreAnnotated(record))
197 return 0;
198 Cache::iterator it = cache_.find(record);
199 if (it != cache_.end())
200 return &it->second;
201 return &cache_.insert(std::make_pair(record, RecordInfo(record, this)))
202 .first->second;
203 }
204
IsStackAllocated()205 bool RecordInfo::IsStackAllocated() {
206 if (is_stack_allocated_ == kNotComputed) {
207 is_stack_allocated_ = kFalse;
208 for (Bases::iterator it = GetBases().begin();
209 it != GetBases().end();
210 ++it) {
211 if (it->second.info()->IsStackAllocated()) {
212 is_stack_allocated_ = kTrue;
213 return is_stack_allocated_;
214 }
215 }
216 for (CXXRecordDecl::method_iterator it = record_->method_begin();
217 it != record_->method_end();
218 ++it) {
219 if (it->getNameAsString() == kNewOperatorName &&
220 it->isDeleted() &&
221 Config::IsStackAnnotated(*it)) {
222 is_stack_allocated_ = kTrue;
223 return is_stack_allocated_;
224 }
225 }
226 }
227 return is_stack_allocated_;
228 }
229
IsNonNewable()230 bool RecordInfo::IsNonNewable() {
231 if (is_non_newable_ == kNotComputed) {
232 bool deleted = false;
233 bool all_deleted = true;
234 for (CXXRecordDecl::method_iterator it = record_->method_begin();
235 it != record_->method_end();
236 ++it) {
237 if (it->getNameAsString() == kNewOperatorName) {
238 deleted = it->isDeleted();
239 all_deleted = all_deleted && deleted;
240 }
241 }
242 is_non_newable_ = (deleted && all_deleted) ? kTrue : kFalse;
243 }
244 return is_non_newable_;
245 }
246
IsOnlyPlacementNewable()247 bool RecordInfo::IsOnlyPlacementNewable() {
248 if (is_only_placement_newable_ == kNotComputed) {
249 bool placement = false;
250 bool new_deleted = false;
251 for (CXXRecordDecl::method_iterator it = record_->method_begin();
252 it != record_->method_end();
253 ++it) {
254 if (it->getNameAsString() == kNewOperatorName) {
255 if (it->getNumParams() == 1) {
256 new_deleted = it->isDeleted();
257 } else if (it->getNumParams() == 2) {
258 placement = !it->isDeleted();
259 }
260 }
261 }
262 is_only_placement_newable_ = (placement && new_deleted) ? kTrue : kFalse;
263 }
264 return is_only_placement_newable_;
265 }
266
DeclaresNewOperator()267 CXXMethodDecl* RecordInfo::DeclaresNewOperator() {
268 for (CXXRecordDecl::method_iterator it = record_->method_begin();
269 it != record_->method_end();
270 ++it) {
271 if (it->getNameAsString() == kNewOperatorName && it->getNumParams() == 1)
272 return *it;
273 }
274 return 0;
275 }
276
277 // An object requires a tracing method if it has any fields that need tracing
278 // or if it inherits from multiple bases that need tracing.
RequiresTraceMethod()279 bool RecordInfo::RequiresTraceMethod() {
280 if (IsStackAllocated())
281 return false;
282 unsigned bases_with_trace = 0;
283 for (Bases::iterator it = GetBases().begin(); it != GetBases().end(); ++it) {
284 if (it->second.NeedsTracing().IsNeeded())
285 ++bases_with_trace;
286 }
287 if (bases_with_trace > 1)
288 return true;
289 GetFields();
290 return fields_need_tracing_.IsNeeded();
291 }
292
293 // Get the actual tracing method (ie, can be traceAfterDispatch if there is a
294 // dispatch method).
GetTraceMethod()295 CXXMethodDecl* RecordInfo::GetTraceMethod() {
296 DetermineTracingMethods();
297 return trace_method_;
298 }
299
300 // Get the static trace dispatch method.
GetTraceDispatchMethod()301 CXXMethodDecl* RecordInfo::GetTraceDispatchMethod() {
302 DetermineTracingMethods();
303 return trace_dispatch_method_;
304 }
305
GetFinalizeDispatchMethod()306 CXXMethodDecl* RecordInfo::GetFinalizeDispatchMethod() {
307 DetermineTracingMethods();
308 return finalize_dispatch_method_;
309 }
310
GetBases()311 RecordInfo::Bases& RecordInfo::GetBases() {
312 if (!bases_)
313 bases_ = CollectBases();
314 return *bases_;
315 }
316
InheritsTrace()317 bool RecordInfo::InheritsTrace() {
318 if (GetTraceMethod())
319 return true;
320 for (Bases::iterator it = GetBases().begin(); it != GetBases().end(); ++it) {
321 if (it->second.info()->InheritsTrace())
322 return true;
323 }
324 return false;
325 }
326
InheritsNonVirtualTrace()327 CXXMethodDecl* RecordInfo::InheritsNonVirtualTrace() {
328 if (CXXMethodDecl* trace = GetTraceMethod())
329 return trace->isVirtual() ? 0 : trace;
330 for (Bases::iterator it = GetBases().begin(); it != GetBases().end(); ++it) {
331 if (CXXMethodDecl* trace = it->second.info()->InheritsNonVirtualTrace())
332 return trace;
333 }
334 return 0;
335 }
336
DeclaresGCMixinMethods()337 bool RecordInfo::DeclaresGCMixinMethods() {
338 DetermineTracingMethods();
339 return has_gc_mixin_methods_;
340 }
341
DeclaresLocalTraceMethod()342 bool RecordInfo::DeclaresLocalTraceMethod() {
343 if (is_declaring_local_trace_ != kNotComputed)
344 return is_declaring_local_trace_;
345 DetermineTracingMethods();
346 is_declaring_local_trace_ = trace_method_ ? kTrue : kFalse;
347 if (is_declaring_local_trace_) {
348 for (auto it = record_->method_begin();
349 it != record_->method_end(); ++it) {
350 if (*it == trace_method_) {
351 is_declaring_local_trace_ = kTrue;
352 break;
353 }
354 }
355 }
356 return is_declaring_local_trace_;
357 }
358
359 // A (non-virtual) class is considered abstract in Blink if it has
360 // no public constructors and no create methods.
IsConsideredAbstract()361 bool RecordInfo::IsConsideredAbstract() {
362 for (CXXRecordDecl::ctor_iterator it = record_->ctor_begin();
363 it != record_->ctor_end();
364 ++it) {
365 if (!it->isCopyOrMoveConstructor() && it->getAccess() == AS_public)
366 return false;
367 }
368 for (CXXRecordDecl::method_iterator it = record_->method_begin();
369 it != record_->method_end();
370 ++it) {
371 if (it->getNameAsString() == kCreateName)
372 return false;
373 }
374 return true;
375 }
376
CollectBases()377 RecordInfo::Bases* RecordInfo::CollectBases() {
378 // Compute the collection locally to avoid inconsistent states.
379 Bases* bases = new Bases;
380 if (!record_->hasDefinition())
381 return bases;
382 for (CXXRecordDecl::base_class_iterator it = record_->bases_begin();
383 it != record_->bases_end();
384 ++it) {
385 const CXXBaseSpecifier& spec = *it;
386 RecordInfo* info = cache_->Lookup(spec.getType());
387 if (!info)
388 continue;
389 CXXRecordDecl* base = info->record();
390 TracingStatus status = info->InheritsTrace()
391 ? TracingStatus::Needed()
392 : TracingStatus::Unneeded();
393 bases->push_back(std::make_pair(base, BasePoint(spec, info, status)));
394 }
395 return bases;
396 }
397
GetFields()398 RecordInfo::Fields& RecordInfo::GetFields() {
399 if (!fields_)
400 fields_ = CollectFields();
401 return *fields_;
402 }
403
CollectFields()404 RecordInfo::Fields* RecordInfo::CollectFields() {
405 // Compute the collection locally to avoid inconsistent states.
406 Fields* fields = new Fields;
407 if (!record_->hasDefinition())
408 return fields;
409 TracingStatus fields_status = TracingStatus::Unneeded();
410 for (RecordDecl::field_iterator it = record_->field_begin();
411 it != record_->field_end();
412 ++it) {
413 FieldDecl* field = *it;
414 // Ignore fields annotated with the GC_PLUGIN_IGNORE macro.
415 if (Config::IsIgnoreAnnotated(field))
416 continue;
417 if (Edge* edge = CreateEdge(field->getType().getTypePtrOrNull())) {
418 fields_status = fields_status.LUB(edge->NeedsTracing(Edge::kRecursive));
419 fields->insert(std::make_pair(field, FieldPoint(field, edge)));
420 }
421 }
422 fields_need_tracing_ = fields_status;
423 return fields;
424 }
425
DetermineTracingMethods()426 void RecordInfo::DetermineTracingMethods() {
427 if (determined_trace_methods_)
428 return;
429 determined_trace_methods_ = true;
430 if (Config::IsGCBase(name_))
431 return;
432 CXXMethodDecl* trace = nullptr;
433 CXXMethodDecl* trace_impl = nullptr;
434 CXXMethodDecl* trace_after_dispatch = nullptr;
435 bool has_adjust_and_mark = false;
436 bool has_is_heap_object_alive = false;
437 for (Decl* decl : record_->decls()) {
438 CXXMethodDecl* method = dyn_cast<CXXMethodDecl>(decl);
439 if (!method) {
440 if (FunctionTemplateDecl* func_template =
441 dyn_cast<FunctionTemplateDecl>(decl))
442 method = dyn_cast<CXXMethodDecl>(func_template->getTemplatedDecl());
443 }
444 if (!method)
445 continue;
446
447 switch (Config::GetTraceMethodType(method)) {
448 case Config::TRACE_METHOD:
449 trace = method;
450 break;
451 case Config::TRACE_AFTER_DISPATCH_METHOD:
452 trace_after_dispatch = method;
453 break;
454 case Config::TRACE_IMPL_METHOD:
455 trace_impl = method;
456 break;
457 case Config::TRACE_AFTER_DISPATCH_IMPL_METHOD:
458 break;
459 case Config::NOT_TRACE_METHOD:
460 if (method->getNameAsString() == kFinalizeName) {
461 finalize_dispatch_method_ = method;
462 } else if (method->getNameAsString() == kAdjustAndMarkName) {
463 has_adjust_and_mark = true;
464 } else if (method->getNameAsString() == kIsHeapObjectAliveName) {
465 has_is_heap_object_alive = true;
466 }
467 break;
468 }
469 }
470
471 // Record if class defines the two GCMixin methods.
472 has_gc_mixin_methods_ =
473 has_adjust_and_mark && has_is_heap_object_alive ? kTrue : kFalse;
474 if (trace_after_dispatch) {
475 trace_method_ = trace_after_dispatch;
476 trace_dispatch_method_ = trace_impl ? trace_impl : trace;
477 } else {
478 // TODO: Can we never have a dispatch method called trace without the same
479 // class defining a traceAfterDispatch method?
480 trace_method_ = trace;
481 trace_dispatch_method_ = nullptr;
482 }
483 if (trace_dispatch_method_ && finalize_dispatch_method_)
484 return;
485 // If this class does not define dispatching methods inherit them.
486 for (Bases::iterator it = GetBases().begin(); it != GetBases().end(); ++it) {
487 // TODO: Does it make sense to inherit multiple dispatch methods?
488 if (CXXMethodDecl* dispatch = it->second.info()->GetTraceDispatchMethod()) {
489 assert(!trace_dispatch_method_ && "Multiple trace dispatching methods");
490 trace_dispatch_method_ = dispatch;
491 }
492 if (CXXMethodDecl* dispatch =
493 it->second.info()->GetFinalizeDispatchMethod()) {
494 assert(!finalize_dispatch_method_ &&
495 "Multiple finalize dispatching methods");
496 finalize_dispatch_method_ = dispatch;
497 }
498 }
499 }
500
501 // TODO: Add classes with a finalize() method that specialize FinalizerTrait.
NeedsFinalization()502 bool RecordInfo::NeedsFinalization() {
503 if (does_need_finalization_ == kNotComputed) {
504 // Rely on hasNonTrivialDestructor(), but if the only
505 // identifiable reason for it being true is the presence
506 // of a safely ignorable class as a direct base,
507 // or we're processing such an 'ignorable' class, then it does
508 // not need finalization.
509 does_need_finalization_ =
510 record_->hasNonTrivialDestructor() ? kTrue : kFalse;
511 if (!does_need_finalization_)
512 return does_need_finalization_;
513
514 CXXDestructorDecl* dtor = record_->getDestructor();
515 if (dtor && dtor->isUserProvided())
516 return does_need_finalization_;
517 for (Fields::iterator it = GetFields().begin();
518 it != GetFields().end();
519 ++it) {
520 if (it->second.edge()->NeedsFinalization())
521 return does_need_finalization_;
522 }
523
524 for (Bases::iterator it = GetBases().begin();
525 it != GetBases().end();
526 ++it) {
527 if (it->second.info()->NeedsFinalization())
528 return does_need_finalization_;
529 }
530 // Destructor was non-trivial due to bases with destructors that
531 // can be safely ignored. Hence, no need for finalization.
532 does_need_finalization_ = kFalse;
533 }
534 return does_need_finalization_;
535 }
536
537 // A class needs tracing if:
538 // - it is allocated on the managed heap,
539 // - it is derived from a class that needs tracing, or
540 // - it contains fields that need tracing.
541 //
NeedsTracing(Edge::NeedsTracingOption option)542 TracingStatus RecordInfo::NeedsTracing(Edge::NeedsTracingOption option) {
543 if (IsGCAllocated())
544 return TracingStatus::Needed();
545
546 if (IsStackAllocated())
547 return TracingStatus::Unneeded();
548
549 for (Bases::iterator it = GetBases().begin(); it != GetBases().end(); ++it) {
550 if (it->second.info()->NeedsTracing(option).IsNeeded())
551 return TracingStatus::Needed();
552 }
553
554 if (option == Edge::kRecursive)
555 GetFields();
556
557 return fields_need_tracing_;
558 }
559
isInStdNamespace(clang::Sema & sema,NamespaceDecl * ns)560 static bool isInStdNamespace(clang::Sema& sema, NamespaceDecl* ns)
561 {
562 while (ns) {
563 if (sema.getStdNamespace()->InEnclosingNamespaceSetOf(ns))
564 return true;
565 ns = dyn_cast<NamespaceDecl>(ns->getParent());
566 }
567 return false;
568 }
569
CreateEdge(const Type * type)570 Edge* RecordInfo::CreateEdge(const Type* type) {
571 if (!type) {
572 return 0;
573 }
574
575 if (type->isPointerType() || type->isReferenceType()) {
576 if (Edge* ptr = CreateEdge(type->getPointeeType().getTypePtrOrNull()))
577 return new RawPtr(ptr, type->isReferenceType());
578 return 0;
579 }
580
581 RecordInfo* info = cache_->Lookup(type);
582
583 // If the type is neither a pointer or a C++ record we ignore it.
584 if (!info) {
585 return 0;
586 }
587
588 TemplateArgs args;
589
590 if (Config::IsRefPtr(info->name()) && info->GetTemplateArgs(1, &args)) {
591 if (Edge* ptr = CreateEdge(args[0]))
592 return new RefPtr(ptr);
593 return 0;
594 }
595
596 if (Config::IsOwnPtr(info->name()) && info->GetTemplateArgs(1, &args)) {
597 if (Edge* ptr = CreateEdge(args[0]))
598 return new OwnPtr(ptr);
599 return 0;
600 }
601
602 if (Config::IsUniquePtr(info->name()) && info->GetTemplateArgs(1, &args)) {
603 // Check that this is std::unique_ptr
604 NamespaceDecl* ns =
605 dyn_cast<NamespaceDecl>(info->record()->getDeclContext());
606 clang::Sema& sema = cache_->instance().getSema();
607 if (!isInStdNamespace(sema, ns))
608 return 0;
609 if (Edge* ptr = CreateEdge(args[0]))
610 return new UniquePtr(ptr);
611 return 0;
612 }
613
614 if (Config::IsMember(info->name()) && info->GetTemplateArgs(1, &args)) {
615 if (Edge* ptr = CreateEdge(args[0]))
616 return new Member(ptr);
617 return 0;
618 }
619
620 if (Config::IsWeakMember(info->name()) && info->GetTemplateArgs(1, &args)) {
621 if (Edge* ptr = CreateEdge(args[0]))
622 return new WeakMember(ptr);
623 return 0;
624 }
625
626 bool is_persistent = Config::IsPersistent(info->name());
627 if (is_persistent || Config::IsCrossThreadPersistent(info->name())) {
628 // Persistent might refer to v8::Persistent, so check the name space.
629 // TODO: Consider using a more canonical identification than names.
630 NamespaceDecl* ns =
631 dyn_cast<NamespaceDecl>(info->record()->getDeclContext());
632 if (!ns || ns->getName() != "blink")
633 return 0;
634 if (!info->GetTemplateArgs(1, &args))
635 return 0;
636 if (Edge* ptr = CreateEdge(args[0])) {
637 if (is_persistent)
638 return new Persistent(ptr);
639 else
640 return new CrossThreadPersistent(ptr);
641 }
642 return 0;
643 }
644
645 if (Config::IsGCCollection(info->name()) ||
646 Config::IsWTFCollection(info->name())) {
647 bool is_root = Config::IsPersistentGCCollection(info->name());
648 bool on_heap = is_root || info->IsHeapAllocatedCollection();
649 size_t count = Config::CollectionDimension(info->name());
650 if (!info->GetTemplateArgs(count, &args))
651 return 0;
652 Collection* edge = new Collection(info, on_heap, is_root);
653 for (TemplateArgs::iterator it = args.begin(); it != args.end(); ++it) {
654 if (Edge* member = CreateEdge(*it)) {
655 edge->members().push_back(member);
656 }
657 // TODO: Handle the case where we fail to create an edge (eg, if the
658 // argument is a primitive type or just not fully known yet).
659 }
660 return edge;
661 }
662
663 return new Value(info);
664 }
665