Exemple #1
0
        /// <summary>
        /// Visits condition used to branch execution to true or false branch.
        /// </summary>
        /// <remarks>
        /// Because of minimal evaluation there is different FlowState for true and false branches,
        /// AND and OR operators have to take this into account.
        /// 
        /// Also some other constructs may have side-effect for known branch,
        /// eg. <c>($x instanceof X)</c> implies ($x is X) in True branch.
        /// </remarks>
        internal void VisitCondition(BoundExpression condition, ConditionBranch branch)
        {
            Contract.ThrowIfNull(condition);

            if (branch != ConditionBranch.AnyResult)
            {
                if (condition is BoundBinaryEx)
                {
                    Visit((BoundBinaryEx)condition, branch);
                    return;
                }
                if (condition is BoundUnaryEx)
                {
                    Visit((BoundUnaryEx)condition, branch);
                    return;
                }
                //if (condition is DirectFcnCall)
                //{
                //    VisitDirectFcnCall((DirectFcnCall)condition, branch);
                //    return;
                //}
                if (condition is BoundInstanceOfEx)
                {
                    Visit((BoundInstanceOfEx)condition, branch);
                    return;
                }
                //if (condition is IssetEx)
                //{
                //    VisitIssetEx((IssetEx)condition, branch);
                //    return;
                //}
                //if (condition is EmptyEx)
                //{
                //    VisitEmptyEx((EmptyEx)condition, branch);
                //    return;
                //}
            }

            // no effect
            condition.Accept(this);
        }
        TypeRefMask ResolveUnaryOperatorExpression(BoundUnaryEx x, ConditionBranch branch)
        {
            //
            Accept(x.Operand);

            //
            switch (x.Operation)
            {
                case Operations.AtSign:
                    return x.Operand.TypeRefMask;

                case Operations.BitNegation:
                    return TypeCtx.GetLongTypeMask();   // TODO: or byte[]

                case Operations.Clone:
                    return x.Operand.TypeRefMask;

                case Operations.LogicNegation:
                    return TypeCtx.GetBooleanTypeMask();

                case Operations.Minus:
                    if (IsDoubleOnly(x.Operand))
                        return TypeCtx.GetDoubleTypeMask(); // double in case operand is double
                    return TypeCtx.GetNumberTypeMask();     // TODO: long in case operand is not a number

                case Operations.ObjectCast:
                    if (IsClassOnly(x.Operand.TypeRefMask))
                        return x.Operand.TypeRefMask;   // (object)<object>

                    return TypeCtx.GetSystemObjectTypeMask();   // TODO: return the exact type in case we know, return stdClass in case of a scalar

                case Operations.Plus:
                    if (IsNumberOnly(x.Operand.TypeRefMask))
                        return x.Operand.TypeRefMask;
                    return TypeCtx.GetNumberTypeMask();

                case Operations.Print:
                    return TypeCtx.GetLongTypeMask();

                case Operations.BoolCast:
                    return TypeCtx.GetBooleanTypeMask();

                case Operations.Int8Cast:
                case Operations.Int16Cast:
                case Operations.Int32Cast:
                case Operations.UInt8Cast:
                case Operations.UInt16Cast:
                // -->
                case Operations.UInt64Cast:
                case Operations.UInt32Cast:
                case Operations.Int64Cast:
                    return TypeCtx.GetLongTypeMask();

                case Operations.DecimalCast:
                case Operations.DoubleCast:
                case Operations.FloatCast:
                    return TypeCtx.GetDoubleTypeMask();

                case Operations.UnicodeCast: // TODO
                case Operations.StringCast:
                    return TypeCtx.GetStringTypeMask(); // binary | unicode | both

                case Operations.BinaryCast:
                    return TypeCtx.GetWritableStringTypeMask(); // binary string builder

                case Operations.ArrayCast:
                    return TypeCtx.GetArrayTypeMask();  // TODO: can we be more specific?

                case Operations.UnsetCast:
                    return TypeCtx.GetNullTypeMask();   // null

                default:
                    throw ExceptionUtilities.Unreachable;
            }
        }
        protected override void Visit(BoundInstanceOfEx x, ConditionBranch branch)
        {
            Accept(x.Operand);
            VisitTypeRef(x.AsType);

            // TOOD: x.ConstantValue // in case we know and the operand is a local variable (we can ignore the expression and emit result immediatelly)

            if (branch == ConditionBranch.ToTrue && x.Operand is BoundVariableRef)
            {
                var vref = (BoundVariableRef)x.Operand;
                if (vref.Name.IsDirect)
                {
                    // if (Variable is T) => variable is T in True branch state
                    State.SetVar(vref.Name.NameValue.Value, TypeCtx.GetTypeMask(x.AsType.TypeRef, true));
                }
            }

            //
            x.TypeRefMask = TypeCtx.GetBooleanTypeMask();
        }
        TypeRefMask ResolveBinaryEx(BoundBinaryEx x, ConditionBranch branch)
        {
            if (x.Operation == Operations.And || x.Operation == Operations.Or)
            {
                this.VisitShortCircuitOp(x.Left, x.Right, x.Operation == Operations.And, branch);
            }
            else
            {
                Accept(x.Left);
                Accept(x.Right);
            }

            switch (x.Operation)
            {
                #region Arithmetic Operations

                case Operations.Add:
                    return GetPlusOperationType(x.Left, x.Right);

                case Operations.Sub:
                case Operations.Div:
                case Operations.Mul:
                case Operations.Pow:
                    if (IsDoubleOnly(x.Left.TypeRefMask) || IsDoubleOnly(x.Right.TypeRefMask)) // some operand is double and nothing else
                        return TypeCtx.GetDoubleTypeMask(); // double if we are sure about operands
                    return TypeCtx.GetNumberTypeMask();

                case Operations.Mod:
                    return TypeCtx.GetLongTypeMask();

                case Operations.ShiftLeft:
                case Operations.ShiftRight:
                    return TypeCtx.GetLongTypeMask();

                #endregion

                #region Boolean and Bitwise Operations

                case Operations.And:
                case Operations.Or:
                case Operations.Xor:
                    return TypeCtx.GetBooleanTypeMask();

                case Operations.BitAnd:
                case Operations.BitOr:
                case Operations.BitXor:

                    if (x.Left.ConstantValue.HasValue && x.Right.ConstantValue.HasValue)
                    {
                        x.ConstantValue = ResolveBitOperation(x.Left.ConstantValue.Value, x.Right.ConstantValue.Value, x.Operation);
                    }

                    return GetBitOperationType(x.Left.TypeRefMask, x.Right.TypeRefMask);    // int or string

                #endregion

                #region Comparing Operations

                case Operations.Equal:
                case Operations.NotEqual:
                case Operations.GreaterThan:
                case Operations.LessThan:
                case Operations.GreaterThanOrEqual:
                case Operations.LessThanOrEqual:
                case Operations.Identical:
                case Operations.NotIdentical:

                    if (branch == ConditionBranch.ToTrue)
                    {
                        if (x.Operation == Operations.LessThan && IsLongOnly(x.Right))
                            LTInt64Max(x.Left as BoundReferenceExpression, true);   // $x < LONG
                    }

                    return TypeCtx.GetBooleanTypeMask();

                #endregion

                case Operations.Concat:
                    return TypeCtx.GetWritableStringTypeMask();

                default:
                    throw ExceptionUtilities.Unreachable;
            }
        }
 protected override void Visit(BoundUnaryEx x, ConditionBranch branch)
 {
     x.TypeRefMask = ResolveUnaryOperatorExpression(x, branch);
 }
