/// <summary> /// Ensures that the variable is of the given type(s) in the positive branch or not of this type in the negative /// branch. If the current branch is unfeasible, assigns an appropriate boolean to the /// <see cref="BoundExpression.ConstantValue"/> of <paramref name="checkExpr"/>. /// </summary> /// <param name="varRef">The reference to the variable whose types to check.</param> /// <param name="targetType">The target type of the variable.</param> /// <param name="branch">The branch to check - <see cref="ConditionBranch.ToTrue"/> is understood as the positive /// branch if <paramref name="isPositiveCheck"/> is true.</param> /// <param name="flowState">The flow state of the branch.</param> /// <param name="skipPositiveIfAnyType">Whether to skip a mask with <see cref="TypeRefMask.IsAnyType"/> in the /// positive branch (in the negative branch, it is always skipped).</param> /// <param name="checkExpr">The expression to have its <see cref="BoundExpression.ConstantValue"/> potentially /// updated.</param> /// <param name="isPositiveCheck">Whether the expression returns true when the type check succeeds. For example, /// in the case of != it would be false.</param> public static void HandleTypeCheckingExpression( BoundVariableRef varRef, TypeRefMask targetType, ConditionBranch branch, FlowState flowState, bool skipPositiveIfAnyType = false, BoundExpression checkExpr = null, bool isPositiveCheck = true) { HandleTypeCheckingExpression(varRef, (var) => targetType, branch, flowState, skipPositiveIfAnyType, checkExpr, isPositiveCheck); }
/// <summary> /// Sets the initial routine return type. /// </summary> /// <param name="ctx"></param> /// <param name="initialState"></param> /// <param name="phpdoc"></param> static void InitReturn(FlowContext /*!*/ ctx, FlowState /*!*/ initialState, PHPDocBlock phpdoc) { Debug.Assert(ctx.ReturnVarIndex >= 0); if (phpdoc != null) { var returnTag = phpdoc.Returns; if (returnTag != null && returnTag.TypeNamesArray.Length != 0) { initialState.SetVar(ctx.ReturnVarIndex, PHPDoc.GetTypeMask(ctx.TypeRefContext, returnTag.TypeNamesArray)); } } }
/// <summary> /// Initializes <c>$this</c> variable, its type and initialized state. /// </summary> private static void InitThisVar(FlowContext /*!*/ ctx, FlowState /*!*/ initialState, int varIndex) { var thisVarType = ctx.TypeRefContext.GetThisTypeMask(); if (thisVarType.IsUninitialized) { thisVarType = TypeRefMask.AnyType; } // initialState.SetVarUsed(varIndex); initialState.SetVar(varIndex, thisVarType); }
/// <summary> /// Creates new type context, flow context and flow state for the routine. /// </summary> public static FlowState CreateInitialState(SourceRoutineSymbol /*!*/ routine, FlowContext flowCtx = null) { Contract.ThrowIfNull(routine); // get or create typeCtx var typeCtx = routine.TypeRefContext; if (flowCtx == null) { // create FlowContext flowCtx = new FlowContext(typeCtx, routine); } // create FlowState var state = new FlowState(flowCtx); // handle input parameters type foreach (var p in routine.SourceParameters) { var local = state.GetLocalHandle(new VariableName(p.Name)); var ptype = p.GetResultType(typeCtx); if (p.IsNotNull) { // remove 'null' type from the mask, // it cannot be null ptype = typeCtx.WithoutNull(ptype); } else if (p.Type.IsReferenceType) // a reference type that can be null { ptype |= typeCtx.GetNullTypeMask(); } state.SetLocalType(local, ptype); if (p.Syntax.PassedByRef && !p.Syntax.IsVariadic) { state.MarkLocalByRef(local); } } // $this if (routine.GetPhpThisVariablePlace() != null) { InitThisVar(flowCtx, state); } // return(state); }
/// <summary> /// Ensures that the variable is of the given type(s) in the positive branch or not of this type in the negative /// branch. If the current branch is unfeasible, assigns an appropriate boolean to the /// <see cref="BoundExpression.ConstantValue"/> of <paramref name="checkExpr"/>. /// </summary> /// <param name="varRef">The reference to the variable whose types to check.</param> /// <param name="targetTypeCallback">The callback that receives the current type mask of the variable and returns /// the target one.</param> /// <param name="branch">The branch to check - <see cref="ConditionBranch.ToTrue"/> is understood as the positive /// branch if <paramref name="isPositiveCheck"/> is true.</param> /// <param name="flowState">The flow state of the branch.</param> /// <param name="skipPositiveIfAnyType">Whether to skip a mask with <see cref="TypeRefMask.IsAnyType"/> in the /// positive branch (in the negative branch, it is always skipped).</param> /// <param name="checkExpr">The expression to have its <see cref="BoundExpression.ConstantValue"/> potentially /// updated.</param> /// <param name="isPositiveCheck">Whether the expression returns true when the type check succeeds. For example, /// in the case of != it would be false.</param> public static void HandleTypeCheckingExpression( BoundVariableRef varRef, Func <TypeRefMask, TypeRefMask> targetTypeCallback, ConditionBranch branch, FlowState flowState, bool skipPositiveIfAnyType = false, BoundExpression checkExpr = null, bool isPositiveCheck = true) { if (!TryGetVariableHandle(varRef.Variable, flowState, out VariableHandle handle)) { return; } var currentType = flowState.GetLocalType(handle); var targetType = targetTypeCallback(currentType); // Model negative type checks (such as $x != null) by inverting branches for the core checking function var branchHlp = isPositiveCheck ? branch : branch.NegativeBranch(); bool isFeasible = HandleTypeChecking(currentType, targetType, branchHlp, flowState, handle, skipPositiveIfAnyType); // If the constant value was not meant to be updated, skip its computation if (checkExpr == null) { return; } if (!currentType.IsRef) { // If the true branch proves to be unfeasible, the function always returns false and vice versa var resultConstVal = isFeasible ? default(Optional <object>) : new Optional <object>(!branch.TargetValue().Value); // Each branch can clean only the constant value it produced during its analysis (in order not to lose result // of the other branch): true branch can produce false value and vice versa if (!resultConstVal.EqualsOptional(checkExpr.ConstantValue) && (resultConstVal.HasValue || checkExpr.ConstantValue.Value is false && branch == ConditionBranch.ToTrue || checkExpr.ConstantValue.Value is true && branch == ConditionBranch.ToFalse)) { checkExpr.ConstantValue = resultConstVal; } } else { // We cannot reason about the result of the check if the variable can be modified by reference checkExpr.ConstantValue = default(Optional <object>); } }
/// <summary> /// Initializes <c>$this</c> variable, its type and initialized state. /// </summary> private static void InitThisVar(FlowContext /*!*/ ctx, FlowState /*!*/ initialState) { var thisVarType = ctx.TypeRefContext.GetThisTypeMask(); if (thisVarType.IsUninitialized) { thisVarType = TypeRefMask.AnyType; } // var thisHandle = ctx.GetVarIndex(VariableName.ThisVariableName); initialState.SetLocalType(thisHandle, thisVarType); // set $this type initialState.VisitLocal(thisHandle); // mark as visited (used) to not report as unused }
public override void VisitCFGConditionalEdge(ConditionalEdge x) { // build state for TrueBlock and FalseBlock properly, take minimal evaluation into account var state = _state; // true branch _state = state.Clone(); VisitCondition(x.Condition, ConditionBranch.ToTrue); TraverseToBlock(x, _state, x.TrueTarget); // false branch _state = state.Clone(); VisitCondition(x.Condition, ConditionBranch.ToFalse); TraverseToBlock(x, _state, x.FalseTarget); }
/// <summary> /// Initializes <c>$this</c> variable, its type and initialized state. /// </summary> private static void InitThisVar(FlowContext /*!*/ ctx, FlowState /*!*/ initialState) { var thisVarType = ctx.TypeRefContext.GetThisTypeMask(); if (thisVarType.IsUninitialized) { thisVarType = TypeRefMask.AnyType; } // var thisIdx = ctx.GetVarIndex(VariableName.ThisVariableName); initialState.SetVarUsed(thisIdx); initialState.SetVar(thisIdx, thisVarType); }
private static bool TryGetVariableHandle(BoundVariable boundvar, FlowState state, out VariableHandle varHandle) { if (boundvar.Name != null) // direct variable name { if (boundvar.VariableKind == VariableKind.LocalVariable || boundvar.VariableKind == VariableKind.Parameter) { varHandle = state.GetLocalHandle(boundvar.Name); return(true); } } // varHandle = default(VariableHandle); return(false); }
private static bool TryGetVariableHandle(IVariableReference boundvar, FlowState state, out VariableHandle varHandle) { if (boundvar is LocalVariableReference local && local.BoundName.IsDirect) // direct variable name { if (local.VariableKind == VariableKind.LocalVariable || local.VariableKind == VariableKind.Parameter || local.VariableKind == VariableKind.LocalTemporalVariable) { varHandle = state.GetLocalHandle(local.BoundName.NameValue); return(true); } } // varHandle = default(VariableHandle); return(false); }
public override void VisitCFGForeachMoveNextEdge(ForeachMoveNextEdge x) { var state = _state; // get type information from Enumeree to determine types value variable var elementType = HandleTraversableUse(x.EnumereeEdge.Enumeree); if (elementType.IsVoid) { elementType = TypeRefMask.AnyType; } // Body branch _state = state.Clone(); // set key variable and value variable at current state var valueVar = x.ValueVariable; if (valueVar is BoundListEx) { throw new NotImplementedException(); //VisitListEx(valueVar.List, elementType); } else { valueVar.Access = valueVar.Access.WithWrite(valueVar.Access.IsWriteRef ? elementType.WithRefFlag : elementType); Accept(valueVar); // var keyVar = x.KeyVariable; if (keyVar != null) { keyVar.Access = keyVar.Access.WithWrite(TypeRefMask.AnyType); Accept(keyVar); } } TraverseToBlock(x, _state, x.BodyBlock); // End branch _state = state.Clone(); TraverseToBlock(x, _state, x.NextBlock); }
/// <summary> /// Creates new type context, flow context and flow state for the routine. /// </summary> public static FlowState CreateInitialState(SourceRoutineSymbol /*!*/ routine, FlowContext flowCtx = null) { Contract.ThrowIfNull(routine); // get or create typeCtx var typeCtx = routine.TypeRefContext; // create or reuse FlowContext flowCtx ??= new FlowContext(typeCtx, routine); // pre-allocate locals map // https://github.com/peachpiecompiler/peachpie/issues/1002 foreach (var variable in routine.LocalsTable.Variables) { if (variable is Semantics.LocalVariableReference local && local.VariableKind == VariableKind.LocalVariable) { flowCtx.GetVarIndex(variable.BoundName.NameValue); } } // create FlowState var state = new FlowState(flowCtx); // populate input parameters type foreach (var p in routine.SourceParameters) { var local = state.GetLocalHandle(new VariableName(p.Name)); var ptype = p.GetResultType(typeCtx); state.SetLocalType(local, ptype); } // $this if (routine.GetPhpThisVariablePlace() != null) { InitThisVar(flowCtx, state); } // return(state); }
/// <summary> /// Creates new type context, flow context and flow state for the routine. /// </summary> public static FlowState CreateInitialState(SourceRoutineSymbol /*!*/ routine) { Contract.ThrowIfNull(routine); // create typeCtx var typeCtx = routine.TypeRefContext; // create FlowContext var flowCtx = new FlowContext(typeCtx, routine); // create FlowState var state = new FlowState(flowCtx); // handle input parameters type var parameters = routine.SourceParameters; foreach (var p in parameters) { var local = state.GetLocalHandle(new VariableName(p.Name)); state.SetLocalType(local, p.GetResultType(typeCtx)); if (p.Syntax.PassedByRef && !p.Syntax.IsVariadic) { state.MarkLocalByRef(local); } } // $this if (routine.GetPhpThisVariablePlace() != null) { InitThisVar(flowCtx, state); } // return(state); }
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); }
/// <summary> /// Creates new type context, flow context and flow state for the routine. /// </summary> public static FlowState CreateInitialState(SourceRoutineSymbol routine) { Contract.ThrowIfNull(routine); var containingType = routine.ContainingType as SourceNamedTypeSymbol; // collect locals var locals = LocalsBinder.BindLocals(routine); var returnIdx = locals.IndexOf(x => x.VariableKind == VariableKind.ReturnVariable); // create typeCtx var typeCtx = routine.TypeRefContext; // create FlowContext var flowCtx = new FlowContext(typeCtx, locals, returnIdx); // create FlowState var state = new FlowState(flowCtx, routine); // handle parameters passed by reference var parameters = routine.Parameters.OfType <SourceParameterSymbol>().ToImmutableArray(); foreach (var p in parameters) { if (p.Syntax.PassedByRef) { state.SetVarRef(p.Name); } } // mark $this as initialized // mark global variables as ByRef, used // mark function parameters as used, initialized, typed // construct initial state for variables int paramIdx = 0; for (int i = 0; i < locals.Length; i++) { switch (locals[i].VariableKind) { case VariableKind.GlobalVariable: state.SetVarRef(i); // => used, byref, initialized break; case VariableKind.Parameter: //state.SetVarUsed(i); var paramtag = PHPDoc.GetParamTag(routine.PHPDocBlock, paramIdx, locals[i].Name); state.SetVar(i, GetParamType(typeCtx, paramtag, parameters[paramIdx].Syntax, default(CallInfo), paramIdx)); paramIdx++; break; //case VariableKind.UseParameter: // state.SetVar(i, TypeRefMask.AnyType); // break; case VariableKind.ThisParameter: InitThisVar(flowCtx, state, i); break; case VariableKind.StaticVariable: state.SetVarInitialized(i); break; case VariableKind.ReturnVariable: InitReturn(flowCtx, state, routine.PHPDocBlock); break; } } // return(state); }