/// <summary> /// Evaluates a string representing a condition from a "condition" attribute. /// If the condition is a malformed string, it throws an InvalidProjectFileException. /// This method uses cached expression trees to avoid generating them from scratch every time it's called. /// This method is thread safe and is called from engine and task execution module threads /// </summary> internal static bool EvaluateCondition <P, I> ( string condition, ParserOptions options, Expander <P, I> expander, ExpanderOptions expanderOptions, string evaluationDirectory, ElementLocation elementLocation, ILoggingService loggingServices, BuildEventContext buildEventContext, IFileSystem fileSystem, ProjectRootElementCacheBase projectRootElementCache = null) where P : class, IProperty where I : class, IItem { return(EvaluateConditionCollectingConditionedProperties( condition, options, expander, expanderOptions, null /* do not collect conditioned properties */, evaluationDirectory, elementLocation, loggingServices, buildEventContext, fileSystem, projectRootElementCache)); }
/// <summary> /// Special cased constructor. Where we are only going to expand properties and metadata, /// it's a waste of memory to use a lookup. Just use the property group. /// PERF: This improves the EvaluateAllItemDefinitions codepath. /// </summary> internal Expander(BuildPropertyGroup properties, string implicitMetadataItemType, Dictionary<string, string> unqualifiedItemMetadata) { this.options = ExpanderOptions.ExpandPropertiesAndMetadata; this.properties = properties; this.itemMetadata = unqualifiedItemMetadata; this.implicitMetadataItemType = implicitMetadataItemType; }
private static bool EvaluateCondition( string condition, ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions, Expander <P, I> expander, LazyItemEvaluator <P, I, M, D> lazyEvaluator ) { if (condition?.Length == 0) { return(true); } MSBuildEventSource.Log.EvaluateConditionStart(condition); using (lazyEvaluator._evaluationProfiler.TrackCondition(element.ConditionLocation, condition)) { bool result = ConditionEvaluator.EvaluateCondition ( condition, parserOptions, expander, expanderOptions, GetCurrentDirectoryForConditionEvaluation(element, lazyEvaluator), element.ConditionLocation, lazyEvaluator._loggingContext.LoggingService, lazyEvaluator._loggingContext.BuildEventContext, lazyEvaluator.FileSystem ); MSBuildEventSource.Log.EvaluateConditionStop(condition, result); return(result); } }
internal ConditionEvaluationState ( string condition, Expander <P, I> expander, ExpanderOptions expanderOptions, Dictionary <string, List <string> > conditionedPropertiesInProject, string evaluationDirectory, ElementLocation elementLocation, IFileSystem fileSystem, ProjectRootElementCacheBase projectRootElementCache = null ) { ErrorUtilities.VerifyThrowArgumentNull(condition, "condition"); ErrorUtilities.VerifyThrowArgumentNull(expander, "expander"); ErrorUtilities.VerifyThrowArgumentNull(evaluationDirectory, "evaluationDirectory"); ErrorUtilities.VerifyThrowArgumentNull(elementLocation, "elementLocation"); Condition = condition; _expander = expander; _expanderOptions = expanderOptions; ConditionedPropertiesInProject = conditionedPropertiesInProject; // May be null EvaluationDirectory = evaluationDirectory; ElementLocation = elementLocation; LoadedProjectsCache = projectRootElementCache; FileSystem = fileSystem; }
private static bool EvaluateCondition( ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions, Expander <P, I> expander, LazyItemEvaluator <P, I, M, D> lazyEvaluator ) { return(EvaluateCondition(element.Condition, element, expanderOptions, parserOptions, expander, lazyEvaluator)); }
private void ProcessMetadataElements(ProjectItemElement itemElement, OperationBuilderWithMetadata operationBuilder) { if (itemElement.HasMetadata) { operationBuilder.Metadata.AddRange(itemElement.Metadata); var values = new List <string>(itemElement.Metadata.Count * 2); // Expand properties here, because a property may have a value which is an item reference (ie "@(Bar)"), and // if so we need to add the right item reference. foreach (var metadatumElement in itemElement.Metadata) { // Since we're just attempting to expand properties in order to find referenced items and not expanding metadata, // unexpected errors may occur when evaluating property functions on unexpanded metadata. Just ignore them if that happens. // See: https://github.com/Microsoft/msbuild/issues/3460 const ExpanderOptions expanderOptions = ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError; var valueWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped( metadatumElement.Value, expanderOptions, metadatumElement.Location); var conditionWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped( metadatumElement.Condition, expanderOptions, metadatumElement.ConditionLocation); values.Add(valueWithPropertiesExpanded); values.Add(conditionWithPropertiesExpanded); } var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values); if (itemsAndMetadataFound.Items != null) { foreach (var itemType in itemsAndMetadataFound.Items) { AddReferencedItemList(itemType, operationBuilder.ReferencedItemLists); } } } }
static bool EvaluateCondition(ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions, Expander <P, I> expander, LazyItemEvaluator <P, I, M, D> lazyEvaluator) { if (element.Condition.Length == 0) { return(true); } bool result = ConditionEvaluator.EvaluateCondition ( element.Condition, parserOptions, expander, expanderOptions, GetCurrentDirectoryForConditionEvaluation(element, lazyEvaluator), element.ConditionLocation, lazyEvaluator._loggingService, lazyEvaluator._buildEventContext ); return(result); }
private static IEnumerable <string> GetExpandedMetadataValuesAndConditions(ICollection <ProjectMetadataElement> metadata, Expander <P, I> expander) { // Since we're just attempting to expand properties in order to find referenced items and not expanding metadata, // unexpected errors may occur when evaluating property functions on unexpanded metadata. Just ignore them if that happens. // See: https://github.com/dotnet/msbuild/issues/3460 const ExpanderOptions expanderOptions = ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError; // Expand properties here, because a property may have a value which is an item reference (ie "@(Bar)"), and // if so we need to add the right item reference. foreach (var metadatumElement in metadata) { yield return(expander.ExpandIntoStringLeaveEscaped( metadatumElement.Value, expanderOptions, metadatumElement.Location)); yield return(expander.ExpandIntoStringLeaveEscaped( metadatumElement.Condition, expanderOptions, metadatumElement.ConditionLocation)); } }
public bool EvaluateConditionWithCurrentState(ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions) { return(EvaluateCondition(element, expanderOptions, parserOptions, _expander, this)); }
protected void DecorateItemsWithMetadata(ImmutableList <I> items, ImmutableList <ProjectMetadataElement> metadata) { if (metadata.Count > 0) { //////////////////////////////////////////////////// // UNDONE: Implement batching here. // // We want to allow built-in metadata in metadata values here. // For example, so that an Idl file can specify that its Tlb output should be named %(Filename).tlb. // // In other words, we want batching. However, we won't need to go to the trouble of using the regular batching code! // That's because that code is all about grouping into buckets of similar items. In this context, we're not // invoking a task, and it's fine to process each item individually, which will always give the correct results. // // For the CTP, to make the minimal change, we will not do this quite correctly. // // We will do this: // -- check whether any metadata values or their conditions contain any bare built-in metadata expressions, // or whether they contain any custom metadata && the Include involved an @(itemlist) expression. // -- if either case is found, we go ahead and evaluate all the metadata separately for each item. // -- otherwise we can do the old thing (evaluating all metadata once then applying to all items) // // This algorithm gives the correct results except when: // -- batchable expressions exist on the include, exclude, or condition on the item element itself // // It means that 99% of cases still go through the old code, which is best for the CTP. // When we ultimately implement this correctly, we should make sure we optimize for the case of very many items // and little metadata, none of which varies between items. // Do not expand properties as they have been already expanded by the lazy evaluator upon item operation construction. // Prior to lazy evaluation ExpanderOptions.ExpandAll was used. const ExpanderOptions metadataExpansionOptions = ExpanderOptions.ExpandAll; List <string> values = new List <string>(metadata.Count * 2); foreach (var metadataElement in metadata) { values.Add(metadataElement.Value); values.Add(metadataElement.Condition); } ItemsAndMetadataPair itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values); bool needToProcessItemsIndividually = false; if (itemsAndMetadataFound.Metadata != null && itemsAndMetadataFound.Metadata.Values.Count > 0) { // If there is bare metadata of any kind, and the Include involved an item list, we should // run items individually, as even non-built-in metadata might differ between items if (_referencedItemLists.Count >= 0) { needToProcessItemsIndividually = true; } else { // If there is bare built-in metadata, we must always run items individually, as that almost // always differs between items. // UNDONE: When batching is implemented for real, we need to make sure that // item definition metadata is included in all metadata operations during evaluation if (itemsAndMetadataFound.Metadata.Values.Count > 0) { needToProcessItemsIndividually = true; } } } if (needToProcessItemsIndividually) { foreach (I item in items) { _expander.Metadata = item; foreach (var metadataElement in metadata) { #if FEATURE_MSBUILD_DEBUGGER //if (DebuggerManager.DebuggingEnabled) //{ // DebuggerManager.PulseState(metadataElementElement.Location, _itemPassLocals); //} #endif if (!EvaluateCondition(metadataElement.Condition, metadataElement, metadataExpansionOptions, ParserOptions.AllowAll, _expander, _lazyEvaluator)) { continue; } string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadataElement.Value, metadataExpansionOptions, metadataElement.Location); item.SetMetadata(metadataElement, FileUtilities.MaybeAdjustFilePath(evaluatedValue, metadataElement.ContainingProject.DirectoryPath)); } } // End of legal area for metadata expressions. _expander.Metadata = null; } // End of pseudo batching //////////////////////////////////////////////////// // Start of old code else { // Metadata expressions are allowed here. // Temporarily gather and expand these in a table so they can reference other metadata elements above. EvaluatorMetadataTable metadataTable = new EvaluatorMetadataTable(_itemType); _expander.Metadata = metadataTable; // Also keep a list of everything so we can get the predecessor objects correct. List <Pair <ProjectMetadataElement, string> > metadataList = new List <Pair <ProjectMetadataElement, string> >(metadata.Count); foreach (var metadataElement in metadata) { // Because of the checking above, it should be safe to expand metadata in conditions; the condition // will be true for either all the items or none if (!EvaluateCondition(metadataElement.Condition, metadataElement, metadataExpansionOptions, ParserOptions.AllowAll, _expander, _lazyEvaluator)) { continue; } #if FEATURE_MSBUILD_DEBUGGER //if (DebuggerManager.DebuggingEnabled) //{ // DebuggerManager.PulseState(metadataElementElement.Location, _itemPassLocals); //} #endif string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadataElement.Value, metadataExpansionOptions, metadataElement.Location); evaluatedValue = FileUtilities.MaybeAdjustFilePath(evaluatedValue, metadataElement.ContainingProject.DirectoryPath); metadataTable.SetValue(metadataElement, evaluatedValue); metadataList.Add(new Pair <ProjectMetadataElement, string>(metadataElement, evaluatedValue)); } // Apply those metadata to each item // Note that several items could share the same metadata objects // Set all the items at once to make a potential copy-on-write optimization possible. // This is valuable in the case where one item element evaluates to // many items (either by semicolon or wildcards) // and that item also has the same piece/s of metadata for each item. _itemFactory.SetMetadata(metadataList, items); // End of legal area for metadata expressions. _expander.Metadata = null; } } }
/// <summary> /// Execute the function on the given instance /// </summary> public object Execute(Expander expander, object objectInstance, BuildPropertyGroup properties, ExpanderOptions options) { object functionResult = String.Empty; object[] args = null; try { // If there is no object instance, then the method invocation will be a static if (objectInstance == null) { // Check that the function that we're going to call is valid to call if (!IsStaticMethodAvailable(ObjectType, name)) { ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionMethodUnavailable", name, ObjectType.FullName); } bindingFlags |= BindingFlags.Static; // For our intrinsic function we need to support calling of internal methods // since we don't want them to be public if (objectType == typeof(Microsoft.Build.BuildEngine.IntrinsicFunctions)) { bindingFlags |= BindingFlags.NonPublic; } } else { bindingFlags |= BindingFlags.Instance; } // We have a methodinfo match, need to plug in the arguments args = new object[arguments.Length]; // Assemble our arguments ready for passing to our method for (int n = 0; n < arguments.Length; n++) { object argument = expander.ExpandPropertiesLeaveTypedAndEscaped(this.arguments[n], null); string argumentValue = argument as string; if (argumentValue != null) { // remove our 'quotes' from the escaped string, leaving escaped quotes intact args[n] = EscapingUtilities.UnescapeAll(argumentValue.Trim('`', '"', '\'')); } else { args[n] = argument; } } // Handle special cases where the object type needs to affect the choice of method // The default binder and method invoke, often chooses the incorrect Equals and CompareTo and // fails the comparison, because what we have on the right is generally a string. // This special casing is to realize that its a comparison that is taking place and handle the // argument type coercion accordingly; effectively pre-preparing the argument type so // that it matches the left hand side ready for the default binder�s method invoke. if (objectInstance != null && args.Length == 1 && (String.Equals("Equals", this.name, StringComparison.OrdinalIgnoreCase) || String.Equals("CompareTo", this.name, StringComparison.OrdinalIgnoreCase))) { // change the type of the final unescaped string into the destination args[0] = Convert.ChangeType(args[0], objectInstance.GetType(), CultureInfo.InvariantCulture); } // If we've been asked for and instance to be constructed, then we // need to locate an appropriate constructor and invoke it if (String.Equals("new", this.name, StringComparison.OrdinalIgnoreCase)) { functionResult = LateBindExecute(null /* no previous exception */, BindingFlags.Public | BindingFlags.Instance, null /* no instance for a constructor */, args, true /* is constructor */); } else { // Execute the function given converted arguments // The only exception that we should catch to try a late bind here is missing method // otherwise there is the potential of running a function twice! try { // First use InvokeMember using the standard binder - this will match and coerce as needed functionResult = objectType.InvokeMember(this.name, bindingFlags, Type.DefaultBinder, objectInstance, args, CultureInfo.InvariantCulture); } catch (MissingMethodException ex) // Don't catch and retry on any other exception { // If we're invoking a method, then there are deeper attempts that // can be made to invoke the method if ((bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod) { // The standard binder failed, so do our best to coerce types into the arguments for the function // This may happen if the types need coercion, but it may also happen if the object represents a type that contains open type parameters, that is, ContainsGenericParameters returns true. functionResult = LateBindExecute(ex, bindingFlags, objectInstance, args, false /* is not constructor */); } else { // We were asked to get a property or field, and we found that we cannot // locate it. Since there is no further argument coersion possible // we'll throw right now. throw; } } } // If the result of the function call is a string, then we need to escape the result // so that we maintain the "engine contains escaped data" state. // The exception is that the user is explicitly calling MSBuild::Unescape or MSBuild::Escape if (functionResult is string && !String.Equals("Unescape", name, StringComparison.OrdinalIgnoreCase) && !String.Equals("Escape", name, StringComparison.OrdinalIgnoreCase)) { functionResult = EscapingUtilities.Escape((string)functionResult); } // There's nothing left to deal within the function expression, return the result from the execution if (String.IsNullOrEmpty(remainder)) { return functionResult; } // Recursively expand the remaining property body after execution return expander.ExpandPropertyBody(remainder, functionResult, properties, options); } // Exceptions coming from the actual function called are wrapped in a TargetInvocationException catch (TargetInvocationException ex) { // We ended up with something other than a function expression string partiallyEvaluated = GenerateStringOfMethodExecuted(expression, objectInstance, name, args); ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.InnerException.Message.Replace("\r\n", " ")); return null; } // Any other exception was thrown by trying to call it catch (Exception ex) { if (ExceptionHandling.NotExpectedFunctionException(ex)) { throw; } // We ended up with something other than a function expression string partiallyEvaluated = GenerateStringOfMethodExecuted(expression, objectInstance, name, args); ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.Message); return null; } }
// Used by BuildItemGroup.Evaluate internal Expander(BuildPropertyGroup properties, Hashtable items, ExpanderOptions options) : this(new ReadOnlyLookup(items, properties), null, options) { }
/// <summary> /// Expand the body of the property, including any functions that it may contain /// </summary> private object ExpandPropertyBody(string propertyBody, object propertyValue, BuildPropertyGroup properties, ExpanderOptions options) { Function function = null; string propertyName = propertyBody; // Trim the body for comatibility reasons: // Spaces are not valid property name chars, but $( Foo ) is allowed, and should always expand to BLANK. // Do a very fast check for leading and trailing whitespace, and trim them from the property body if we have any. // But we will do a property name lookup on the propertyName that we held onto. if (Char.IsWhiteSpace(propertyBody[0]) || Char.IsWhiteSpace(propertyBody[propertyBody.Length - 1])) { propertyBody = propertyBody.Trim(); } // If we don't have a clean propertybody then we'll do deeper checks to see // if what we have is a function if (!IsValidPropertyName(propertyBody)) { if (propertyBody.Contains(".") || propertyBody[0] == '[') { // This is a function function = Function.ExtractPropertyFunction(propertyBody, propertyValue); // We may not have been able to parse out a function if (function != null) { // We will have either extracted the actual property name // or realised that there is none (static function), and have recorded a null propertyName = function.ExpressionRootName; } else { // In the event that we have been handed an unrecognized property body, throw // an invalid function property exception. ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionPropertyExpression", propertyBody, String.Empty); return null; } } else { // In the event that we have been handed an unrecognized property body, throw // an invalid function property exception. ProjectErrorUtilities.ThrowInvalidProject(null, "InvalidFunctionPropertyExpression", propertyBody, String.Empty); return null; } } // Find the property value in our property collection. This // will automatically return "" (empty string) if the property // doesn't exist in the collection, and we're not executing a static function if (!String.IsNullOrEmpty(propertyName)) { BuildProperty property; if (lookup != null) { // We're using a lookup property = lookup.GetProperty(propertyName); } else { // We're only using a property group property = properties[propertyName]; } if (property == null) { propertyValue = String.Empty; } else { propertyValue = property.FinalValueEscaped; } } if (function != null) { // Because of the rich expansion capabilities of MSBuild, we need to keep things // as strings, since property expansion & string embedding can happen anywhere // propertyValue can be null here, when we're invoking a static function propertyValue = function.Execute(this, propertyValue, properties, options); } return propertyValue; }
/// <summary> /// Special cased constructor. Where we are only going to expand properties, /// it's a waste of memory to use a lookup. Just use the property group. /// PERF: This improves the EvaluateAllPropertyGroups codepath. /// </summary> internal Expander(BuildPropertyGroup properties) { this.options = ExpanderOptions.ExpandProperties; this.properties = properties; }
/// <summary> /// Create an expander from another expander, but with different /// options /// </summary> internal Expander(Expander expander, ExpanderOptions options) : this(expander.lookup, expander.itemMetadata, options) { }
/// <summary> /// Evaluates a string representing a condition from a "condition" attribute. /// If the condition is a malformed string, it throws an InvalidProjectFileException. /// This method uses cached expression trees to avoid generating them from scratch every time it's called. /// This method is thread safe and is called from engine and task execution module threads /// Logging service may be null. /// </summary> internal static bool EvaluateConditionCollectingConditionedProperties <P, I> ( string condition, ParserOptions options, Expander <P, I> expander, ExpanderOptions expanderOptions, Dictionary <string, List <string> > conditionedPropertiesTable, string evaluationDirectory, ElementLocation elementLocation, ILoggingService loggingServices, BuildEventContext buildEventContext, IFileSystem fileSystem, ProjectRootElementCacheBase projectRootElementCache = null ) where P : class, IProperty where I : class, IItem { ErrorUtilities.VerifyThrowArgumentNull(condition, "condition"); ErrorUtilities.VerifyThrowArgumentNull(expander, "expander"); ErrorUtilities.VerifyThrowArgumentLength(evaluationDirectory, "evaluationDirectory"); ErrorUtilities.VerifyThrowArgumentNull(buildEventContext, "buildEventContext"); // An empty condition is equivalent to a "true" condition. if (condition.Length == 0) { return(true); } // If the condition wasn't empty, there must be a location for it ErrorUtilities.VerifyThrowArgumentNull(elementLocation, "elementLocation"); // Get the expression tree cache for the current parsing options. var cachedExpressionTreesForCurrentOptions = s_cachedExpressionTrees.GetOrAdd( (int)options, _ => new ExpressionTreeForCurrentOptionsWithSize(new ConcurrentDictionary <string, ConcurrentStack <GenericExpressionNode> >(StringComparer.Ordinal))); cachedExpressionTreesForCurrentOptions = FlushCacheIfLargerThanThreshold(options, cachedExpressionTreesForCurrentOptions); // Get the pool of expressions for this condition. var expressionPool = cachedExpressionTreesForCurrentOptions.GetOrAdd(condition, _ => new ConcurrentStack <GenericExpressionNode>()); // Try and see if there's an available expression tree in the pool. // If not, parse a new expression tree and add it back to the pool. if (!expressionPool.TryPop(out var parsedExpression)) { var conditionParser = new Parser(); #region REMOVE_COMPAT_WARNING conditionParser.LoggingServices = loggingServices; conditionParser.LogBuildEventContext = buildEventContext; #endregion parsedExpression = conditionParser.Parse(condition, options, elementLocation); } bool result; var state = new ConditionEvaluationState <P, I>( condition, expander, expanderOptions, conditionedPropertiesTable, evaluationDirectory, elementLocation, fileSystem, projectRootElementCache); // We are evaluating this expression now and it can cache some state for the duration, // so we don't want multiple threads working on the same expression lock (parsedExpression) { try { result = parsedExpression.Evaluate(state); } finally { parsedExpression.ResetState(); if (!s_disableExpressionCaching) { // Finished using the expression tree. Add it back to the pool so other threads can use it. expressionPool.Push(parsedExpression); } } } return(result); }
/// <summary> /// Evaluates a string representing a condition from a "condition" attribute. /// If the condition is a malformed string, it throws an InvalidProjectFileException. /// This method uses cached expression trees to avoid generating them from scratch every time it's called. /// This method is thread safe and is called from engine and task execution module threads /// Logging service may be null. /// </summary> internal static bool EvaluateConditionCollectingConditionedProperties <P, I> ( string condition, ParserOptions options, Expander <P, I> expander, ExpanderOptions expanderOptions, Dictionary <string, List <string> > conditionedPropertiesTable, string evaluationDirectory, ElementLocation elementLocation, ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache = null ) where P : class, IProperty where I : class, IItem { ErrorUtilities.VerifyThrowArgumentNull(condition, "condition"); ErrorUtilities.VerifyThrowArgumentNull(expander, "expander"); ErrorUtilities.VerifyThrowArgumentLength(evaluationDirectory, "evaluationDirectory"); ErrorUtilities.VerifyThrowArgumentNull(buildEventContext, "buildEventContext"); // An empty condition is equivalent to a "true" condition. if (condition.Length == 0) { return(true); } // If the condition wasn't empty, there must be a location for it ErrorUtilities.VerifyThrowArgumentNull(elementLocation, "elementLocation"); Hashtable cachedExpressionTreesForCurrentOptions = s_cachedExpressionTrees[(int)options]; // We only need to lock on writes to the table if (cachedExpressionTreesForCurrentOptions == null) { // Given property functions, casing in conditional expressions isn't necessarily ignored. cachedExpressionTreesForCurrentOptions = new Hashtable(50, StringComparer.Ordinal); lock (s_cachedExpressionTrees) { s_cachedExpressionTrees[(int)options] = cachedExpressionTreesForCurrentOptions; } } // VS stress tests could fill up this cache without end, for example if they use // random configuration names - those appear in conditional expressions. // So if we hit a limit that we should never hit in normal circumstances in VS, // and rarely, periodically hit in normal circumstances in large tree builds, // just clear out the cache. It can start repopulating again. Some kind of prioritized // aging isn't worth it: although the hit rate of these caches is excellent (nearly 100%) // the cost of reparsing expressions should the cache be cleared is not particularly large. // Loading Australian Government in VS, there are 3 of these tables, two with about 50 // entries and one with about 650 entries. So 3000 seems large enough. if (cachedExpressionTreesForCurrentOptions.Count > 3000) // threadsafe { lock (cachedExpressionTreesForCurrentOptions) { cachedExpressionTreesForCurrentOptions.Clear(); } } // Try and see if we have an expression tree for this condition already GenericExpressionNode parsedExpression = (GenericExpressionNode)cachedExpressionTreesForCurrentOptions[condition]; if (parsedExpression == null) { Parser conditionParser = new Parser(); #region REMOVE_COMPAT_WARNING conditionParser.LoggingServices = loggingServices; conditionParser.LogBuildEventContext = buildEventContext; #endregion parsedExpression = conditionParser.Parse(condition, options, elementLocation); // It's possible two threads will add a different tree to the same entry in the hashtable, // but it should be rare and it's not a problem - the previous entry will be thrown away. // We could ensure no dupes with double check locking but it's not really necessary here. // Also, we don't want to lock on every read. lock (cachedExpressionTreesForCurrentOptions) { if (!s_disableExpressionCaching) { cachedExpressionTreesForCurrentOptions[condition] = parsedExpression; } } } bool result; ConditionEvaluationState <P, I> state = new ConditionEvaluationState <P, I>(condition, expander, expanderOptions, conditionedPropertiesTable, evaluationDirectory, elementLocation, projectRootElementCache); // We are evaluating this expression now and it can cache some state for the duration, // so we don't want multiple threads working on the same expression lock (parsedExpression) { try { result = parsedExpression.Evaluate(state); } finally { parsedExpression.ResetState(); } } return(result); }
// Used by unit tests internal Expander(ReadOnlyLookup lookup, Dictionary<string, string> itemMetadata, ExpanderOptions options) { ErrorUtilities.VerifyThrow(options != ExpanderOptions.Invalid, "Must specify options"); this.lookup = lookup; this.itemMetadata = itemMetadata; this.options = options; }