Exemple #6
0
        /// <summary>
        /// Resolves value of the function call in compile time if possible and updates the variable type if necessary
        /// </summary>
        public static void HandleFunctionCall(BoundGlobalFunctionCall call, ExpressionAnalysis analysis, ConditionBranch branch)
        {
            // Only direct function names
            if (!HasSimpleName(call, out string name))
            {
                return;
            }

            // Type checking functions
            if (branch != ConditionBranch.AnyResult && CanBeTypeCheckingFunction(call, name, out var arg))
            {
                if (HandleTypeCheckingFunctions(call, name, arg, analysis, branch))
                {
                    return;
                }
            }

            // Functions with all arguments resolved
            if (call.ArgumentsInSourceOrder.All(a => a.Value.ConstantValue.HasValue))
            {
                // Clear out the constant value result from the previous run of this method (if it was valid, it will be reassigned below)
                call.ConstantValue = default(Optional <object>);

                var args = call.ArgumentsInSourceOrder;
                switch (name)
                {
                // bool function_exists ( string $function_name )
                case "function_exists":
                    if (args.Length == 1)
                    {
                        // TRUE <=> function is defined unconditionally in a reference library (PE assembly)
                        var function_name = args[0].Value.ConstantValue.Value as string;
                        if (function_name != null)
                        {
                            var tmp = analysis.Model.ResolveFunction(NameUtils.MakeQualifiedName(function_name, true));
                            if (tmp is PEMethodSymbol || (tmp is AmbiguousMethodSymbol && ((AmbiguousMethodSymbol)tmp).Ambiguities.All(f => f is PEMethodSymbol)))      // TODO: unconditional declaration ?
                            {
                                call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                                return;
                            }
                        }
                    }
                    break;

                // bool class_exists ( string $class_name [, bool $autoload = true ] )
                case "class_exists":
                    if (args.Length >= 1)
                    {
                        // TRUE <=> class is defined unconditionally in a reference library (PE assembly)
                        var class_name = args[0].Value.ConstantValue.Value as string;
                        if (class_name != null)
                        {
                            var tmp = analysis.Model.GetType(NameUtils.MakeQualifiedName(class_name, true));
                            if (tmp is PENamedTypeSymbol)       // TODO: unconditional declaration ?
                            {
                                call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                                return;
                            }
                        }
                    }
                    break;

                // bool method_exists ( string $class_name , string $method_name )
                case "method_exists":
                    if (args.Length == 2)
                    {
                        var class_name    = args[0].Value.ConstantValue.Value as string;
                        var function_name = args[1].Value.ConstantValue.Value as string;
                        if (class_name != null && function_name != null)
                        {
                            var tmp = (NamedTypeSymbol)analysis.Model.GetType(NameUtils.MakeQualifiedName(class_name, true));
                            if (tmp is PENamedTypeSymbol)
                            {
                                if (tmp.LookupMethods(function_name).Any())
                                {
                                    call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                                    return;
                                }
                            }
                        }
                    }
                    break;
                }
            }
        }
 protected override void Visit(BoundBinaryEx x, ConditionBranch branch)
 {
     x.TypeRefMask = ResolveBinaryEx(x, branch);
 }
