// Given an expression string resolving to a UClass* (which is assumed to point to a **Blueprint** class), this determines // the UE4 (non-prefixed) name of the native base class. public static string GetBlueprintClassNativeBaseName(string uclass_expr_str, DkmVisualizedExpression context_expr) { var uclass_em = ExpressionManipulator.FromExpression(uclass_expr_str); do { var super_em = uclass_em.PtrCast(Typ.UStruct).PtrMember(Memb.SuperStruct).PtrCast(Typ.UClass); var is_native_res = IsNativeUClassOrUInterface(super_em.Expression, context_expr); if (!is_native_res.IsValid) { return(null); } if (is_native_res.Value) { var name_em = super_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName); return(GetFNameAsString(name_em.Expression, context_expr)); } uclass_em = super_em; }while (DefaultEE.DefaultEval(uclass_em.PtrCast(Cpp.Void).Expression, context_expr, true).TagValue == DkmEvaluationResult.Tag.SuccessResult); // This shouldn't really happen return(null); }
// Returns the type of property identified by the expression string // (As in, "IntProperty", "ObjectProperty", etc) protected string GetPropertyType(string uprop_expr_str, DkmVisualizedExpression context_expr) { // We get this from the value of the Name member of the UProperty's UClass. var uprop_em = ExpressionManipulator.FromExpression(uprop_expr_str); string prop_class_name_expr_str = uprop_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjClass).PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; return(UE4Utility.GetFNameAsString(prop_class_name_expr_str, context_expr)); }
protected static string GetBoolPropertyFieldMask(string uboolprop_expr_str, DkmVisualizedExpression context_expr) { var boolprop_em = ExpressionManipulator.FromExpression(uboolprop_expr_str).PtrMember(Memb.FieldMask); // uint8 property var eval = DefaultEE.DefaultEval(boolprop_em.Expression, context_expr, true) as DkmSuccessEvaluationResult; var val_str = eval.Value; return(Utility.GetNumberFromUcharValueString(val_str)); }
// @TODO: Unsafe, assumes valid and non-empty. public static string GetFStringAsString(string expr_str, DkmVisualizedExpression context_expr) { var em = ExpressionManipulator.FromExpression(expr_str); em = em.DirectMember("Data").DirectMember("AllocatorInstance").DirectMember("Data").PtrCast("wchar_t"); var data_eval = DefaultEE.DefaultEval(em.Expression, context_expr, true); Debug.Assert(data_eval != null); return(data_eval.GetUnderlyingString()); }
public static BoolEvaluation TestUClassFlags(string uclass_expr_str, string flags, DkmVisualizedExpression context_expr) { ExpressionManipulator em = ExpressionManipulator.FromExpression(uclass_expr_str); em = em.PtrCast(Typ.UClass).PtrMember(Memb.ClassFlags); var class_flags_expr_str = em.Expression; // @TODO: Could do the flag test on this side by hard coding in the value of RF_Native, but for now // doing it the more robust way, and evaluating the expression in the context of the debugee. return(TestExpressionFlags(class_flags_expr_str, flags, context_expr)); }
// Given an expression which resolves to a pointer of any kind, returns the pointer value as an integer. public static ulong EvaluatePointerAddress(string pointer_expr_str, DkmVisualizedExpression context_expr) { ExpressionManipulator em = ExpressionManipulator.FromExpression(pointer_expr_str); // Cast to void* to avoid unnecessary visualization processing. string address_expr_str = em.PtrCast(Cpp.Void).Expression; var address_eval = (DkmEvaluationResult)DefaultEE.DefaultEval(address_expr_str, context_expr, true); if (address_eval.TagValue == DkmEvaluationResult.Tag.SuccessResult) { var eval = address_eval as DkmSuccessEvaluationResult; return(eval.Address != null ? eval.Address.Value : 0); } return(0); }
// Takes in an address string which identifies the location of the property value in memory, // along with property type information. // Outputs a string expression which will evaluate to the precise location with the correct C++ type. protected string AdjustPropertyExpressionStringForType(string address_str, string prop_type, string uprop_expr_str, DkmVisualizedExpression context_expr, CppTypeInfo cpp_type_info) { // Special cases first switch (prop_type) { case Prop.Bool: { // Needs special treatment since can be a single bit field as well as just a regular bool // Get a UBoolProperty context var uboolprop_em = ExpressionManipulator.FromExpression(uprop_expr_str).PtrCast(CppProp.Bool); // Read the byte offset and field mask properties string byte_offset_str = GetBoolPropertyByteOffset(uboolprop_em.Expression, context_expr); string field_mask_str = GetBoolPropertyFieldMask(uboolprop_em.Expression, context_expr); // Format an expression which adds the byte offset onto the address, derefs // and then bitwise ANDs with the field mask. return(String.Format("(*({0} + {1}) & {2}) != 0", address_str, byte_offset_str, field_mask_str )); } case Prop.Byte: { // Enum properties are a bit awkward, since due to the different ways the enums can be declared, // we can't reliably access them by a cast and dereference. // eg. A regular enum type will be considered 64 bits. // So, we need to use uint8 to get at the memory, and then do the cast, or rather conversion, // *after* dereferencing in order to get the correct type. return(String.Format("({0})*({1}*){2}{3}", cpp_type_info.Type, Typ.Byte, address_str, cpp_type_info.Format )); } default: break; } // If we got here, we just need to get the corresponding C++ type, then cast the address // to it and dereference. // [*(type*)address,fmt] return(String.Format("*({0}*){1}{2}", cpp_type_info.Type, address_str, cpp_type_info.Format)); }
// NOTE: 'expr' must resolve to either <UObject-type>* or <UObject-type>. // Furthermore, it must have already been passed to a UObjectVisualizer, which has // performed the initial evalution. public UPropertyAccessContext(DkmVisualizedExpression expr) { context_expr_ = expr; string base_expression_str = Utility.GetExpressionFullName(context_expr_); base_expression_str = Utility.StripExpressionFormatting(base_expression_str); obj_em_ = ExpressionManipulator.FromExpression(base_expression_str); // Determine if our base expression is <UObject-type>* or <UObject-type> var uobj_data = context_expr_.GetDataItem <UObjectDataItem>(); Debug.Assert(uobj_data != null); if (!uobj_data.IsPointer) { obj_em_ = obj_em_.AddressOf(); } }
// @NOTE: Currently ignoring FName::Number, and assuming valid. public static string GetFNameAsString(string expr_str, DkmVisualizedExpression context_expr) { var em = ExpressionManipulator.FromExpression(expr_str); em = em.DirectMember(Memb.CompIndex); DkmSuccessEvaluationResult comp_idx_eval = DefaultEE.DefaultEval(em.Expression, context_expr, true) as DkmSuccessEvaluationResult; // @TODO: For now, to avoid requiring more lookups, we'll allow a failure and return null, // and let the caller decide how to interpret it. //Debug.Assert(comp_idx_eval != null); if (comp_idx_eval == null) { return(null); } string comp_idx_str = comp_idx_eval.Value; string ansi_expr_str = String.Format("((FNameEntry*)(((FNameEntry***)GFNameTableForDebuggerVisualizers_MT)[{0} / 16384][{0} % 16384]))->AnsiName", comp_idx_str); var ansi_eval = DefaultEE.DefaultEval(ansi_expr_str, context_expr, true); return(ansi_eval.GetUnderlyingString()); }
// Flags should be in a form that can be inserted into a bit test expression in the // debuggee context (eg. a raw integer, or a combination of RF_*** flags) public static bool TestUObjectFlags(string uobj_expr_str, string flags, DkmVisualizedExpression context_expr) { ExpressionManipulator em = ExpressionManipulator.FromExpression(uobj_expr_str); em = em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjFlags); var obj_flags_expr_str = em.Expression; // @NOTE: Value string is formatted as 'RF_... | RF_... (<integer value>)' // So for now, rather than extracting what we want, just inject the full expression for // the flags member into the below flag test expression. //string obj_flags_str = ((DkmSuccessEvaluationResult)obj_flags_expr.EvaluationResult).Value; // @TODO: Could do the flag test on this side by hard coding in the value of RF_Native, but for now // doing it the more robust way, and evaluating the expression in the context of the debugee. string flag_test_expr_str = String.Format( "({0} & {1}) != 0", obj_flags_expr_str, flags ); return(EvaluateBooleanExpression(flag_test_expr_str, context_expr)); }
protected DkmEvaluationResult GeneratePropertyValueEval(string container_expr_str, string uprop_expr_str, uint index, DkmVisualizedExpression context_expr) { Debug.Print("UE4PV: Trying to generate property value for property #{0}", index + 1); var uprop_em = ExpressionManipulator.FromExpression(uprop_expr_str); // Get name of property string prop_name_expr_str = uprop_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; string prop_name = UE4Utility.GetFNameAsString(prop_name_expr_str, context_expr); // And the property type string prop_type = GetPropertyType(uprop_em.Expression, context_expr); // Now, determine address of the actual property value // First requires container address // Cast to void* first, so that expression evaluation is simplified and faster container_expr_str = ExpressionManipulator.FromExpression(container_expr_str).PtrCast(Cpp.Void).Expression; var container_eval = DefaultEE.DefaultEval(container_expr_str, context_expr, true) as DkmSuccessEvaluationResult; Debug.Assert(container_eval != null && container_eval.Address != null); string address_str = container_eval.Address.Value.ToString(); // Next need offset in container (which is an int32 property of the UProperty class) var offset_expr_str = uprop_em.PtrMember(Memb.PropOffset).Expression; var offset_eval = DefaultEE.DefaultEval(offset_expr_str, context_expr, true) as DkmSuccessEvaluationResult; string offset_str = offset_eval.Value; // Then need to create an expression which adds on the offset address_str = String.Format("((uint8*){0} + {1})", address_str, offset_str); // Next, we need to cast this expression depending on the type of property we have. // Retrieve a list of possible cast expressions. var cpp_type_info_list = GetCppTypeForPropertyType(prop_type, uprop_em.Expression, context_expr); // Accept the first one that is successfully evaluated DkmSuccessEvaluationResult success_eval = null; string display_type = null; foreach (var cpp_type_info in cpp_type_info_list) { string prop_value_expr_str = AdjustPropertyExpressionStringForType(address_str, prop_type, uprop_em.Expression, context_expr, cpp_type_info); Debug.Print("UE4PV: Attempting exp eval as: '{0}'", prop_value_expr_str); // Attempt to evaluate the expression DkmEvaluationResult raw_eval = DefaultEE.DefaultEval(prop_value_expr_str, context_expr, false); if (raw_eval.TagValue == DkmEvaluationResult.Tag.SuccessResult) { // Success success_eval = raw_eval as DkmSuccessEvaluationResult; display_type = cpp_type_info.Display; break; } } if (success_eval == null) { // Was not able to find an evaluatable expression. return(null); } return(DkmSuccessEvaluationResult.Create( expression_.InspectionContext, expression_.StackFrame, prop_name, success_eval.FullName, //prop_value_expr_str, success_eval.Flags, success_eval.Value, // @TODO: Perhaps need to disable for some stuff, like bitfield bool? success_eval.EditableValue, display_type, //success_eval.Type, success_eval.Category, success_eval.Access, success_eval.StorageType, success_eval.TypeModifierFlags, success_eval.Address, success_eval.CustomUIVisualizers, success_eval.ExternalModules, null )); }
// Takes in a UE4 property type string (eg. IntPropery, ObjectProperty, etc) along with a // expression string which evaluates to a UProperty*, and maps to the corresponding C++ type. protected CppTypeInfo[] GetCppTypeForPropertyType(string prop_type, string uprop_expr_str, DkmVisualizedExpression context_expr) { switch (prop_type) { case Prop.Bool: return(new CppTypeInfo[] { new CppTypeInfo("bool") }); case Prop.Struct: { // This is gonna be effort. // Get UStructProperty var ustructprop_em = ExpressionManipulator.FromExpression(uprop_expr_str).PtrCast(CppProp.Struct); // Need to access its UScriptStruct* member 'Struct', and get its object name. var struct_name_expr_str = ustructprop_em.PtrMember(Memb.CppStruct).PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; // Get that name string string struct_name = UE4Utility.GetFNameAsString(struct_name_expr_str, context_expr); // Add the 'F' prefix return(new CppTypeInfo[] { new CppTypeInfo("F" + struct_name) }); } case Prop.Byte: { // Could be plain uint8, or a UEnum. // Get UByteProperty var ubyteprop_em = ExpressionManipulator.FromExpression(uprop_expr_str).PtrCast(CppProp.Byte); // Need to access its UEnum* member 'Enum'. var uenum_em = ubyteprop_em.PtrMember(Memb.EnumType); // Evaluate this to see if it is null or not bool is_enum_valid = !UE4Utility.IsPointerNull(uenum_em.Expression, context_expr); if (is_enum_valid) { // This property is an enum, so the type we want is the fully qualified C++ enum type. // @NOTE: Seems that the CppType member should be exactly what we need, but appears to actually be unused. //string cpp_enum_name_expr_str = uenum_em.PtrMember("CppType").Expression; string uenum_fname_expr_str = uenum_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; string uenum_name = UE4Utility.GetFNameAsString(uenum_fname_expr_str, context_expr); // We need to know if it's a namespaced enum or not string is_namespaced_enum_expr_str = String.Format( "{0}==UEnum::ECppForm::Namespaced", uenum_em.PtrMember(Memb.EnumCppForm).Expression ); var is_namespaced_res = UE4Utility.EvaluateBooleanExpression(is_namespaced_enum_expr_str, context_expr); // @TODO: on evaluation failure?? CppTypeInfo primary; if (is_namespaced_res.IsValid && is_namespaced_res.Value) { // A namespaced enum type should (hopefully) always be <UEnum::Name>::Type primary = new CppTypeInfo(String.Format("{0}::Type", uenum_name), uenum_name); } else { // For regular or enum class enums, the full type name should be just the name of the UEnum object. primary = new CppTypeInfo(uenum_name); } return(new CppTypeInfo[] { primary, // Fallback onto regular byte display, in case enum name symbol not available new CppTypeInfo(Typ.Byte, uenum_name + "?") }); } else { // Must just be a regular uint8 return(new CppTypeInfo[] { new CppTypeInfo(Typ.Byte) }); } } case Prop.Array: { // Okay, cast to UArrayProperty and get the inner property type var array_prop_em = ExpressionManipulator.FromExpression(uprop_expr_str).PtrCast(CppProp.Array); var inner_prop_em = array_prop_em.PtrMember(Memb.ArrayInner); // @TODO: Need to look into how TArray< bool > is handled. string inner_prop_type = GetPropertyType(inner_prop_em.Expression, context_expr); var inner_cpp_type_info = GetCppTypeForPropertyType(inner_prop_type, inner_prop_em.Expression, context_expr); var result = new CppTypeInfo[inner_cpp_type_info.Length]; for (int i = 0; i < inner_cpp_type_info.Length; ++i) { // Type needed is TArray< %inner_cpp_type%, FDefaultAllocator > string type = String.Format("{0}<{1},{2}>", Typ.Array, inner_cpp_type_info[i].Type, Typ.DefaultAlloc); // Omit allocator from display string, since for UPROPERTY arrays it can't be anything else string display = String.Format("{0}<{1}>", Typ.Array, inner_cpp_type_info[i].Display); result[i] = new CppTypeInfo(type, display); } return(result); } case Prop.Object: case Prop.Asset: { if (!Config.ShowExactUObjectTypes) { break; } string obj_cpp_type_name = Typ.UObject; // Need to find out the subtype of the property, which is specified by UObjectPropertyBase::PropertyClass var objprop_em = ExpressionManipulator.FromExpression(uprop_expr_str).PtrCast(CppProp.ObjectBase); var subtype_uclass_em = objprop_em.PtrMember(Memb.ObjectSubtype); var uclass_fname_em = subtype_uclass_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName); string uclass_fname = UE4Utility.GetFNameAsString(uclass_fname_em.Expression, context_expr); // Is the property class native? var is_native_res = UE4Utility.IsNativeUClassOrUInterface(subtype_uclass_em.Expression, context_expr); // @TODO: currently not really handling failed eval bool is_native = is_native_res.IsValid ? is_native_res.Value : true; string native_uclass_fname; if (is_native) { // Yes native_uclass_fname = uclass_fname; } else { // No, we need to retrieve the name of its native base native_uclass_fname = UE4Utility.GetBlueprintClassNativeBaseName(subtype_uclass_em.Expression, context_expr); } Debug.Assert(native_uclass_fname != null); // Now we have to convert the unprefixed name, to a prefixed C++ type name obj_cpp_type_name = UE4Utility.DetermineNativeUClassCppTypeName(native_uclass_fname, context_expr); string uclass_display_name = UE4Utility.GetBlueprintClassDisplayName(uclass_fname); switch (prop_type) { case Prop.Object: { // if not native, add a suffix to the display type showing the blueprint class of the property // @NOTE: this is nothing to do with what object the value points to and what its type may be. property meta data only. string suffix = is_native ? String.Empty : String.Format(" [{0}]", uclass_display_name); string primary_type = String.Format("{0} *", obj_cpp_type_name); string primary_display = String.Format("{0} *{1}", obj_cpp_type_name, suffix); // fallback, no symbols available for the native base type, so use 'UObject' instead string fallback_type = String.Format("{0} *", Typ.UObject); string fallback_display = String.Format("{0}? *{1}", obj_cpp_type_name, suffix); return(new CppTypeInfo[] { new CppTypeInfo(primary_type, primary_display), new CppTypeInfo(fallback_type, fallback_display) }); } case Prop.Asset: { // @NOTE: Don't really see anything to gain by casting to TAssetPtr< xxx >, since it's just another level of encapsulation that isn't // needed for visualization purposes. string suffix = String.Format(" [{0}]", is_native ? obj_cpp_type_name : uclass_display_name); string primary_type = Typ.AssetPtr; //String.Format("TAssetPtr<{0}>", obj_cpp_type_name); string primary_display = String.Format("{0}{1}", Typ.AssetPtr, suffix); // If just using FAssetPtr, no need for a fallback since we don't need to evaluate the specialized template parameter type return(new CppTypeInfo[] { new CppTypeInfo(primary_type, primary_display) }); } default: Debug.Assert(false); return(null); } } /* @TODO: Not so important. What's below is wrong, but essentially if we implement this, it's just to differentiate between UClass, UBlueprintGeneratedClass, etc * case "ClassProperty": * case "AssetClassProperty": * { * if (!Config.ShowExactUObjectTypes) * { * break; * } * * // Need to find out the subtype of the property, which is specified by UClassProperty::MetaClass/UAssetClassProperty::MetaClass * // Cast to whichever property type we are (either UClassProperty or UAssetClassProperty) * string propclass_name = String.Format("U{0}", prop_type); * var classprop_em = ExpressionManipulator.FromExpression(uprop_expr_str).PtrCast(propclass_name); * // Either way, we have a member called 'MetaClass' which specified the base UClass stored in this property * var subtype_uclass_em = classprop_em.PtrMember("MetaClass"); * var subtype_fname_em = subtype_uclass_em.PtrCast("UObjectBase").PtrMember("Name"); * string subtype_fname = UE4Utility.GetFNameAsString(subtype_fname_em.Expression, context_expr); * return String.Format("U{0}*", subtype_fname); * } */ } // Standard cases, just use cpp type stored in map. // If not found, null string will be returned. string cpp_type = null; if (ue4_proptype_map_.TryGetValue(prop_type, out cpp_type)) { return(new CppTypeInfo[] { new CppTypeInfo(cpp_type) }); } else { return(null); } }
public void EvaluateProperties() { List <DkmEvaluationResult> evals = new List <DkmEvaluationResult>(); // Assume we've been constructed with the fabricated property list expression DkmChildVisualizedExpression proplist_expr = (DkmChildVisualizedExpression)expression_; Debug.Assert(proplist_expr != null); // start could be an expression with the type of any UObject-derived class DkmVisualizedExpression start_expr = proplist_expr.Parent; string base_expression_str = Utility.GetExpressionFullName(start_expr); base_expression_str = Utility.StripExpressionFormatting(base_expression_str); ExpressionManipulator obj_em = null; // @TODO: Deal with non-pointer start expression obj_em = ExpressionManipulator.FromExpression(base_expression_str); // Determine if our base expression is <UObject-type>* or <UObject-type> bool is_pointer = start_expr.GetDataItem <UObjectDataItem>().IsPointer; if (!is_pointer) { obj_em = obj_em.AddressOf(); } var uclass_em = obj_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjClass); if (Config.PropertyDisplayPolicy == Config.PropDisplayPolicyType.BlueprintOnly) { // See if the actual class of the object instance whose properties we want to enumerate // is native or not. var is_native_res = UE4Utility.TestUClassFlags( uclass_em.Expression, ClassFlags.Native, start_expr ); // If the instance class is native, then it can't possibly have any non-native properties, // so bail out now. // @TODO: Even if the class is not native, we should still be able to avoid doing all the work // for enumerating every native property in order to find the non-native ones... // @TODO: How best to deal with failed is_native evaluation? if (is_native_res.IsValid && is_native_res.Value) { return; } } // Get the UStruct part of the UClass, in order to begin iterating properties var ustruct_em = uclass_em.PtrCast(Typ.UStruct); // Now access PropertyLink member, which is the start of the linked list of properties var prop_em = ustruct_em.PtrMember(Memb.FirstProperty); uint idx = 0; while (true) { Debug.Print("UE4PV: Invoking raw eval on UProperty* expression"); var prop_eval = DefaultEE.DefaultEval(prop_em.Expression, start_expr, true) as DkmSuccessEvaluationResult; Debug.Assert(prop_eval != null); if (prop_eval.Address.Value == 0) { // nullptr, end of property list break; } bool should_skip = false; if (!should_skip && Config.PropertyDisplayPolicy == Config.PropDisplayPolicyType.BlueprintOnly) { // Check to see if this property is native or blueprint // We can test this by getting the UProperty's Outer, and checking its object flags for RF_Native. var prop_outer_em = prop_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjOuter); // @NOTE: RF_Native has gone, and checking for RF_MarkAsNative never seems to return true... //var is_native_res = UE4Utility.TestUObjectFlags(prop_outer_em.Expression, ObjFlags.Native, start_expr); // So, access class flags instead. // Note that we make the assumption here that the property's outer is a UClass, which should be safe since // we're starting out with a uobject, so all properties, including inherited ones, should be outered to a uclass. var prop_outer_uclass_em = prop_outer_em.PtrCast(Typ.UClass); var is_native_res = UE4Utility.TestUClassFlags(prop_outer_uclass_em.Expression, ClassFlags.Native, start_expr); // According to UE4 UStruct API docs, property linked list is ordered from most-derived // to base. If so, we should be able to bail out here knowing that having hit a native property, // all following properties must be native too. if (is_native_res.IsValid && is_native_res.Value) { return; } } if (!should_skip) { // @TODO: Here we use the starting expression for the container. // May not work if the root expression was not of pointer type!! var prop_val_eval = GeneratePropertyValueEval( obj_em.Expression, prop_em.Expression, idx, start_expr ); if (prop_val_eval != null && !Config.IsPropertyHidden(prop_val_eval.Name)) { prop_evals_[prop_val_eval.Name] = prop_val_eval; ++idx; } } // Advance to next link prop_em = prop_em.PtrMember(Memb.NextProperty); } }
protected void EvaluateExpressionResult() { // We're going to customize the unexpanded display string, as well as the expanded // view (if requested later). // @TODO: Really don't understand why, but when we invoke the default eval below, and we get // reentrant calls for child member UObjects, they are coming back as root expressions // with an empty FullName. This subsequently fails to evaluate if we pass it through to // default eval again. Perhaps this is somehow related to breaking the potential infinite // recursion of visualizing children in order to visualize the parent, but don't follow how it // it supposed to be dealt with. if (expression_.TagValue == DkmVisualizedExpression.Tag.RootVisualizedExpression) { var as_root = expression_ as DkmRootVisualizedExpression; if (as_root.FullName.Length == 0) { string display_str = "{...}"; eval_ = DkmSuccessEvaluationResult.Create( expression_.InspectionContext, expression_.StackFrame, as_root.Name, as_root.Name, DkmEvaluationResultFlags.ReadOnly, display_str, "", #if !VS2013 as_root.Type, #else "Unknown", #endif DkmEvaluationResultCategory.Other, DkmEvaluationResultAccessType.None, DkmEvaluationResultStorageType.None, DkmEvaluationResultTypeModifierFlags.None, null, null, null, null ); state_ = EvaluationState.MinimalEvaluation; return; } } // string custom_display_str = Resources.UE4PropVis.IDS_DISP_CONDENSED; DkmSuccessEvaluationResult proto_eval = null; bool is_pointer; bool is_null; string address_str = ""; // @NOTE: Initially here we were executing the full default evaluation of our expression. // Problem is that this will call back into us for all UObject children of the expression, // because it needs to generate a visualization for them in order to construct its {member vals...} display string. // And then, we just ignore that anyway and display our own... // The following attempts to avoid that by casting our expression to void* and then evaluating that. // If it fails, we assume we are non-pointer. If it succeeds, we can determine from the result whether we are null or not. // @WARNING: This may not be so safe, since we can't determine whether evaluation failed because we tried to cast a non-pointer // to void*, or because the passed in expression was not valid in the first place. Believe we should be okay, since we should // only be receiving expressions that have already been determined to be suitable for our custom visualizer. // Ideally though, we'd be able to get a raw expression evaluation, without any visualization taking place. // Seems there must be a way to do this, but looks like it requires using a different API... const bool UseVoidCastOptimization = true; string default_expr_str = Utility.GetExpressionFullName(expression_); if (UseVoidCastOptimization) { default_expr_str = ExpressionManipulator.FromExpression(default_expr_str).PtrCast(Cpp.Void).Expression; } DkmEvaluationResult eval = DefaultEE.DefaultEval(default_expr_str, expression_, true); if (!UseVoidCastOptimization) { if (eval.TagValue != DkmEvaluationResult.Tag.SuccessResult) { // Genuine failure to evaluate the passed in expression eval_ = eval; state_ = EvaluationState.Failed; return; } else { proto_eval = (DkmSuccessEvaluationResult)eval; custom_display_str = proto_eval.Value; is_pointer = IsPointer(proto_eval); is_null = is_pointer && IsPointerNull(proto_eval); // @TODO: need to extract address string } } else { DkmDataAddress address = null; if (eval.TagValue != DkmEvaluationResult.Tag.SuccessResult) { // Assume the failure just implies the expression was non-pointer (thereby assuming that it was itself valid!) // @TODO: Could actually fall back onto standard evaluation here, in order to determine for sure // that the original expression is valid. Failure wouldn't be common since most of the time we are // dealing with pointer expressions, so any potential optimization should still be gained. is_pointer = false; is_null = false; } else { var success = (DkmSuccessEvaluationResult)eval; Debug.Assert(IsPointer(success)); is_pointer = true; is_null = is_pointer && IsPointerNull(success); address = success.Address; address_str = success.Value; } proto_eval = DkmSuccessEvaluationResult.Create( expression_.InspectionContext, expression_.StackFrame, "", "", DkmEvaluationResultFlags.ReadOnly | DkmEvaluationResultFlags.Expandable, "", "", #if !VS2013 Utility.GetExpressionType(expression_), #else is_pointer ? "UObject *" : "UObject", #endif DkmEvaluationResultCategory.Other, DkmEvaluationResultAccessType.None, DkmEvaluationResultStorageType.None, DkmEvaluationResultTypeModifierFlags.None, address, null, null, null ); } string obj_expression_str = Utility.GetExpressionFullName(expression_); var obj_em = ExpressionManipulator.FromExpression(obj_expression_str); // Store pointer flags on the expression expression_.SetDataItem( DkmDataCreationDisposition.CreateAlways, new UObjectDataItem(is_pointer, is_null) ); if (!is_pointer) { // All subsequent manipulations of the expression assume it starts as a pointer to a // UObject-derived class, so if our expression is to a dereferenced UObject, just // prefix an 'address of' to the expression string. obj_em = obj_em.AddressOf(); } // Generate the condensed display string. if (is_null) { if (Config.CustomNullObjectPreview) { custom_display_str = "<NULL> UObject"; } else { var null_eval = DefaultEE.DefaultEval("(void*)0", expression_, true) as DkmSuccessEvaluationResult; custom_display_str = null_eval.Value + " <NULL>"; } } else if (Config.DisplayUObjectPreview) { // Prefix the address, if this is a pointer expression string address_prefix_str = ""; if (is_pointer) { address_prefix_str = address_str + " "; } // Specialized display for UClass? bool uclass_specialized = false; if (Config.DisplaySpecializedUClassPreview) { var uclass_em = obj_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjClass); var _uclass_fname_expr_str = uclass_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; string _obj_uclass_name_str = UE4Utility.GetFNameAsString(_uclass_fname_expr_str, expression_); // @TODO: To simplify and for performance reasons, just hard coding known UClass variants if (_obj_uclass_name_str == "Class" || _obj_uclass_name_str == "BlueprintGeneratedClass" || _obj_uclass_name_str == "WidgetBlueprintGeneratedClass") { var fname_expr_str = obj_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; string obj_name_str = UE4Utility.GetFNameAsString(fname_expr_str, expression_); var parent_uclass_fname_expr_str = obj_em.PtrCast(Typ.UStruct).PtrMember(Memb.SuperStruct).PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; // This will return null if lookup failed for any reason. // We'll assume this meant no super class exists (ie. we're dealing with UClass itself) string parent_uclass_name_str = UE4Utility.GetFNameAsString(parent_uclass_fname_expr_str, expression_); if (parent_uclass_name_str == null) { parent_uclass_name_str = "None"; } custom_display_str = String.Format( "{0}{{ClassName='{1}', Parent='{2}'}}", address_prefix_str, obj_name_str, parent_uclass_name_str ); uclass_specialized = true; } } if (!uclass_specialized) { // For standard UObject condensed display string, show the object FName and its UClass. // @TODO: The evaluations required for this may be a performance issue, since they'll be done for all UObject children of any default visualized // aggregate type, even when it is not expanded (the default behaviour is to create a {...} display list of child member visualizations). var fname_expr_str = obj_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; string obj_name_str = UE4Utility.GetFNameAsString(fname_expr_str, expression_); var uclass_fname_expr_str = obj_em.PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjClass).PtrCast(Typ.UObjectBase).PtrMember(Memb.ObjName).Expression; string obj_uclass_name_str = UE4Utility.GetFNameAsString(uclass_fname_expr_str, expression_); custom_display_str = String.Format( "{0}{{Name='{1}', Class='{2}'}}", address_prefix_str, obj_name_str, obj_uclass_name_str ); } } eval_ = DkmSuccessEvaluationResult.Create( proto_eval.InspectionContext, proto_eval.StackFrame, // Override the eval name with the original expression name, since it will // have inherited the ",!" suffix. Utility.GetExpressionName(expression_), Utility.GetExpressionFullName(expression_), proto_eval.Flags, custom_display_str, //success_eval.Value, proto_eval.EditableValue, proto_eval.Type, proto_eval.Category, proto_eval.Access, proto_eval.StorageType, proto_eval.TypeModifierFlags, proto_eval.Address, proto_eval.CustomUIVisualizers, proto_eval.ExternalModules, null ); state_ = EvaluationState.Evaluated; }