static TypeAndOrName
FixupTypeAndOrName (const TypeAndOrName& type_andor_name,
                    ValueObject& parent)
{
    TypeAndOrName ret(type_andor_name);
    if (type_andor_name.HasType())
    {
        // The type will always be the type of the dynamic object.  If our parent's type was a pointer,
        // then our type should be a pointer to the type of the dynamic object.  If a reference, then the original type
        // should be okay...
        ClangASTType orig_type = type_andor_name.GetClangASTType();
        ClangASTType corrected_type = orig_type;
        if (parent.IsPointerType())
            corrected_type = orig_type.GetPointerType ();
        else if (parent.IsPointerOrReferenceType())
            corrected_type = orig_type.GetLValueReferenceType ();
        ret.SetClangASTType(corrected_type);
    }
    else /*if (m_dynamic_type_info.HasName())*/
    {
        // If we are here we need to adjust our dynamic type name to include the correct & or * symbol
        std::string corrected_name (type_andor_name.GetName().GetCString());
        if (parent.IsPointerType())
            corrected_name.append(" *");
        else if (parent.IsPointerOrReferenceType())
            corrected_name.append(" &");
        // the parent type should be a correctly pointer'ed or referenc'ed type
        ret.SetClangASTType(parent.GetClangType());
        ret.SetName(corrected_name.c_str());
    }
    return ret;
}
Exemple #2
0
bool
AppleObjCRuntime::CouldHaveDynamicValue (ValueObject &in_value)
{
    return in_value.GetClangType().IsPossibleDynamicType (NULL,
                                                          false, // do not check C++
                                                          true); // check ObjC
}
Exemple #3
0
// this code relies on the assumption that an Objective-C object always starts
// with an ISA at offset 0.
ObjCLanguageRuntime::ObjCISA
AppleObjCRuntimeV1::GetISA(ValueObject& valobj)
{
    if (ClangASTType::GetMinimumLanguage(valobj.GetClangAST(),valobj.GetClangType()) != eLanguageTypeObjC)
        return 0;
    
    // if we get an invalid VO (which might still happen when playing around
    // with pointers returned by the expression parser, don't consider this
    // a valid ObjC object)
    if (valobj.GetValue().GetContextType() == Value::eContextTypeInvalid)
        return 0;
    
    addr_t isa_pointer = valobj.GetPointerValue();
    
    ExecutionContext exe_ctx (valobj.GetExecutionContextRef());
    
    Process *process = exe_ctx.GetProcessPtr();
    if (process)
    {
        uint8_t pointer_size = process->GetAddressByteSize();
        
        Error error;
        return process->ReadUnsignedIntegerFromMemory (isa_pointer,
                                                       pointer_size,
                                                       0,
                                                       error);
    }
    return 0;
}
bool
ItaniumABILanguageRuntime::CouldHaveDynamicValue (ValueObject &in_value)
{
    const bool check_cxx = true;
    const bool check_objc = false;
    return in_value.GetClangType().IsPossibleDynamicType (NULL, check_cxx, check_objc);
}
bool
lldb_private::formatters::WCharSummaryProvider (ValueObject& valobj, Stream& stream)
{
    DataExtractor data;
    valobj.GetData(data);
    
    clang::ASTContext* ast = valobj.GetClangType().GetASTContext();
    
    if (!ast)
        return false;
    
    ClangASTType wchar_clang_type = ClangASTContext::GetBasicType(ast, lldb::eBasicTypeWChar);
    const uint32_t wchar_size = wchar_clang_type.GetBitSize();
    std::string value;
    
    switch (wchar_size)
    {
        case 8:
            // utf 8
            valobj.GetValueAsCString(lldb::eFormatChar, value);
            if (!value.empty())
                stream.Printf("%s ", value.c_str());
            return DumpUTFBufferToStream<UTF8>(nullptr,
                                               data,
                                               stream,
                                               'L',
                                               '\'',
                                               1);
        case 16:
            // utf 16
            valobj.GetValueAsCString(lldb::eFormatUnicode16, value);
            if (!value.empty())
                stream.Printf("%s ", value.c_str());
            return DumpUTFBufferToStream<UTF16>(ConvertUTF16toUTF8,
                                                data,
                                                stream,
                                                'L',
                                                '\'',
                                                1);
        case 32:
            // utf 32
            valobj.GetValueAsCString(lldb::eFormatUnicode32, value);
            if (!value.empty())
                stream.Printf("%s ", value.c_str());
            return DumpUTFBufferToStream<UTF32>(ConvertUTF32toUTF8,
                                                data,
                                                stream,
                                                'L',
                                                '\'',
                                                1);
        default:
            stream.Printf("size for wchar_t is not valid");
            return true;
    }
    return true;
}
bool
lldb_private::formatters::WCharStringSummaryProvider (ValueObject& valobj, Stream& stream, const TypeSummaryOptions&)
{
    ProcessSP process_sp = valobj.GetProcessSP();
    if (!process_sp)
        return false;

    lldb::addr_t data_addr = 0;
    
    if (valobj.IsPointerType())
        data_addr = valobj.GetValueAsUnsigned(0);
    else if (valobj.IsArrayType())
        data_addr = valobj.GetAddressOf();

    if (data_addr == 0 || data_addr == LLDB_INVALID_ADDRESS)
        return false;

    clang::ASTContext* ast = valobj.GetClangType().GetASTContext();
    
    if (!ast)
        return false;

    ClangASTType wchar_clang_type = ClangASTContext::GetBasicType(ast, lldb::eBasicTypeWChar);
    const uint32_t wchar_size = wchar_clang_type.GetBitSize(nullptr); // Safe to pass NULL for exe_scope here

    ReadStringAndDumpToStreamOptions options(valobj);
    options.SetLocation(data_addr);
    options.SetProcessSP(process_sp);
    options.SetStream(&stream);
    options.SetPrefixToken('L');
    
    switch (wchar_size)
    {
        case 8:
            return ReadStringAndDumpToStream<StringElementType::UTF8>(options);
        case 16:
            return ReadStringAndDumpToStream<StringElementType::UTF16>(options);
        case 32:
            return ReadStringAndDumpToStream<StringElementType::UTF32>(options);
        default:
            stream.Printf("size for wchar_t is not valid");
            return true;
    }
    return true;
}
bool
lldb_private::formatters::WCharStringSummaryProvider (ValueObject& valobj, Stream& stream)
{
    ProcessSP process_sp = valobj.GetProcessSP();
    if (!process_sp)
        return false;

    lldb::addr_t data_addr = 0;
    
    if (valobj.IsPointerType())
        data_addr = valobj.GetValueAsUnsigned(0);
    else if (valobj.IsArrayType())
        data_addr = valobj.GetAddressOf();

    if (data_addr == 0 || data_addr == LLDB_INVALID_ADDRESS)
        return false;

    clang::ASTContext* ast = valobj.GetClangType().GetASTContext();
    
    if (!ast)
        return false;

    ClangASTType wchar_clang_type = ClangASTContext::GetBasicType(ast, lldb::eBasicTypeWChar);
    const uint32_t wchar_size = wchar_clang_type.GetBitSize();

    switch (wchar_size)
    {
        case 8:
        {
            // utf 8
            
            ReadUTFBufferAndDumpToStreamOptions<UTF8> options;
            options.SetLocation(data_addr);
            options.SetConversionFunction(nullptr);
            options.SetProcessSP(process_sp);
            options.SetStream(&stream);
            options.SetPrefixToken('L');

            return ReadUTFBufferAndDumpToStream(options);
        }
        case 16:
        {
            // utf 16
            ReadUTFBufferAndDumpToStreamOptions<UTF16> options;
            options.SetLocation(data_addr);
            options.SetConversionFunction(ConvertUTF16toUTF8);
            options.SetProcessSP(process_sp);
            options.SetStream(&stream);
            options.SetPrefixToken('L');
            
            return ReadUTFBufferAndDumpToStream(options);
        }
        case 32:
        {
            // utf 32
            ReadUTFBufferAndDumpToStreamOptions<UTF32> options;
            options.SetLocation(data_addr);
            options.SetConversionFunction(ConvertUTF32toUTF8);
            options.SetProcessSP(process_sp);
            options.SetStream(&stream);
            options.SetPrefixToken('L');
            
            return ReadUTFBufferAndDumpToStream(options);
        }
        default:
            stream.Printf("size for wchar_t is not valid");
            return true;
    }
    return true;
}
bool
ItaniumABILanguageRuntime::GetDynamicTypeAndAddress (ValueObject &in_value, 
                                                     lldb::DynamicValueType use_dynamic, 
                                                     TypeAndOrName &class_type_or_name, 
                                                     Address &dynamic_address)
{
    // For Itanium, if the type has a vtable pointer in the object, it will be at offset 0
    // in the object.  That will point to the "address point" within the vtable (not the beginning of the
    // vtable.)  We can then look up the symbol containing this "address point" and that symbol's name 
    // demangled will contain the full class name.
    // The second pointer above the "address point" is the "offset_to_top".  We'll use that to get the
    // start of the value object which holds the dynamic type.
    //
    
    class_type_or_name.Clear();
    
    // Only a pointer or reference type can have a different dynamic and static type:
    if (CouldHaveDynamicValue (in_value))
    {
        // First job, pull out the address at 0 offset from the object.
        AddressType address_type;
        lldb::addr_t original_ptr = in_value.GetPointerValue(&address_type);
        if (original_ptr == LLDB_INVALID_ADDRESS)
            return false;
        
        ExecutionContext exe_ctx (in_value.GetExecutionContextRef());

        Target *target = exe_ctx.GetTargetPtr();
        Process *process = exe_ctx.GetProcessPtr();

        char memory_buffer[16];
        DataExtractor data(memory_buffer, sizeof(memory_buffer), 
                           process->GetByteOrder(), 
                           process->GetAddressByteSize());
        size_t address_byte_size = process->GetAddressByteSize();
        Error error;
        size_t bytes_read = process->ReadMemory (original_ptr, 
                                                 memory_buffer, 
                                                 address_byte_size, 
                                                 error);
        if (!error.Success() || (bytes_read != address_byte_size))
        {
            return false;
        }
        
        lldb::offset_t offset = 0;
        lldb::addr_t vtable_address_point = data.GetAddress (&offset);
            
        if (offset == 0)
            return false;
        
        // Now find the symbol that contains this address:
        
        SymbolContext sc;
        Address address_point_address;
        if (target && !target->GetSectionLoadList().IsEmpty())
        {
            if (target->GetSectionLoadList().ResolveLoadAddress (vtable_address_point, address_point_address))
            {
                target->GetImages().ResolveSymbolContextForAddress (address_point_address, eSymbolContextSymbol, sc);
                Symbol *symbol = sc.symbol;
                if (symbol != NULL)
                {
                    const char *name = symbol->GetMangled().GetDemangledName().AsCString();
                    if (strstr(name, vtable_demangled_prefix) == name)
                    {
                        Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_OBJECT));
                        if (log)
                            log->Printf ("0x%16.16" PRIx64 ": static-type = '%s' has vtable symbol '%s'\n",
                                         original_ptr,
                                         in_value.GetTypeName().GetCString(),
                                         name);
                        // We are a C++ class, that's good.  Get the class name and look it up:
                        const char *class_name = name + strlen(vtable_demangled_prefix);
                        class_type_or_name.SetName (class_name);
                        const bool exact_match = true;
                        TypeList class_types;
                        
                        uint32_t num_matches = 0;
                        // First look in the module that the vtable symbol came from
                        // and look for a single exact match.
                        if (sc.module_sp)
                        {
                            num_matches = sc.module_sp->FindTypes (sc,
                                                                   ConstString(class_name),
                                                                   exact_match,
                                                                   1,
                                                                   class_types);
                        }
                        
                        // If we didn't find a symbol, then move on to the entire
                        // module list in the target and get as many unique matches
                        // as possible
                        if (num_matches == 0)
                        {
                            num_matches = target->GetImages().FindTypes (sc,
                                                                         ConstString(class_name),
                                                                         exact_match,
                                                                         UINT32_MAX,
                                                                         class_types);
                        }
                        
                        lldb::TypeSP type_sp;
                        if (num_matches == 0)
                        {
                            if (log)
                                log->Printf("0x%16.16" PRIx64 ": is not dynamic\n", original_ptr);
                            return false;
                        }
                        if (num_matches == 1)
                        {
                            type_sp = class_types.GetTypeAtIndex(0);
                            if (log)
                                log->Printf ("0x%16.16" PRIx64 ": static-type = '%s' has dynamic type: uid={0x%" PRIx64 "}, type-name='%s'\n",
                                             original_ptr,
                                             in_value.GetTypeName().AsCString(),
                                             type_sp->GetID(),
                                             type_sp->GetName().GetCString());

                            class_type_or_name.SetTypeSP(class_types.GetTypeAtIndex(0));
                        }
                        else if (num_matches > 1)
                        {
                            size_t i;
                            if (log)
                            {
                                for (i = 0; i < num_matches; i++)
                                {
                                    type_sp = class_types.GetTypeAtIndex(i);
                                    if (type_sp)
                                    {
                                        if (log)
                                            log->Printf ("0x%16.16" PRIx64 ": static-type = '%s' has multiple matching dynamic types: uid={0x%" PRIx64 "}, type-name='%s'\n",
                                                         original_ptr,
                                                         in_value.GetTypeName().AsCString(),
                                                         type_sp->GetID(),
                                                         type_sp->GetName().GetCString());
                                    }
                                }
                            }

                            for (i = 0; i < num_matches; i++)
                            {
                                type_sp = class_types.GetTypeAtIndex(i);
                                if (type_sp)
                                {
                                    if (type_sp->GetClangFullType().IsCXXClassType())
                                    {
                                        if (log)
                                            log->Printf ("0x%16.16" PRIx64 ": static-type = '%s' has multiple matching dynamic types, picking this one: uid={0x%" PRIx64 "}, type-name='%s'\n",
                                                         original_ptr,
                                                         in_value.GetTypeName().AsCString(),
                                                         type_sp->GetID(),
                                                         type_sp->GetName().GetCString());
                                        class_type_or_name.SetTypeSP(type_sp);
                                        break;
                                    }
                                }
                            }
                            
                            if (i == num_matches)
                            {
                                if (log)
                                    log->Printf ("0x%16.16" PRIx64 ": static-type = '%s' has multiple matching dynamic types, didn't find a C++ match\n",
                                                 original_ptr,
                                                 in_value.GetTypeName().AsCString());
                                return false;
                            }
                        }

                        // There can only be one type with a given name,
                        // so we've just found duplicate definitions, and this
                        // one will do as well as any other.
                        // We don't consider something to have a dynamic type if
                        // it is the same as the static type.  So compare against
                        // the value we were handed.
                        if (type_sp)
                        {
                            if (ClangASTContext::AreTypesSame (in_value.GetClangType(),
                                                               type_sp->GetClangFullType()))
                            {
                                // The dynamic type we found was the same type,
                                // so we don't have a dynamic type here...
                                return false;
                            }

                            // The offset_to_top is two pointers above the address.
                            Address offset_to_top_address = address_point_address;
                            int64_t slide = -2 * ((int64_t) target->GetArchitecture().GetAddressByteSize());
                            offset_to_top_address.Slide (slide);
                            
                            Error error;
                            lldb::addr_t offset_to_top_location = offset_to_top_address.GetLoadAddress(target);
                            
                            size_t bytes_read = process->ReadMemory (offset_to_top_location, 
                                                                     memory_buffer, 
                                                                     address_byte_size, 
                                                                     error);
                                                                     
                            if (!error.Success() || (bytes_read != address_byte_size))
                            {
                                return false;
                            }
                            
                            offset = 0;
                            int64_t offset_to_top = data.GetMaxS64(&offset, process->GetAddressByteSize());
                            
                            // So the dynamic type is a value that starts at offset_to_top
                            // above the original address.
                            lldb::addr_t dynamic_addr = original_ptr + offset_to_top;
                            if (!target->GetSectionLoadList().ResolveLoadAddress (dynamic_addr, dynamic_address))
                            {
                                dynamic_address.SetRawAddress(dynamic_addr);
                            }
                            return true;
                        }
                    }
                }
            }
        }
    }
    
    return class_type_or_name.IsEmpty() == false;
}
bool
ItaniumABILanguageRuntime::GetDynamicTypeAndAddress (ValueObject &in_value, 
                                                     lldb::DynamicValueType use_dynamic, 
                                                     TypeAndOrName &class_type_or_name, 
                                                     Address &dynamic_address)
{
    // For Itanium, if the type has a vtable pointer in the object, it will be at offset 0
    // in the object.  That will point to the "address point" within the vtable (not the beginning of the
    // vtable.)  We can then look up the symbol containing this "address point" and that symbol's name 
    // demangled will contain the full class name.
    // The second pointer above the "address point" is the "offset_to_top".  We'll use that to get the
    // start of the value object which holds the dynamic type.
    //
    
    // Only a pointer or reference type can have a different dynamic and static type:
    if (CouldHaveDynamicValue (in_value))
    {
        // FIXME: Can we get the Clang Type and ask it if the thing is really virtual?  That would avoid false positives,
        // at the cost of not looking for the dynamic type of objects if DWARF->Clang gets it wrong.
        
        // First job, pull out the address at 0 offset from the object.
        AddressType address_type;
        lldb::addr_t original_ptr = in_value.GetPointerValue(&address_type);
        if (original_ptr == LLDB_INVALID_ADDRESS)
            return false;
            
        Target *target = in_value.GetUpdatePoint().GetTargetSP().get();
        Process *process = in_value.GetUpdatePoint().GetProcessSP().get();

        char memory_buffer[16];
        DataExtractor data(memory_buffer, sizeof(memory_buffer), 
                           process->GetByteOrder(), 
                           process->GetAddressByteSize());
        size_t address_byte_size = process->GetAddressByteSize();
        Error error;
        size_t bytes_read = process->ReadMemory (original_ptr, 
                                                 memory_buffer, 
                                                 address_byte_size, 
                                                 error);
        if (!error.Success() || (bytes_read != address_byte_size))
        {
            return false;
        }
        
        uint32_t offset_ptr = 0;
        lldb::addr_t vtable_address_point = data.GetAddress (&offset_ptr);
            
        if (offset_ptr == 0)
            return false;
        
        // Now find the symbol that contains this address:
        
        SymbolContext sc;
        Address address_point_address;
        if (target && !target->GetSectionLoadList().IsEmpty())
        {
            if (target->GetSectionLoadList().ResolveLoadAddress (vtable_address_point, address_point_address))
            {
                target->GetImages().ResolveSymbolContextForAddress (address_point_address, eSymbolContextSymbol, sc);
                Symbol *symbol = sc.symbol;
                if (symbol != NULL)
                {
                    const char *name = symbol->GetMangled().GetDemangledName().AsCString();
                    if (strstr(name, vtable_demangled_prefix) == name)
                    {
                         // We are a C++ class, that's good.  Get the class name and look it up:
                        const char *class_name = name + strlen(vtable_demangled_prefix);
                        class_type_or_name.SetName (class_name);
                        TypeList class_types;
                        uint32_t num_matches = target->GetImages().FindTypes (sc, 
                                                                              ConstString(class_name),
                                                                              true,
                                                                              UINT32_MAX,
                                                                              class_types);
                        if (num_matches == 1)
                        {
                            class_type_or_name.SetTypeSP(class_types.GetTypeAtIndex(0));
                        }
                        else if (num_matches > 1)
                        {
                            for (size_t i = 0; i < num_matches; i++)
                            {
                                lldb::TypeSP this_type(class_types.GetTypeAtIndex(i));
                                if (this_type)
                                {
                                    if (ClangASTContext::IsCXXClassType(this_type->GetClangFullType()))
                                    {
                                        // There can only be one type with a given name,
                                        // so we've just found duplicate definitions, and this
                                        // one will do as well as any other.
                                        // We don't consider something to have a dynamic type if
                                        // it is the same as the static type.  So compare against
                                        // the value we were handed:
                                        
                                        clang::ASTContext *in_ast_ctx = in_value.GetClangAST ();
                                        clang::ASTContext *this_ast_ctx = this_type->GetClangAST ();
                                        if (in_ast_ctx != this_ast_ctx
                                            || !ClangASTContext::AreTypesSame (in_ast_ctx, 
                                                                               in_value.GetClangType(),
                                                                               this_type->GetClangFullType()))
                                        {
                                            class_type_or_name.SetTypeSP (this_type);
                                            return true;
                                        }
                                        return false;
                                    }
                                }
                            }
                        }
                        else
                            return false;
                            
                        // The offset_to_top is two pointers above the address.
                        Address offset_to_top_address = address_point_address;
                        int64_t slide = -2 * ((int64_t) target->GetArchitecture().GetAddressByteSize());
                        offset_to_top_address.Slide (slide);
                        
                        Error error;
                        lldb::addr_t offset_to_top_location = offset_to_top_address.GetLoadAddress(target);
                        
                        size_t bytes_read = process->ReadMemory (offset_to_top_location, 
                                                                 memory_buffer, 
                                                                 address_byte_size, 
                                                                 error);
                                                                 
                        if (!error.Success() || (bytes_read != address_byte_size))
                        {
                            return false;
                        }
                        
                        offset_ptr = 0;
                        int64_t offset_to_top = data.GetMaxS64(&offset_ptr, process->GetAddressByteSize());
                        
                        // So the dynamic type is a value that starts at offset_to_top
                        // above the original address.
                        lldb::addr_t dynamic_addr = original_ptr + offset_to_top;
                        if (!target->GetSectionLoadList().ResolveLoadAddress (dynamic_addr, dynamic_address))
                        {
                            dynamic_address.SetOffset(dynamic_addr);
                            dynamic_address.SetSection(NULL);
                        }
                        return true;
                    }
                }
            }
        }
        
    }
    
    return false;
}
bool
ValueObjectChild::UpdateValue ()
{
    m_error.Clear();
    SetValueIsValid (false);
    ValueObject* parent = m_parent;
    if (parent)
    {
        if (parent->UpdateValueIfNeeded(false))
        {
            m_value.SetClangType(GetClangType());

            // Copy the parent scalar value and the scalar value type
            m_value.GetScalar() = parent->GetValue().GetScalar();
            Value::ValueType value_type = parent->GetValue().GetValueType();
            m_value.SetValueType (value_type);

            if (parent->GetClangType().IsPointerOrReferenceType ())
            {
                lldb::addr_t addr = parent->GetPointerValue ();
                m_value.GetScalar() = addr;

                if (addr == LLDB_INVALID_ADDRESS)
                {
                    m_error.SetErrorString ("parent address is invalid.");
                }
                else if (addr == 0)
                {
                    m_error.SetErrorString ("parent is NULL");
                }
                else
                {
                    m_value.GetScalar() += m_byte_offset;
                    AddressType addr_type = parent->GetAddressTypeOfChildren();
                    
                    switch (addr_type)
                    {
                        case eAddressTypeFile:
                            {
                                lldb::ProcessSP process_sp (GetProcessSP());
                                if (process_sp && process_sp->IsAlive() == true)
                                    m_value.SetValueType (Value::eValueTypeLoadAddress);
                                else
                                    m_value.SetValueType(Value::eValueTypeFileAddress);
                            }
                            break;
                        case eAddressTypeLoad:
                            m_value.SetValueType (Value::eValueTypeLoadAddress);
                            break;
                        case eAddressTypeHost:
                            m_value.SetValueType(Value::eValueTypeHostAddress);
                            break;
                        case eAddressTypeInvalid:
                            // TODO: does this make sense?
                            m_value.SetValueType(Value::eValueTypeScalar);
                            break;
                    }
                }
            }
            else
            {
                switch (value_type)
                {
                case Value::eValueTypeLoadAddress:
                case Value::eValueTypeFileAddress:
                case Value::eValueTypeHostAddress:
                    {
                        lldb::addr_t addr = m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS);
                        if (addr == LLDB_INVALID_ADDRESS)
                        {
                            m_error.SetErrorString ("parent address is invalid.");
                        }
                        else if (addr == 0)
                        {
                            m_error.SetErrorString ("parent is NULL");
                        }
                        else
                        {
                            // Set this object's scalar value to the address of its
                            // value by adding its byte offset to the parent address
                            m_value.GetScalar() += GetByteOffset();
                        }
                    }
                    break;

                case Value::eValueTypeScalar:
                    // TODO: What if this is a register value? Do we try and
                    // extract the child value from within the parent data?
                    // Probably...
                default:
                    m_error.SetErrorString ("parent has invalid value.");
                    break;
                }
            }

            if (m_error.Success())
            {
                ExecutionContext exe_ctx (GetExecutionContextRef().Lock());
                m_error = m_value.GetValueAsData (&exe_ctx, m_data, 0, GetModule().get());
            }
        }
        else
        {
            m_error.SetErrorStringWithFormat("parent failed to evaluate: %s", parent->GetError().AsCString());
        }
    }
    else
    {
        m_error.SetErrorString("ValueObjectChild has a NULL parent ValueObject.");
    }
    
    return m_error.Success();
}