Exemple #8
0
        /// <summary>
        /// Processes functions such as is_int, is_bool etc. Returns whether the function was one of these.
        /// </summary>
        private static bool HandleTypeCheckingFunctions <T>(
            BoundGlobalFunctionCall call,
            string name,
            BoundVariableRef arg,
            ExpressionAnalysis <T> analysis,
            ConditionBranch branch)
        {
            var typeCtx   = analysis.TypeCtx;
            var flowState = analysis.State;

            switch (name)
            {
            case "is_int":
            case "is_integer":
            case "is_long":
                HandleTypeCheckingExpression(arg, typeCtx.GetLongTypeMask(), branch, flowState, checkExpr: call);
                return(true);

            case "is_bool":
                HandleTypeCheckingExpression(arg, typeCtx.GetBooleanTypeMask(), branch, flowState, checkExpr: call);
                return(true);

            case "is_float":
            case "is_double":
            case "is_real":
                HandleTypeCheckingExpression(arg, typeCtx.GetDoubleTypeMask(), branch, flowState, checkExpr: call);
                return(true);

            case "is_string":
                var stringMask = typeCtx.GetStringTypeMask() | typeCtx.GetWritableStringTypeMask();
                HandleTypeCheckingExpression(arg, stringMask, branch, flowState, checkExpr: call);
                return(true);

            case "is_resource":
                HandleTypeCheckingExpression(arg, typeCtx.GetResourceTypeMask(), branch, flowState, checkExpr: call);
                return(true);

            case "is_null":
                HandleTypeCheckingExpression(arg, typeCtx.GetNullTypeMask(), branch, flowState, checkExpr: call);
                return(true);

            case "is_array":
                HandleTypeCheckingExpression(
                    arg,
                    currentType => typeCtx.GetArraysFromMask(currentType),
                    branch,
                    flowState,
                    skipPositiveIfAnyType: true,
                    checkExpr: call);
                return(true);

            case "is_object":
                // Keep IncludesSubclasses flag in the true branch and clear it in the false branch
                HandleTypeCheckingExpression(
                    arg,
                    currentType => typeCtx.GetObjectsFromMask(currentType).WithSubclasses,
                    branch,
                    flowState,
                    skipPositiveIfAnyType: true,
                    checkExpr: call);
                return(true);

            // TODO
            //case "is_scalar":
            //    return;

            case "is_numeric":
                HandleTypeCheckingExpression(
                    arg,
                    currentType =>
                {
                    // Specify numeric types if they are present
                    var targetType = typeCtx.IsLong(currentType) ? typeCtx.GetLongTypeMask() : 0;
                    targetType    |= typeCtx.IsDouble(currentType) ? typeCtx.GetDoubleTypeMask() : 0;

                    if (branch == ConditionBranch.ToTrue)
                    {
                        // Also string types can make is_numeric return true, but not anything else
                        targetType |= typeCtx.IsReadonlyString(currentType) ? typeCtx.GetStringTypeMask() : 0;
                        targetType |= typeCtx.IsWritableString(currentType) ? typeCtx.GetWritableStringTypeMask() : 0;

                        return(targetType);
                    }
                    else
                    {
                        // For number, is_numeric always returns true -> remove numeric types from false branch
                        return(targetType);
                    }
                },
                    branch,
                    flowState,
                    skipPositiveIfAnyType: true,
                    checkExpr: call);
                return(true);

            case "is_callable":
                HandleTypeCheckingExpression(
                    arg,
                    currentType =>
                {
                    // Closure and lambdas are specified in both branches
                    TypeRefMask targetType = typeCtx.GetClosureTypeMask();
                    targetType            |= typeCtx.GetLambdasFromMask(currentType);

                    if (branch == ConditionBranch.ToTrue)
                    {
                        // Also string types, arrays and objects can make is_callable return true, but not anything else
                        targetType |= typeCtx.IsReadonlyString(currentType) ? typeCtx.GetStringTypeMask() : 0;
                        targetType |= typeCtx.IsWritableString(currentType) ? typeCtx.GetWritableStringTypeMask() : 0;
                        targetType |= typeCtx.GetArraysFromMask(currentType);
                        targetType |= typeCtx.GetObjectsFromMask(currentType);

                        return(targetType);
                    }
                    else
                    {
                        // For closure and lambdas, is_callable always returns true -> remove them from false branch,
                        // don't remove IncludeSubclasses flag
                        return(targetType);
                    }
                },
                    branch,
                    flowState,
                    skipPositiveIfAnyType: true,
                    checkExpr: call);
                return(true);

            // TODO
            //case "is_iterable":
            //    return;

            default:
                return(false);
            }
        }
