/// <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); }
/// <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); }
/// <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); } }
/// <summary> /// Switches <see cref="ConditionBranch.ToTrue"/> and <see cref="ConditionBranch.ToFalse"/>. /// </summary> public static ConditionBranch NegativeBranch(ConditionBranch branch) { return (ConditionBranch)(-((int)branch)); }
protected virtual void Visit(BoundInstanceOfEx x, ConditionBranch branch) { base.VisitInstanceOf(x); }
protected virtual void Visit(BoundIsSetEx x, ConditionBranch branch) { base.VisitIsSet(x); }
protected virtual void Visit(BoundUnaryEx x, ConditionBranch branch) { base.VisitUnaryExpression(x); }
public virtual void VisitGlobalFunctionCall(BoundGlobalFunctionCall x, ConditionBranch branch) { base.VisitGlobalFunctionCall(x); }
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); }
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); } } }
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; } } }