1 //------------------------------------------------------------------------------
2 // <copyright file="CoordinatorScratchpad.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 using System.Collections.Generic;
10 using System.Data.Objects.ELinq;
11 using System.Data.Query.InternalTrees;
12 using System.Diagnostics;
14 using System.Linq.Expressions;
15 using System.Reflection;
16 using System.Runtime.CompilerServices;
17 using System.Security;
18 using System.Security.Permissions;
20 namespace System.Data.Common.Internal.Materialization
23 /// Used in the Translator to aggregate information about a (nested) reader
24 /// coordinator. After the translator visits the columnMaps, it will compile
25 /// the coordinator(s) which produces an immutable CoordinatorFactory that
26 /// can be shared amongst many query instances.
28 internal class CoordinatorScratchpad
32 private readonly Type _elementType;
33 private CoordinatorScratchpad _parent;
34 private readonly List<CoordinatorScratchpad> _nestedCoordinatorScratchpads;
36 /// Map from original expressions to expressions with detailed error handling.
38 private readonly Dictionary<Expression, Expression> _expressionWithErrorHandlingMap;
40 /// Expressions that should be precompiled (i.e. reduced to constants in
41 /// compiled delegates.
43 private readonly HashSet<LambdaExpression> _inlineDelegates;
49 internal CoordinatorScratchpad(Type elementType)
51 _elementType = elementType;
52 _nestedCoordinatorScratchpads = new List<CoordinatorScratchpad>();
53 _expressionWithErrorHandlingMap = new Dictionary<Expression, Expression>();
54 _inlineDelegates = new HashSet<LambdaExpression>();
59 #region "public" surface area
62 /// For nested collections, returns the parent coordinator.
64 internal CoordinatorScratchpad Parent
66 get { return _parent; }
70 /// Gets or sets an Expression setting key values (these keys are used
71 /// to determine when a collection has entered a new chapter) from the
72 /// underlying store data reader.
74 internal Expression SetKeys { get; set; }
77 /// Gets or sets an Expression returning 'true' when the key values for
78 /// the current nested result (see SetKeys) are equal to the current key
79 /// values on the underlying data reader.
81 internal Expression CheckKeys { get; set; }
84 /// Gets or sets an expression returning 'true' if the current row in
85 /// the underlying data reader contains an element of the collection.
87 internal Expression HasData { get; set; }
90 /// Gets or sets an Expression yielding an element of the current collection
91 /// given values in the underlying data reader.
93 internal Expression Element { get; set; }
96 /// Gets or sets an Expression initializing the collection storing results from this coordinator.
98 internal Expression InitializeCollection { get; set; }
101 /// Indicates which Shaper.State slot is home for this collection's coordinator.
102 /// Used by Parent to pull out nested collection aggregators/streamers.
104 internal int StateSlotNumber { get; set; }
107 /// Gets or sets the depth of the current coordinator. A root collection has depth 0.
109 internal int Depth { get; set; }
112 /// List of all record types that we can return at this level in the query.
114 private List<RecordStateScratchpad> _recordStateScratchpads;
117 /// Allows sub-expressions to register an 'interest' in exceptions thrown when reading elements
118 /// for this coordinator. When an exception is thrown, we rerun the delegate using the slower
119 /// but more error-friendly versions of expressions (e.g. reader.GetValue + type check instead
120 /// of reader.GetInt32())
122 /// <param name="expression">The lean and mean raw version of the expression</param>
123 /// <param name="expressionWithErrorHandling">The slower version of the same expression with better
124 /// error handling</param>
125 internal void AddExpressionWithErrorHandling(Expression expression, Expression expressionWithErrorHandling)
127 _expressionWithErrorHandlingMap[expression] = expressionWithErrorHandling;
131 /// Registers a lambda expression for pre-compilation (i.e. reduction to a constant expression)
132 /// within materialization expression. Otherwise, the expression will be compiled every time
133 /// the enclosing delegate is invoked.
135 /// <param name="expression">Lambda expression to register.</param>
136 internal void AddInlineDelegate(LambdaExpression expression)
138 _inlineDelegates.Add(expression);
142 /// Registers a coordinator for a nested collection contained in elements of this collection.
144 internal void AddNestedCoordinator(CoordinatorScratchpad nested)
146 Debug.Assert(nested.Depth == this.Depth + 1, "can only nest depth + 1");
147 nested._parent = this;
148 _nestedCoordinatorScratchpads.Add(nested);
152 /// Use the information stored on the scratchpad to compile an immutable factory used
153 /// to construct the coordinators used at runtime when materializing results.
155 [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
156 internal CoordinatorFactory Compile()
158 RecordStateFactory[] recordStateFactories;
159 if (null != _recordStateScratchpads)
161 recordStateFactories = new RecordStateFactory[_recordStateScratchpads.Count];
162 for (int i = 0; i < recordStateFactories.Length; i++)
164 recordStateFactories[i] = _recordStateScratchpads[i].Compile();
169 recordStateFactories = new RecordStateFactory[0];
172 CoordinatorFactory[] nestedCoordinators = new CoordinatorFactory[_nestedCoordinatorScratchpads.Count];
173 for (int i = 0; i < nestedCoordinators.Length; i++)
175 nestedCoordinators[i] = _nestedCoordinatorScratchpads[i].Compile();
178 // compile inline delegates
179 ReplacementExpressionVisitor replacementVisitor = new ReplacementExpressionVisitor(null, this._inlineDelegates);
180 Expression element = new SecurityBoundaryExpressionVisitor().Visit(replacementVisitor.Visit(this.Element));
182 // substitute expressions that have error handlers into a new expression (used
183 // when a more detailed exception message is needed)
184 replacementVisitor = new ReplacementExpressionVisitor(this._expressionWithErrorHandlingMap, this._inlineDelegates);
185 Expression elementWithErrorHandling = new SecurityBoundaryExpressionVisitor().Visit(replacementVisitor.Visit(this.Element));
187 CoordinatorFactory result = (CoordinatorFactory)Activator.CreateInstance(typeof(CoordinatorFactory<>).MakeGenericType(_elementType), new object[] {
189 this.StateSlotNumber,
195 elementWithErrorHandling,
196 this.InitializeCollection,
203 /// Allocates a new RecordStateScratchpad and adds it to the list of the ones we're
204 /// responsible for; will create the list if it hasn't alread been created.
206 internal RecordStateScratchpad CreateRecordStateScratchpad()
208 RecordStateScratchpad recordStateScratchpad = new RecordStateScratchpad();
210 if (null == _recordStateScratchpads)
212 _recordStateScratchpads = new List<RecordStateScratchpad>();
214 _recordStateScratchpads.Add(recordStateScratchpad);
215 return recordStateScratchpad;
222 /// Visitor supporting (non-recursive) replacement of LINQ sub-expressions and
223 /// compilation of inline delegates.
225 private class ReplacementExpressionVisitor : EntityExpressionVisitor
227 // Map from original expressions to replacement expressions.
228 private readonly Dictionary<Expression, Expression> _replacementDictionary;
229 private readonly HashSet<LambdaExpression> _inlineDelegates;
231 internal ReplacementExpressionVisitor(Dictionary<Expression, Expression> replacementDictionary,
232 HashSet<LambdaExpression> inlineDelegates)
234 this._replacementDictionary = replacementDictionary;
235 this._inlineDelegates = inlineDelegates;
238 internal override Expression Visit(Expression expression)
240 if (null == expression)
247 // check to see if a substitution has been provided for this expression
248 Expression replacement;
249 if (null != this._replacementDictionary && this._replacementDictionary.TryGetValue(expression, out replacement))
251 // once a substitution is found, we stop walking the sub-expression and
252 // return immediately (since recursive replacement is not needed or wanted)
253 result = replacement;
257 // check if we need to precompile an inline delegate
258 bool preCompile = false;
259 LambdaExpression lambda = null;
261 if (expression.NodeType == ExpressionType.Lambda &&
262 null != _inlineDelegates)
264 lambda = (LambdaExpression)expression;
265 preCompile = _inlineDelegates.Contains(lambda);
270 // do replacement in the body of the lambda expression
271 Expression body = Visit(lambda.Body);
273 // compile to a delegate
274 result = Expression.Constant(Translator.Compile(body.Type, body));
278 result = base.Visit(expression);
287 /// Used to replace references to user expressions with compiled delegates
288 /// which represent those expressions.
291 /// The materialization delegate used to be one big function, which included
292 /// user-provided expressions in various places in the tree. Due to security reasons
293 /// (Dev11 311339), we need to separate this delegate into two pieces: trusted code,
294 /// run under a security assert, and untrusted code, run under the current AppDomain's
297 /// This visitor does that separation by compiling the untrusted code into delegates
298 /// and re-inserting them back into the expression tree. When the untrusted code is
299 /// run, it will run in another stack frame that does not have a security assert
300 /// associated with it; therefore, any attempt to take advantage of MemberAccess
301 /// reflection permissions will be blocked by the CLR.
303 /// The compiled user delegates accept two parameters, one of type DbDataReader
304 /// to speed up access to the current reader, and the other of type object[],
305 /// which contains all other values that they might require to correctly materialize an object. Most of these
306 /// objects require the <see cref="Shaper"/>, so they must be run inside of trusted code.
308 private sealed class SecurityBoundaryExpressionVisitor : EntityExpressionVisitor
310 private static readonly MethodInfo s_userMaterializationFuncInvokeMethod = typeof(Func<DbDataReader, object[], object>).GetMethod("Invoke");
311 private ParameterExpression _values = Expression.Parameter(typeof(object[]), "values");
312 private ParameterExpression _reader = Expression.Parameter(typeof(DbDataReader), "reader");
313 private List<Expression> _initializationArguments = new List<Expression>();
314 private int _userExpressionDepth;
317 /// Used to track the type of a constructor argument or member assignment
318 /// when it could be a special type we create (e.g., CompensatingCollection{T}
319 /// for collections and Grouping{K,V} for groups).
321 private Type _userArgumentType;
323 internal override Expression Visit(Expression exp)
330 var nex = exp as NewExpression;
331 if (nex != null && _userExpressionDepth >= 1)
333 // We are creating an internal type like CompensatingCollection<T> or Grouping<K, V>
334 // and at this particular point we are sure that the user isn't creating these
335 // since this.userArgumentType is not null.
336 if (_userArgumentType != null && !nex.Type.IsPublic && nex.Type.Assembly == typeof(SecurityBoundaryExpressionVisitor).Assembly)
338 return this.CreateInitializationArgumentReplacement(nex, _userArgumentType);
341 var constructorParameters = nex.Constructor.GetParameters();
342 var arguments = nex.Arguments;
343 var newArguments = new List<Expression>();
344 for (var i = 0; i < arguments.Count; ++i)
346 var argument = arguments[i];
348 // Visit this argument because it itself could be a user expression e.g.
349 // new { Argument = new SecureString { m_length = 32 } }
350 _userArgumentType = constructorParameters[i].ParameterType;
351 var visitedArgument = this.Visit(argument);
353 // If it hasn't changed, it's trusted code. (Untrusted code would have its
354 // Convert and MarkAsUserExpression expressions removed.)
355 if (visitedArgument == argument)
357 var convert = this.CreateInitializationArgumentReplacement(argument);
359 // Change the argument to access the values array.
360 newArguments.Add(convert);
364 newArguments.Add(visitedArgument);
368 nex = Expression.New(nex.Constructor, newArguments);
370 if (_userExpressionDepth == 1)
372 var userMaterializationFunc = Expression.Lambda<Func<DbDataReader, object[], object>>(nex, _reader, _values).Compile();
374 // Convert the constructor invocation into a func that runs without elevated permissions.
375 return Expression.Convert(
377 Expression.Constant(userMaterializationFunc),
378 s_userMaterializationFuncInvokeMethod,
379 Translator.Shaper_Reader,
380 Expression.NewArrayInit(typeof(object), _initializationArguments)),
387 return base.Visit(exp);
390 internal override Expression VisitConditional(ConditionalExpression c)
392 if (_userExpressionDepth >= 1 && _userArgumentType != null)
394 var test = c.Test as MethodCallExpression;
395 var ifFalse = c.IfFalse as MethodCallExpression;
397 // We can optimize the path that checks for DbNull and then
398 // reads a value directly off the reader or invokes another user expression.
399 if (test != null && test.Object != null
400 && typeof(DbDataReader).IsAssignableFrom(test.Object.Type)
401 && test.Method.Name == "IsDBNull")
403 if (ifFalse != null && (ifFalse.Object != null && typeof(DbDataReader).IsAssignableFrom(ifFalse.Object.Type) || IsUserExpressionMethod(ifFalse.Method)))
405 return base.VisitConditional(c);
409 // If there's something more complicated then we have to replace it all.
410 // We can't just replace the false expression because it may not be evaluated
411 // if the test returns true.
412 return this.CreateInitializationArgumentReplacement(c);
415 return base.VisitConditional(c);
418 internal override Expression VisitMemberAccess(MemberExpression m)
420 if (_userExpressionDepth >= 1)
422 // Sometimes we will add expressions inside of a user expression that is actually
423 // our code, but we need to rewrite it since it accesses the shaper's reader to check if a column is null.
424 // e.g. Select(x => new { Y = new Entity { Name = x.Name } })
425 // -> new f<>__AnonymousType`1(IIF($shaper.Reader.IsDbNull(0), null, new Entity { Name = $shaper.Reader.GetString(0) }))
426 if (typeof(DbDataReader).IsAssignableFrom(m.Type))
428 var shaper = m.Expression as ParameterExpression;
429 if (shaper != null && shaper == Translator.Shaper_Parameter)
436 return base.VisitMemberAccess(m);
439 internal override Expression VisitMemberInit(MemberInitExpression init)
441 if (_userExpressionDepth >= 1)
443 var newMemberInit = base.VisitMemberInit(init);
445 // Only compile into a delegate if this is the top-level user expression.
446 if (newMemberInit != init && _userExpressionDepth == 1)
448 var userMaterializationFunc = Expression.Lambda<Func<DbDataReader, object[], object>>(newMemberInit, _reader, _values).Compile();
450 // Convert the object initializer into a func that runs without elevated permissions.
451 return Expression.Convert(
453 Expression.Constant(userMaterializationFunc),
454 s_userMaterializationFuncInvokeMethod,
455 Translator.Shaper_Reader,
456 Expression.NewArrayInit(typeof(object), _initializationArguments)),
461 return newMemberInit;
465 return base.VisitMemberInit(init);
468 internal override MemberAssignment VisitMemberAssignment(MemberAssignment assignment)
470 if (_userExpressionDepth >= 1)
472 var fieldInfo = assignment.Member as FieldInfo;
473 var propertyInfo = assignment.Member as PropertyInfo;
474 if (fieldInfo != null)
476 _userArgumentType = fieldInfo.FieldType;
478 else if (propertyInfo != null)
480 _userArgumentType = propertyInfo.PropertyType;
484 return base.VisitMemberAssignment(assignment);
487 internal override Expression VisitMethodCall(MethodCallExpression m)
489 var method = m.Method;
490 if (IsUserExpressionMethod(method))
493 m.Arguments.Count == 1,
494 "m.Arguments.Count == 1",
495 "There should be one expression argument provided to the user expression marker.");
499 // Clear this type because we are about to process a user expression
500 _userArgumentType = null;
502 _userExpressionDepth++;
503 return this.Visit(m.Arguments[0]);
507 _userExpressionDepth--;
510 else if (_userExpressionDepth >= 1)
512 // If this method call is on a DbDataReader then we can replace it; otherwise,
513 // assume it's something on the shaper and extract the value into the values array.
514 if (m.Object != null && typeof(DbDataReader).IsAssignableFrom(m.Object.Type))
516 return base.VisitMethodCall(m);
519 return this.CreateInitializationArgumentReplacement(m);
522 return base.VisitMethodCall(m);
525 private Expression CreateInitializationArgumentReplacement(Expression original)
527 return this.CreateInitializationArgumentReplacement(original, original.Type);
530 private Expression CreateInitializationArgumentReplacement(Expression original, Type expressionType)
532 _initializationArguments.Add(Expression.Convert(original, typeof(object)));
534 return Expression.Convert(
535 Expression.MakeBinary(ExpressionType.ArrayIndex, _values, Expression.Constant(_initializationArguments.Count - 1)),
539 private static bool IsUserExpressionMethod(MethodInfo method)
541 return method.IsGenericMethod && method.GetGenericMethodDefinition() == InitializerMetadata.UserExpressionMarker;