Exemple #9
0
 /// <summary>
 /// Switches <see cref="ConditionBranch.ToTrue"/> and <see cref="ConditionBranch.ToFalse"/>.
 /// </summary>
 public static ConditionBranch NegativeBranch(ConditionBranch branch)
 {
     return (ConditionBranch)(-((int)branch));
 }
Exemple #10
0
 protected virtual void Visit(BoundInstanceOfEx x, ConditionBranch branch)
 {
     base.VisitInstanceOf(x);
 }
Exemple #11
0
 protected virtual void Visit(BoundIsSetEx x, ConditionBranch branch)
 {
     base.VisitIsSet(x);
 }
Exemple #12
0
 protected virtual void Visit(BoundUnaryEx x, ConditionBranch branch)
 {
     base.VisitUnaryExpression(x);
 }
Exemple #13
0
 public virtual void VisitGlobalFunctionCall(BoundGlobalFunctionCall x, ConditionBranch branch)
 {
     base.VisitGlobalFunctionCall(x);
 }
Exemple #14
0
        private static bool HandleTypeChecking(
            TypeRefMask currentType,
            TypeRefMask targetType,
            ConditionBranch branch,
            FlowState flowState,
            VariableHandle handle,
            bool skipTrueIfAnyType)
        {
            // Information whether this path can ever be taken
            bool isFeasible = true;

            if (branch == ConditionBranch.ToTrue)
            {
                // In the true branch the IsAnyType case can be optionally skipped
                if (skipTrueIfAnyType && currentType.IsAnyType)
                {
                    return(isFeasible);
                }

                // Intersect the possible types with those checked by the function, always keeping the IsRef flag.
                // IncludesSubclasses is kept only if it is specified in targetType.
                TypeRefMask resultType = (currentType & (targetType | TypeRefMask.IsRefMask));

                if (resultType.IsVoid)
                {
                    // Clearing the type out in this branch means the variable will never be of that type.
                    // In order to prevent errors in analysis and code generation, set the type to the one specified.
                    resultType = targetType | (currentType & TypeRefMask.IsRefMask);

                    isFeasible = false;
                }

                flowState.SetLocalType(handle, resultType);
            }
            else
            {
                Debug.Assert(branch == ConditionBranch.ToFalse);

                // In the false branch we cannot handle the IsAnyType case
                if (currentType.IsAnyType)
                {
                    return(isFeasible);
                }

                // Remove the types and flags excluded by the fact that the function returned false
                TypeRefMask resultType = currentType & (~targetType);

                if (resultType.IsVoid)
                {
                    // Clearing the type out in this branch means the variable will always be of that type
                    // In order to prevent errors in analysis and code generation, do not alter the type in this case.

                    isFeasible = false;
                }
                else
                {
                    flowState.SetLocalType(handle, resultType);
                }
            }

            return(isFeasible);
        }
