/// <summary> /// Processes functions such as is_int, is_bool etc. Returns whether the function was one of these. /// </summary> private static bool HandleTypeCheckingFunctions( BoundGlobalFunctionCall call, string name, BoundVariableRef arg, ExpressionAnalysis 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).WithIncludesSubclasses, 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 is specified in both branches TypeRefMask targetType = 0; AddTypeIfInContext(typeCtx, type => type.IsLambda, false, ref targetType); 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, is_callable always returns true -> remove the closure type 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); } }
public override object VisitGlobalFunctionCall(BoundGlobalFunctionCall x) { if (x.Name.NameValue == NameUtils.SpecialNames.dirname) { // dirname( __FILE__ ) -> __DIR__ if (x.ArgumentsInSourceOrder.Length == 1 && x.ArgumentsInSourceOrder[0].Value is BoundPseudoConst pc && pc.ConstType == Ast.PseudoConstUse.Types.File) { TransformationCount++; return(new BoundPseudoConst(Ast.PseudoConstUse.Types.Dir).WithAccess(x.Access)); } } else if (x.Name.NameValue == NameUtils.SpecialNames.get_parent_class) { bool TryResolveParentClassInCurrentClassContext(SourceRoutineSymbol routine, out BoundLiteral newExpression) { // in global function, always FALSE if (routine is SourceFunctionSymbol) { // FALSE newExpression = new BoundLiteral(false.AsObject()); return(true); } // in a method, we can resolve in compile time: if (routine is SourceMethodSymbol m && m.ContainingType is SourceTypeSymbol t && !t.IsTrait) { if (t.BaseType == null || t.BaseType.IsObjectType()) { // FALSE newExpression = new BoundLiteral(false.AsObject()) { ConstantValue = false.AsOptional() }; return(true); } else { // {class name} var baseTypeName = t.BaseType.PhpQualifiedName().ToString(); newExpression = new BoundLiteral(baseTypeName) { ConstantValue = baseTypeName }; return(true); } } // newExpression = default; return(false); } // get_parent_class() -> {class name} | FALSE if (x.ArgumentsInSourceOrder.Length == 0) { if (TryResolveParentClassInCurrentClassContext(_routine, out var newExpression)) { TransformationCount++; return(newExpression.WithContext(x)); } } // get_parent_class( ??? ) -> parent::class | FALSE if (x.ArgumentsInSourceOrder.Length == 1) { // get_parent_class($this), get_parent_class(__CLASS__) -> {class name} | FALSE if ((x.ArgumentsInSourceOrder[0].Value is BoundVariableRef varref && varref.Variable is ThisVariableReference) || (x.ArgumentsInSourceOrder[0].Value is BoundPseudoConst pc && pc.ConstType == Ast.PseudoConstUse.Types.Class)) { if (TryResolveParentClassInCurrentClassContext(_routine, out var newExpression)) { TransformationCount++; return(newExpression.WithContext(x)); } } } } else if (x.Name.NameValue == NameUtils.SpecialNames.method_exists) { // method_exists(FALSE, ...) -> FALSE if (x.ArgumentsInSourceOrder.Length >= 1) { var value = x.ArgumentsInSourceOrder[0].Value.ConstantValue; if (value.HasValue && value.TryConvertToBool(out var bvalue) && !bvalue) { TransformationCount++; return(new BoundLiteral(false.AsObject()) { ConstantValue = false.AsOptional() }.WithContext(x)); } } } // return(base.VisitGlobalFunctionCall(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(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>); 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; } } }
public virtual void VisitGlobalFunctionCall(BoundGlobalFunctionCall x, ConditionBranch branch) { base.VisitGlobalFunctionCall(x); }
public sealed override void VisitGlobalFunctionCall(BoundGlobalFunctionCall x) => VisitGlobalFunctionCall(x, ConditionBranch.Default);
/// <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; } } var args = call.ArgumentsInSourceOrder; // 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; string str; 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) if (IsUnconditionalDeclaration(analysis.Model.ResolveFunction(NameUtils.MakeQualifiedName(str, true)))) { call.ConstantValue = ConstantValueExtensions.AsOptional(true); } } 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 (!string.IsNullOrEmpty(class_name)) { 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 && !tmp.IsPhpUserType()) { if (tmp.LookupMethods(str).Count != 0) // TODO: why not User Types // TODO: why not resolve FALSE as well below? { call.ConstantValue = ConstantValueExtensions.AsOptional(true); } } } } break; case "defined": // defined(CONST_NAME) case "constant": // constant(CONST_NAME) if (args.Length == 1 && args[0].Value.ConstantValue.TryConvertToString(out str)) { // TODO: const_name in form of "{CLASS}::{NAME}" // TODO: also user constants defined in the same scope? // quick evaluation of PE constants that can't be changed at run time var tmp = analysis.Model.ResolveConstant(str); if (tmp is PEFieldSymbol fld) { if (name == "defined") { call.ConstantValue = ConstantValueExtensions.AsOptional(true); } else // name == "constant" { if (fld.Type.Is_Func_Context_TResult(out var tresult)) { call.TypeRefMask = TypeRefFactory.CreateMask(analysis.TypeCtx, tresult); } else { var cvalue = fld.GetConstantValue(false); call.ConstantValue = (cvalue != null) ? new Optional <object>(cvalue.Value) : null; call.TypeRefMask = TypeRefFactory.CreateMask(analysis.TypeCtx, fld.Type, notNull: fld.IsNotNull()); } } } 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, notNull: prop.IsNotNull()); } } } break; case "strlen": if (args.Length == 1 && args[0].Value.ConstantValue.TryConvertToString(out string value) && StringUtils.IsAsciiString(value)) { call.ConstantValue = new Optional <object>(value.Length); } break; case "file_exists": if (args.Length == 1) { if (TryResolveFile(analysis.Model, analysis.Routine, args[0].Value, out var script)) { // there is compiled script at this path, // the expression will be always true call.ConstantValue = true.AsOptional(); } } break; } }
/// <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; } } }