Exemple #15
0
 protected virtual void Visit(BoundUnaryEx x, ConditionBranch branch)
 {
     base.VisitUnaryExpression(x);
 }
        private void VisitShortCircuitOp(BoundExpression lExpr, BoundExpression rExpr, bool isAndOp, ConditionBranch branch)
        {
            // Each operand has to be evaluated in various states and then the state merged.
            // Simulates short-circuit evaluation in runtime:

            var state = this.State; // original state

            if (branch == ConditionBranch.AnyResult)
            {
                if (isAndOp)
                {
                    // A == True && B == Any
                    // A == False

                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToTrue);
                    VisitCondition(rExpr, ConditionBranch.AnyResult);
                    var tmp = State;
                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToFalse);
                    State = State.Merge(tmp);
                }
                else
                {
                    // A == False && B == Any
                    // A == True

                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToFalse);
                    VisitCondition(rExpr, ConditionBranch.AnyResult);
                    var tmp = State;
                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToTrue);
                    State = State.Merge(tmp);
                }
            }
            else if (branch == ConditionBranch.ToTrue)
            {
                if (isAndOp)
                {
                    // A == True && B == True

                    VisitCondition(lExpr, ConditionBranch.ToTrue);
                    VisitCondition(rExpr, ConditionBranch.ToTrue);
                }
                else
                {
                    // A == False && B == True
                    // A == True

                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToFalse);
                    VisitCondition(rExpr, ConditionBranch.ToTrue);
                    var tmp = State;
                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToTrue);
                    State = State.Merge(tmp);
                }
            }
            else if (branch == ConditionBranch.ToFalse)
            {
                if (isAndOp)
                {
                    // A == True && B == False
                    // A == False

                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToTrue);
                    VisitCondition(rExpr, ConditionBranch.ToFalse);
                    var tmp = State;
                    State = state.Clone();
                    VisitCondition(lExpr, ConditionBranch.ToFalse);
                    State = State.Merge(tmp);
                }
                else
                {
                    // A == False && B == False

                    VisitCondition(lExpr, ConditionBranch.ToFalse);
                    VisitCondition(rExpr, ConditionBranch.ToFalse);
                }
            }
        }
Exemple #17
0
 protected virtual void Visit(BoundInstanceOfEx x, ConditionBranch branch)
 {
     base.VisitInstanceOf(x);
 }
        /// <summary>
        /// Resolves value of the function call in compile time if possible and updates the variable type if necessary
        /// </summary>
        public static void HandleSpecialFunctionCall <T>(BoundGlobalFunctionCall call, ExpressionAnalysis <T> analysis, ConditionBranch branch)
        {
            // Only direct function names
            if (!HasSimpleName(call, out string name))
            {
                return;
            }

            // Type checking functions
            if (branch != ConditionBranch.AnyResult && CanBeTypeCheckingFunction(call, name, out var arg))
            {
                if (HandleTypeCheckingFunctions(call, name, arg, analysis, branch))
                {
                    return;
                }
            }

            // Functions with all arguments resolved
            if (call.ArgumentsInSourceOrder.All(a => a.Value.ConstantValue.HasValue))
            {
                // Clear out the constant value result from the previous run of this method (if it was valid, it will be reassigned below)
                call.ConstantValue = default(Optional <object>);

                string str;

                var args = call.ArgumentsInSourceOrder;
                switch (name)           // TODO: case insensitive
                {
                case "is_callable":     // bool is_callable( string $function_name )
                case "function_exists": // bool function_exists ( string $function_name )
                    if (args.Length == 1 && args[0].Value.ConstantValue.TryConvertToString(out str))
                    {
                        // TRUE <=> function is defined unconditionally in a reference library (PE assembly)
                        var tmp = analysis.Model.ResolveFunction(NameUtils.MakeQualifiedName(str, true));
                        if (tmp is PEMethodSymbol || (tmp is AmbiguousMethodSymbol && ((AmbiguousMethodSymbol)tmp).Ambiguities.All(f => f is PEMethodSymbol))) // TODO: unconditional declaration ?
                        {
                            if (!tmp.ContainingType.IsPhpSourceFile())                                                                                         // only functions declared in libraries, not in PHP source file
                            {
                                call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                                return;
                            }
                        }
                    }
                    break;

                // bool class_exists ( string $class_name [, bool $autoload = true ] )
                case "class_exists":
                case "interface_exists":
                    if (args.Length >= 1)
                    {
                        // TRUE <=> class is defined unconditionally in a reference library (PE assembly)
                        var class_name = args[0].Value.ConstantValue.Value as string;
                        if (class_name != null)
                        {
                            var tmp = (TypeSymbol)analysis.Model.ResolveType(NameUtils.MakeQualifiedName(class_name, true));
                            if (tmp is PENamedTypeSymbol && !tmp.IsPhpUserType())       // TODO: + SourceTypeSymbol when reachable unconditional declaration
                            {
                                bool @interface = (name == "interface_exists");
                                if (tmp.TypeKind == (@interface ? TypeKind.Interface : TypeKind.Class))
                                {
                                    call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                                }
                            }
                        }
                    }
                    break;

                // bool method_exists ( string $class_name , string $method_name )
                case "method_exists":
                    if (args.Length == 2)
                    {
                        var class_name = args[0].Value.ConstantValue.Value as string;
                        if (class_name != null && args[1].Value.ConstantValue.TryConvertToString(out str))
                        {
                            var tmp = (NamedTypeSymbol)analysis.Model.ResolveType(NameUtils.MakeQualifiedName(class_name, true));
                            if (tmp is PENamedTypeSymbol)
                            {
                                if (tmp.LookupMethods(str).Any())
                                {
                                    call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                                    return;
                                }
                            }
                        }
                    }
                    break;

                case "extension_loaded":        // bool extension_loaded(name)
                    if (args.Length == 1 && args[0].Value.ConstantValue.TryConvertToString(out str))
                    {
                        if (analysis.Model.Extensions.Contains(str, StringComparer.OrdinalIgnoreCase))
                        {
                            call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                            return;
                        }
                    }
                    break;

                case "defined":
                case "constant":
                    if (args.Length == 1 && args[0].Value.ConstantValue.TryConvertToString(out str))
                    {
                        // TODO: const_name in form of "{CLASS}::{NAME}"
                        var tmp = analysis.Model.ResolveConstant(str);
                        if (tmp is PEFieldSymbol fld)        // TODO: also user constants defined in the same scope
                        {
                            if (name == "defined")
                            {
                                call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                            }
                            else     // name == "constant"
                            {
                                var cvalue = fld.GetConstantValue(false);
                                call.ConstantValue = (cvalue != null) ? new Optional <object>(cvalue.Value) : null;
                                call.TypeRefMask   = TypeRefFactory.CreateMask(analysis.TypeCtx, fld.Type);
                            }

                            return;
                        }
                        else if (tmp is PEPropertySymbol prop)
                        {
                            if (name == "defined")
                            {
                                call.ConstantValue = ConstantValueExtensions.AsOptional(true);
                            }
                            else     // name == "constant"
                            {
                                call.TypeRefMask = TypeRefFactory.CreateMask(analysis.TypeCtx, prop.Type);
                            }
                        }
                    }
                    break;

                case "strlen":
                    if (args.Length == 1 && args[0].Value.ConstantValue.TryConvertToString(out string value))
                    {
                        call.ConstantValue = new Optional <object>(value.Length);
                    }
                    return;
                }
            }
        }