commit 3a1793da2a14cae75ebe0d32d0cc52a5c1663631 Author: Microsoft GitHub User Date: Thu Apr 21 19:17:27 2016 -0700 Initial commit This is a subset of: https://cciast.codeplex.com/ @ 0699625623992943b12f66eefedc05e664fc78b6 https://ccimetadata.codeplex.com/ @ c1d8a9f6b9eb4aa0c3bb25097246afca8a02ebbc Tests and samples are excluded for now to prevent large binaries from being rooted by the master branch. The larger codeplex history is disjoint from the GitHub master branch but can still be seen on GitHub in the codeplex/cci{ast,metadata,samples} branches. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..87a3690 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,67 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain + +# Force bash scripts to always use lf line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.sh text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f8a120 --- /dev/null +++ b/.gitignore @@ -0,0 +1,260 @@ +syntax: glob + +### VisualStudio ### + +# Tool Runtime Dir +/[Tt]ools/ + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +msbuild.log + +# Cross building rootfs +cross/rootfs/ + +# Visual Studio 2015 +.vs/ + +# Visual Studio 2015 Pre-CTP6 +*.sln.ide +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +*.pubxml +*.publishproj + +# NuGet Packages +*.nuget.props +*.nuget.targets +*.nupkg +**/packages/* + +# NuGet package restore lockfiles +project.lock.json + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +*.metaproj +*.metaproj.tmp + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +### MonoDevelop ### + +*.pidb +*.userprefs + +### Windows ### + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Linux ### + +*~ + +# KDE directory preferences +.directory + +### OSX ### + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# vim temporary files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ diff --git a/.gitmirrorall b/.gitmirrorall new file mode 100644 index 0000000..9ee5c57 --- /dev/null +++ b/.gitmirrorall @@ -0,0 +1 @@ +This folder will be mirrored by the Git-TFS Mirror recursively. \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/AbstractDomains/Intervals.cs b/Metadata/Sources/AnalyisUtilities/AbstractDomains/Intervals.cs new file mode 100644 index 0000000..6a8c7db --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/AbstractDomains/Intervals.cs @@ -0,0 +1,1984 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; +using Microsoft.Cci.MutableCodeModel; +using System; + +namespace Microsoft.Cci.Analysis { + + /// + /// An inclusive numeric interval. + /// + public sealed class Interval { + + /// + /// An inclusive numeric interval from minus infinity to plus infinity. + /// + public Interval() { + this.lowerBound = Dummy.Constant; + this.upperBound = Dummy.Constant; + } + + /// + /// An inclusive numeric interval. If the bounds are floating point numbers, they are finite. The bounds must contain values of the same type. + /// + /// The inclusive lower bound of the interval. If this value is null or Dummy.Constant, it means the lower bound is unknown. + /// The inclusive upper bound of the interval. If this value is null or Dummy.Constant, it means the lower bound is unknown. + /// If true, an expression that results in this interval at compile time can never result in -1 at runtime. If false, + /// the value of this.ExcludesMinusOne will be determined by the given lower and upper bounds. + /// If true, an expression that results in this interval at compile time can never result in zero at runtime. If false, + /// the value of this.ExcludesZero will be determined by the given lower and upper bounds. + /// If true, an expression that results in this interval at compile time may result in division by zero at runtime. + /// If true, an expression that results in this integer interval at compile time may result in an overflow value at runtime. + /// If true, an expression that results in this integer interval at compile time may result in an underflow value at runtime. + public Interval(IMetadataConstant/*?*/ lowerBound, IMetadataConstant/*?*/ upperBound, bool excludesMinusOne = false, bool excludesZero = false, + bool includesDivisionByZero = false, bool includesOverflow = false, bool includesUnderflow = false) { + this.lowerBound = lowerBound??Dummy.Constant; + this.upperBound = upperBound??Dummy.Constant; + this.includesDivisionByZero = includesDivisionByZero; + this.includesOverflow = includesOverflow; + this.includesUnderflow = includesUnderflow; + this.excludesMinusOne = excludesMinusOne || !Evaluator.IsNegative(this.LowerBound) || Evaluator.IsSmallerThanMinusOne(this.UpperBound); + this.excludesZero = excludesZero || Evaluator.IsPositive(this.LowerBound) || Evaluator.IsNegative(this.UpperBound); + } + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.lowerBound != null); + Contract.Invariant(this.upperBound != null); + } + + /// + /// True if -1 is not a member of this interval. + /// + public bool ExcludesMinusOne { + get { return this.excludesMinusOne; } + } + bool excludesMinusOne; + + /// + /// True if 0 is not a member of this interval. + /// + public bool ExcludesZero { + get { return this.excludesZero; } + } + bool excludesZero; + + /// + /// If true, an expression that results in this interval at compile time may result in division by zero at runtime. + /// + public bool IncludesDivisionByZero { + get { return this.includesDivisionByZero; } + } + bool includesDivisionByZero; + + /// + /// If true, an expression that results in this integer interval at compile time may result in an overflow value at runtime. + /// If expression does not check for overflow then then lower and upper bounds of the + /// interval will be the minimum and maximum values of the appropriate integer type, respectively. + /// + public bool IncludesOverflow { + get { return this.includesOverflow; } + } + bool includesOverflow; + + /// + /// If true, an expression that results in this integer interval at compile time may result in an underflow value at runtime. + /// If expression does not check for overflow then then lower and upper bounds of the + /// interval will be the minimum and maximum values of the appropriate integer type, respectively. + /// + public bool IncludesUnderflow { + get { return this.includesUnderflow; } + } + bool includesUnderflow; + + /// + /// True if both the lower and upper bounds of the interval are known and finite and if the interval does not include division by zero or overflow values. + /// + public bool IsFinite { + get { + return !(this.IncludesDivisionByZero || this.IncludesOverflow || this.IncludesUnderflow); + } + } + + /// + /// True if the expression that results in this interval is known at compile time to always result in a runtime exception. + /// + public bool IsUnusable { + get { + return Evaluator.IsNumericallyGreaterThan(this.LowerBound, this.UpperBound); + } + } + + /// + /// The inclusive lower bound of the interval. + /// + public IMetadataConstant LowerBound { + get { + Contract.Ensures(Contract.Result() != null); + return this.lowerBound; + } + } + private IMetadataConstant lowerBound; + + /// + /// The inclusive upper bound of the interval. + /// + public IMetadataConstant UpperBound { + get { + Contract.Ensures(Contract.Result() != null); + return this.upperBound; + } + } + private IMetadataConstant upperBound; + + /// + /// Returns a shallow copy of this Interval. + /// + /// + public Interval Clone() { + return new Interval(this.LowerBound, this.UpperBound, excludesMinusOne: this.ExcludesMinusOne, excludesZero: this.ExcludesZero, includesDivisionByZero: this.IncludesDivisionByZero, + includesOverflow: this.IncludesOverflow, includesUnderflow: this.IncludesUnderflow); + } + + /// + /// True if the lower bound of this integer interval is greater than the minimum value of the given type. + /// + private bool ExcludesMinimumValue(ITypeReference type) { + Contract.Requires(type != null); + return Evaluator.IsNumericallyGreaterThan(this.LowerBound, Evaluator.GetMinValue(type)); + } + + /// + /// If the interval includes a single number return that number. + /// + public IMetadataConstant/*?*/ GetAsSingleton() { + if (this.IsFinite && Evaluator.IsNumericallyEqual(this.LowerBound, this.UpperBound)) + return this.lowerBound; + return null; + } + + /// + /// Returns the smallest interval that contains the intervals obtained for each of the variables whose values are joined + /// together by the given join. If no such interval exists, the result is null. + /// + private static Interval/*?*/ GetIntervalFor(Join join, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(join != null); + Contract.Requires(mappings != null); + + Contract.Assume(join.Join1 != null); + var interval = GetIntervalFor(join.Join1, join.Block1, mappings); + if (interval == null) return null; + + if (join.Join2 != null) { + var interval2 = GetIntervalFor(join.Join2, join.Block2, mappings); + if (interval2 == null) return null; + interval = interval.Join(interval2); + + if (join.OtherJoins != null) { + Contract.Assume(join.OtherBlocks != null && join.OtherBlocks.Count == join.OtherJoins.Count); + for (int i = 0, n = join.OtherJoins.Count; i < n; i++) { + Contract.Assume(join.OtherJoins[i] != null); + Contract.Assert(join.OtherBlocks.Count == n); + Contract.Assume(join.OtherBlocks[i] != null); + var intervalI = GetIntervalFor(join.OtherJoins[i], join.OtherBlocks[i], mappings); + if (intervalI == null) return null; + interval = interval.Join(intervalI); + } + } + } + return interval; + } + + /// + /// Returns the smallest interval that contains all possible runtime values the given variable may take on in while control is inside the given block + /// + private static Interval/*?*/ GetIntervalFor(INamedEntity variable, object block, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(variable != null); + Contract.Requires(mappings != null); + + var cv = mappings.GetCompileTimeConstantValueFor(variable); + if (cv != null) return new Interval(cv, cv); + var definingExpression = mappings.GetDefiningExpressionFor(variable); + if (definingExpression == null) return null; + Contract.Assume(block is AiBasicBlock); + var interval = mappings.GetIntervalFor(definingExpression, (AiBasicBlock)block); + return interval; + } + + /// + /// Returns an interval with includes this interval as well as the other interval. + /// + public Interval/*?*/ Join(Interval/*?*/ other) { + Contract.Ensures(other == null || Contract.Result() != null); + if (other == null) return this; + if (this.IsUnusable) return other; + if (other.IsUnusable) return this; + return new Interval( + Evaluator.Min(this.LowerBound, other.LowerBound), + Evaluator.Max(this.UpperBound, other.UpperBound), + excludesMinusOne: this.ExcludesMinusOne && other.ExcludesMinusOne, + excludesZero: this.ExcludesZero && other.ExcludesZero, + includesDivisionByZero: this.IncludesDivisionByZero || other.IncludesDivisionByZero, + includesOverflow: this.IncludesOverflow || other.IncludesOverflow, + includesUnderflow: this.IncludesUnderflow || other.IncludesUnderflow); + } + + /// + /// Narrows the given interval which is associated with the given expression, by applying the constraints that are known to hold inside the referring block. + /// If the joinBlock is not null, the interval is first narrowed with any constraints that apply to the transition from definingBlock to joinBlock. + /// + public static Interval Narrow(Instruction expression, Interval interval, AiBasicBlock referringBlock, AiBasicBlock joinBlock, + AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(interval != null); + Contract.Requires(expression != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + Contract.Ensures(Contract.Result() != null); + + if (joinBlock != null) { + var predecessors = joinBlock.Predecessors; + Contract.Assume(predecessors != null); + var n = predecessors.Count; + Contract.Assume(n == joinBlock.ConstraintsAtEntry.Count); + for (int i = 0; i < n; i++) { + if (predecessors[i] == definingBlock) { + if (joinBlock.ConstraintsAtEntry[i] == null) continue; + interval = Narrow(expression, interval.Clone(), joinBlock.ConstraintsAtEntry[i], referringBlock, mappings); + break; + } + } + if (joinBlock == referringBlock) return interval; + } + + Interval union = null; + foreach (var constraintList in referringBlock.ConstraintsAtEntry) { + if (constraintList == null) continue; + var narrowedInterval = Narrow(expression, interval.Clone(), constraintList, referringBlock, mappings); + if (union == null) + union = narrowedInterval; + else + union = union.Join(narrowedInterval); + } + return union??interval; + } + + /// + /// Narrows the given interval, which is associated with the given expression, by applying the given list of constraints that are known to hold inside the referring block + /// whenever it referes to the given expression. + /// + private static Interval Narrow(Instruction expression, Interval interval, List constraintList, AiBasicBlock referringBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(interval != null); + Contract.Requires(referringBlock != null); + Contract.Requires(constraintList != null); + Contract.Requires(mappings != null); + Contract.Ensures(Contract.Result() != null); + + foreach (var constraint in constraintList) { + if (constraint == null) continue; + Narrow(expression, interval, constraint, referringBlock, mappings); + } + return interval; + } + + /// + /// Narrows the given interval, which is associated with the given expression, by applying the given constraint that is known to hold inside the referring block + /// whenever it refers to the given expression. + /// + private static void Narrow(Instruction expression, Interval interval, Instruction constraint, AiBasicBlock block, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(interval != null); + Contract.Requires(block != null); + Contract.Requires(constraint != null); + Contract.Requires(mappings != null); + + var operand1 = constraint.Operand1 as Instruction; + if (operand1 == null) return; + var operand2 = constraint.Operand2 as Instruction; + if (operand2 == null) return; + if (operand1 == expression) { + var cv = mappings.GetCompileTimeConstantValueFor(operand2); + if (cv != null) { + Narrow(interval, constraint.Operation.OperationCode, cv); + } + } else if (operand2 == expression) { + var cv = mappings.GetCompileTimeConstantValueFor(operand1); + if (cv != null) { + Narrow2(cv, constraint.Operation.OperationCode, interval); + } + } else if (constraint.Operation.OperationCode == OperationCode.And) { + Narrow(expression, interval, operand1, block, mappings); + Narrow(expression, interval, operand2, block, mappings); + } else if (constraint.Operation.OperationCode == OperationCode.Or) { + var interval1 = interval.Clone(); + var interval2 = interval.Clone(); + Narrow(expression, interval1, operand1, block, mappings); + Narrow(expression, interval2, operand2, block, mappings); + var interval1join2 = interval1.Join(interval2); + Narrow(interval, OperationCode.Beq, interval1join2); + } + } + + /// + /// Narrows the given interval, which is associated with the given local or parameter, by applying the constraints that are known to hold inside the referring block. + /// If the joinBlock is not null, the interval is first narrowed with any constraints that apply to the transition from definingBlock to joinBlock. + /// + public static Interval Narrow(INamedEntity localOrParameter, Interval interval, AiBasicBlock referringBlock, AiBasicBlock joinBlock, + AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(localOrParameter != null); + Contract.Requires(interval != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + Contract.Ensures(Contract.Result() != null); + + if (joinBlock != null) { + var predecessors = joinBlock.Predecessors; + Contract.Assume(predecessors != null); + var n = predecessors.Count; + Contract.Assume(n == joinBlock.ConstraintsAtEntry.Count); + for (int i = 0; i < n; i++) { + if (predecessors[i] == definingBlock) { + if (joinBlock.ConstraintsAtEntry[i] == null) continue; + interval = Narrow(localOrParameter, interval.Clone(), joinBlock.ConstraintsAtEntry[i], referringBlock, mappings); + break; + } + } + if (joinBlock == referringBlock) return interval; + } + + Interval union = null; + foreach (var constraintList in referringBlock.ConstraintsAtEntry) { + if (constraintList == null) continue; + var narrowedInterval = Narrow(localOrParameter, interval.Clone(), constraintList, referringBlock, mappings); + if (union == null) + union = narrowedInterval; + else + union = union.Join(narrowedInterval); + } + return union??interval; + } + + /// + /// Narrows the given interval, which is associated with the given local or parameter, by applying the given list of constraints that are known to hold inside the referring block + /// whenever it referes to the given local or parameter. + /// + private static Interval Narrow(INamedEntity localOrParameter, Interval interval, List constraintList, AiBasicBlock referringBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(localOrParameter != null); + Contract.Requires(interval != null); + Contract.Requires(referringBlock != null); + Contract.Requires(constraintList != null); + Contract.Requires(mappings != null); + Contract.Ensures(Contract.Result() != null); + + foreach (var constraint in constraintList) { + if (constraint == null) continue; + Narrow(localOrParameter, interval, constraint, referringBlock, mappings); + } + return interval; + } + + /// + /// Narrows the given interval, which is associated with the given local or parameter, by applying the given constraint that is known to hold inside the referring block + /// whenever it referes to the given local or parameter. + /// + private static void Narrow(INamedEntity localOrParameter, Interval interval, Instruction constraint, AiBasicBlock block, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(localOrParameter != null); + Contract.Requires(interval != null); + Contract.Requires(block != null); + Contract.Requires(constraint != null); + Contract.Requires(mappings != null); + + var operand1 = constraint.Operand1 as Instruction; + if (operand1 == null) return; + var operand2 = constraint.Operand2 as Instruction; + if (operand2 == null) return; + if (operand1.Operation.Value == localOrParameter) { + if (operand1.Operation.OperationCode != OperationCode.Ldarg && operand1.Operation.OperationCode != OperationCode.Ldloc) return; + var cv = mappings.GetCompileTimeConstantValueFor(operand2); + if (cv != null) { + Narrow(interval, constraint.Operation.OperationCode, cv); + } + } else if (operand2.Operation.Value == localOrParameter) { + if (operand2.Operation.OperationCode != OperationCode.Ldarg && operand2.Operation.OperationCode != OperationCode.Ldloc) return; + var cv = mappings.GetCompileTimeConstantValueFor(operand1); + if (cv != null) { + Narrow2(cv, constraint.Operation.OperationCode, interval); + } + } + if (constraint.Operation.OperationCode == OperationCode.And) { + Narrow(localOrParameter, interval, operand1, block, mappings); + Narrow(localOrParameter, interval, operand2, block, mappings); + } else if (constraint.Operation.OperationCode == OperationCode.Or) { + var interval1 = interval.Clone(); + var interval2 = interval.Clone(); + Narrow(localOrParameter, interval1, operand1, block, mappings); + Narrow(localOrParameter, interval2, operand2, block, mappings); + var interval1join2 = interval1.Join(interval2); + Narrow(interval, OperationCode.Beq, interval1join2); + } + } + + /// + /// Narrows the given interval by applying a constraint expressed by the given operation code and compile time constant. + /// Think of this as enforcing the constraint that x op cv must be true for any value x in the given interval. + /// For example, if the operation code is Bge and the constant is 10 then the lower bound of the interval is set to 10, if + /// this will result in a narrower interval. The change is peformed by mutating the given interval. + /// + private static void Narrow(Interval interval, OperationCode operationCode, IMetadataConstant cv) { + Contract.Requires(interval != null); + Contract.Requires(cv != null); + + if (!MetadataExpressionHelper.IsFiniteNumeric(cv)) return; + switch (operationCode) { + //interval == cv + case OperationCode.Beq: + case OperationCode.Beq_S: + interval.lowerBound = cv; + interval.upperBound = cv; + interval.excludesMinusOne |= !MetadataExpressionHelper.IsIntegralMinusOne(cv); + interval.excludesZero |= !MetadataExpressionHelper.IsIntegralZero(cv); + break; + + //interval >= cv + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + if (interval.LowerBound == Dummy.Constant || Evaluator.IsNumericallyGreaterThan(cv, interval.LowerBound)) { + interval.lowerBound = cv; + interval.includesUnderflow = false; + } + interval.excludesMinusOne |= !Evaluator.IsNegative(cv); + interval.excludesZero |= Evaluator.IsPositive(cv); + break; + + //interval > cv + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + if (interval.LowerBound == Dummy.Constant || Evaluator.IsNumericallyGreaterThan(cv, interval.LowerBound)) { + interval.lowerBound = Evaluator.IncreaseBySmallestInterval(cv); + interval.includesUnderflow = false; + } + interval.excludesMinusOne |= !Evaluator.IsNegative(cv); + interval.excludesZero |= !Evaluator.IsNegative(cv); + break; + + //interval <= cv + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + if (interval.UpperBound == Dummy.Constant || Evaluator.IsNumericallyLessThan(cv, interval.UpperBound)) { + interval.upperBound = cv; + interval.includesOverflow = false; + } + interval.excludesMinusOne |= Evaluator.IsNegative(cv) && !MetadataExpressionHelper.IsIntegralMinusOne(cv); + interval.excludesZero |= Evaluator.IsNegative(cv); + break; + + //interval < cv + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + if (interval.UpperBound == Dummy.Constant || Evaluator.IsNumericallyLessThan(cv, interval.UpperBound)) { + interval.upperBound = Evaluator.DecreaseBySmallestInterval(cv); + interval.includesOverflow = false; + } + interval.excludesMinusOne |= !Evaluator.IsPositive(cv) && !MetadataExpressionHelper.IsIntegralMinusOne(cv); + interval.excludesZero |= !Evaluator.IsPositive(cv); + break; + + //interval != cv + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + if (Evaluator.IsNumericallyEqual(interval.LowerBound, cv)) + interval.lowerBound = Evaluator.IncreaseBySmallestInterval(cv); + if (Evaluator.IsNumericallyEqual(interval.UpperBound, cv)) + interval.upperBound = Evaluator.DecreaseBySmallestInterval(cv); + if (MetadataExpressionHelper.IsIntegralMinusOne(cv)) + interval.excludesMinusOne = true; + if (MetadataExpressionHelper.IsIntegralZero(cv)) + interval.excludesZero = true; + break; + } + } + + /// + /// Narrows interval1 by applying a constraint expressed by the given operation code and another interval, interval2. + /// Think of this as enforcing the constraint that x op y must be true for any value x in interval1 and any value y in interval2. + /// For example, if the operation code is Bge and the other interval is 10..20 then the lower bound of the interval is set to 20, if + /// this will result in a narrower interval. The change is peformed by mutating the given interval. + /// + private static void Narrow(Interval interval1, OperationCode operationCode, Interval interval2) { + Contract.Requires(interval1 != null); + Contract.Requires(interval2 != null); + + switch (operationCode) { + //interval == interval2 + case OperationCode.Beq: + case OperationCode.Beq_S: + if (interval1.LowerBound == Dummy.Constant || Evaluator.IsNumericallyGreaterThan(interval2.LowerBound, interval1.LowerBound)) { + interval1.lowerBound = interval2.LowerBound; + interval1.includesUnderflow = false; + } + if (interval1.UpperBound == Dummy.Constant || Evaluator.IsNumericallyLessThan(interval2.UpperBound, interval1.UpperBound)) { + interval1.upperBound = interval2.UpperBound; + interval1.includesOverflow = false; + } + interval1.excludesMinusOne = interval2.ExcludesMinusOne; + interval1.excludesZero |= interval2.ExcludesZero; + break; + + //interval >= interval2 + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + if (interval1.LowerBound == Dummy.Constant || Evaluator.IsNumericallyGreaterThan(interval2.UpperBound, interval1.LowerBound)) { + interval1.lowerBound = interval2.UpperBound; + interval1.includesUnderflow = false; + } + break; + + //interval > interval2 + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + if (interval1.LowerBound == Dummy.Constant || Evaluator.IsNumericallyGreaterThan(interval2.UpperBound, interval1.LowerBound)) { + interval1.lowerBound = Evaluator.IncreaseBySmallestInterval(interval2.UpperBound); + interval1.includesUnderflow = false; + } + break; + + //interval <= interval2 + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + if (interval1.UpperBound == Dummy.Constant || Evaluator.IsNumericallyLessThan(interval2.LowerBound, interval1.UpperBound)) { + interval1.upperBound = interval2.LowerBound; + interval1.includesOverflow = false; + } + break; + + //interval < interval2 + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + if (interval1.UpperBound == Dummy.Constant || Evaluator.IsNumericallyLessThan(interval2.LowerBound, interval1.UpperBound)) { + interval1.upperBound = Evaluator.DecreaseBySmallestInterval(interval2.LowerBound); + interval1.includesOverflow = false; + } + break; + + } + } + + /// + /// Narrows the given interval by applying a constraint expressed by the given operation code and compile time constant. + /// Think of this as enforcing the constraint that cv op x must be true for any value x in the given interval. + /// For example, if the operation code is Bge and the constant is 10 then the upper bound of the interval is set to 10, if + /// this will result in a narrower interval. The change is peformed by mutating the given interval. + /// + private static void Narrow2(IMetadataConstant cv, OperationCode operationCode, Interval interval) { + Contract.Requires(interval != null); + Contract.Requires(cv != null); + + switch (operationCode) { + //cv == interval + case OperationCode.Beq: + case OperationCode.Beq_S: + interval.lowerBound = cv; + interval.upperBound = cv; + interval.excludesMinusOne |= !MetadataExpressionHelper.IsIntegralMinusOne(cv); + interval.excludesZero |= !MetadataExpressionHelper.IsIntegralZero(cv); + break; + + // cv >= interval + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + if (interval.UpperBound == Dummy.Constant || Evaluator.IsNumericallyLessThan(cv, interval.UpperBound)) { + interval.upperBound = cv; + interval.includesOverflow = false; + } + interval.excludesMinusOne |= Evaluator.IsNegative(cv) && !MetadataExpressionHelper.IsIntegralMinusOne(cv); + interval.excludesZero |= Evaluator.IsNegative(cv); + break; + + //cv > interval + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + if (interval.UpperBound == Dummy.Constant || Evaluator.IsNumericallyLessThan(cv, interval.UpperBound)) { + interval.upperBound = Evaluator.DecreaseBySmallestInterval(cv); + interval.includesOverflow = false; + } + interval.excludesMinusOne |= Evaluator.IsNegative(cv); + interval.excludesZero |= !Evaluator.IsPositive(cv); + break; + + //cv <= interval + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + if (interval.LowerBound == Dummy.Constant || Evaluator.IsNumericallyGreaterThan(cv, interval.LowerBound)) { + interval.lowerBound = cv; + interval.includesUnderflow = false; + } + interval.excludesMinusOne |= !Evaluator.IsNegative(cv); + interval.excludesZero |= Evaluator.IsPositive(cv); + break; + + //cv < interval + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + if (interval.LowerBound == Dummy.Constant || Evaluator.IsNumericallyGreaterThan(cv, interval.LowerBound)) { + interval.lowerBound = Evaluator.IncreaseBySmallestInterval(cv); + interval.includesUnderflow = false; + } + interval.excludesMinusOne |= !Evaluator.IsNegative(cv) || MetadataExpressionHelper.IsIntegralMinusOne(cv); + interval.excludesZero |= !Evaluator.IsNegative(cv); + break; + + // cv != interval + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + if (Evaluator.IsNumericallyEqual(interval.LowerBound, cv)) + interval.lowerBound = Evaluator.IncreaseBySmallestInterval(cv); + if (Evaluator.IsNumericallyEqual(interval.UpperBound, cv)) + interval.upperBound = Evaluator.DecreaseBySmallestInterval(cv); + if (MetadataExpressionHelper.IsIntegralMinusOne(cv)) + interval.excludesMinusOne = true; + if (MetadataExpressionHelper.IsIntegralZero(cv)) + interval.excludesZero = true; + break; + } + } + + /// + /// Computes a numeric interval to bound the value that the given expression results in. + /// If the expression cannot be bounded by a numeric interval, the result is null. + /// + /// An instruction that results in a value. + /// A block providing the context for the interval. The entry contraints of the block are used to narrow the interval if possible. + /// If the expression is a component of a join ("phi" node), then joinBlock is the block in which the join is defined. + /// If the expression is a component of a join ("phi" node), then definingBlock is the block from which the component expression flowed to joinBlock. + /// Provides several maps from expressions to concrete and abstract values. + /// + /// An interval that bounds the values of the expression to a value between upper and lower bounds. + /// If the expression cannot be bounded, for instance because it does not result int a numeric type, the result is null. + /// + /// + /// This method does not add its result to the cache, nor does it check the cache for existing result. + /// The reason for this is that while the abstract interpretation proceeds, intervals can become more refined. + /// Once abstract interpretation is done, clients will obtain intervals via mappings, not this class. + /// If there is a cache miss, mappings will call this class and do the caching. + /// + [ContractVerification(false)] + internal static Interval/*?*/ TryToGetAsInterval(Instruction expression, AiBasicBlock referringBlock, AiBasicBlock joinBlock, + AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + + Interval result = null; + var singleton = mappings.GetCompileTimeConstantValueFor(expression); + if (singleton != null && TypeHelper.IsPrimitiveInteger(singleton.Type)) + return new Interval(singleton, singleton); + else { + if (expression.Operation.OperationCode == OperationCode.Nop && expression.Operation.Value is INamedEntity) + result = TryToGetAsJoinedInterval(expression, referringBlock, joinBlock, definingBlock, mappings); + else { + var operand1 = expression.Operand1 as Instruction; + if (operand1 == null) + result = TryToGetAsInterval0(expression, referringBlock, joinBlock, definingBlock, mappings); + else { + if (expression.Operand2 == null) + result = TryToGetAsInterval1(expression, operand1, referringBlock, joinBlock, definingBlock, mappings); + else { + var operand2 = expression.Operand2 as Instruction; + if (operand2 != null) + result = TryToGetAsInterval2(expression, operand1, operand2, referringBlock, joinBlock, definingBlock, mappings); + else { + Contract.Assume(expression.Operand2 is Instruction[]); + result = TryToGetAsIntervalN(expression, operand1, (Instruction[])expression.Operand2, mappings); + } + } + } + } + } + if (result != null) { + return Narrow(expression, result, referringBlock, joinBlock, definingBlock, mappings); + } + return result; + } + + /// + /// Computes a numeric interval to bound the value that the given nullary expression results in. + /// If the expression cannot be bounded by a numeric interval, the result is null. + /// + /// An instruction that results in a value. + /// A block providing the context for the interval. The entry contraints of the block are used to narrow the interval if possible. + /// If the expression is a component of a join ("phi" node), then joinBlock is the block in which the join is defined. + /// If the expression is a component of a join ("phi" node), then definingBlock is the block from which the component expression flowed to joinBlock. + /// Provides several maps from expressions to concrete and abstract values. + /// + /// An interval that bounds the values of the expression to a value between upper and lower bounds. + /// If the expression cannot be bounded, for instance because it does not result int a numeric type, the result is null. + /// + private static Interval/*?*/ TryToGetAsInterval0(Instruction expression, AiBasicBlock referringBlock, AiBasicBlock joinBlock, + AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + + var typeInterval = GetIntervalFor(expression.Type, mappings); + if (typeInterval == null) return null; + var operation = expression.Operation; + switch (operation.OperationCode) { + //Instructions that are side effect free and whose results can be cached and reused, but whose result values can never be known at compile time. + case OperationCode.Arglist: + case OperationCode.Call: + case OperationCode.Ldftn: + case OperationCode.Ldtoken: + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + case OperationCode.Ldsflda: + return null; + + //Instructions that transfer control to a successor block. + case OperationCode.Br: + case OperationCode.Br_S: + case OperationCode.Leave: + case OperationCode.Leave_S: + return null; + + //Instructions that are side-effect free and that result in compile time constant values. + case OperationCode.Ldc_I4: + case OperationCode.Ldc_I4_0: + case OperationCode.Ldc_I4_1: + case OperationCode.Ldc_I4_2: + case OperationCode.Ldc_I4_3: + case OperationCode.Ldc_I4_4: + case OperationCode.Ldc_I4_5: + case OperationCode.Ldc_I4_6: + case OperationCode.Ldc_I4_7: + case OperationCode.Ldc_I4_8: + case OperationCode.Ldc_I4_M1: + case OperationCode.Ldc_I4_S: + case OperationCode.Ldc_I8: + return null; //Don't bother with an interval, clients are not expect to ask for an interval unless they know the expression is not a compile time constant. + case OperationCode.Ldc_R4: + case OperationCode.Ldc_R8: + case OperationCode.Ldnull: + case OperationCode.Ldstr: + return null; //Don't bother with an interval, clients are not expect to ask for an interval unless they know the expression is not a compile time constant. + + //Instructions that are side-effect free and cacheable and that *could* result in compile time constant values. + //We attempt to compute the compile time values. + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + var arg = operation.Value as IParameterDefinition; + if (arg == null) return null; + var definingJoin = mappings.GetDefiningJoinFor(arg); + if (definingJoin != null) { + var joinedInterval = GetIntervalFor(definingJoin, mappings); + return Narrow(arg, joinedInterval??typeInterval, referringBlock, joinBlock, definingBlock, mappings); + } + var definingExpression = mappings.GetDefiningExpressionFor(arg); + if (definingExpression != null && !Evaluator.Contains(definingExpression, expression)) + return Narrow(arg, TryToGetAsInterval(definingExpression, referringBlock, joinBlock, definingBlock, mappings)??typeInterval, referringBlock, joinBlock, definingBlock, mappings); + else + return Narrow(arg, typeInterval, referringBlock, joinBlock, definingBlock, mappings); + + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + var local = operation.Value as ILocalDefinition; + Contract.Assume(local != null); + definingJoin = mappings.GetDefiningJoinFor(local); + if (definingJoin != null) { + var joinedInterval = GetIntervalFor(definingJoin, mappings); + return Narrow(local, joinedInterval??typeInterval, referringBlock, joinBlock, definingBlock, mappings); + } + definingExpression = mappings.GetDefiningExpressionFor(local); + if (definingExpression != null && !Evaluator.Contains(definingExpression, expression)) + return Narrow(local, TryToGetAsInterval(definingExpression, referringBlock, joinBlock, definingBlock, mappings)??typeInterval, referringBlock, joinBlock, definingBlock, mappings); + else + return Narrow(local, typeInterval, referringBlock, joinBlock, definingBlock, mappings); + + //Instructions that are side-effect free and that *could* result in compile time constant values. + //We do NOT attempt to compute the compile time values at this time. + case OperationCode.Ldsfld: + return typeInterval; + + //Instructions that transfer control out of the method being interpreted. + case OperationCode.Jmp: + case OperationCode.Rethrow: + case OperationCode.Ret: + return null; + + //Instruction modifier to track in the future. + case OperationCode.Volatile_: + return null; + + default: + Contract.Assume(false); + return null; + } + } + + /// + /// Computes a numeric interval to bound the value that the given unary expression results in. + /// If the expression cannot be bounded by a numeric interval, the result is null. + /// + /// An instruction that results in a value. + /// + /// A block providing the context for the interval. The entry contraints of the block are used to narrow the interval if possible. + /// If the expression is a component of a join ("phi" node), then joinBlock is the block in which the join is defined. + /// If the expression is a component of a join ("phi" node), then definingBlock is the block from which the component expression flowed to joinBlock. + /// Provides several maps from expressions to concrete and abstract values. + /// + /// An interval that bounds the values of the expression to a value between upper and lower bounds. + /// If the expression cannot be bounded, for instance because it does not result int a numeric type, the result is null. + /// + private static Interval/*?*/ TryToGetAsInterval1(Instruction expression, Instruction operand, AiBasicBlock referringBlock, AiBasicBlock joinBlock, + AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(operand != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + + var operandInterval = TryToGetAsInterval(operand, referringBlock, joinBlock, definingBlock, mappings); + var typeInterval = GetIntervalFor(expression.Type, mappings); + if (typeInterval == null) return null; + IOperation operation = expression.Operation; + switch (operation.OperationCode) { + //Instructions that cause or depend on side-effects. We'll keep them as is. + case OperationCode.Box: + case OperationCode.Call: + case OperationCode.Calli: + case OperationCode.Callvirt: + case OperationCode.Initobj: + case OperationCode.Ldobj: + case OperationCode.Localloc: + case OperationCode.Mkrefany: + case OperationCode.Newarr: + case OperationCode.Newobj: + case OperationCode.Pop: + case OperationCode.Stsfld: + return null; + case OperationCode.Unbox: + case OperationCode.Unbox_Any: + return typeInterval; + + //Insructions that are side effect free and whose results can be cached and reused, but whose result values can never be known at compile time. + case OperationCode.Castclass: + case OperationCode.Ckfinite: + case OperationCode.Isinst: + case OperationCode.Ldvirtftn: + case OperationCode.Refanytype: + case OperationCode.Refanyval: //TODO: If we track object contents, we might be able to know the value of this at compile time. + return null; + case OperationCode.Ldlen: + return mappings.Int64Interval; + case OperationCode.Sizeof: + return mappings.Int32Interval; + + //Instructions that conditionally affect control flow. We keep them as is, but update the control flow appropriately. + case OperationCode.Brfalse: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue: + case OperationCode.Brtrue_S: + return null; + + //Instructions that are side-effect free and that could result in concrete compile time values. + //We attempt to compute the compile time values. + case OperationCode.Conv_I: + case OperationCode.Conv_I1: + case OperationCode.Conv_I2: + case OperationCode.Conv_I4: + case OperationCode.Conv_I8: + case OperationCode.Conv_Ovf_I: + case OperationCode.Conv_Ovf_I_Un: + case OperationCode.Conv_Ovf_I1: + case OperationCode.Conv_Ovf_I1_Un: + case OperationCode.Conv_Ovf_I2: + case OperationCode.Conv_Ovf_I2_Un: + case OperationCode.Conv_Ovf_I4: + case OperationCode.Conv_Ovf_I4_Un: + case OperationCode.Conv_Ovf_I8: + case OperationCode.Conv_Ovf_I8_Un: + case OperationCode.Conv_Ovf_U: + case OperationCode.Conv_Ovf_U_Un: + case OperationCode.Conv_Ovf_U1: + case OperationCode.Conv_Ovf_U1_Un: + case OperationCode.Conv_Ovf_U2: + case OperationCode.Conv_Ovf_U2_Un: + case OperationCode.Conv_Ovf_U4: + case OperationCode.Conv_Ovf_U4_Un: + case OperationCode.Conv_Ovf_U8: + case OperationCode.Conv_Ovf_U8_Un: + case OperationCode.Conv_R_Un: + case OperationCode.Conv_R4: + case OperationCode.Conv_R8: + case OperationCode.Conv_U: + case OperationCode.Conv_U1: + case OperationCode.Conv_U2: + case OperationCode.Conv_U4: + case OperationCode.Conv_U8: + if (operandInterval == null) return typeInterval; + var convertedLower = Evaluator.Evaluate(operation, operandInterval.LowerBound); + Contract.Assume(convertedLower != null); + var convertedUpper = Evaluator.Evaluate(operation, operandInterval.UpperBound); + Contract.Assume(convertedUpper != null); + var lowerUnderflows = convertedLower == Dummy.Constant || !Evaluator.IsNumericallyEqual(convertedLower, operandInterval.LowerBound); + var upperOverflows = convertedUpper == Dummy.Constant || !Evaluator.IsNumericallyEqual(convertedUpper, operandInterval.UpperBound); + if (lowerUnderflows && upperOverflows && Evaluator.IsNumericallyEqual(convertedLower, convertedUpper)) { + lowerUnderflows = Evaluator.IsNegative(operandInterval.LowerBound); + upperOverflows = Evaluator.IsPositive(operandInterval.UpperBound); + } + if (lowerUnderflows || upperOverflows) { convertedLower = typeInterval.LowerBound; convertedUpper = typeInterval.UpperBound; } + return new Interval(convertedLower, convertedUpper, includesOverflow: upperOverflows, includesUnderflow: lowerUnderflows, + excludesMinusOne: operandInterval.ExcludesMinusOne, excludesZero: operandInterval.ExcludesZero && !lowerUnderflows && !upperOverflows); + case OperationCode.Dup: + if (operandInterval == null) return typeInterval; + return operandInterval; + case OperationCode.Neg: + if (operandInterval == null) return typeInterval; + var negatedLowerBound = Evaluator.Evaluate(operation, operandInterval.LowerBound); + Contract.Assume(negatedLowerBound != null); + var negatedUpperBound = Evaluator.Evaluate(operation, operandInterval.UpperBound); + Contract.Assume(negatedUpperBound != null); + var lowerOverflows = Evaluator.IsNumericallyEqual(negatedLowerBound, operandInterval.LowerBound); + if (lowerOverflows) { negatedUpperBound = typeInterval.LowerBound; negatedLowerBound = typeInterval.UpperBound; } + return new Interval(negatedUpperBound, negatedLowerBound, includesOverflow: lowerOverflows, + excludesMinusOne: operandInterval.ExcludesMinusOne, excludesZero: operandInterval.ExcludesZero); + case OperationCode.Not: + if (operandInterval == null) return typeInterval; + var complementedLowerBound = Evaluator.Evaluate(operation, operandInterval.LowerBound); + Contract.Assume(complementedLowerBound != null); + var complementedUpperBound = Evaluator.Evaluate(operation, operandInterval.UpperBound); + Contract.Assume(complementedUpperBound != null); + return new Interval(complementedUpperBound, complementedLowerBound); + + //Instructions that can be cached in the absence of volatility, aliasing and multiple writes. + case OperationCode.Ldfld: + case OperationCode.Ldflda: + case OperationCode.Ldind_I: + case OperationCode.Ldind_I1: + case OperationCode.Ldind_I2: + case OperationCode.Ldind_I4: + case OperationCode.Ldind_I8: + case OperationCode.Ldind_R4: + case OperationCode.Ldind_R8: + case OperationCode.Ldind_Ref: + case OperationCode.Ldind_U1: + case OperationCode.Ldind_U2: + case OperationCode.Ldind_U4: + return typeInterval; + //TODO: track what the pointer points to and see if that has a known value or interval. + + //Instructions that affect the SSA environment. + case OperationCode.Starg: + case OperationCode.Starg_S: + case OperationCode.Stloc: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + return null; + + //Instructions that transfer control out of the method being interpreted. + case OperationCode.Ret: + case OperationCode.Throw: + return null; + + default: + Contract.Assume(false); + return null; + } + + } + + private static readonly Operation add_ovf = new Operation() { OperationCode = OperationCode.Add_Ovf }; + private static readonly Operation mul_ovf = new Operation() { OperationCode = OperationCode.Mul_Ovf }; + private static readonly Operation sub_ovf = new Operation() { OperationCode = OperationCode.Sub_Ovf }; + + /// + /// Computes a numeric interval to bound the value that the given binary expression results in. + /// If the expression cannot be bounded by a numeric interval, the result is null. + /// + /// An instruction that results in a value. + /// + /// + /// A block providing the context for the interval. The entry contraints of the block are used to narrow the interval if possible. + /// If the expression is a component of a join ("phi" node), then joinBlock is the block in which the join is defined. + /// If the expression is a component of a join ("phi" node), then definingBlock is the block from which the component expression flowed to joinBlock. + /// Provides several maps from expressions to concrete and abstract values. + /// + /// An interval that bounds the values of the expression to a value between upper and lower bounds. + /// If the expression cannot be bounded, for instance because it does not result int a numeric type, the result is null. + /// + private static Interval/*?*/ TryToGetAsInterval2(Instruction expression, Instruction operand1, Instruction operand2, AiBasicBlock referringBlock, AiBasicBlock joinBlock, + AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + + var typeInterval = GetIntervalFor(expression.Type, mappings); + if (typeInterval == null) return null; + var operand1Interval = TryToGetAsInterval(operand1, referringBlock, joinBlock, definingBlock, mappings); + if (operand1Interval != null) { + var singleton1 = operand1Interval.GetAsSingleton(); + if (singleton1 != null) return TryToGetAsInterval2(expression, typeInterval, singleton1, operand2, referringBlock, joinBlock, definingBlock, mappings); + } + var operand2Interval = TryToGetAsInterval(operand2, referringBlock, joinBlock, definingBlock, mappings); + if (operand2Interval != null) { + var singleton2 = operand2Interval.GetAsSingleton(); + if (singleton2 != null) return TryToGetAsInterval2(expression, typeInterval, operand1, singleton2, referringBlock, joinBlock, definingBlock, mappings); + } + + IOperation operation = expression.Operation; + switch (operation.OperationCode) { + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We attempt to compute the compile time values. + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + if (operand1Interval == null || operand2Interval == null) return typeInterval; + var op = operation.OperationCode == OperationCode.Add ? add_ovf : operation; + var lowerBound = Evaluator.Evaluate(op, operand1Interval.LowerBound, operand2Interval.LowerBound); + var upperBound = Evaluator.Evaluate(op, operand1Interval.UpperBound, operand2Interval.UpperBound); + var lowerBoundUnderflows = lowerBound == Dummy.Constant; + var upperBoundOverflows = upperBound == Dummy.Constant; + if (lowerBoundUnderflows || upperBoundOverflows) { lowerBound = typeInterval.LowerBound; upperBound = typeInterval.UpperBound; }; + return new Interval(lowerBound, upperBound, includesOverflow: upperBoundOverflows, includesUnderflow: lowerBoundUnderflows); + + case OperationCode.And: + if (operand1Interval == null || operand2Interval == null) return typeInterval; + if (Evaluator.IsNonNegative(operand1Interval.LowerBound)) { + if (Evaluator.IsNonNegative(operand2Interval.LowerBound)) + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.Min(operand1Interval.UpperBound, operand2Interval.UpperBound)); + else + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } else if (Evaluator.IsNonNegative(operand2Interval.LowerBound)) { + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } else { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } + + case OperationCode.Ceq: + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + case OperationCode.Clt: + case OperationCode.Clt_Un: + return null; + + case OperationCode.Div: + case OperationCode.Div_Un: + if (operand1Interval == null || operand2Interval == null) return typeInterval; + //p1..p2 / p3..p4 p1/p4..p2/p3 + //p1..p2 / 0..p4 p1/p4..p2 + zero div + //p1..p2 / n3..p4 -p2..p2 + zero div + //p1..p2 / n3..0 -p2..p2 + zero div + //p1..p2 / n3..n4 p2/n4..p1/n3 + if (Evaluator.IsPositive(operand1Interval.LowerBound) && Evaluator.IsPositive(operand2Interval.UpperBound)) { + if (Evaluator.IsPositive(operand2Interval.UpperBound)) { + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.UpperBound), + Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero); + } else if (!Evaluator.IsNegative(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.UpperBound), + operand1Interval.UpperBound, excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + return new Interval(Evaluator.Negate(operand1Interval.UpperBound), + operand1Interval.UpperBound, excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } + } else if (!Evaluator.IsNegative(operand2Interval.UpperBound)) { + return new Interval(Evaluator.Negate(operand1Interval.UpperBound), + operand1Interval.UpperBound, excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.UpperBound), + Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.UpperBound), excludesZero: operand1Interval.ExcludesZero); + } + } + //n1..p2 / p3..p4 n1/p3..p2/p3 + //n1..p2 / 0..p4 n1..p2 + zero div + //n1..p2 / n3..p4 n1..max(-n1, p2) + zero div + //min..p2 / n3..p4 min..max + zero div + overflow + //n1..p2 / n3..0 -p2..-n1 + zero div + //min..p2 / n3..0 min..max + zero div + overflow + //min..p2 / n3..-1 min..max + overflow + //n1..p2 / n3..n4 p2/n4..n1/n4 + if (Evaluator.IsNegative(operand1Interval.LowerBound) && Evaluator.IsPositive(operand1Interval.UpperBound)) { + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.LowerBound), + Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero); + } else if (!Evaluator.IsNegative(operand2Interval.LowerBound)) { + return new Interval(operand1Interval.LowerBound, operand1Interval.UpperBound, excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + if (Evaluator.IsPositive(operand2Interval.UpperBound)) { + if (operand1Interval.ExcludesMinimumValue(expression.Type)) { + return new Interval(operand1Interval.LowerBound, + Evaluator.Max(Evaluator.Negate(operand1Interval.LowerBound), operand1Interval.UpperBound), excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: + !operand2Interval.ExcludesZero, includesOverflow: !operand2Interval.ExcludesMinusOne); + } + } else if (!Evaluator.IsNegative(operand2Interval.UpperBound)) { + if (operand1Interval.ExcludesMinimumValue(expression.Type)) { + return new Interval(Evaluator.Negate(operand1Interval.UpperBound), Evaluator.Negate(operand1Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero, + includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, excludesZero: operand1Interval.ExcludesZero, + includesDivisionByZero: !operand2Interval.ExcludesZero, includesOverflow: !operand2Interval.ExcludesMinusOne); + } + } else { + if (!operand1Interval.ExcludesMinimumValue(expression.Type) && !operand2Interval.ExcludesMinusOne) { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, excludesZero: operand1Interval.ExcludesZero, includesOverflow: true); + } else { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.UpperBound), + Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.UpperBound), excludesZero: operand1Interval.ExcludesZero); + } + } + } + } + //n1..n2 / p3..p4 n1/p3..n2/p3 + //n1..n2 / 0..p4 n1..-n1 + zero div + //n1..n2 / n3..p4 n1..-n1 + zero div + //n1..n2 / n3..0 -n2..-n1 + zero div + //n1..n2 / n3..n4 n2/n3..n1/n4 + //min..n2 / p3..p4 min/p3..n2/p3 + //min..n2 / 0..p4 n1..-n1 + zero div + //min..n2 / n3..p4 min..max + zero div + overflow + //min..n2 / n3..0 min..max + zero div + overflow + //min..n2 / n3..-1 min..max + overflow + //min..n2 / n3..n4 n2/n3..min/n4 + Contract.Assume(Evaluator.IsNegative(operand1Interval.LowerBound) && Evaluator.IsNegative(operand1Interval.UpperBound)); //since the lower bound is never larger than the upper bound + { + if (operand1Interval.ExcludesMinimumValue(expression.Type)) { + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.LowerBound), + Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero); + } else if (!Evaluator.IsNegative(operand2Interval.LowerBound)) { + return new Interval(operand1Interval.LowerBound, Evaluator.Negate(operand1Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + if (Evaluator.IsPositive(operand2Interval.UpperBound)) { + return new Interval(operand1Interval.LowerBound, Evaluator.Negate(operand1Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else if (!Evaluator.IsNegative(operand2Interval.UpperBound)) { + return new Interval(Evaluator.Negate(operand1Interval.LowerBound), Evaluator.Negate(operand1Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero, + includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.LowerBound), + Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.UpperBound), excludesZero: operand1Interval.ExcludesZero); + } + } + } else { + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2Interval.LowerBound), + Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero); + } else if (!Evaluator.IsNegative(operand2Interval.LowerBound)) { + return new Interval(operand1Interval.LowerBound, Evaluator.Negate(operand1Interval.LowerBound), excludesZero: operand1Interval.ExcludesZero, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + if (Evaluator.IsPositive(operand2Interval.UpperBound)) { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, excludesZero: operand1Interval.ExcludesZero, + includesDivisionByZero: !operand2Interval.ExcludesZero, includesOverflow: true); + } else if (!Evaluator.IsNegative(operand2Interval.UpperBound)) { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, excludesZero: operand1Interval.ExcludesZero, + includesDivisionByZero: !operand2Interval.ExcludesZero, includesOverflow: !operand2Interval.ExcludesMinusOne); + } else if (!operand2Interval.ExcludesMinusOne) { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, excludesZero: operand1Interval.ExcludesZero, includesOverflow: !operand2Interval.ExcludesMinusOne); + } else { + return new Interval(Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2Interval.LowerBound), + Evaluator.Evaluate(operation, typeInterval.LowerBound, operand2Interval.UpperBound), excludesZero: operand1Interval.ExcludesZero); + } + } + } + } + + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: { + if (operand1Interval == null || operand2Interval == null) return typeInterval; + var excludesZero = operand1Interval.ExcludesZero && operand2Interval.ExcludesZero; + op = operation.OperationCode == OperationCode.Mul ? mul_ovf : operation; + var llrl = Evaluator.Evaluate(op, operand1Interval.LowerBound, operand2Interval.LowerBound); + var lurl = Evaluator.Evaluate(op, operand1Interval.UpperBound, operand2Interval.LowerBound); + var llru = Evaluator.Evaluate(op, operand1Interval.LowerBound, operand2Interval.UpperBound); + var luru = Evaluator.Evaluate(op, operand1Interval.UpperBound, operand2Interval.UpperBound); + if (llrl == null || lurl == null || llru == null || luru == null || + llrl == Dummy.Constant || lurl == Dummy.Constant || llru == Dummy.Constant || luru == Dummy.Constant) { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, includesOverflow: true); + } + lowerBound = Evaluator.Min(Evaluator.Min(llrl, lurl), Evaluator.Min(llru, luru)); + upperBound = Evaluator.Max(Evaluator.Max(llrl, lurl), Evaluator.Max(llru, luru)); + return new Interval(lowerBound, upperBound, excludesZero: excludesZero); + } + + case OperationCode.Or: + if (operand1Interval == null || operand2Interval == null) return typeInterval; + if (Evaluator.IsNegative(operand1Interval.LowerBound)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else if (Evaluator.IsNegative(operand2Interval.LowerBound)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else { + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } + + case OperationCode.Rem: + if (operand2Interval == null) return typeInterval; + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + if (operand1Interval != null) { + if (Evaluator.IsNonNegative(operand1Interval.LowerBound)) { + if (Evaluator.IsNumericallyGreaterThan(operand2Interval.LowerBound, operand1Interval.UpperBound)) { + //Since the divisor is always greater than the dividend, the remainder is always equal to the dividend. + return operand1Interval; + } + return new Interval(Evaluator.GetZero(expression.Type), operand2Interval.UpperBound); + } else if (Evaluator.IsNegative(operand1Interval.UpperBound)) { + var negLower = Evaluator.Negate(operand1Interval.LowerBound); + if (Evaluator.IsNumericallyLessThan(operand1Interval.LowerBound, negLower)) { + //Since the divisor is always greater than the absolute dividend, the remainder is always equal to the dividend. + return operand1Interval; + } + return new Interval(Evaluator.Negate(operand2Interval.UpperBound), Evaluator.GetZero(expression.Type)); + } + } + return new Interval(Evaluator.Negate(operand2Interval.UpperBound), operand2Interval.UpperBound); + } + if (operand2Interval.ExcludesZero) return typeInterval; + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, includesDivisionByZero: true); + + case OperationCode.Rem_Un: + if (operand2Interval == null) return typeInterval; + var cop2lb = Evaluator.ConvertToUnsigned(operand2Interval.LowerBound); + var cop2up = Evaluator.ConvertToUnsigned(operand2Interval.UpperBound); + var op2up = Evaluator.Max(cop2lb, cop2up); + if (operand1Interval != null) { + var op2lb = Evaluator.Min(cop2lb, cop2up); + var cop1lb = Evaluator.ConvertToUnsigned(operand1Interval.LowerBound); + var cop1up = Evaluator.ConvertToUnsigned(operand1Interval.UpperBound); + var op1up = Evaluator.Max(cop1lb, cop1up); + if (Evaluator.IsNumericallyGreaterThan(op2lb, op1up)) { + //Since the divisor is always greater than the dividend, the remainder is always equal to the dividend. + return new Interval(Evaluator.Min(cop1lb, cop1up), op1up); //need a new interval to capture the potential conversion of the dividend to unsigned. + } + } + return new Interval(Evaluator.GetZero(expression.Type), op2up); + + case OperationCode.Shl: + goto case OperationCode.Mul; + + case OperationCode.Shr: + case OperationCode.Shr_Un: + goto case OperationCode.Div; + + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + if (operand1Interval == null || operand2Interval == null) return typeInterval; + op = operation.OperationCode == OperationCode.Sub ? sub_ovf : operation; + lowerBound = Evaluator.Evaluate(op, operand1Interval.LowerBound, operand2Interval.UpperBound); + upperBound = Evaluator.Evaluate(op, operand1Interval.UpperBound, operand2Interval.LowerBound); + var underflow = lowerBound == Dummy.Constant; + var overflow = upperBound == Dummy.Constant; + if (underflow || overflow) { lowerBound = typeInterval.LowerBound; upperBound = typeInterval.UpperBound; } + return new Interval(lowerBound, upperBound, includesUnderflow: underflow, includesOverflow: overflow); + + case OperationCode.Xor: + return typeInterval; + + //Instructions that conditionally affect control flow + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + return null; + + //Instructions that cause side-effect that we do not currently track. + case OperationCode.Call: + case OperationCode.Calli: + case OperationCode.Callvirt: + case OperationCode.Cpblk: + case OperationCode.Cpobj: + case OperationCode.Initblk: + case OperationCode.Stfld: + case OperationCode.Stind_I: + case OperationCode.Stind_I1: + case OperationCode.Stind_I2: + case OperationCode.Stind_I4: + case OperationCode.Stind_I8: + case OperationCode.Stind_R4: + case OperationCode.Stind_R8: + case OperationCode.Stind_Ref: + case OperationCode.Stobj: + return null; + + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We do NOT attempt to compute the compile time values at this time. + case OperationCode.Ldelem_I: + case OperationCode.Ldelem_I1: + case OperationCode.Ldelem_I2: + case OperationCode.Ldelem_I4: + case OperationCode.Ldelem_I8: + case OperationCode.Ldelem_R4: + case OperationCode.Ldelem_R8: + case OperationCode.Ldelem_Ref: + case OperationCode.Ldelem_U1: + case OperationCode.Ldelem_U2: + case OperationCode.Ldelem_U4: + case OperationCode.Ldelema: + return typeInterval; + + default: + Contract.Assume(false); + return null; + } + } + + private static Interval/*?*/ TryToGetAsInterval2(Instruction expression, Interval typeInterval, IMetadataConstant operand1, Instruction operand2, + AiBasicBlock referringBlock, AiBasicBlock joinBlock, AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(typeInterval != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + + IOperation operation = expression.Operation; + var operand2Interval = TryToGetAsInterval(operand2, referringBlock, joinBlock, definingBlock, mappings); + if (operand2Interval == null) return null; + var singleton2 = operand2Interval.GetAsSingleton(); + if (singleton2 != null) { + var op = operation; + switch (operation.OperationCode) { + case OperationCode.Add: op = add_ovf; break; + case OperationCode.Mul: op = mul_ovf; break; + case OperationCode.Sub: op = sub_ovf; break; + } + var result = Evaluator.Evaluate(op, operand1, singleton2); + if (result != null && result != Dummy.Constant) + return new Interval(result, result); + } + switch (operation.OperationCode) { + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We attempt to compute the compile time values. + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: { + if (operand2Interval == null) return typeInterval; + var op = operation.OperationCode == OperationCode.Add ? add_ovf : operation; + var lowerBound = Evaluator.Evaluate(op, operand1, operand2Interval.LowerBound); + var upperBound = Evaluator.Evaluate(op, operand1, operand2Interval.UpperBound); + var lowerBoundUnderflows = lowerBound == Dummy.Constant; + var upperBoundOverflows = upperBound == Dummy.Constant; + if (lowerBoundUnderflows || upperBoundOverflows) { lowerBound = typeInterval.LowerBound; upperBound = typeInterval.UpperBound; }; + return new Interval(lowerBound, upperBound, includesOverflow: upperBoundOverflows, includesUnderflow: lowerBoundUnderflows); + } + + case OperationCode.And: + if (operand2Interval == null) return typeInterval; + if (Evaluator.IsNonNegative(operand1)) { + if (Evaluator.IsNonNegative(operand2Interval.LowerBound)) + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.Min(operand1, operand2Interval.UpperBound)); + else + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } else if (Evaluator.IsNegative(operand2Interval.UpperBound)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else { + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } + + case OperationCode.Ceq: + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + case OperationCode.Clt: + case OperationCode.Clt_Un: + return null; + + case OperationCode.Div: + case OperationCode.Div_Un: + //p1 / p3..p4 p1/p4..p1/p3 + //p1 / 0..p4 p1/p4..p1 + zero div + //p1 / n3..p4 -p1..p1 + zero div + //p1 / n3..0 -p1..p1/n3 + zero div + //p1 / n3..n4 p1/n4..p1/n3 + + //n1 / p3..p4 n1/p3..n1/p4 + //n1 / 0..p4 n1..n1/p4 + zero div + //n1 / n3..p4 n1..-n1 + zero div + //n1 / n3..0 n1/n3..-n1 + zero div + //n1 / n3..n4 n1/n3..n1/n4 + + if (Evaluator.IsNumericallyEqual(operand1, Evaluator.GetMinValue(expression.Type)) && !operand2Interval.ExcludesMinusOne) + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, + includesDivisionByZero: !operand2Interval.ExcludesZero, includesOverflow: true); + if (Evaluator.IsPositive(operand1)) { + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1, operand2Interval.UpperBound), Evaluator.Evaluate(operation, operand1, operand2Interval.LowerBound)); + } else if (!Evaluator.IsNegative(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1, operand2Interval.UpperBound), operand1, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else if (Evaluator.IsPositive(operand2Interval.UpperBound)) { + return new Interval(Evaluator.Negate(operand1), operand1, includesDivisionByZero: !operand2Interval.ExcludesZero); + } else if (!Evaluator.IsNegative(operand2Interval.UpperBound)) { + return new Interval(Evaluator.Negate(operand1), Evaluator.Evaluate(operation, operand1, operand2Interval.LowerBound), includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + return new Interval(Evaluator.Evaluate(operation, operand1, operand2Interval.UpperBound), Evaluator.Evaluate(operation, operand1, operand2Interval.LowerBound)); + } + } else if (Evaluator.IsNegative(operand1)) { + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1, operand2Interval.LowerBound), Evaluator.Evaluate(operation, operand1, operand2Interval.UpperBound)); + } else if (!Evaluator.IsNegative(operand2Interval.LowerBound)) { + return new Interval(operand1, Evaluator.Evaluate(operation, operand1, operand2Interval.UpperBound), includesDivisionByZero: !operand2Interval.ExcludesZero); + } else if (Evaluator.IsPositive(operand2Interval.UpperBound)) { + return new Interval(operand1, Evaluator.Negate(operand1), includesDivisionByZero: !operand2Interval.ExcludesZero); + } else if (!Evaluator.IsNegative(operand2Interval.UpperBound)) { + return new Interval(Evaluator.Evaluate(operation, operand1, operand2Interval.LowerBound), Evaluator.Negate(operand1), includesDivisionByZero: !operand2Interval.ExcludesZero); + } else { + return new Interval(Evaluator.Evaluate(operation, operand1, operand2Interval.LowerBound), Evaluator.Evaluate(operation, operand1, operand2Interval.UpperBound)); + } + } + Contract.Assume(MetadataExpressionHelper.IsIntegralZero(operand1)); + return new Interval(operand1, operand1); + + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: { + if (operand2Interval == null) return typeInterval; + var excludesZero = MetadataExpressionHelper.IsIntegralNonzero(operand1) && operand2Interval.ExcludesZero; + var op = operation.OperationCode == OperationCode.Mul ? mul_ovf : operation; + var lrl = Evaluator.Evaluate(op, operand1, operand2Interval.LowerBound); + var lru = Evaluator.Evaluate(op, operand1, operand2Interval.UpperBound); + if (lrl == null || lru == null || lrl == Dummy.Constant || lru == Dummy.Constant) { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, includesOverflow: true); + } + var lower = Evaluator.Min(lrl, lru); + var upper = Evaluator.Max(lrl, lru); + return new Interval(lower, upper, excludesZero: excludesZero); + } + + case OperationCode.Or: + if (operand2Interval == null) return typeInterval; + if (Evaluator.IsNegative(operand1)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else if (Evaluator.IsNegative(operand2Interval.UpperBound)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else if (Evaluator.IsNonNegative(operand2Interval.LowerBound)) { + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } else { + return typeInterval; + } + + case OperationCode.Rem: + if (operand2Interval == null) return typeInterval; + if (Evaluator.IsPositive(operand2Interval.LowerBound)) { + if (Evaluator.IsNonNegative(operand1)) { + if (Evaluator.IsNumericallyGreaterThan(operand2Interval.LowerBound, operand1)) { + //Since the divisor is always greater than the dividend, the remainder is always equal to the dividend. + return new Interval(operand1, operand1); + } + return new Interval(Evaluator.GetZero(expression.Type), operand2Interval.UpperBound); + } + } + return typeInterval; + + case OperationCode.Rem_Un: + if (operand2Interval == null) return typeInterval; + var cop2lb = Evaluator.ConvertToUnsigned(operand2Interval.LowerBound); + var cop2up = Evaluator.ConvertToUnsigned(operand2Interval.UpperBound); + var op2up = Evaluator.Max(cop2lb, cop2up); + var op2lb = Evaluator.Min(cop2lb, cop2up); + var op1 = Evaluator.ConvertToUnsigned(operand1); + if (Evaluator.IsNumericallyGreaterThan(op2lb, op1)) { + //Since the divisor is always greater than the dividend, the remainder is always equal to the dividend. + return new Interval(op1, op1); + } + return new Interval(Evaluator.GetZero(expression.Type), op2up); + + case OperationCode.Shl: { + var operand2aLower = Evaluator.Evaluate(operation, Evaluator.GetOne(expression.Type), operand2Interval.LowerBound); + var operand2aUpper = Evaluator.Evaluate(operation, Evaluator.GetOne(expression.Type), operand2Interval.UpperBound); + if (Evaluator.IsNumericallyLessThan(operand2aLower, operand2Interval.LowerBound) || Evaluator.IsNumericallyLessThan(operand2aUpper, operand2Interval.UpperBound)) + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, includesOverflow: true); + operation = mul_ovf; + operand2Interval = new Interval(operand2aLower, operand2aUpper, excludesZero: true); + goto case OperationCode.Mul; + } + + case OperationCode.Shr: + case OperationCode.Shr_Un: + goto case OperationCode.Div; + + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: { + if (operand2Interval == null) return typeInterval; + var op = operation.OperationCode == OperationCode.Sub ? sub_ovf : operation; + var lowerBound = Evaluator.Evaluate(op, operand1, operand2Interval.LowerBound); + var upperBound = Evaluator.Evaluate(op, operand1, operand2Interval.UpperBound); + var underflow = lowerBound == Dummy.Constant; + var overflow = upperBound == Dummy.Constant; + if (underflow || overflow) { lowerBound = typeInterval.LowerBound; upperBound = typeInterval.UpperBound; } + return new Interval(lowerBound, upperBound, includesUnderflow: underflow, includesOverflow: overflow); + } + + case OperationCode.Xor: + return typeInterval; + + //Instructions that conditionally affect control flow + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + return null; + + //Instructions that cause side-effect that we do not currently track. + case OperationCode.Call: + case OperationCode.Calli: + case OperationCode.Callvirt: + case OperationCode.Cpblk: + case OperationCode.Cpobj: + case OperationCode.Initblk: + case OperationCode.Stfld: + case OperationCode.Stind_I: + case OperationCode.Stind_I1: + case OperationCode.Stind_I2: + case OperationCode.Stind_I4: + case OperationCode.Stind_I8: + case OperationCode.Stind_R4: + case OperationCode.Stind_R8: + case OperationCode.Stind_Ref: + case OperationCode.Stobj: + return null; + + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We do NOT attempt to compute the compile time values at this time. + case OperationCode.Ldelem_I: + case OperationCode.Ldelem_I1: + case OperationCode.Ldelem_I2: + case OperationCode.Ldelem_I4: + case OperationCode.Ldelem_I8: + case OperationCode.Ldelem_R4: + case OperationCode.Ldelem_R8: + case OperationCode.Ldelem_Ref: + case OperationCode.Ldelem_U1: + case OperationCode.Ldelem_U2: + case OperationCode.Ldelem_U4: + case OperationCode.Ldelema: + return typeInterval; + + default: + Contract.Assume(false); + return null; + } + } + + private static Interval/*?*/ TryToGetAsInterval2(Instruction expression, Interval typeInterval, Instruction operand1, IMetadataConstant operand2, + AiBasicBlock referringBlock, AiBasicBlock joinBlock, AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(typeInterval != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + + IOperation operation = expression.Operation; + var operand1Interval = TryToGetAsInterval(operand1, referringBlock, joinBlock, definingBlock, mappings); + if (operand1Interval == null) return null; + var singleton1 = operand1Interval.GetAsSingleton(); + if (singleton1 != null) { + var op = operation; + switch (operation.OperationCode) { + case OperationCode.Add: op = add_ovf; break; + case OperationCode.Mul: op = mul_ovf; break; + case OperationCode.Sub: op = sub_ovf; break; + } + var result = Evaluator.Evaluate(op, singleton1, operand2); + if (result != null && result != Dummy.Constant) + return new Interval(result, result); + } + switch (operation.OperationCode) { + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We attempt to compute the compile time values. + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: { + if (operand1Interval == null) return typeInterval; + var op = operation.OperationCode == OperationCode.Add ? add_ovf : operation; + var lowerBound = Evaluator.Evaluate(op, operand1Interval.LowerBound, operand2); + var upperBound = Evaluator.Evaluate(op, operand1Interval.UpperBound, operand2); + var lowerBoundUnderflows = lowerBound == Dummy.Constant; + var upperBoundOverflows = upperBound == Dummy.Constant; + if (lowerBoundUnderflows || upperBoundOverflows) { lowerBound = typeInterval.LowerBound; upperBound = typeInterval.UpperBound; }; + return new Interval(lowerBound, upperBound, includesOverflow: upperBoundOverflows, includesUnderflow: lowerBoundUnderflows); + } + + case OperationCode.And: + if (operand1Interval == null) return typeInterval; + if (Evaluator.IsNonNegative(operand2)) { + if (Evaluator.IsNonNegative(operand1Interval.LowerBound)) + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.Min(operand1Interval.LowerBound, operand2)); + else + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } else if (Evaluator.IsNegative(operand1Interval.UpperBound)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else { + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } + + case OperationCode.Ceq: + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + case OperationCode.Clt: + case OperationCode.Clt_Un: + return null; + + case OperationCode.Div: + case OperationCode.Div_Un: + if (operand1Interval == null) return null; + if (MetadataExpressionHelper.IsIntegralZero(operand2)) return null; + if (MetadataExpressionHelper.IsIntegralMinusOne(operand2) && !operand1Interval.ExcludesMinimumValue(expression.Type)) + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, includesOverflow: true); + if (Evaluator.IsPositive(operand2)) + return new Interval(Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2), Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2)); + else + return new Interval(Evaluator.Evaluate(operation, operand1Interval.UpperBound, operand2), Evaluator.Evaluate(operation, operand1Interval.LowerBound, operand2)); + + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: { + if (operand1Interval == null) return typeInterval; + var excludesZero = operand1Interval.ExcludesZero && MetadataExpressionHelper.IsIntegralNonzero(operand2); + var op = operation.OperationCode == OperationCode.Mul ? mul_ovf : operation; + Contract.Assume(operand2 != null); + var llr = Evaluator.Evaluate(op, operand1Interval.LowerBound, operand2); + var lur = Evaluator.Evaluate(op, operand1Interval.UpperBound, operand2); + if (llr == null || lur == null || llr == Dummy.Constant || lur == Dummy.Constant) { + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, includesOverflow: true); + } + var lower = Evaluator.Min(llr, lur); + var upper = Evaluator.Max(llr, lur); + return new Interval(lower, upper, excludesZero: excludesZero); + } + + case OperationCode.Or: + if (operand1Interval == null) return typeInterval; + if (Evaluator.IsNegative(operand2)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else if (Evaluator.IsNegative(operand1Interval.UpperBound)) { + return new Interval(Evaluator.GetMinValue(expression.Type), Evaluator.GetMinusOne(expression.Type)); + } else if (Evaluator.IsNonNegative(operand1Interval.LowerBound)) { + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMaxValue(expression.Type)); + } else { + return typeInterval; + } + + case OperationCode.Rem: + if (operand1Interval == null) return typeInterval; + if (MetadataExpressionHelper.IsIntegralZero(operand2)) + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMinusOne(expression.Type), includesDivisionByZero: true); + if (Evaluator.IsPositive(operand2)) { + if (Evaluator.IsNonNegative(operand1Interval.LowerBound)) { + if (Evaluator.IsNumericallyGreaterThan(operand2, operand1Interval.UpperBound)) { + //Since the divisor is always greater than the dividend, the remainder is always equal to the dividend. + return operand1Interval; + } + return new Interval(Evaluator.GetZero(expression.Type), operand2); + } + } + return typeInterval; + + case OperationCode.Rem_Un: + if (operand1Interval == null) return typeInterval; + if (MetadataExpressionHelper.IsIntegralZero(operand2)) + return new Interval(Evaluator.GetZero(expression.Type), Evaluator.GetMinusOne(expression.Type), includesDivisionByZero: true); + var cop1lb = Evaluator.ConvertToUnsigned(operand1Interval.LowerBound); + var cop1up = Evaluator.ConvertToUnsigned(operand1Interval.UpperBound); + var op1up = Evaluator.Max(cop1lb, cop1up); + var op1lb = Evaluator.Min(cop1lb, cop1up); + var op2 = Evaluator.ConvertToUnsigned(operand2); + if (Evaluator.IsNumericallyGreaterThan(op2, op1up)) { + //Since the divisor is always greater than the dividend, the remainder is always equal to the dividend. + return new Interval(op1lb, op1up); + } + return new Interval(Evaluator.GetZero(expression.Type), op2); + + case OperationCode.Shl: { + var operand2a = Evaluator.Evaluate(operation, Evaluator.GetOne(expression.Type), operand2); + if (Evaluator.IsNumericallyLessThan(operand2a, operand2)) + return new Interval(typeInterval.LowerBound, typeInterval.UpperBound, includesOverflow: true); + operation = mul_ovf; + operand2 = operand2a; + goto case OperationCode.Mul; + } + + case OperationCode.Shr: + case OperationCode.Shr_Un: + goto case OperationCode.Div; + + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: { + if (operand1Interval == null) return typeInterval; + var op = operation.OperationCode == OperationCode.Sub ? sub_ovf : operation; + var lowerBound = Evaluator.Evaluate(op, operand1Interval.LowerBound, operand2); + var upperBound = Evaluator.Evaluate(op, operand1Interval.UpperBound, operand2); + var underflow = lowerBound == Dummy.Constant; + var overflow = upperBound == Dummy.Constant; + if (underflow || overflow) { lowerBound = typeInterval.LowerBound; upperBound = typeInterval.UpperBound; } + return new Interval(lowerBound, upperBound, includesUnderflow: underflow, includesOverflow: overflow); + } + + case OperationCode.Xor: + return typeInterval; + + //Instructions that conditionally affect control flow + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + return null; + + //Instructions that cause side-effect that we do not currently track. + case OperationCode.Call: + case OperationCode.Calli: + case OperationCode.Callvirt: + case OperationCode.Cpblk: + case OperationCode.Cpobj: + case OperationCode.Initblk: + case OperationCode.Stfld: + case OperationCode.Stind_I: + case OperationCode.Stind_I1: + case OperationCode.Stind_I2: + case OperationCode.Stind_I4: + case OperationCode.Stind_I8: + case OperationCode.Stind_R4: + case OperationCode.Stind_R8: + case OperationCode.Stind_Ref: + case OperationCode.Stobj: + return null; + + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We do NOT attempt to compute the compile time values at this time. + case OperationCode.Ldelem_I: + case OperationCode.Ldelem_I1: + case OperationCode.Ldelem_I2: + case OperationCode.Ldelem_I4: + case OperationCode.Ldelem_I8: + case OperationCode.Ldelem_R4: + case OperationCode.Ldelem_R8: + case OperationCode.Ldelem_Ref: + case OperationCode.Ldelem_U1: + case OperationCode.Ldelem_U2: + case OperationCode.Ldelem_U4: + case OperationCode.Ldelema: + return typeInterval; + + default: + Contract.Assume(false); + return null; + } + } + + private static Interval/*?*/ TryToGetAsIntervalN(Instruction expression, Instruction operand1, Instruction[] operands2ToN, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(operand1 != null); + Contract.Requires(operands2ToN != null); + Contract.Requires(mappings != null); + + return GetIntervalFor(expression.Type, mappings); + } + + private static Interval/*?*/ TryToGetAsJoinedInterval(Instruction expression, AiBasicBlock referringBlock, AiBasicBlock joinBlock, + AiBasicBlock definingBlock, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(expression != null); + Contract.Requires(referringBlock != null); + Contract.Requires(mappings != null); + + var exprDefBlock = mappings.GetDefiningBlockFor(expression); + if (exprDefBlock == null) return null; + var predecessorBlocks = exprDefBlock.Predecessors; + Contract.Assume(predecessorBlocks != null); + var operand1 = expression.Operand1 as Instruction; + if (operand1 == null) return null; + Contract.Assume(predecessorBlocks.Count > 0); + var block1 = predecessorBlocks[0]; + var interval1 = TryToGetAsInterval(operand1, referringBlock, exprDefBlock, block1, mappings); + if (interval1 == null) return null; + var operand2 = expression.Operand2 as Instruction; + if (operand2 != null) { + Contract.Assume(predecessorBlocks.Count > 1); + var block2 = predecessorBlocks[1]; + if (operand2.Operation.OperationCode == OperationCode.Add) { + var singleton1 = interval1.GetAsSingleton(); + if (singleton1 != null && operand2.Operand2 is Instruction) { + var increment = mappings.GetCompileTimeConstantValueFor((Instruction)operand2.Operand2); + if (increment != null && Evaluator.IsPositive(increment)) { + var typeInterval = GetIntervalFor(expression.Type, mappings)??new Interval(); + var result = new Interval(singleton1, typeInterval.UpperBound); + Contract.Assume(operand2.Operand1 is Instruction); + result = Narrow(operand2, result, referringBlock, exprDefBlock, block2, mappings); + return result; + } + } + } else if (operand2.Operation.OperationCode == OperationCode.Sub) { + var singleton1 = interval1.GetAsSingleton(); + if (singleton1 != null && operand2.Operand2 is Instruction) { + var decrement = mappings.GetCompileTimeConstantValueFor((Instruction)operand2.Operand2); + if (decrement != null && Evaluator.IsPositive(decrement)) { + var typeInterval = GetIntervalFor(expression.Type, mappings)??new Interval(); + var result = new Interval(typeInterval.LowerBound, singleton1); + Contract.Assume(operand2.Operand1 is Instruction); + result = Narrow(operand2, result, referringBlock, exprDefBlock, block2, mappings); + return result; + } + } + } + var interval2 = TryToGetAsInterval(operand2, referringBlock, exprDefBlock, block2, mappings); + if (interval2 == null) return null; + return interval1.Join(interval2); + } + var operands2ToN = expression.Operand2 as Instruction[]; + if (operands2ToN == null) return interval1; + var interval = interval1; + for (int i = 0, n = operands2ToN.Length; i < n; i++) { + var operandi = operands2ToN[i]; + Contract.Assume(operandi != null); + Contract.Assume(predecessorBlocks.Count > i+1); + var blocki = predecessorBlocks[i+1]; + var intervalI = TryToGetAsInterval(operandi, referringBlock, exprDefBlock, blocki, mappings); + if (intervalI == null) return null; + interval = interval.Join(intervalI); + } + return interval; + } + + private static Interval/*?*/ GetIntervalFor(ITypeReference type, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(type != null); + Contract.Requires(mappings != null); + + switch (type.TypeCode) { + case PrimitiveTypeCode.Int16: return mappings.Int16Interval; + case PrimitiveTypeCode.Int32: return mappings.Int32Interval; + case PrimitiveTypeCode.Int64: return mappings.Int64Interval; + case PrimitiveTypeCode.Int8: return mappings.Int8Interval; + case PrimitiveTypeCode.UInt16: return mappings.UInt16Interval; + case PrimitiveTypeCode.UInt32: return mappings.UInt32Interval; + case PrimitiveTypeCode.UInt64: return mappings.UInt64Interval; + case PrimitiveTypeCode.UInt8: return mappings.UInt8Interval; + } + return null; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() { + return "[" + this.LowerBound.ToString() + "..." + this.UpperBound.ToString() + "]"; + } + + } +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/AbstractInterpretation.cs b/Metadata/Sources/AnalyisUtilities/AbstractInterpretation.cs new file mode 100644 index 0000000..6a4c426 --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/AbstractInterpretation.cs @@ -0,0 +1,1329 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; +using Microsoft.Cci.MutableCodeModel; +using System; + +namespace Microsoft.Cci.Analysis { + + /// + /// Provides a static method that interprets a control and data flow graph in order to compute the concrete (if possible) or abstract values of the local variables defined + /// in the graph. This analysis requires the graph to be in SSA format. + /// + /// A type that is a subtype of Microsoft.Cci.Analysis.SSABasicBlock. + /// A type that is a subtype of Microsoft.Cci.Analysis.Instruction and that has a default constructor. + public class AbstractInterpreter + where BasicBlock : Microsoft.Cci.Analysis.AiBasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + /// + /// Interprets the instructions in the given control and data flow graph, computing an abstract value for each instruction that produces a value. + /// In some cases, it is also possible to compute concrete values. When these values are stored into variables, this is recorded in the given + /// environment. The input graph is required to be in SSA format, so the recorded values will be accurate at all points where the variables + /// are accessed in the graph. + /// + /// A control and data flow graph in SSA form, to interpret in order to compute concrete and abstract values for variables in the graph. + /// Presents information derived from a simple control flow graph. For example, traversal orders, predecessors, dominators and dominance frontiers. + /// Provides several maps from expressions to concrete and abstract values. + public static void InterpretUsingAbstractValues(ControlAndDataFlowGraph cdfg, ControlGraphQueries cfgQueries, ValueMappings mappings) { + Contract.Requires(cdfg != null); + Contract.Requires(cfgQueries != null); + Contract.Requires(mappings != null); + var interpreter = new AbstractInterpreter(cdfg, cfgQueries, mappings); + interpreter.Interpret(); + } + + /// + /// Creates an object that interprets a control and data flow graph in order to compute the concrete (if possible) or abstract values of the local variables defined + /// in the graph. This analysis requires the graph to be in SSA format. + /// + /// A control and data flow graph in SSA form, to interpret in order to compute concrete and abstract values for variables in the graph. + /// Presents information derived from a simple control flow graph. For example, traversal orders, predecessors, dominators and dominance frontiers. + /// Provides several maps from expressions to concrete and abstract values. + private AbstractInterpreter(ControlAndDataFlowGraph cdfg, ControlGraphQueries cfgQueries, ValueMappings mappings) { + Contract.Requires(cdfg != null); + Contract.Requires(cfgQueries != null); + Contract.Requires(mappings != null); + this.cdfg = cdfg; + this.cfgQueries = cfgQueries; + this.mappings = mappings; + this.expressionCanonicalizer = new ExpressionCanonicalizer(mappings); + } + + /// + /// A control and data flow graph in SSA form, to interpret in order to compute concrete and abstract values for variables in the graph. + /// + ControlAndDataFlowGraph cdfg; + /// + /// Presents information derived from a simple control flow graph. For example, traversal orders, predecessors, dominators and dominance frontiers. + /// + ControlGraphQueries cfgQueries; + /// + /// Provides several maps from expressions to concrete and abstract values. + /// + ValueMappings mappings; + /// + /// Keeps track of all of the blocks that should be considered again by the interpreter. A block can show up in here more than once. + /// + Queue blocksToInterpret = new Queue(); + /// + /// A set of all basic blocks that have been interpreted at least once by the abstract interpreter. + /// A block can be interpreted more than once when loops are encountered and a fix point is being calculated. + /// The main reason to have this set is to allow the fix point to be detected in such a way that each + /// block is interpreted at least once. + /// + SetOfObjects blocksInterpretedAtLeastOnce = new SetOfObjects(); + /// + /// Zero or more expressions that are currently known to be true. + /// + List constraints = new List(2); + /// + /// The basic block that is currently being interpreted. + /// + BasicBlock currentBlock = new BasicBlock(); + /// + /// A special kind of hash table that maps expressions to equivalent expressions that have been canonicalized. + /// + ExpressionCanonicalizer expressionCanonicalizer; + /// + /// True if the last interpreted instruction was an unconditional branch. (This includes condition branches whose conditions are compile time constants.) + /// + bool lastStatementWasUnconditionalTransfer; + /// + /// The set of blocks that are not known at this point to be unreachable from the block currently being interpreted. + /// Add the target block to this set whenever interpreting a branch instruction that might be taken. Also add the fall through block + /// if the last interpreted instruction was not an unconditional transfer. + /// + SetOfObjects liveSuccessorBlocks = new SetOfObjects(); + + MultiHashtable readVariables = new MultiHashtable(); + DoubleHashtable inputExpressions = new DoubleHashtable(); + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.cdfg != null); + Contract.Invariant(this.cfgQueries != null); + Contract.Invariant(this.mappings != null); + Contract.Invariant(this.blocksToInterpret != null); + Contract.Invariant(this.blocksInterpretedAtLeastOnce != null); + Contract.Invariant(this.constraints != null); + Contract.Invariant(this.currentBlock != null); + Contract.Invariant(this.expressionCanonicalizer != null); + Contract.Invariant(this.liveSuccessorBlocks != null); + Contract.Invariant(this.readVariables != null); + Contract.Invariant(this.inputExpressions != null); + } + + /// + /// Starting with each root, run through all reachable blocks and abstractly interpret their instructions. + /// At the end of this, every instruction that computes a value will be associated with an expression that + /// represents that value and optionally with a compile time constant if the value is known at compile time. + /// Note that expressions are just instructions plus their data flow graphs (for which an instruction happens to be a convenient object model) + /// but the "expression object" associated with a particular instruction is not necessarily the same object as the instruction, + /// because the instruction, or a predecessor instruction in its data flow graph, may be equivalent to an earlier instruction. + /// In other words common subexpression elimination is performed. This reason for this is to make it easier to reason about the relationship between + /// different expressions. + /// + private void Interpret() { + //First set up the fall through block map. Note that this map is not quite a subset of the successor map, since we set it up for all + //blocks, including blocks that end on an unconditional transfers that do not target the blocks that immediately follow them. + BasicBlock previousBlock = null; + foreach (var block in this.cdfg.AllBlocks) { + Contract.Assume(block != null); + if (previousBlock != null) + previousBlock.FallThroughBlock = block; + previousBlock = block; + //Also track the predecessors so that we don't always need to pass around this.cfgQueries. + //This may not seem so bad, but it would require a lot more type parameters in a lot more places. + //Instead, we pay a runtime price for compile time ease. + var predecessors = this.cfgQueries.PredeccessorsFor(block); + block.Predecessors = predecessors; + //We also need to make the order of operands in stack setup instructions deterministic (i.e. operands must appear in the order of the precedessor blocks). + if (block.OperandStack.Count > 0) MakeStackSetupInstructionsDeterministic(block, predecessors); + } + //Starting with each root, run through all blocks that are reachable from that root. + foreach (var rootblock in this.cdfg.RootBlocks) { + rootblock.ConstraintsAtEntry.Add(new List(0)); + this.blocksToInterpret.Enqueue(rootblock); + while (this.blocksToInterpret.Count > 0) { + var block = this.blocksToInterpret.Dequeue(); + Contract.Assume(block != null); + this.Interpret(block); //This will add blocks that can be reached from block to this.blocksToInterpret. + } + } + } + + private static void MakeStackSetupInstructionsDeterministic(BasicBlock block, Sublist predecessors) { + Contract.Requires(block != null); + int pc = predecessors.Count; + if (pc == 0) return; //Can only happen if block is the first block of an exception handler + var predecessorOperands = new Microsoft.Cci.Analysis.Instruction[pc]; + for (int i = 0, n = block.OperandStack.Count; i < n; i++) { + var stackSetupInstruction = block.OperandStack[i]; + predecessorOperands[0] = stackSetupInstruction.Operand1; + if (pc == 2) { + Contract.Assume(stackSetupInstruction.Operand2 is Microsoft.Cci.Analysis.Instruction); + predecessorOperands[1] = (Microsoft.Cci.Analysis.Instruction)stackSetupInstruction.Operand2; + } else { + Contract.Assume(stackSetupInstruction.Operand2 is Microsoft.Cci.Analysis.Instruction[]); + var operands2ToN = stackSetupInstruction.Operand2 as Microsoft.Cci.Analysis.Instruction[]; + if (operands2ToN != null) { + Contract.Assume(operands2ToN.Length == pc-1); + for (int k = 1; k < pc; k++) predecessorOperands[k] = operands2ToN[k-1]; + } + } + Array.Sort(predecessorOperands, (x, y) => ((int)x.Operation.Offset) - (int)y.Operation.Offset); + stackSetupInstruction.Operand1 = predecessorOperands[0]; + if (pc == 2) + stackSetupInstruction.Operand2 = predecessorOperands[1]; + else { + var operands2ToN = stackSetupInstruction.Operand2 as Microsoft.Cci.Analysis.Instruction[]; + Contract.Assume(operands2ToN != null); + Contract.Assume(pc == operands2ToN.Length); + for (int k = 1; k < pc; k++) operands2ToN[k-1] = predecessorOperands[k]; + } + } + } + + /// + /// Abstractly interprets the instructions in the given block, if appropriate. + /// By appropriate we mean that either the block has not yet been interpreted, + /// or that that something has changed in the environment since we last interpreted + /// the block. In the latter case, we interpret once again. As it happens, no precision + /// is gained since the expressions that are produced by this interpreter retain full precision + /// at all times. What we do gain from the additional iterations is further normalization of expressions + /// that can lead to better subexpression identification and more constant folding. + /// + /// + private void Interpret(BasicBlock block) { + Contract.Requires(block != null); + + //First set up the SSA local environment for this block and at the same time see if we actually need to interpret this block again. + bool joinsAreTheSameAsLastTime = this.blocksInterpretedAtLeastOnce.Contains(block); + if (!joinsAreTheSameAsLastTime) this.SetupReadVariablesFor(block); + if (block.Joins != null) { + foreach (var join in block.Joins) { + var newLocal = join.NewLocal as INamedEntity; + Contract.Assume(newLocal != null); + if (this.mappings.GetCompileTimeConstantValueFor(newLocal) != null) continue; //nothing is going to change because of this variable. + this.mappings.SetDefininingJoinFor(newLocal, join); + var currentValue = this.mappings.GetDefiningExpressionFor(newLocal); //will be normalized via common subexpresion elimination. + var newValue = this.UnionOfJoinedValues(join, block); //will be normalized via common subexpresion elimination. + this.mappings.SetDefininingBlockFor(newValue, block); + if (currentValue != newValue) { + //The last time we interpreted this block (if we did do so), we did it with an expression for newLocal + //that is different from the expression that we now get (after we have (again) interpreted "other" blocks that branch back to this block). + if (currentValue == null || !this.mappings.IsRecursive(currentValue)) { + this.mappings.SetDefininingExpressionFor(newLocal, newValue); + joinsAreTheSameAsLastTime = false; + if (currentValue != null && Evaluator.Contains(newValue, currentValue)) + this.mappings.SetIsRecursive(newValue); + } else { + //If newValue contains currentValue, then this is a loop variable and further iterations will not tell us more about it. + } + } + } + } + if (this.AllReadVariablesHaveTheSameDefiningExpressionsAsLastTime(block) && joinsAreTheSameAsLastTime) { + return; //We have reached a fixpoint. Another pass through this block will not be helpful. + } + this.blocksInterpretedAtLeastOnce.Add(block); + this.currentBlock = block; + block.IntervalForExpression.Clear(); + block.SatSolverContext = null; + + //Get constraints from the predecessors + Contract.Assume(block.Predecessors != null); + var pcount = block.Predecessors.Count; + while (block.ConstraintsAtEntry.Count < pcount) block.ConstraintsAtEntry.Add(null); + for (int i = 0; i < pcount; i++) { + var predecessor = block.Predecessors[i] as BasicBlock; + Contract.Assume(predecessor != null); + var predSuccessors = this.cdfg.SuccessorsFor(predecessor); + for (int j = 0, m = predSuccessors.Count; j < m; j++) { + if (predSuccessors[j] == block && predecessor.ConstraintsAtExit.Count > j) + block.ConstraintsAtEntry[i] = new List(predecessor.ConstraintsAtExit[j]); + } + } + + for (int i = 0, n = block.OperandStack.Count; i < n; i++) { + Contract.Assume(n == block.OperandStack.Count); + var stackSetupInstruction = block.OperandStack[i]; + if (stackSetupInstruction.Operation.Value == null) + stackSetupInstruction.Operation = new Operation() { Value = Dummy.LocalVariable, Offset = (uint)i }; + if (stackSetupInstruction.Operand1 != null) + { + var canon = this.expressionCanonicalizer.GetCanonicalExpression(stackSetupInstruction, (Instruction)stackSetupInstruction.Operand1, + stackSetupInstruction.Operand2 as Instruction, stackSetupInstruction.Operand2 as Instruction[]); + stackSetupInstruction.Operand1 = canon.Operand1; + stackSetupInstruction.Operand2 = canon.Operand2; + } + this.mappings.SetCanonicalExpressionFor(stackSetupInstruction, this.expressionCanonicalizer.GetCanonicalExpression(stackSetupInstruction)); + this.mappings.SetDefininingBlockFor(stackSetupInstruction, block); + } + //Combine the constraints established by the predecessor blocks into a single disjunction of conjunctions. + Instruction disjunction = null; + for (int i = 0, n = block.ConstraintsAtEntry.Count; i < n; i++) { + Instruction conjunction = null; + var constraintsFromParticularPredecessor = block.ConstraintsAtEntry[i]; + if (constraintsFromParticularPredecessor == null) continue; + for (int j = 0, m = constraintsFromParticularPredecessor.Count; j < m; j++) { + var constraint = constraintsFromParticularPredecessor[j]; + if (constraint == null) continue; + if (conjunction == null) + conjunction = constraint; + else { + var and = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.And }, Operand1 = conjunction, Operand2 = constraint, Type = constraint.Type }; + conjunction = this.expressionCanonicalizer.GetCanonicalExpression(and, conjunction, constraint); + } + } + if (conjunction == null) { + //constraintsFromParticularPredecessor is empty, so this is effectively false and the combined constraint is useless + disjunction = null; + break; + } + if (disjunction == null) + disjunction = conjunction; + else { + var or = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Or }, Operand1 = disjunction, Operand2 = conjunction, Type = conjunction.Type }; + disjunction = this.expressionCanonicalizer.GetCanonicalExpression(or, disjunction, conjunction); + } + } + this.constraints.Clear(); + if (disjunction != null && !(disjunction.Operation.OperationCode == OperationCode.Ldc_I4 && disjunction.Operation.Value is int && 1 == (int)disjunction.Operation.Value)) + this.constraints.Add(disjunction); + + //Now run through the instructions until we encounter an unconditional branch or we reach the end of the block. + var successors = this.cdfg.SuccessorsFor(block); + this.liveSuccessorBlocks.Clear(); + this.lastStatementWasUnconditionalTransfer = false; + foreach (var instruction in block.Instructions) { + Contract.Assume(instruction != null); + this.lastStatementWasUnconditionalTransfer = false; + this.Interpret(instruction, block); + if (this.lastStatementWasUnconditionalTransfer) break; + } + if (!this.lastStatementWasUnconditionalTransfer) { + var fallThroughBlock = block.FallThroughBlock as BasicBlock; + if (fallThroughBlock != null) { //it might be null if this block is the very last one and it erroneously does not end on an unconditional branch. + var i = successors.Find(fallThroughBlock); + if (i >= 0) { + this.liveSuccessorBlocks.Add(fallThroughBlock); + if (this.constraints.Count > 0) { + while (block.ConstraintsAtExit.Count <= i) block.ConstraintsAtExit.Add(new List(4)); + var constraintsAtExit = block.ConstraintsAtExit[i]; + Contract.Assume(constraintsAtExit != null); + constraintsAtExit.Clear(); + constraintsAtExit.AddRange(this.constraints); + } + } + } + } + + //Now add all of the blocks that can actually be reached from this block to this.blocksToInterpet. + //Note that this block might be its own successor. The code at the start of this method ensures that the resulting loop terminates. + foreach (var successor in this.cdfg.SuccessorsFor(block)) { + Contract.Assume(successor != null); + if (this.liveSuccessorBlocks.Contains(successor)) { + this.blocksToInterpret.Enqueue(successor); + } + } + } + + private bool AllReadVariablesHaveTheSameDefiningExpressionsAsLastTime(BasicBlock block) { + var result = true; + foreach (INamedEntity variable in this.readVariables.GetValuesFor(block.Offset)) { + var key = (uint)variable.Name.UniqueKey; + var lastExpression = this.inputExpressions.Find(block.Offset, key); + Contract.Assume(lastExpression != null); + var currentExpression = this.mappings.GetDefiningExpressionFor(variable); + Contract.Assume(currentExpression != null); + if (lastExpression != currentExpression && !this.mappings.IsRecursive(lastExpression)) { + this.inputExpressions.Add(block.Offset, key, currentExpression); + result = false; + } + } + return result; + } + + private void SetupReadVariablesFor(BasicBlock block) { + Contract.Requires(block != null); + var writtenVariables = new SetOfUints(); + foreach (var instruction in block.Instructions) { + var operation = instruction.Operation; + switch (operation.OperationCode) { + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: { + var variable = operation.Value as INamedEntity; + if (variable == null) break; //happens when ldarg_0 refers to the this pointer of an instance method + var key = (uint)variable.Name.UniqueKey; + if (!writtenVariables.Contains(key)) { + this.readVariables.Add(block.Offset, variable); + var definingExpression = this.mappings.GetDefiningExpressionFor(variable); + if (definingExpression == null) { + definingExpression = this.GetCanonicalizedLoadInstruction(operation.Value??Dummy.ParameterDefinition); + this.mappings.SetDefininingExpressionFor(variable, definingExpression); + } + this.inputExpressions.Add(block.Offset, key, definingExpression); + } + break; + } + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + case OperationCode.Starg: + case OperationCode.Starg_S: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: { + var variable = operation.Value as INamedEntity; + Contract.Assume(variable != null); + writtenVariables.Add((uint)variable.Name.UniqueKey); + break; + } + } + } + } + + /// + /// Interprets the given instruction using abstract values and associates an abstract value with the instruction (if it computes a value). + /// Also updates this.liveSuccessorBlocks if instruction is a branch that might be taken. Sets this.lastStatementWasUnconditionalTransfer to + /// true if instruction is an unconditional branch (which can be a conditional branch whose condition is a compile time constant). + /// + [ContractVerification(false)] + private void Interpret(Instruction instruction, BasicBlock block) { + Contract.Requires(instruction != null); + Contract.Requires(block != null); + + if (this.mappings.GetCompileTimeConstantValueFor(instruction) != null) return; //Already as simple as we can get. + + Contract.Assume(instruction.Operand1 is Instruction || instruction.Operand1 == null); //The type of the field is Microsoft.Cci.Analysis.Instruction. + var operand1 = (Instruction)instruction.Operand1; + if (operand1 == null) + this.InterpretNullary(instruction); + else { + var operand2 = instruction.Operand2 as Instruction; + if (operand2 != null) { + this.InterpretBinary(instruction, operand1, operand2, block); + } else { + var operandArray = instruction.Operand2 as Instruction[]; + if (operandArray == null) + this.InterpretUnary(instruction, operand1, block); + else + this.InterpretNary(instruction, operand1, operandArray); + } + } + } + + /// + /// Interprets an instruction with no operands, using abstract values from the SSA environment if appropriate. + /// + private void InterpretNullary(Instruction instruction) { + Contract.Requires(instruction != null); + + var operation = instruction.Operation; + switch (operation.OperationCode) { + //Instructions that are side effect free and whose results can be cached and reused, but whose result values can never be known at compile time. + case OperationCode.Arglist: + case OperationCode.Ldftn: + case OperationCode.Ldtoken: + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + case OperationCode.Ldsflda: + this.mappings.SetCanonicalExpressionFor(instruction, this.expressionCanonicalizer.GetCanonicalExpression(instruction)); + break; + + //Instructions that transfer control to a successor block. + case OperationCode.Br: + case OperationCode.Br_S: + case OperationCode.Leave: + case OperationCode.Leave_S: + Contract.Assume(operation.Value is uint); //This is an informally specified property of the Metadata model. + var targetOffset = (uint)instruction.Operation.Value; + var targetBlock = this.cdfg.BlockFor[targetOffset]; + this.liveSuccessorBlocks.Add(targetBlock); + this.lastStatementWasUnconditionalTransfer = true; + var i = this.cdfg.SuccessorsFor(this.currentBlock).Find(targetBlock); + if (i >= 0) { + while (this.currentBlock.ConstraintsAtExit.Count <= i) this.currentBlock.ConstraintsAtExit.Add(new List(4)); + var constraintsForTarget = this.currentBlock.ConstraintsAtExit[i]; + Contract.Assume(constraintsForTarget != null); + constraintsForTarget.Clear(); + constraintsForTarget.AddRange(this.constraints); + } + break; + + //Instructions that are side-effect free and that result in compile time constant values. + case OperationCode.Ldc_I4: + case OperationCode.Ldc_I4_0: + case OperationCode.Ldc_I4_1: + case OperationCode.Ldc_I4_2: + case OperationCode.Ldc_I4_3: + case OperationCode.Ldc_I4_4: + case OperationCode.Ldc_I4_5: + case OperationCode.Ldc_I4_6: + case OperationCode.Ldc_I4_7: + case OperationCode.Ldc_I4_8: + case OperationCode.Ldc_I4_M1: + case OperationCode.Ldc_I4_S: + case OperationCode.Ldc_I8: + case OperationCode.Ldc_R4: + case OperationCode.Ldc_R8: + case OperationCode.Ldnull: + case OperationCode.Ldstr: + var constval = Evaluator.GetAsCompileTimeConstantValue(instruction); + this.mappings.SetCompileTimeConstantValueFor(instruction, constval); + var constLoad = this.expressionCanonicalizer.GetAsCanonicalizedLoadConstant(constval, instruction); + this.mappings.SetCanonicalExpressionFor(instruction, constLoad); + this.mappings.SetCompileTimeConstantValueFor(constLoad, constval); + break; + + //Instructions that are side-effect free and that *could* result in compile time constant values. + //We attempt to compute the compile time values. + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + var variable = operation.Value as INamedEntity; + var constantValue = variable == null ? null : this.mappings.GetCompileTimeConstantValueFor(variable); + if (constantValue != null) { + this.mappings.SetCompileTimeConstantValueFor(instruction, constantValue); + constLoad = this.expressionCanonicalizer.GetAsCanonicalizedLoadConstant(constantValue, instruction); + this.mappings.SetCanonicalExpressionFor(instruction, constLoad); + this.mappings.SetCompileTimeConstantValueFor(constLoad, constantValue); + } else { + var definingExpression = variable == null ? null : this.mappings.GetDefiningExpressionFor(variable); + if (definingExpression != null) + this.mappings.SetCanonicalExpressionFor(instruction, definingExpression); + else { + var canonicalExpr = this.GetCanonicalizedLoadInstruction(operation.Value??Dummy.ParameterDefinition); + this.mappings.SetCanonicalExpressionFor(instruction, canonicalExpr); + } + } + break; + + //Instructions that are side-effect free and that *could* result in compile time constant values. + //We do NOT attempt to compute the compile time values at this time. + case OperationCode.Ldsfld: + //TODO: track and map this when not affected by a volatile modifier. + break; + + case OperationCode.Call: + case OperationCode.Endfinally: + case OperationCode.Newobj: + case OperationCode.Nop: + break; + + //Instructions that transfer control out of the method being interpreted. + case OperationCode.Jmp: + case OperationCode.Rethrow: + case OperationCode.Ret: + this.lastStatementWasUnconditionalTransfer = true; + break; + + //Instruction modifier to track in the future. + case OperationCode.Volatile_: + //TODO: track its occurrence and disable any CSE on the next field load. None happens right now. + break; + + default: + Contract.Assume(false); + break; + } + } + + /// + /// Interprets an instruction with a single operand (which was computed by a previous instruction), using values from the SSA environment. + /// + private void InterpretUnary(Instruction unaryInstruction, Instruction operand1, BasicBlock block) { + Contract.Requires(unaryInstruction != null); + Contract.Requires(operand1 != null); + Contract.Requires(block != null); + + IOperation operation = unaryInstruction.Operation; + switch (operation.OperationCode) { + //Instructions that cause or depend on side-effects. We'll keep them as is. + case OperationCode.Box: + case OperationCode.Call: + case OperationCode.Calli: + case OperationCode.Callvirt: + case OperationCode.Initobj: + case OperationCode.Ldobj: + case OperationCode.Localloc: + case OperationCode.Mkrefany: + case OperationCode.Newarr: + case OperationCode.Newobj: + case OperationCode.Pop: + case OperationCode.Stsfld: + case OperationCode.Unbox: + case OperationCode.Unbox_Any: + break; + + //Insructions that are side effect free and whose results can be cached and reused, but whose result values can never be known at compile time. + case OperationCode.Castclass: + case OperationCode.Ckfinite: + case OperationCode.Isinst: + case OperationCode.Ldlen: + case OperationCode.Ldvirtftn: + case OperationCode.Refanytype: + case OperationCode.Refanyval: //TODO: If we track object contents, we might be able to know the value of this at compile time. + case OperationCode.Sizeof: + this.mappings.SetCanonicalExpressionFor(unaryInstruction, this.expressionCanonicalizer.GetCanonicalExpression(unaryInstruction, operand1)); + break; + + //Instructions that conditionally affect control flow. We keep them as is, but update the control flow appropriately. + case OperationCode.Brfalse: + case OperationCode.Brfalse_S: + operand1.Type = operand1.Type.PlatformType.SystemBoolean; + var canonicalOperand1 = this.mappings.GetCanonicalExpressionFor(operand1); + if (canonicalOperand1 != null) { + var cv1 = this.TryToGetCompileTimeConstantValueFor(canonicalOperand1); + if (cv1 != null) { + if (MetadataExpressionHelper.IsIntegralNonzero(cv1)) break; + this.lastStatementWasUnconditionalTransfer = true; + } else { + var result = this.mappings.CheckIfExpressionIsTrue(canonicalOperand1, block); + if (result != null) { + if (result.Value) break; + this.lastStatementWasUnconditionalTransfer = true; + } + } + canonicalOperand1 = this.ConvertUnionIntoConditionIfPossible(canonicalOperand1, block); + canonicalOperand1 = this.expressionCanonicalizer.GetCanonicalExpression(new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Not }, Type = operand1.Type }, operand1); + } + goto addTargetToLiveSuccessorSet; + case OperationCode.Brtrue: + case OperationCode.Brtrue_S: + operand1.Type = operand1.Type.PlatformType.SystemBoolean; + canonicalOperand1 = this.mappings.GetCanonicalExpressionFor(operand1); + if (canonicalOperand1 != null) { + var cv1 = this.TryToGetCompileTimeConstantValueFor(canonicalOperand1); + if (cv1 != null) { + if (MetadataExpressionHelper.IsIntegralZero(cv1)) break; + this.lastStatementWasUnconditionalTransfer = true; + } else { + var result = this.mappings.CheckIfExpressionIsTrue(canonicalOperand1, block); + if (result != null) { + if (!result.Value) break; + this.lastStatementWasUnconditionalTransfer = true; + } + } + canonicalOperand1 = this.ConvertUnionIntoConditionIfPossible(canonicalOperand1, block); + } + addTargetToLiveSuccessorSet: + Contract.Assume(unaryInstruction.Operation.Value is uint); //This is an informally specified property of the Metadata model. + var targetOffset = (uint)unaryInstruction.Operation.Value; + var targetBlock = this.cdfg.BlockFor[targetOffset]; + var i = this.cdfg.SuccessorsFor(this.currentBlock).Find(targetBlock); + if (i >= 0) { + while (this.currentBlock.ConstraintsAtExit.Count <= i) this.currentBlock.ConstraintsAtExit.Add(new List(4)); + var constraintsForTarget = this.currentBlock.ConstraintsAtExit[i]; + Contract.Assume(constraintsForTarget != null); + constraintsForTarget.Clear(); + constraintsForTarget.AddRange(this.constraints); + if (operand1.Type.TypeCode == PrimitiveTypeCode.Boolean) + constraintsForTarget.Add(canonicalOperand1??operand1); + } + if (operand1.Type.TypeCode == PrimitiveTypeCode.Boolean) { + if (canonicalOperand1 != null) { + var invertedBranchCondition = this.expressionCanonicalizer.GetCanonicalExpression(new Instruction() { Operation = new Operation { OperationCode = OperationCode.Not }, Type = operand1.Type }, canonicalOperand1); + this.constraints.Add(invertedBranchCondition); + } else { + this.constraints.Add(new Instruction() { Operation = new Operation { OperationCode = OperationCode.Not }, Operand1 = operand1, Type = operand1.Type }); + } + } + this.liveSuccessorBlocks.Add(targetBlock); + break; + + //Instructions that are side-effect free and that could result in concrete compile time values. + //We attempt to compute the compile time values. + case OperationCode.Conv_I: + case OperationCode.Conv_I1: + case OperationCode.Conv_I2: + case OperationCode.Conv_I4: + case OperationCode.Conv_I8: + case OperationCode.Conv_Ovf_I: + case OperationCode.Conv_Ovf_I_Un: + case OperationCode.Conv_Ovf_I1: + case OperationCode.Conv_Ovf_I1_Un: + case OperationCode.Conv_Ovf_I2: + case OperationCode.Conv_Ovf_I2_Un: + case OperationCode.Conv_Ovf_I4: + case OperationCode.Conv_Ovf_I4_Un: + case OperationCode.Conv_Ovf_I8: + case OperationCode.Conv_Ovf_I8_Un: + case OperationCode.Conv_Ovf_U: + case OperationCode.Conv_Ovf_U_Un: + case OperationCode.Conv_Ovf_U1: + case OperationCode.Conv_Ovf_U1_Un: + case OperationCode.Conv_Ovf_U2: + case OperationCode.Conv_Ovf_U2_Un: + case OperationCode.Conv_Ovf_U4: + case OperationCode.Conv_Ovf_U4_Un: + case OperationCode.Conv_Ovf_U8: + case OperationCode.Conv_Ovf_U8_Un: + case OperationCode.Conv_R_Un: + case OperationCode.Conv_R4: + case OperationCode.Conv_R8: + case OperationCode.Conv_U: + case OperationCode.Conv_U1: + case OperationCode.Conv_U2: + case OperationCode.Conv_U4: + case OperationCode.Conv_U8: + case OperationCode.Dup: + case OperationCode.Neg: + case OperationCode.Not: + canonicalOperand1 = this.mappings.GetCanonicalExpressionFor(operand1); + if (canonicalOperand1 != null) { + //If the operand has been canonicalized (i.e. if it is side-effect free) we can canonicalize the unary expression and potentially constant fold it. + var canonicalExpression = this.expressionCanonicalizer.GetCanonicalExpression(unaryInstruction, canonicalOperand1); + this.mappings.SetCanonicalExpressionFor(unaryInstruction, canonicalExpression); + var cv1 = this.TryToGetCompileTimeConstantValueFor(operand1); + if (cv1 != null) { + var cr = Evaluator.Evaluate(operation, cv1); + if (cr != null) { + this.mappings.SetCompileTimeConstantValueFor(unaryInstruction, cr); + this.mappings.SetCompileTimeConstantValueFor(canonicalExpression, cr); + } + } + } + break; + + //Instructions that can be cached in the absence of volatility, aliasing and multiple writes. + case OperationCode.Ldfld: + case OperationCode.Ldflda: + case OperationCode.Ldind_I: + case OperationCode.Ldind_I1: + case OperationCode.Ldind_I2: + case OperationCode.Ldind_I4: + case OperationCode.Ldind_I8: + case OperationCode.Ldind_R4: + case OperationCode.Ldind_R8: + case OperationCode.Ldind_Ref: + case OperationCode.Ldind_U1: + case OperationCode.Ldind_U2: + case OperationCode.Ldind_U4: + //TODO: track the values that pointers point to + break; + + //Instructions that affect the SSA environment. + case OperationCode.Starg: + case OperationCode.Starg_S: + case OperationCode.Stloc: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + { + var variable = operation.Value as INamedEntity; + if (variable != null) + { + var cv1 = this.TryToGetCompileTimeConstantValueFor(operand1); + if (cv1 != null) + { + var canon = this.GetCanonicalizedLoadInstruction(variable); + canon = this.expressionCanonicalizer.GetAsCanonicalizedLoadConstant(cv1, canon); + this.mappings.SetDefininingExpressionFor(variable, canon); + this.mappings.SetCompileTimeConstantValueFor(variable, cv1); + this.mappings.SetCompileTimeConstantValueFor(canon, cv1); + } + else + { + var canon = this.mappings.GetCanonicalExpressionFor(operand1); + if (canon != null) + { + var oldExpr = this.mappings.GetDefiningExpressionFor(variable); + if (oldExpr == null || !this.mappings.IsRecursive(oldExpr)) + { + this.mappings.SetDefininingExpressionFor(variable, canon); + if (oldExpr != null && Evaluator.Contains(canon, oldExpr)) + { + this.mappings.SetIsRecursive(canon); + if (this.constraints.Count > 0 && this.constraints[0] != null) + { + this.constraints[0] = Purger.Purge(this.constraints[0], variable, this.expressionCanonicalizer); + } + } + } + } + } + } + } + break; + + //Instructions that transfer control out of the method being interpreted. + case OperationCode.Ret: + case OperationCode.Throw: + this.lastStatementWasUnconditionalTransfer = true; + break; + + //Instructions that are side-effect free and that *could* result in compile time constant values. + //We attempt to compute the compile time values. + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + { + var variable = operation.Value as INamedEntity; + var constantValue = variable == null ? null : this.mappings.GetCompileTimeConstantValueFor(variable); + if (constantValue != null) + { + this.mappings.SetCompileTimeConstantValueFor(unaryInstruction, constantValue); + var constLoad = this.expressionCanonicalizer.GetAsCanonicalizedLoadConstant(constantValue, unaryInstruction); + this.mappings.SetCanonicalExpressionFor(unaryInstruction, constLoad); + this.mappings.SetCompileTimeConstantValueFor(constLoad, constantValue); + } + else + { + var definingExpression = variable == null ? null : this.mappings.GetDefiningExpressionFor(variable); + if (definingExpression != null) + this.mappings.SetCanonicalExpressionFor(unaryInstruction, definingExpression); + else + { + var canonicalExpr = this.GetCanonicalizedLoadInstruction(operation.Value ?? Dummy.ParameterDefinition); + this.mappings.SetCanonicalExpressionFor(unaryInstruction, canonicalExpr); + } + } + } + break; + + default: + Contract.Assume(false); + break; + } + + } + + private Instruction ConvertUnionIntoConditionIfPossible(Instruction expression, AiBasicBlock block) { + Contract.Requires(expression != null); + Contract.Requires(block != null); + Contract.Ensures(Contract.Result() != null); + + if (expression.Operation.OperationCode != OperationCode.Nop || !(expression.Operation.Value is INamedEntity)) return expression; //Not a union + var operand1 = expression.Operand1 as Instruction; + if (operand1 == null) return expression; + var operand2 = expression.Operand2 as Instruction; + if (operand2 == null) return expression; + if (block.ConstraintsAtEntry.Count != 2) return expression; + var cv1 = this.mappings.GetCompileTimeConstantValueFor(operand1); + if (cv1 != null) { + if (MetadataExpressionHelper.IsIntegralOne(cv1) && operand2.Type.TypeCode == PrimitiveTypeCode.Boolean) { + var predecessorConstraints = block.ConstraintsAtEntry[0]; + Contract.Assume(predecessorConstraints != null); + if (predecessorConstraints.Count == 0) return expression; + var condition1 = predecessorConstraints[predecessorConstraints.Count-1]; + Contract.Assume(condition1 != null); + var disjuntion = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Or }, Operand1 = condition1, Operand2 = operand2, Type = operand2.Type }; + return this.expressionCanonicalizer.GetCanonicalExpression(disjuntion, condition1, operand2); + } + } else { + var cv2 = this.mappings.GetCompileTimeConstantValueFor(operand2); + if (cv2 != null) { + if (MetadataExpressionHelper.IsIntegralOne(cv2) && operand1.Type.TypeCode == PrimitiveTypeCode.Boolean) { + var predecessorConstraints = block.ConstraintsAtEntry[1]; + Contract.Assume(predecessorConstraints != null); + if (predecessorConstraints.Count == 0) return expression; + var condition2 = predecessorConstraints[predecessorConstraints.Count-1]; + Contract.Assume(condition2 != null); + var disjuntion = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Or }, Operand1 = condition2, Operand2 = operand1, Type = operand1.Type }; + return this.expressionCanonicalizer.GetCanonicalExpression(disjuntion, condition2, operand1); + } + } + } + return expression; + } + + /// + /// Interprets an instruction with two operands (which were computed by previous instructions), using values from the SSA environment. + /// + private Instruction InterpretBinary(Instruction binaryInstruction, Instruction operand1, Instruction operand2, AiBasicBlock block) { + Contract.Requires(binaryInstruction != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Requires(block != null); + Contract.Ensures(Contract.Result() != null); + + IOperation operation = binaryInstruction.Operation; + switch (operation.OperationCode) { + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We attempt to compute the compile time values. + case OperationCode.Add: + goto case OperationCode.Add_Ovf_Un; + case OperationCode.Add_Ovf: + goto case OperationCode.Add_Ovf_Un; + case OperationCode.Add_Ovf_Un: + case OperationCode.And: + case OperationCode.Ceq: + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + case OperationCode.Clt: + case OperationCode.Clt_Un: + case OperationCode.Div: + case OperationCode.Div_Un: + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + case OperationCode.Or: + case OperationCode.Rem: + case OperationCode.Rem_Un: + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + case OperationCode.Xor: + var canonicalOperand1 = this.mappings.GetCanonicalExpressionFor(operand1); + var canonicalOperand2 = this.mappings.GetCanonicalExpressionFor(operand2); + if (canonicalOperand1 != null && canonicalOperand2 != null) { + //Both operands are side effect free, so we can try to canonicalize the binary expression and perhaps constant fold it. + var canonicalExpression = this.expressionCanonicalizer.GetCanonicalExpression(binaryInstruction, canonicalOperand1, canonicalOperand2); + this.mappings.SetCanonicalExpressionFor(binaryInstruction, canonicalExpression); + var cv1 = this.TryToGetCompileTimeConstantValueFor(operand1); + if (cv1 != null) { + var cv2 = this.TryToGetCompileTimeConstantValueFor(operand2); + if (cv2 != null) { + var cr = Evaluator.Evaluate(operation, cv1, cv2); + if (cr != null) { + this.mappings.SetCompileTimeConstantValueFor(binaryInstruction, cr); + this.mappings.SetCompileTimeConstantValueFor(canonicalExpression, cr); + break; + } + } else { + var cr = Evaluator.Evaluate(operation, cv1, operand2, this.mappings); + if (cr != null) { + this.mappings.SetCompileTimeConstantValueFor(binaryInstruction, cr); + this.mappings.SetCompileTimeConstantValueFor(canonicalExpression, cr); + break; + } + } + } else { + var cv2 = this.TryToGetCompileTimeConstantValueFor(operand2); + if (cv2 != null) { + var cr = Evaluator.Evaluate(operation, operand1, cv2, this.mappings); + if (cr != null) { + this.mappings.SetCompileTimeConstantValueFor(canonicalExpression, cr); + this.mappings.SetCompileTimeConstantValueFor(binaryInstruction, cr); + break; + } + } else { + var cr = Evaluator.Evaluate(operation, operand1, operand2, this.mappings); + if (cr != null) { + this.mappings.SetCompileTimeConstantValueFor(binaryInstruction, cr); + this.mappings.SetCompileTimeConstantValueFor(canonicalExpression, cr); + break; + } + } + } + } + break; + + //Instructions that conditionally affect control flow + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + Contract.Assume(binaryInstruction.Operation.Value is uint); //This is an informally specified property of the Metadata model. + var targetOffset = (uint)binaryInstruction.Operation.Value; + var targetBlock = this.cdfg.BlockFor[targetOffset]; + canonicalOperand1 = this.mappings.GetCanonicalExpressionFor(operand1); + canonicalOperand2 = this.mappings.GetCanonicalExpressionFor(operand2); + Instruction branchCondition = binaryInstruction; + if (canonicalOperand1 != null && canonicalOperand2 != null) { + branchCondition = this.expressionCanonicalizer.GetCanonicalExpression(binaryInstruction, canonicalOperand1, canonicalOperand2); + branchCondition.Type = binaryInstruction.Type.PlatformType.SystemBoolean; + var cv1 = this.TryToGetCompileTimeConstantValueFor(operand1); + if (cv1 != null) { + var cv2 = this.TryToGetCompileTimeConstantValueFor(operand2); + if (cv2 != null) { + var cr = Evaluator.Evaluate(operation, cv1, cv2); + if (cr != null) { + if (MetadataExpressionHelper.IsIntegralZero(cr)) { + //We now know this instruction does not affect control flow, so cache the result so that we don't consider this instruction again. + this.mappings.SetCompileTimeConstantValueFor(binaryInstruction, cr); + this.mappings.SetCompileTimeConstantValueFor(branchCondition, cr); + break; + } + this.lastStatementWasUnconditionalTransfer = true; + } + } + } + } + if (!this.lastStatementWasUnconditionalTransfer) { + var result = this.mappings.CheckIfExpressionIsTrue(branchCondition, block); + if (result != null) { + if (!result.Value) break; + this.lastStatementWasUnconditionalTransfer = true; + } + } + var i = this.cdfg.SuccessorsFor(this.currentBlock).Find(targetBlock); + if (i >= 0) { + while (this.currentBlock.ConstraintsAtExit.Count <= i) this.currentBlock.ConstraintsAtExit.Add(new List(4)); + var constraintsForTarget = this.currentBlock.ConstraintsAtExit[i]; + Contract.Assume(constraintsForTarget != null); + constraintsForTarget.Clear(); + constraintsForTarget.AddRange(this.constraints); + constraintsForTarget.Add(branchCondition); + } + var invertedBranchCondition = this.expressionCanonicalizer.GetCanonicalExpression( + new Instruction() { Operation = new Operation { OperationCode = OperationCode.Not }, Type = binaryInstruction.Type }, branchCondition); + this.constraints.Add(invertedBranchCondition); + this.liveSuccessorBlocks.Add(targetBlock); + break; + + //Instructions that cause side-effect that we do not currently track. + case OperationCode.Call: + case OperationCode.Calli: + case OperationCode.Callvirt: + case OperationCode.Cpblk: + case OperationCode.Cpobj: + case OperationCode.Initblk: + case OperationCode.Newobj: + case OperationCode.Stfld: + case OperationCode.Stind_I: + case OperationCode.Stind_I1: + case OperationCode.Stind_I2: + case OperationCode.Stind_I4: + case OperationCode.Stind_I8: + case OperationCode.Stind_R4: + case OperationCode.Stind_R8: + case OperationCode.Stind_Ref: + case OperationCode.Stobj: + break; + + //Instructions that are side-effect free and cacheable and that could result in compile time values. + //We do NOT attempt to compute the compile time values at this time. + case OperationCode.Ldelem_I: + case OperationCode.Ldelem_I1: + case OperationCode.Ldelem_I2: + case OperationCode.Ldelem_I4: + case OperationCode.Ldelem_I8: + case OperationCode.Ldelem_R4: + case OperationCode.Ldelem_R8: + case OperationCode.Ldelem_Ref: + case OperationCode.Ldelem_U1: + case OperationCode.Ldelem_U2: + case OperationCode.Ldelem_U4: + case OperationCode.Ldelema: + //if (this.expressionCanonicalizer.HasCachedEntryFor(operand1) && this.expressionCanonicalizer.HasCachedEntryFor(operand2)) { + // this.mappings.SetCanonicalExpressionFor(binaryInstruction, this.expressionCanonicalizer.GetCanonicalExpression(binaryInstruction, operand1, operand2)); + //} + break; + + default: + Contract.Assume(false); + break; + } + + return binaryInstruction; + } + + /// + /// Interprets an instruction with three or more operands. + /// + private Instruction InterpretNary(Instruction naryInstruction, Instruction operand1, Instruction[] operands2toN) { + Contract.Requires(naryInstruction != null); + Contract.Requires(operand1 != null); + Contract.Requires(operands2toN != null); + + IOperation operation = naryInstruction.Operation; + switch (operation.OperationCode) { + //Instructions that cause or depend on side-effects. We'll keep them as is. + case OperationCode.Array_Addr: + case OperationCode.Array_Create: + case OperationCode.Array_Create_WithLowerBound: + case OperationCode.Array_Get: + case OperationCode.Array_Set: + case OperationCode.Call: //TODO: traverse into calls, if possible, but just one level deep. Also parameterize with a contract provider. + case OperationCode.Calli: + case OperationCode.Callvirt: + case OperationCode.Newobj: + case OperationCode.Stelem: + case OperationCode.Stelem_I: + case OperationCode.Stelem_I1: + case OperationCode.Stelem_I2: + case OperationCode.Stelem_I4: + case OperationCode.Stelem_I8: + case OperationCode.Stelem_R4: + case OperationCode.Stelem_R8: + case OperationCode.Stelem_Ref: + //TODO: update the environment to track the element values. + break; + + default: + Contract.Assume(false); + break; + } + + return naryInstruction; + } + + /// + /// If the value that the given expression evaluates to at runtime is known at compile time, return that value as an IMetadataConstant instance. + /// If it is known at compile time that the expression will fail at runtime, or if its runtime value is not known at compile time, the result of this method is null. + /// + /// An instruction that results in a value at runtime. + private IMetadataConstant/*?*/ TryToGetCompileTimeConstantValueFor(Instruction expression) { + Contract.Requires(expression != null); + + var cc = this.mappings.GetCompileTimeConstantValueFor(expression); + if (cc == Dummy.Constant) return null; //Dummy.Constant signals that the expression is known to fail at runtime. + if (cc == null) { + var canonicalExpression = this.expressionCanonicalizer.GetCanonicalExpression(expression); + if (canonicalExpression != expression) { + cc = this.mappings.GetCompileTimeConstantValueFor(canonicalExpression); + if (cc == Dummy.Constant) return null; + } + } + return cc; + } + + /// + /// Returns a canonicalized expression with a Dummy operation value and a set of operands that + /// represent all of the values that may be assigned to this variable by preceding instructions. + /// + /// + /// When canonicalizing expressions that contain unions as subexpressions, + /// distribute over unions so that the union is always the outer expression of the canonical expression. + /// For example i + union(j, k) becomes union(i+j, i+k) + /// and union (i, j) + union (j, k) becomes union(i+j, i+k, j+j, j+k). + /// + [ContractVerification(false)] + private Instruction UnionOfJoinedValues(Join join, BasicBlock block) { + Contract.Requires(join != null); + Contract.Requires(block != null); + Contract.Ensures(Contract.Result() != null); + + var predecessors = this.cfgQueries.PredeccessorsFor(block); + var n = predecessors.Count; + var joinOperation = new Operation() { Value = join.NewLocal }; + var result = new Instruction() { Operation = joinOperation, Type = join.Type }; + if (n > 0) { + result.Operand1 = this.GetDefininingExpressionFor(join, predecessors[0], block); + if (n > 1) { + result.Operand2 = this.GetDefininingExpressionFor(join, predecessors[1], block); + if (n > 2) { + var operands2ToN = new Instruction[n-1]; + operands2ToN[0] = (Instruction)result.Operand2; + result.Operand2 = operands2ToN; + for (int i = 1; i < n-1; i++) { + operands2ToN[i] = this.GetDefininingExpressionFor(join, predecessors[i+1], block); + } + } + } + } + return this.expressionCanonicalizer.GetCanonicalExpression(result, (Instruction)result.Operand1, result.Operand2 as Instruction, result.Operand2 as Instruction[]); + } + + private Instruction GetDefininingExpressionFor(Join join, AiBasicBlock predecessor, BasicBlock blockDefiningTheJoin) { + Contract.Requires(join != null); + Contract.Requires(predecessor != null); + + var ssaName = join.Join1; + if (join.Block1 != predecessor) { + ssaName = join.Join2; + if (join.Block2 != predecessor) { + ssaName = null; + if (join.OtherBlocks != null) { + for (int i = 0, n = join.OtherBlocks.Count; i < n; i++) { + if (join.OtherBlocks[i] == predecessor) { + Contract.Assume(join.OtherJoins != null && join.OtherJoins.Count == n); + ssaName = join.OtherJoins[i]; + break; + } + } + } + } + } + if (ssaName == null) { + Contract.Assume(join.OriginalLocal is INamedEntity); + ssaName = (INamedEntity)join.OriginalLocal; + } + var result = this.mappings.GetDefiningExpressionFor(ssaName); + if (result == null) + result = this.GetCanonicalizedLoadInstruction(ssaName); + return result; + } + + /// + /// Given a local or a parameter, return a canonicalized expression that will load the value of the local or parameter at runtime. + /// + private Instruction GetCanonicalizedLoadInstruction(object localOrParameter) { + Contract.Requires(localOrParameter != null); + Contract.Ensures(Contract.Result() != null); + + OperationCode operationCode; + ITypeReference type = null; + var local = localOrParameter as ILocalDefinition; + if (local != null) { + type = local.Type; operationCode = OperationCode.Ldloc; + } else { + Contract.Assume(localOrParameter is IParameterDefinition); + var parameter = (IParameterDefinition)localOrParameter; + type = parameter.Type; + if (type is Dummy) { + Contract.Assume(parameter == Dummy.ParameterDefinition); //Should be the this argument. + type = this.cdfg.MethodBody.MethodDefinition.ContainingTypeDefinition; + localOrParameter = null; + operationCode = OperationCode.Ldarg_0; + } else { + operationCode = OperationCode.Ldarg; + } + } + var loadVar = new Operation() { OperationCode = operationCode, Value = localOrParameter }; + var result = new Instruction() { Operation = loadVar, Type = type }; + return this.expressionCanonicalizer.GetCanonicalExpression(result); + } + + + } + + /// + /// + /// + /// + public class AiBasicBlock : Microsoft.Cci.Analysis.SSABasicBlock + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + /// + /// The block, if any, that follows this block in the sequence of instructions. It may not be reachable from the block. + /// If this block is the last block, the value will be null. + /// + public AiBasicBlock/*?*/ FallThroughBlock; + + /// + /// A non null (but delay initialized) list of the blocks that transfer control to this block. + /// + public ISimpleReadonlyList> Predecessors; + + /// + /// A table keeping track of Interval values that have been computed for instructions in the context + /// of this block. (An expression can result in a different interval or constnat in this block because this + /// block might have entry constraints that narrow the interval.) + /// + internal Hashtable ConstantForExpression { + get { + Contract.Ensures(Contract.Result>() != null); + if (this.constantForExpression == null) + this.constantForExpression = new Hashtable(); + return this.constantForExpression; + } + } + Hashtable/*?*/ constantForExpression; + + /// + /// A list of lists where every element list is a list of constraints that were established by one of the predecessors to this block. + /// + public List> ConstraintsAtEntry { + get { + Contract.Ensures(Contract.Result>>() != null); + if (this.constraintsAtEntry == null) + this.constraintsAtEntry = new List>(4); + return this.constraintsAtEntry; + } + } + private List>/*?*/ constraintsAtEntry; + + /// + /// A list of lists where every element list is a list of constraints that hold when transfer controls to a successor block. + /// The indices of the outer list match the indices of the Successor list. + /// + public List> ConstraintsAtExit { + get { + Contract.Ensures(Contract.Result>>() != null); + if (this.constraintsAtExit == null) + this.constraintsAtExit = new List>(4); + return constraintsAtExit; + } + } + private List>/*?*/ constraintsAtExit; + + /// + /// A table keeping track of Interval values that have been computed for instructions in the context + /// of this block. (An expression can result in a different interval or constnat in this block because this + /// block might have entry constraints that narrow the interval.) + /// + internal Hashtable IntervalForExpression { + get { + Contract.Ensures(Contract.Result>() != null); + if (this.intervalForExpression == null) + this.intervalForExpression = new Hashtable(); + return this.intervalForExpression; + } + } + Hashtable/*?*/ intervalForExpression; + + /// + /// A context in which to keep the constraints at entry in SAT solver format, so that they can be used + /// together with particular expressions to determine satisfiablity. + /// + internal ISatSolverContext/*?*/ SatSolverContext { + get { + return this.satSolverContext; + } + set { + this.satSolverContext = value; + } + } + ISatSolverContext/*?*/ satSolverContext; + } + +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/AnalysisUtilities.csproj b/Metadata/Sources/AnalyisUtilities/AnalysisUtilities.csproj new file mode 100644 index 0000000..d7c9903 --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/AnalysisUtilities.csproj @@ -0,0 +1,126 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {E8B89C56-383A-4A1D-9EEE-E5FE9FC9DE03} + Library + Properties + Microsoft.Cci.Analysis + Microsoft.Cci.Analysis.AnalysisUtilities + v4.0 + 512 + + 0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + False + False + True + False + False + False + True + True + False + True + False + True + True + True + True + False + False + False + False + True + True + True + False + True + + + + + -show unreached + + True + Full + Build + 2 + bin\Debug\Microsoft.Cci.Analysis.AnalysisUtilities.XML + + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Microsoft.Cci.Analysis.AnalysisUtilities.XML + + + true + + + ..\Common\InterimKey.snk + + + + Build\Version.cs + + + + + + + + + + + + + + + {4A34A3C5-6176-49D7-A4C5-B2B671247F8F} + MetadataHelper + + + {33CAB640-0D03-43DF-81BD-22CDC6C0A597} + MetadataModel + + + {319E151C-8F33-49E7-81C9-30F02F9BA90A} + MutableMetadataModel + + + {4B0054FD-124A-4037-9965-BDB55E6BF389} + SourceModel + + + {2596EFB0-87AE-42CE-89EB-84F35D6350D2} + ControlAndDataFlowGraph + + + + + + + + \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/Evaluator.cs b/Metadata/Sources/AnalyisUtilities/Evaluator.cs new file mode 100644 index 0000000..8236c21 --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/Evaluator.cs @@ -0,0 +1,3367 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; +using Microsoft.Cci.MutableCodeModel; +using System; + +namespace Microsoft.Cci.Analysis { + + /// + /// Provides methods that carry out IL operations with compile time constant operand values at compile time, if possible. + /// + public static class Evaluator { + + internal static bool Contains(Instruction definingExpression, Instruction oldDefiningExpression) { + Contract.Requires(definingExpression != null); + Contract.Requires(oldDefiningExpression != null); + + if (definingExpression == oldDefiningExpression) return true; + if (definingExpression.Operand1 == null) return false; + if (definingExpression.Operand1 == oldDefiningExpression) return true; + if (Contains((Instruction)definingExpression.Operand1, oldDefiningExpression)) return true; + if (definingExpression.Operand2 == null) return false; + if (definingExpression.Operand2 == oldDefiningExpression) return true; + var operand2 = definingExpression.Operand2 as Instruction; + if (operand2 != null) return Contains(operand2, oldDefiningExpression); + Contract.Assume(definingExpression.Operand2 is Instruction[]); + var operands2toN = (Instruction[])definingExpression.Operand2; + foreach (var operandi in operands2toN) { + if (operandi == oldDefiningExpression) return true; + Contract.Assume(operandi != null); + if (Contains(operandi, oldDefiningExpression)) return true; + } + return false; + } + + + /// + /// + /// + /// + /// + public static IMetadataConstant ConvertToUnsigned(IMetadataConstant operand) { + Contract.Requires(operand != null); + Contract.Ensures(Contract.Result() != null); + + ITypeReference type = Dummy.TypeReference; + object value = null; + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + value = (byte)(sbyte)operand.Value; + type = operand.Type.PlatformType.SystemUInt8; + break; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + value = (ushort)(short)operand.Value; + type = operand.Type.PlatformType.SystemUInt16; + break; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + value = (uint)(int)operand.Value; + type = operand.Type.PlatformType.SystemUInt32; + break; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + value = (ulong)(long)operand.Value; + type = operand.Type.PlatformType.SystemUInt64; + break; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + value = (UIntPtr)(long)(IntPtr)operand.Value; + type = operand.Type.PlatformType.SystemUIntPtr; + break; + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt8: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt64: + case PrimitiveTypeCode.UIntPtr: + return operand; + default: + return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = type }; + } + + /// + /// Returns a compile time constant that is the same as the given constant, except that numeric values + /// are decremented by the smallest interval appropriate to its type. If the decrement causes underflow to + /// happen, the result is just the given constant. + /// + public static IMetadataConstant DecreaseBySmallestInterval(IMetadataConstant operand) { + Contract.Requires(operand != null); + Contract.Ensures(Contract.Result() != null); + + object value = null; + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + var sb = (sbyte)operand.Value; + if (sb == sbyte.MinValue) return operand; + value = --sb; + break; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + var s = (short)operand.Value; + if (s == short.MinValue) return operand; + value = --s; + break; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + var i = (int)operand.Value; + if (i == int.MinValue) return operand; + value = --i; + break; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + var l = (long)operand.Value; + if (l == long.MinValue) return operand; + value = --l; + break; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + var iptr = (long)(IntPtr)operand.Value; + if (iptr == long.MinValue) return operand; + value = (IntPtr)(--iptr); + break; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand.Value is byte); + var b = (byte)operand.Value; + if (b == byte.MinValue) return operand; + value = --b; + break; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand.Value is ushort); + var us = (ushort)operand.Value; + if (us == ushort.MinValue) return operand; + value = --us; + break; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand.Value is uint); + var ui = (uint)operand.Value; + if (ui == uint.MinValue) return operand; + value = --ui; + break; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand.Value is ulong); + var ul = (ulong)operand.Value; + if (ul == ulong.MinValue) return operand; + value = --ul; + break; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand.Value is UIntPtr); + var uptr = (ulong)(UIntPtr)operand.Value; + if (uptr == ulong.MinValue) return operand; + value = (UIntPtr)(--uptr); + break; + case PrimitiveTypeCode.Float32: + Contract.Assume(operand.Value is float); + var f = (float)operand.Value; + var incr = float.Epsilon; + var fincr = f - incr; + while (fincr == f) { + incr *= 2; + fincr -= incr; + } + if (float.IsNegativeInfinity(fincr)) return operand; + value = fincr; + break; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand.Value is double); + var d = (double)operand.Value; + var incrd = double.Epsilon; + var dincr = d + incrd; + while (dincr == d) { + incrd *= 2; + dincr += incrd; + } + if (double.IsNegativeInfinity(dincr)) return operand; + value = dincr; + break; + default: + return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = operand.Type }; + } + + /// + /// Evaluates the given unary operation for the given compile time constant. + /// If the operation will fail if carried out at runtime, the result is Dummy.Constant. + /// If the result of the operation cannot be known until runtime, for example because the size of IntPtr is only known at runtime, then the result is null. + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Evaluate(IOperation operation, IMetadataConstant operand) { + Contract.Requires(operation != null); + Contract.Requires(operand != null); + + if (operand == Dummy.Constant) return Dummy.Constant; + var platfomType = operand.Type.PlatformType; + object resultValue = null; + ITypeReference resultType = null; + switch (operand.Type.TypeCode) { + //these cases all push a 32-bit value on the operand stack, with sign propagation as appropriate. + //The operations then treat the 32-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand.Value is bool); + var u4 = ((bool)operand.Value) ? 1u : 0u; + var i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand.Value is byte); + u4 = (byte)operand.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Char: + Contract.Assume(operand.Value is char); + u4 = (char)operand.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand.Value is ushort); + u4 = (ushort)operand.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand.Value is uint); + u4 = (uint)operand.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + i4 = (int)(sbyte)operand.Value; + u4 = (byte)i4; + goto do4; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + i4 = (short)operand.Value; + u4 = (ushort)i4; + goto do4; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + i4 = (int)operand.Value; + u4 = (uint)i4; + goto do4; + + //These cases push 32 bit or 64 bit values on the stack, depending on the platform, with sign propagation as appropriate. + //The operations then treat the 32/64-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + var i8 = (long)(IntPtr)operand.Value; + var u8 = (ulong)(IntPtr)operand.Value; + goto do8; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand.Value is IntPtr); + i8 = (long)(IntPtr)operand.Value; + u8 = (ulong)(IntPtr)operand.Value; + goto do8; + + //These cases push 64 bit values on the stack, with sign propagation as appropriate. + //The operations then treat the 64-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + i8 = (long)operand.Value; + u8 = (ulong)i8; + goto do8; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand.Value is ulong); + u8 = (ulong)operand.Value; + i8 = (long)u8; + goto do8; + + do4: + u8 = (ulong)u4; + i8 = (long)i4; + do8: + switch (operation.OperationCode) { + case OperationCode.Conv_I1: + resultValue = (sbyte)i8; + resultType = platfomType.SystemInt8; + break; + case OperationCode.Conv_I2: + resultValue = (short)i8; + resultType = platfomType.SystemInt16; + break; + case OperationCode.Conv_I4: + resultValue = (int)i8; + resultType = platfomType.SystemInt32; + break; + case OperationCode.Conv_I8: + resultValue = (long)i8; + resultType = platfomType.SystemInt64; + break; + case OperationCode.Conv_R4: + resultValue = (float)i8; + resultType = platfomType.SystemFloat32; + break; + case OperationCode.Conv_R8: + resultValue = (double)i8; + resultType = platfomType.SystemFloat64; + break; + case OperationCode.Conv_U1: + resultValue = (byte)i8; + resultType = platfomType.SystemUInt8; + break; + case OperationCode.Conv_U2: + resultValue = (ushort)i8; + resultType = platfomType.SystemUInt16; + break; + case OperationCode.Conv_U4: + resultValue = (uint)i8; + resultType = platfomType.SystemUInt32; + break; + case OperationCode.Conv_U8: + resultValue = (ulong)i8; + resultType = platfomType.SystemUInt64; + break; + case OperationCode.Conv_I: + resultValue = (IntPtr)i8; + resultType = platfomType.SystemIntPtr; + break; + case OperationCode.Conv_U: + resultValue = (UIntPtr)i8; + resultType = platfomType.SystemUIntPtr; + break; + case OperationCode.Conv_R_Un: + resultValue = (double)u8; + resultType = platfomType.SystemFloat64; + break; + + case OperationCode.Conv_Ovf_I1: + if (i8 < sbyte.MinValue || i8 > sbyte.MaxValue) return Dummy.Constant; //known to fail. + goto case OperationCode.Conv_I1; + case OperationCode.Conv_Ovf_I2: + if (i8 < short.MinValue || i8 > short.MaxValue) return Dummy.Constant; //known to fail. + goto case OperationCode.Conv_I2; + case OperationCode.Conv_Ovf_I4: + if (i8 < int.MinValue || i8 > int.MaxValue) return Dummy.Constant; //known to fail. + goto case OperationCode.Conv_I4; + case OperationCode.Conv_Ovf_I8: + goto case OperationCode.Conv_I8; + case OperationCode.Conv_Ovf_U1: + if (i8 < 0 || i8 > byte.MaxValue) return Dummy.Constant; //known to fail. + goto case OperationCode.Conv_U1; + case OperationCode.Conv_Ovf_U2: + if (i8 < 0 || i8 > ushort.MaxValue) return Dummy.Constant; //known to fail. + goto case OperationCode.Conv_U2; + case OperationCode.Conv_Ovf_U4: + if (i8 < 0 || i8 > uint.MaxValue) return Dummy.Constant; //known to fail. + goto case OperationCode.Conv_U4; + case OperationCode.Conv_Ovf_U8: + if (i8 < 0) return Dummy.Constant; //known to fail. + goto case OperationCode.Conv_U8; + case OperationCode.Conv_Ovf_I: + if (i8 < int.MinValue || i8 > int.MaxValue) return Dummy.Constant; //might fail. + goto case OperationCode.Conv_I; + case OperationCode.Conv_Ovf_U: + if (i8 < 0) return Dummy.Constant; //known to fail. + if (i8 > uint.MaxValue) return Dummy.Constant; //might fail. + goto case OperationCode.Conv_U; + + case OperationCode.Conv_Ovf_I1_Un: + if (u8 > (long)sbyte.MaxValue) return Dummy.Constant; //known to fail. + resultValue = (sbyte)u8; + resultType = platfomType.SystemInt8; + break; + case OperationCode.Conv_Ovf_I2_Un: + if (u8 > (long)short.MaxValue) return Dummy.Constant; //known to fail. + resultValue = (short)u8; + resultType = platfomType.SystemInt16; + break; + case OperationCode.Conv_Ovf_I4_Un: + if (u8 > int.MaxValue) return Dummy.Constant; //known to fail. + resultValue = (int)u8; + resultType = platfomType.SystemInt32; + break; + case OperationCode.Conv_Ovf_I8_Un: + if (u8 > long.MaxValue) return Dummy.Constant; //known to fail. + resultValue = (long)u8; + resultType = platfomType.SystemInt64; + break; + case OperationCode.Conv_Ovf_U1_Un: + if (u8 > byte.MaxValue) return Dummy.Constant; //known to fail. + resultValue = (byte)u8; + resultType = platfomType.SystemUInt8; + break; + case OperationCode.Conv_Ovf_U2_Un: + if (u8 > ushort.MaxValue) return Dummy.Constant; //known to fail. + resultValue = (ushort)u8; + resultType = platfomType.SystemUInt16; + break; + case OperationCode.Conv_Ovf_U4_Un: + if (u8 > uint.MaxValue) return Dummy.Constant; //known to fail. + resultValue = u8; + resultType = platfomType.SystemUInt32; + break; + case OperationCode.Conv_Ovf_U8_Un: + resultValue = u8; + resultType = platfomType.SystemUInt64; + break; + case OperationCode.Conv_Ovf_I_Un: + if (u8 > int.MaxValue) return Dummy.Constant; //might fail. + resultValue = (IntPtr)u8; + resultType = platfomType.SystemIntPtr; + break; + case OperationCode.Conv_Ovf_U_Un: + if (u8 > uint.MaxValue) return Dummy.Constant; //might fail. + resultValue = (UIntPtr)u8; + resultType = platfomType.SystemUIntPtr; + break; + + case OperationCode.Dup: + return operand; + + case OperationCode.Neg: + var ni8 = i8 == long.MinValue ? i8 : -i8; + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int64: + case PrimitiveTypeCode.UInt64: + resultValue = ni8; + resultType = platfomType.SystemInt64; + break; + case PrimitiveTypeCode.IntPtr: + case PrimitiveTypeCode.UIntPtr: + resultValue = (IntPtr)ni8; + resultType = platfomType.SystemIntPtr; + break; + default: + resultValue = (int)ni8; + resultType = platfomType.SystemInt32; + break; + } + break; + + case OperationCode.Not: + var ci8 = ~i8; + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int64: + case PrimitiveTypeCode.UInt64: + resultValue = ci8; + resultType = platfomType.SystemInt64; + break; + case PrimitiveTypeCode.IntPtr: + case PrimitiveTypeCode.UIntPtr: + resultValue = (IntPtr)ci8; + resultType = platfomType.SystemIntPtr; + break; + default: + resultValue = (int)ci8; + resultType = platfomType.SystemInt32; + break; + } + break; + + } + break; + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand.Value is float); + var f = (float)operand.Value; + double d = (double)f; + i8 = (long)(int)f; + u8 = (ulong)(uint)(int)f; + goto doDouble; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand.Value is double); + d = (double)operand.Value; + i8 = (long)d; + u8 = (ulong)i8; + doDouble: + switch (operation.OperationCode) { + case OperationCode.Conv_R4: + resultValue = (float)d; + resultType = platfomType.SystemFloat32; + break; + case OperationCode.Conv_R8: + resultValue = d; + resultType = platfomType.SystemFloat64; + break; + case OperationCode.Conv_R_Un: + resultValue = (double)u8; + resultType = platfomType.SystemFloat64; + break; + + case OperationCode.Dup: + return operand; + + default: + goto do8; //Only for operations that deal with floats after they have been converted to integers. + + } + break; + + case PrimitiveTypeCode.Pointer: + case PrimitiveTypeCode.Reference: + //TODO: we might know that the pointer is 0 + return Dummy.Constant; //We only known the value of the pointer at runtime. + + case PrimitiveTypeCode.String: + case PrimitiveTypeCode.NotPrimitive: + //TODO: might be able to remove a castclass and fold an isinst. + return Dummy.Constant; //We only known the value of the pointer at runtime. + } + + if (resultValue != null && resultType != null) { + var result = new MetadataConstant() { Value = resultValue, Type = resultType }; + if (result.Locations == null) result.Locations = new List(1); + result.Locations.Add(operation.Location); + return result; + } + return null; //force runtime evaluation. + } + + /// + /// + /// + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Evaluate(IOperation operation, IMetadataConstant operand1, IMetadataConstant operand2) { + Contract.Requires(operation != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return Dummy.Constant; + var platfomType = operand1.Type.PlatformType; + switch (operand1.Type.TypeCode) { + //these cases all push a 32-bit value on the operand stack, with sign propagation as appropriate. + //The operations then treat the 32-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + var u4 = ((bool)operand1.Value) ? 1u : 0u; + var i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + u4 = (byte)operand1.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + u4 = (char)operand1.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + u4 = (ushort)operand1.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + u4 = (uint)operand1.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + i4 = (int)(sbyte)operand1.Value; + u4 = (byte)i4; + goto do4; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + i4 = (short)operand1.Value; + u4 = (ushort)i4; + goto do4; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + i4 = (int)operand1.Value; + u4 = (uint)i4; + do4: + return Evaluate(operation, u4, i4, operand2); + + //These cases push 32 bit or 64 bit values on the stack, depending on the platform, with sign propagation as appropriate. + //The operations then treat the 32/64-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + var i8 = (long)(IntPtr)operand1.Value; + var u8 = (ulong)(IntPtr)operand1.Value; + goto do8; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is IntPtr); + i8 = (long)(IntPtr)operand1.Value; + u8 = (ulong)(IntPtr)operand1.Value; + goto do8; + + //These cases push 64 bit values on the stack, with sign propagation as appropriate. + //The operations then treat the 64-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + i8 = (long)operand1.Value; + u8 = (ulong)i8; + goto do8; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + u8 = (ulong)operand1.Value; + i8 = (long)u8; + do8: + return Evaluate(operation, u8, i8, operand2); + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var f = (float)operand1.Value; + double d = (double)f; + i8 = (long)(int)f; + u8 = (ulong)(uint)(int)f; + goto doDouble; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d = (double)operand1.Value; + i8 = (long)d; + u8 = (ulong)i8; + doDouble: + return Evaluate(operation, d, operand2); + + case PrimitiveTypeCode.Pointer: + case PrimitiveTypeCode.Reference: + //TODO: we might know that the pointer is 0 + return Dummy.Constant; //We only known the value of the pointer at runtime. + + case PrimitiveTypeCode.String: + case PrimitiveTypeCode.NotPrimitive: + //TODO: might be able to remove a castclass and fold an isinst. + return Dummy.Constant; //We only known the value of the pointer at runtime. + } + return null; //force runtime evaluation. + } + + private static IMetadataConstant Evaluate(IOperation operation, uint u41, int i41, IMetadataConstant operand2) { + Contract.Requires(operation != null); + Contract.Requires(operand2 != null); + + switch (operand2.Type.TypeCode) { + //these cases all push a 32-bit value on the operand stack, with sign propagation as appropriate. + //The operations then treat the 32-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var u4 = ((bool)operand2.Value) ? 1u : 0u; + var i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + u4 = (byte)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + u4 = (char)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + u4 = (ushort)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + u4 = (uint)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + i4 = (int)(sbyte)operand2.Value; + u4 = (byte)i4; + goto do4; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + i4 = (short)operand2.Value; + u4 = (ushort)i4; + goto do4; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + i4 = (int)operand2.Value; + u4 = (uint)i4; + do4: + return Evaluate(operation, operand2.Type.PlatformType, u41, i41, u4, i4); + } + return null; //force runtime evaluation. + } + + private static IMetadataConstant Evaluate(IOperation operation, ulong u81, long i81, IMetadataConstant operand2) { + Contract.Requires(operation != null); + Contract.Requires(operand2 != null); + + switch (operand2.Type.TypeCode) { + //these cases all push a 32-bit value on the operand stack, with sign propagation as appropriate. + //The operations then treat the 32-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var u4 = ((bool)operand2.Value) ? 1u : 0u; + var i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + u4 = (byte)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + u4 = (char)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + u4 = (ushort)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + u4 = (uint)operand2.Value; + i4 = (int)u4; + goto do4; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + i4 = (int)(sbyte)operand2.Value; + u4 = (byte)i4; + goto do4; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + i4 = (short)operand2.Value; + u4 = (ushort)i4; + goto do4; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + i4 = (int)operand2.Value; + u4 = (uint)i4; + do4: + long i8 = i4; + ulong u8 = u4; + goto do8; + + //These cases push 32 bit or 64 bit values on the stack, depending on the platform, with sign propagation as appropriate. + //The operations then treat the 32/64-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + i8 = (long)(IntPtr)operand2.Value; + u8 = (ulong)(IntPtr)operand2.Value; + goto do8; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is IntPtr); + i8 = (long)(IntPtr)operand2.Value; + u8 = (ulong)(IntPtr)operand2.Value; + goto do8; + + //These cases push 64 bit values on the stack, with sign propagation as appropriate. + //The operations then treat the 64-bit value as either signed or unsigned, based on the operation code, regardless of how the value got onto the stack. + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + i8 = (long)operand2.Value; + u8 = (ulong)i8; + goto do8; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + u8 = (ulong)operand2.Value; + i8 = (long)u8; + goto do8; + do8: + return Evaluate(operation, operand2.Type.PlatformType, u81, i81, u8, i8); + + } + return null; //force runtime evaluation. + } + + private static IMetadataConstant Evaluate(IOperation operation, double d1, IMetadataConstant operand2) { + Contract.Requires(operation != null); + Contract.Requires(operand2 != null); + + var platfomType = operand2.Type.PlatformType; + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var f = (float)operand2.Value; + var d2 = (double)f; + goto doDouble; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doDouble: + return Evaluate(operation, operand2.Type.PlatformType, d1, d2); + } + return null; //force runtime evaluation. + } + + private static IMetadataConstant Evaluate(IOperation operation, IPlatformType platformType, uint u41, int i41, uint u42, int i42) { + Contract.Requires(operation != null); + Contract.Requires(platformType != null); + + object resultValue = null; + ITypeReference resultType = platformType.SystemInt32; + switch (operation.OperationCode) { + case OperationCode.Add: + resultValue = i41 + i42; + break; + case OperationCode.Add_Ovf: + var i8 = i41 + (long)i42; + var i4 = (int)i8; + if (i8 != (long)i4) return Dummy.Constant; //known to fail. + resultValue = i4; + break; + case OperationCode.Add_Ovf_Un: + var u8 = u41 + (ulong)u42; + var u4 = (uint)u8; + if (u8 != (ulong)u4) return Dummy.Constant; //known to fail + resultValue = (int)u4; + break; + case OperationCode.And: + resultValue = i41 & i42; + break; + case OperationCode.Ceq: + resultValue = i41 == i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Cgt: + resultValue = i41 > i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Cgt_Un: + resultValue = u41 > u42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Clt: + resultValue = i41 < i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Clt_Un: + resultValue = u41 < u42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Div: + if (i42 == 0) return Dummy.Constant; //known to fail. + if (i41 == int.MinValue && i42 == -1) return Dummy.Constant; //known to fail. + resultValue = i41 / i42; + break; + case OperationCode.Div_Un: + if (u42 == 0) return Dummy.Constant; //known to fail. + resultValue = u41 / u42; + break; + case OperationCode.Mul: + resultValue = i41 * i42; + break; + case OperationCode.Mul_Ovf: + i4 = i41 * i42; + if (i4 == int.MinValue && (i41 == -1 || i42 == -1)) return Dummy.Constant; //This can happen when the other value is int.MinValue, + //in which case there is an overflow and this operation is known to fail. + if (i42 != 0 && i4 / i42 != i41) return Dummy.Constant; //known to fail. + resultValue = i4; + break; + case OperationCode.Mul_Ovf_Un: + u4 = u41 * u42; + if (u42 != 0 && u4 / u42 != u41) return Dummy.Constant; //known to fail. + resultValue = u4; + break; + case OperationCode.Or: + resultValue = i41 | i42; + break; + case OperationCode.Rem: + if (i42 == 0) return Dummy.Constant; //known to fail. + resultValue = i41 % i42; + break; + case OperationCode.Rem_Un: + if (u42 == 0) return Dummy.Constant; //known to fail. + resultValue = u41 % u42; + break; + case OperationCode.Shl: + resultValue = i41 << i42; + break; + case OperationCode.Shr: + resultValue = i41 >> i42; + break; + case OperationCode.Shr_Un: + resultValue = u41 >> i42; + break; + case OperationCode.Sub: + resultValue = i41 - i42; + break; + case OperationCode.Sub_Ovf: + i4 = i41 - i42; + if (i41 < 0) { + if (i42 > 0) { + if (i4 > i41) return Dummy.Constant; //known to fail. + } + } else if (i42 < 0) { + if (i4 < i41) return Dummy.Constant; //known to fail. + } + resultValue = i4; + break; + case OperationCode.Sub_Ovf_Un: + if (u41 < u42) return Dummy.Constant; //known to fail + u4 = u41 - u42; + resultValue = (int)u4; + break; + case OperationCode.Xor: + resultValue = i41 ^ i42; + break; + + //These instructions result in no values, but it is interesting to know the values of their conditions. + case OperationCode.Beq: + case OperationCode.Beq_S: + resultValue = i41 == i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bge: + case OperationCode.Bge_S: + resultValue = i41 >= i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + resultValue = u41 >= u42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bgt: + case OperationCode.Bgt_S: + resultValue = i41 > i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + resultValue = u41 > u42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Ble: + case OperationCode.Ble_S: + resultValue = i41 <= i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + resultValue = u41 <= u42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Blt: + case OperationCode.Blt_S: + resultValue = i41 < i42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + resultValue = u41 < u42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + resultValue = u41 != u42; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Nop: + if (i41 == i42) + resultValue = i41; + break; + } + + if (resultValue != null && resultType != null) { + var result = new MetadataConstant() { Value = resultValue, Type = resultType }; + if (result.Locations == null) result.Locations = new List(1); + result.Locations.Add(operation.Location); + return result; + } + return null; //force runtime evaluation. + } + + private static IMetadataConstant Evaluate(IOperation operation, IPlatformType platformType, ulong u81, long i81, ulong u82, long i82) { + Contract.Requires(operation != null); + Contract.Requires(platformType != null); + + object resultValue = null; + ITypeReference resultType = platformType.SystemInt64; + switch (operation.OperationCode) { + case OperationCode.Add: + resultValue = i81 + i82; + break; + case OperationCode.Add_Ovf: + var i8 = i81 + i82; + if (i81 < 0) { + if (i82 < 0) { + if (i8 > i81) return Dummy.Constant; //known to fail. + } + } else if (i82 > 0) { + if (i8 < i81) return Dummy.Constant; //known to fail. + } + resultValue = i8; + break; + case OperationCode.Add_Ovf_Un: + var u8 = u81 + u82; + if (u8 < u81) return Dummy.Constant; //known to fail + resultValue = (long)u8; + break; + case OperationCode.And: + resultValue = i81 & i82; + break; + case OperationCode.Ceq: + resultValue = i81 == i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Cgt: + resultValue = i81 > i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Cgt_Un: + resultValue = u81 > u82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Clt: + resultValue = i81 < i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Clt_Un: + resultValue = u81 < u82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Div: + if (i82 == 0) return Dummy.Constant; //known to fail. + if (i81 == long.MinValue && i82 == -1) return Dummy.Constant; //known to fail. + resultValue = i81 / i82; + break; + case OperationCode.Div_Un: + if (u82 == 0) return Dummy.Constant; //known to fail. + resultValue = u81 / u82; + break; + case OperationCode.Mul: + resultValue = i81 * i82; + break; + case OperationCode.Mul_Ovf: + i8 = i81 * i82; + if (i8 == long.MinValue && (i81 == -1 || i82 == -1)) return Dummy.Constant; //This can happen when the other value is long.MinValue, + //in which case there is an overflow and this operation is known to fail. + if (i82 != 0 && i8 / i82 != i81) return Dummy.Constant; //known to fail. + resultValue = i8; + break; + case OperationCode.Mul_Ovf_Un: + u8 = u81 * u82; + if (u82 != 0 && u8 / u82 != u81) return Dummy.Constant; //known to fail. + resultValue = u8; + break; + case OperationCode.Or: + resultValue = i81 | i82; + break; + case OperationCode.Rem: + if (i82 == 0) return Dummy.Constant; //known to fail. + resultValue = i81 % i82; + break; + case OperationCode.Rem_Un: + if (u82 == 0) return Dummy.Constant; //known to fail. + resultValue = u81 % u82; + break; + case OperationCode.Shl: + resultValue = i81 << (int)i82; + break; + case OperationCode.Shr: + resultValue = i81 >> (int)i82; + break; + case OperationCode.Shr_Un: + resultValue = u81 >> (int)i82; + break; + case OperationCode.Sub: + resultValue = i81 - i82; + break; + case OperationCode.Sub_Ovf: + i8 = i81 - i82; + if (i81 < 0) { + if (i82 > 0) { + if (i8 > i81) return Dummy.Constant; //known to fail. + } + } else if (i82 < 0) { + if (i8 < i81) return Dummy.Constant; //known to fail. + } + resultValue = i8; + break; + case OperationCode.Sub_Ovf_Un: + if (u81 < u82) return Dummy.Constant; //known to fail + u8 = u81 - u82; + resultValue = (long)u8; + break; + case OperationCode.Xor: + resultValue = i81 ^ i82; + break; + + //These instructions result in no values, but it is interesting to know the values of their conditions. + case OperationCode.Beq: + case OperationCode.Beq_S: + resultValue = i81 == i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bge: + case OperationCode.Bge_S: + resultValue = i81 >= i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + resultValue = u81 >= u82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bgt: + case OperationCode.Bgt_S: + resultValue = i81 > i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + resultValue = u81 > u82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Ble: + case OperationCode.Ble_S: + resultValue = i81 <= i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + resultValue = u81 <= u82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Blt: + case OperationCode.Blt_S: + resultValue = i81 < i82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + resultValue = u81 < u82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + resultValue = u81 != u82; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Nop: + if (i81 == i82) + resultValue = i81; + break; + } + + if (resultValue != null && resultType != null) { + var result = new MetadataConstant() { Value = resultValue, Type = resultType }; + if (result.Locations == null) result.Locations = new List(1); + result.Locations.Add(operation.Location); + return result; + } + return null; //force runtime evaluation. + } + + private static IMetadataConstant Evaluate(IOperation operation, IPlatformType platformType, double d1, double d2) { + Contract.Requires(operation != null); + Contract.Requires(platformType != null); + + object resultValue = null; + ITypeReference resultType = platformType.SystemFloat64; + switch (operation.OperationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + resultValue = d1 + d2; + break; + case OperationCode.And: + return Dummy.Constant; //known to fail. (illegal instruction.) + case OperationCode.Ceq: + resultValue = d1 == d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Cgt: + resultValue = d1 > d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Cgt_Un: + resultValue = d1 > d2 || double.IsNaN(d1) || double.IsNaN(d2); + resultType = platformType.SystemBoolean; + break; + case OperationCode.Clt: + resultValue = d1 < d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Clt_Un: + resultValue = d1 < d2 || double.IsNaN(d1) || double.IsNaN(d2); + resultType = platformType.SystemBoolean; + break; + case OperationCode.Div: + resultValue = d1 / d2; + break; + case OperationCode.Div_Un: + return Dummy.Constant; //known to fail. (illegal instruction.) + case OperationCode.Mul: + resultValue = d1 * d2; + break; + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + case OperationCode.Or: + return Dummy.Constant; //known to fail. (illegal instruction.) + case OperationCode.Rem: + resultValue = d1 % d2; + break; + case OperationCode.Rem_Un: + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + return Dummy.Constant; //known to fail. (illegal instruction.) + case OperationCode.Sub: + resultValue = d1 - d2; + break; + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + case OperationCode.Xor: + return Dummy.Constant; //known to fail. (illegal instruction.) + + //These instructions result in no values, but it is interesting to know the values of their conditions. + case OperationCode.Beq: + case OperationCode.Beq_S: + resultValue = d1 == d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bge: + case OperationCode.Bge_S: + resultValue = d1 >= d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + resultValue = !(d1 < d2); + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bgt: + case OperationCode.Bgt_S: + resultValue = d1 > d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + resultValue = !(d1 <= d2); + resultType = platformType.SystemBoolean; + break; + case OperationCode.Ble: + case OperationCode.Ble_S: + resultValue = d1 <= d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + resultValue = !(d1 > d2); + resultType = platformType.SystemBoolean; + break; + case OperationCode.Blt: + case OperationCode.Blt_S: + resultValue = d1 < d2; + resultType = platformType.SystemBoolean; + break; + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + resultValue = !(d1 >= d2); + resultType = platformType.SystemBoolean; + break; + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + resultValue = !(d1 == d2); + resultType = platformType.SystemBoolean; + break; + case OperationCode.Nop: + if (d1 == d2) + resultValue = d1; + break; + } + + if (resultValue != null && resultType != null) { + var result = new MetadataConstant() { Value = resultValue, Type = resultType }; + if (result.Locations == null) result.Locations = new List(1); + result.Locations.Add(operation.Location); + return result; + } + return null; //force runtime evaluation. + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Evaluate(IOperation operation, IMetadataConstant operand1, Instruction operand2, ValueMappings mappings, AiBasicBlock/*?*/ block = null) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(operation != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Requires(mappings != null); + + if (operand1 == Dummy.Constant) return Dummy.Constant; + bool operand1IsZero = MetadataExpressionHelper.IsIntegralZero(operand1); + bool operand1IsMinusOne = MetadataExpressionHelper.IsIntegralMinusOne(operand1); + IMetadataConstant cv2 = null; + Interval interval2 = null; + if (block != null) { + //In this case the expression is being evaluated with respect to a particular block + //so any constraints in the block can be taken into account. We get hold of these via the Interval domain. + interval2 = mappings.GetIntervalFor(operand2, block); + if (interval2 != null) cv2 = interval2.GetAsSingleton(); + } + if (cv2 != null) return Evaluate(operation, operand1, cv2); + + switch (operation.OperationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + break; + case OperationCode.And: + if (operand1IsZero) return operand1; + break; + case OperationCode.Ceq: + break; + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + goto case OperationCode.Bgt; + case OperationCode.Clt: + case OperationCode.Clt_Un: + goto case OperationCode.Blt; + case OperationCode.Div: + case OperationCode.Div_Un: + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + if (operand1IsZero) return operand1; + break; + case OperationCode.Or: + if (operand1IsMinusOne) return operand1; + break; + case OperationCode.Rem: + case OperationCode.Rem_Un: + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + if (operand1IsZero) return operand1; + break; + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + break; + case OperationCode.Xor: + break; + + //These instructions result in no values, but it is interesting to know the values of their conditions. + case OperationCode.Beq: + case OperationCode.Beq_S: + if (interval2 != null && interval2.IsFinite) { + if (Evaluator.IsNumericallyLessThan(operand1, interval2.LowerBound) || Evaluator.IsNumericallyGreaterThan(operand1, interval2.UpperBound)) + return new MetadataConstant() { Value = false, Type = operand1.Type.PlatformType.SystemBoolean }; + } + break; + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + if (interval2 != null && interval2.UpperBound != Dummy.Constant) + return NullUnlessTrue(Evaluate(operation, operand1, interval2.UpperBound)); + break; + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + if (interval2 != null && interval2.LowerBound != Dummy.Constant) + return NullUnlessTrue(Evaluate(operation, operand1, interval2.LowerBound)); + break; + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + if (interval2 != null && interval2.IsFinite) { + if (Evaluator.IsNumericallyLessThan(operand1, interval2.LowerBound) || Evaluator.IsNumericallyGreaterThan(operand1, interval2.UpperBound)) + return new MetadataConstant() { Value = true, Type = operand1.Type.PlatformType.SystemBoolean }; + } + break; + } + return null; + } + + internal static IMetadataConstant Negate(IMetadataConstant compileTimeConstant) { + Contract.Requires(compileTimeConstant != null); + var val = compileTimeConstant.Value as IConvertible; + if (val == null) return compileTimeConstant; + switch (val.GetTypeCode()) { + case TypeCode.Double: return new MetadataConstant() { Value = -val.ToDouble(null), Type = compileTimeConstant.Type }; + case TypeCode.Int16: return new MetadataConstant() { Value = (short)-val.ToInt16(null), Type = compileTimeConstant.Type }; + case TypeCode.Int32: return new MetadataConstant() { Value = -val.ToInt32(null), Type = compileTimeConstant.Type }; + case TypeCode.Int64: return new MetadataConstant() { Value = -val.ToInt64(null), Type = compileTimeConstant.Type }; + case TypeCode.SByte: return new MetadataConstant() { Value = (sbyte)-val.ToSByte(null), Type = compileTimeConstant.Type }; + case TypeCode.Single: return new MetadataConstant() { Value = -val.ToSingle(null), Type = compileTimeConstant.Type }; + } + return compileTimeConstant; + } + + private static IMetadataConstant/*?*/ NullUnlessTrue(IMetadataConstant compileTimeConstant) { + if (compileTimeConstant == null) return null; + if (!(compileTimeConstant.Value is bool)) return null; + if ((bool)compileTimeConstant.Value) return compileTimeConstant; + return null; + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Evaluate(IOperation operation, Instruction operand1, IMetadataConstant operand2, ValueMappings mappings, AiBasicBlock/*?*/ block = null) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(operation != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Requires(mappings != null); + + IMetadataConstant cv1 = null; + Interval interval1 = null; + if (block != null) { + //In this case the expression is being evaluated with respect to a particular block + //so any constraints in the block can be taken into account. We get hold of these via the Interval domain. + interval1 = mappings.GetIntervalFor(operand1, block); + if (interval1 != null) cv1 = interval1.GetAsSingleton(); + } + if (cv1 != null) return Evaluate(operation, cv1, operand2); + if (operand2 == Dummy.Constant) return Dummy.Constant; + bool operand2IsZero = MetadataExpressionHelper.IsIntegralZero(operand2); + + switch (operation.OperationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + break; + case OperationCode.And: + if (operand2IsZero) return operand2; + break; + case OperationCode.Ceq: + break; + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + goto case OperationCode.Bgt; + case OperationCode.Clt: + case OperationCode.Clt_Un: + goto case OperationCode.Blt; + case OperationCode.Div: + case OperationCode.Div_Un: + if (operand2IsZero) return Dummy.Constant; //known to fail. + break; + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + if (operand2IsZero) return operand2; + break; + case OperationCode.Or: + break; + case OperationCode.Rem: + case OperationCode.Rem_Un: + if (operand2IsZero) return Dummy.Constant; //known to fail. + break; + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + break; + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + break; + case OperationCode.Xor: + break; + + //These instructions result in no values, but it is interesting to know the values of their conditions. + case OperationCode.Beq: + case OperationCode.Beq_S: + if (interval1 != null && interval1.IsFinite) { + if (Evaluator.IsNumericallyLessThan(operand2, interval1.LowerBound) || Evaluator.IsNumericallyGreaterThan(operand2, interval1.UpperBound)) + return new MetadataConstant() { Value = false, Type = operand1.Type.PlatformType.SystemBoolean }; + } + break; + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + if (interval1 != null && interval1.LowerBound != Dummy.Constant) + return NullUnlessTrue(Evaluate(operation, interval1.LowerBound, operand2)); + break; + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + if (interval1 != null && interval1.UpperBound != Dummy.Constant) + return NullUnlessTrue(Evaluate(operation, interval1.UpperBound, operand2)); + break; + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + if (interval1 != null && interval1.IsFinite) { + if (Evaluator.IsNumericallyLessThan(interval1.UpperBound, operand2) || Evaluator.IsNumericallyGreaterThan(interval1.LowerBound, operand2)) + return new MetadataConstant() { Value = true, Type = operand1.Type.PlatformType.SystemBoolean }; + } + break; + } + + return null; + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Evaluate(IOperation operation, Instruction operand1, Instruction operand2, ValueMappings mappings, AiBasicBlock/*?*/ block = null) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(operation != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Requires(mappings != null); + + IMetadataConstant cv1 = null; + Interval interval1 = null; + IMetadataConstant cv2 = null; + Interval interval2 = null; + if (block != null) { + //In this case the expression is being evaluated with respect to a particular block + //so any constraints in the block can be taken into account. We get hold of these via the Interval domain. + interval1 = mappings.GetIntervalFor(operand1, block); + if (interval1 != null) cv1 = interval1.GetAsSingleton(); + interval2 = mappings.GetIntervalFor(operand2, block); + if (interval2 != null) cv2 = interval2.GetAsSingleton(); + } + if (cv1 != null) { + if (cv2 != null) return Evaluate(operation, cv1, cv2); + return Evaluate(operation, cv1, operand2, mappings, block); + } + if (cv2 != null) + return Evaluate(operation, operand1, cv2, mappings, block); + bool floatingPoint = operand1.Type.TypeCode == PrimitiveTypeCode.Float32 || operand1.Type.TypeCode == PrimitiveTypeCode.Float64; + + object resultValue = null; + ITypeReference resultType = null; + switch (operation.OperationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + break; + case OperationCode.And: + break; + case OperationCode.Ceq: + if (operand1 == operand2 && !floatingPoint) { + resultValue = true; + resultType = operand1.Type.PlatformType.SystemBoolean; + } + break; + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + goto case OperationCode.Bgt; + case OperationCode.Clt: + case OperationCode.Clt_Un: + goto case OperationCode.Blt; + case OperationCode.Div: + if (operand1 == operand2 && !floatingPoint && interval1 != null && interval1.ExcludesZero) { + if (TypeHelper.SizeOfType(operand1.Type) == 4) { + resultValue = 1; + resultType = operand1.Type.PlatformType.SystemInt32; + } else { + resultValue = 1L; + resultType = operand1.Type.PlatformType.SystemInt64; + } + } + break; + case OperationCode.Div_Un: + if (operand1 == operand2 && !floatingPoint && interval1 != null && interval1.ExcludesZero) { + if (TypeHelper.SizeOfType(operand1.Type) == 4) { + resultValue = 1u; + resultType = operand1.Type.PlatformType.SystemUInt32; + } else { + resultValue = 1UL; + resultType = operand1.Type.PlatformType.SystemUInt64; + } + } + break; + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + break; + case OperationCode.Or: + break; + case OperationCode.Rem: + case OperationCode.Rem_Un: + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + break; + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + case OperationCode.Xor: + if (operand1 == operand2 && !floatingPoint) { + if (TypeHelper.SizeOfType(operand1.Type) == 4) { + resultValue = 0; + resultType = operand1.Type.PlatformType.SystemInt32; + } else { + resultValue = 0L; + resultType = operand1.Type.PlatformType.SystemInt64; + } + } + break; + + //These instructions result in no values, but it is interesting to know the values of their conditions. + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + if (operand1 == operand2) { + resultValue = true; + resultType = operand1.Type.PlatformType.SystemBoolean; + } + if (operation.OperationCode == OperationCode.Beq || operation.OperationCode == OperationCode.Beq_S) break; + goto case OperationCode.Bgt; + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + if (operand1 == operand2 && !floatingPoint) { + resultValue = false; + resultType = operand1.Type.PlatformType.SystemBoolean; + } + if (interval1 != null && interval2 != null && interval1.LowerBound != Dummy.Constant && interval2.UpperBound != Dummy.Constant) + return NullUnlessTrue(Evaluator.Evaluate(operation, interval1.LowerBound, interval2.UpperBound)); + break; + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + if (operand1 == operand2) { + resultValue = true; + resultType = operand1.Type.PlatformType.SystemBoolean; + } + goto case OperationCode.Blt; + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + if (operand1 == operand2 && !floatingPoint) { + resultValue = false; + resultType = operand1.Type.PlatformType.SystemBoolean; + } + if (interval1 != null && interval2 != null && interval1.UpperBound != Dummy.Constant && interval2.LowerBound != Dummy.Constant) + return NullUnlessTrue(Evaluator.Evaluate(operation, interval1.UpperBound, interval2.LowerBound)); + break; + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + if (operand1 == operand2 && !floatingPoint) { + resultValue = false; + resultType = operand1.Type.PlatformType.SystemBoolean; + } + if (interval1 != null && interval1.IsFinite && interval2 != null && interval2.IsFinite) { + if (Evaluator.IsNumericallyLessThan(interval1.UpperBound, interval2.LowerBound) || Evaluator.IsNumericallyGreaterThan(interval1.LowerBound, interval2.UpperBound)) + return new MetadataConstant() { Value = true, Type = operand1.Type.PlatformType.SystemBoolean }; + } + break; + } + + if (resultValue != null && resultType != null) { + var result = new MetadataConstant() { Value = resultValue, Type = resultType }; + if (result.Locations == null) result.Locations = new List(1); + result.Locations.Add(operation.Location); + return result; + } + return null; //force runtime evaluation. + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Evaluate(IOperation operation, Instruction operand1, Instruction[] operands2toN, ValueMappings mappings, AiBasicBlock/*?*/ block = null) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(operation != null); + Contract.Requires(operand1 != null); + Contract.Requires(operands2toN != null); + Contract.Requires(mappings != null); + + switch (operation.OperationCode) { + case OperationCode.Nop: + var phiVar = operation.Value as INamedEntity; + if (phiVar == null) return null;; + //We have a phi node. If all operands are constants and are moreover equal, we can reduce the phi node to a constant. + var cv1 = mappings.GetCompileTimeConstantValueFor(operand1, block); + if (cv1 == null) return null; + foreach (var operandi in operands2toN) { + Contract.Assume(operandi != null); + var cvi = mappings.GetCompileTimeConstantValueFor(operandi, block); + if (cvi == null) return null; + if (!Evaluator.IsNumericallyEqual(cv1, cvi)) return null; + } + return cv1; + } + return null; + } + + /// + /// Returns an IMetadataConstant instance that corresponds to the value that the given instruction will evaluate to at runtime. + /// + /// + /// + public static IMetadataConstant GetAsCompileTimeConstantValue(Instruction instruction) { + Contract.Requires(instruction != null); + Contract.Ensures(Contract.Result() != null); + + var operation = instruction.Operation; + object value = null; + switch (operation.OperationCode) { + case OperationCode.Ldc_I4_0: value = 0; break; + case OperationCode.Ldc_I4_1: value = 1; break; + case OperationCode.Ldc_I4_2: value = 2; break; + case OperationCode.Ldc_I4_3: value = 3; break; + case OperationCode.Ldc_I4_4: value = 4; break; + case OperationCode.Ldc_I4_5: value = 5; break; + case OperationCode.Ldc_I4_6: value = 6; break; + case OperationCode.Ldc_I4_7: value = 7; break; + case OperationCode.Ldc_I4_8: value = 8; break; + case OperationCode.Ldc_I4_M1: value = -1; break; + case OperationCode.Ldc_I4: + case OperationCode.Ldc_I4_S: + case OperationCode.Ldc_I8: + case OperationCode.Ldc_R4: + case OperationCode.Ldc_R8: + case OperationCode.Ldnull: + case OperationCode.Ldstr: value = operation.Value; break; + default: + Contract.Assume(false); + break; + } + return new MetadataConstant() { Value = value, Type = instruction.Type }; + } + + /// + /// + /// + /// + /// + public static IMetadataConstant GetMaxValue(ITypeReference type) { + Contract.Requires(type != null); + Contract.Ensures(Contract.Result() != null); + + object value = null; + switch (type.TypeCode) { + case PrimitiveTypeCode.Boolean: value = true; break; + case PrimitiveTypeCode.Char: value = char.MaxValue; break; + case PrimitiveTypeCode.Int8: value = sbyte.MaxValue; break; + case PrimitiveTypeCode.Int16: value = short.MaxValue; break; + case PrimitiveTypeCode.Int32: value = int.MaxValue; break; + case PrimitiveTypeCode.Int64: value = long.MaxValue; break; + case PrimitiveTypeCode.IntPtr: return Dummy.Constant; + case PrimitiveTypeCode.UInt8: value = byte.MaxValue; break; + case PrimitiveTypeCode.UInt16: value = ushort.MaxValue; break; + case PrimitiveTypeCode.UInt32: value = uint.MaxValue; break; + case PrimitiveTypeCode.UInt64: value = ulong.MaxValue; break; + case PrimitiveTypeCode.UIntPtr: return Dummy.Constant; + case PrimitiveTypeCode.Float32: value = float.MaxValue; break; + case PrimitiveTypeCode.Float64: value = double.MaxValue; break; + default: return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = type }; + } + + /// + /// + /// + /// + /// + public static IMetadataConstant GetMinValue(ITypeReference type) { + Contract.Requires(type != null); + Contract.Ensures(Contract.Result() != null); + + object value = null; + switch (type.TypeCode) { + case PrimitiveTypeCode.Boolean: value = false; break; + case PrimitiveTypeCode.Char: value = char.MinValue; break; + case PrimitiveTypeCode.Int8: value = sbyte.MinValue; break; + case PrimitiveTypeCode.Int16: value = short.MinValue; break; + case PrimitiveTypeCode.Int32: value = int.MinValue; break; + case PrimitiveTypeCode.Int64: value = long.MinValue; break; + case PrimitiveTypeCode.IntPtr: return Dummy.Constant; + case PrimitiveTypeCode.UInt8: value = byte.MinValue; break; + case PrimitiveTypeCode.UInt16: value = ushort.MinValue; break; + case PrimitiveTypeCode.UInt32: value = uint.MinValue; break; + case PrimitiveTypeCode.UInt64: value = ulong.MinValue; break; + case PrimitiveTypeCode.UIntPtr: return Dummy.Constant; + case PrimitiveTypeCode.Float32: value = float.MinValue; break; + case PrimitiveTypeCode.Float64: value = double.MinValue; break; + default: return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = type }; + } + + /// + /// + /// + /// + /// + public static IMetadataConstant GetMinusOne(ITypeReference type) { + Contract.Requires(type != null); + Contract.Ensures(Contract.Result() != null); + + object value = null; + switch (type.TypeCode) { + case PrimitiveTypeCode.Boolean: value = true; break; + case PrimitiveTypeCode.Char: value = (char)0xFFFF; break; + case PrimitiveTypeCode.Int8: value = (sbyte)-1; break; + case PrimitiveTypeCode.Int16: value = (short)-1; break; + case PrimitiveTypeCode.Int32: value = (int)-1; break; + case PrimitiveTypeCode.Int64: value = (long)-1; break; + case PrimitiveTypeCode.IntPtr: value = (IntPtr)(-1); break; + case PrimitiveTypeCode.UInt8: value = byte.MaxValue; break; + case PrimitiveTypeCode.UInt16: value = ushort.MaxValue; break; + case PrimitiveTypeCode.UInt32: value = uint.MaxValue; break; + case PrimitiveTypeCode.UInt64: value = ulong.MaxValue; break; + case PrimitiveTypeCode.UIntPtr: value = (UIntPtr)ulong.MaxValue; break; + case PrimitiveTypeCode.Float32: value = (float)-1; break; + case PrimitiveTypeCode.Float64: value = (double)-1; break; + default: return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = type }; + } + + /// + /// + /// + /// + /// + public static IMetadataConstant GetOne(ITypeReference type) { + Contract.Requires(type != null); + Contract.Ensures(Contract.Result() != null); + + object value = null; + switch (type.TypeCode) { + case PrimitiveTypeCode.Boolean: value = true; break; + case PrimitiveTypeCode.Char: value = (char)1; break; + case PrimitiveTypeCode.Int8: value = (sbyte)1; break; + case PrimitiveTypeCode.Int16: value = (short)1; break; + case PrimitiveTypeCode.Int32: value = (int)1; break; + case PrimitiveTypeCode.Int64: value = (long)1; break; + case PrimitiveTypeCode.IntPtr: value = (IntPtr)1; break; + case PrimitiveTypeCode.UInt8: value = (byte)1; break; + case PrimitiveTypeCode.UInt16: value = (ushort)1; break; + case PrimitiveTypeCode.UInt32: value = (uint)1; break; + case PrimitiveTypeCode.UInt64: value = (ulong)1; break; + case PrimitiveTypeCode.UIntPtr: value = (UIntPtr)1; break; + case PrimitiveTypeCode.Float32: value = (float)1; break; + case PrimitiveTypeCode.Float64: value = (double)1; break; + default: return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = type }; + } + + /// + /// + /// + /// + /// + public static IMetadataConstant GetZero(ITypeReference type) { + Contract.Requires(type != null); + Contract.Ensures(Contract.Result() != null); + + object value = null; + switch (type.TypeCode) { + case PrimitiveTypeCode.Boolean: value = false; break; + case PrimitiveTypeCode.Char: value = (char)0; break; + case PrimitiveTypeCode.Int8: value = (sbyte)0; break; + case PrimitiveTypeCode.Int16: value = (short)0; break; + case PrimitiveTypeCode.Int32: value = (int)0; break; + case PrimitiveTypeCode.Int64: value = (long)0; break; + case PrimitiveTypeCode.IntPtr: value = IntPtr.Zero; break; + case PrimitiveTypeCode.UInt8: value = (byte)0; break; + case PrimitiveTypeCode.UInt16: value = (ushort)0; break; + case PrimitiveTypeCode.UInt32: value = (uint)0; break; + case PrimitiveTypeCode.UInt64: value = (ulong)0; break; + case PrimitiveTypeCode.UIntPtr: value = UIntPtr.Zero; break; + case PrimitiveTypeCode.Float32: value = (float)0; break; + case PrimitiveTypeCode.Float64: value = (double)0; break; + default: return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = type }; + } + + /// + /// Returns a compile time constant that is the same as the given constant, except that numeric values + /// are incremented by the smallest interval appropriate to its type. If the increment causes overflow to + /// happen, the result is the given constant. + /// + public static IMetadataConstant IncreaseBySmallestInterval(IMetadataConstant operand) { + Contract.Requires(operand != null); + Contract.Ensures(Contract.Result() != null); + + object value = null; + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + var sb = (sbyte)operand.Value; + if (sb == sbyte.MaxValue) return operand; + value = ++sb; + break; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + var s = (short)operand.Value; + if (s == short.MaxValue) return operand; + value = ++s; + break; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + var i = (int)operand.Value; + if (i == int.MaxValue) return operand; + value = ++i; + break; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + var l = (long)operand.Value; + if (l == long.MaxValue) return operand; + value = ++l; + break; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + var iptr = (long)(IntPtr)operand.Value; + if (iptr == long.MaxValue) return operand; + value = (IntPtr)(++iptr); + break; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand.Value is byte); + var b = (byte)operand.Value; + if (b == byte.MaxValue) return operand; + value = ++b; + break; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand.Value is ushort); + var us = (ushort)operand.Value; + if (us == ushort.MaxValue) return operand; + value = ++us; + break; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand.Value is uint); + var ui = (uint)operand.Value; + if (ui == uint.MaxValue) return operand; + value = ++ui; + break; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand.Value is ulong); + var ul = (ulong)operand.Value; + if (ul == ulong.MaxValue) return operand; + value = ++ul; + break; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand.Value is UIntPtr); + var uptr = (ulong)(UIntPtr)operand.Value; + if (uptr == ulong.MaxValue) return operand; + value = (UIntPtr)(++uptr); + break; + case PrimitiveTypeCode.Float32: + Contract.Assume(operand.Value is float); + var f = (float)operand.Value; + var incr = float.Epsilon; + var fincr = f + incr; + while (fincr == f) { + incr *= 2; + fincr += incr; + } + if (float.IsPositiveInfinity(fincr)) return operand; + value = fincr; + break; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand.Value is double); + var d = (double)operand.Value; + var incrd = double.Epsilon; + var dincr = d + incrd; + while (dincr == d) { + incrd *= 2; + dincr += incrd; + } + if (double.IsPositiveInfinity(dincr)) return operand; + value = dincr; + break; + default: + return Dummy.Constant; + } + return new MetadataConstant() { Value = value, Type = operand.Type }; + } + + /// + /// + /// + /// + /// + /// + [ContractVerification(false)] + public static bool IsNumericallyEqual(IMetadataConstant/*?*/ operand1, IMetadataConstant/*?*/ operand2) { + if (operand1 == null || operand2 == null) return false; + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return false; + + long signed1 = 0; + ulong unsigned1 = 0; + switch (operand1.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + signed1 = (long)(sbyte)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + signed1 = (long)(short)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + signed1 = (long)(int)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + signed1 = (long)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed1 = (long)(IntPtr)operand1.Value; + doSigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + var signed2 = (long)(sbyte)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + signed2 = (long)(short)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + signed2 = (long)(int)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + signed2 = (long)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed2 = (long)(IntPtr)operand2.Value; + goto doSigned2; + doSigned2: + return signed1 == signed2; + default: + unsigned1 = (ulong)signed1; + goto doUnsigned1; + } + + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + unsigned1 = ((bool)operand1.Value) ? 1UL : 0UL; + goto doUnsigned1; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + unsigned1 = (ulong)(char)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + unsigned1 = (ulong)(byte)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + unsigned1 = (ulong)(ushort)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + unsigned1 = (ulong)(uint)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + unsigned1 = (ulong)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is UIntPtr); + unsigned1 = (ulong)(UIntPtr)operand1.Value; + doUnsigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var unsigned2 = ((bool)operand2.Value) ? 1UL : 0UL; + goto doUnsigned2; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + unsigned2 = (ulong)(char)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + unsigned2 = (ulong)(sbyte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + unsigned2 = (ulong)(short)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + unsigned2 = (ulong)(int)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + unsigned2 = (ulong)(IntPtr)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + unsigned2 = (ulong)(byte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + unsigned2 = (ulong)(ushort)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + unsigned2 = (ulong)(uint)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is UIntPtr); + unsigned2 = (ulong)(UIntPtr)operand2.Value; + doUnsigned2: + return unsigned1 == unsigned2; + default: + return false; + } + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var d1 = (double)(float)operand1.Value; + goto doFloat1; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d1 = (double)operand1.Value; + doFloat1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var d2 = (double)(float)operand2.Value; + goto doFloat2; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doFloat2: + return d1 == d2; + default: + return false; + } + } + return false; + } + + /// + /// + /// + /// + /// + /// + public static bool IsNumericallyGreaterThan(IMetadataConstant/*?*/ operand1, IMetadataConstant/*?*/ operand2) { + if (operand1 == null || operand2 == null) return false; + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return false; + + long signed1 = 0; + ulong unsigned1 = 0; + switch (operand1.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + signed1 = (long)(sbyte)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + signed1 = (long)(short)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + signed1 = (long)(int)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + signed1 = (long)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed1 = (long)(IntPtr)operand1.Value; + doSigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + var signed2 = (long)(sbyte)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + signed2 = (long)(short)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + signed2 = (long)(int)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + signed2 = (long)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + signed2 = (long)(IntPtr)operand2.Value; + doSigned2: + return signed1 > signed2; + default: + unsigned1 = (ulong)signed1; + goto doUnsigned1; + } + + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + unsigned1 = ((bool)operand1.Value) ? 1UL : 0UL; + goto doUnsigned1; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + unsigned1 = (ulong)(char)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + unsigned1 = (ulong)(byte)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + unsigned1 = (ulong)(ushort)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + unsigned1 = (ulong)(uint)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + unsigned1 = (ulong)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is UIntPtr); + unsigned1 = (ulong)(UIntPtr)operand1.Value; + doUnsigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var unsigned2 = ((bool)operand2.Value) ? 1UL : 0UL; + goto doUnsigned2; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + unsigned2 = (ulong)(char)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + unsigned2 = (ulong)(sbyte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + unsigned2 = (ulong)(short)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + unsigned2 = (ulong)(int)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + unsigned2 = (ulong)(IntPtr)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + unsigned2 = (ulong)(byte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + unsigned2 = (ulong)(ushort)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + unsigned2 = (ulong)(uint)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is UIntPtr); + unsigned2 = (ulong)(UIntPtr)operand2.Value; + doUnsigned2: + return unsigned1 > unsigned2; + default: + return false; + } + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var d1 = (double)(float)operand1.Value; + goto doFloat1; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d1 = (double)operand1.Value; + doFloat1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var d2 = (double)(float)operand2.Value; + goto doFloat2; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doFloat2: + return d1 > d2; + default: + return false; + } + } + return false; + } + + /// + /// + /// + /// + /// + /// + public static bool IsNumericallyGreaterThanOrEqualTo(IMetadataConstant/*?*/ operand1, IMetadataConstant/*?*/ operand2) { + if (operand1 == null || operand2 == null) return false; + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return false; + + long signed1 = 0; + ulong unsigned1 = 0; + switch (operand1.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + signed1 = (long)(sbyte)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + signed1 = (long)(short)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + signed1 = (long)(int)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + signed1 = (long)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed1 = (long)(IntPtr)operand1.Value; + doSigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + var signed2 = (long)(sbyte)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + signed2 = (long)(short)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + signed2 = (long)(int)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + signed2 = (long)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + signed2 = (long)(IntPtr)operand2.Value; + doSigned2: + return signed1 >= signed2; + default: + unsigned1 = (ulong)signed1; + goto doUnsigned1; + } + + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + unsigned1 = ((bool)operand1.Value) ? 1UL : 0UL; + goto doUnsigned1; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + unsigned1 = (ulong)(char)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + unsigned1 = (ulong)(byte)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + unsigned1 = (ulong)(ushort)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + unsigned1 = (ulong)(uint)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + unsigned1 = (ulong)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is UIntPtr); + unsigned1 = (ulong)(UIntPtr)operand1.Value; + doUnsigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var unsigned2 = ((bool)operand2.Value) ? 1UL : 0UL; + goto doUnsigned2; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + unsigned2 = (ulong)(char)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + unsigned2 = (ulong)(sbyte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + unsigned2 = (ulong)(short)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + unsigned2 = (ulong)(int)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + unsigned2 = (ulong)(IntPtr)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + unsigned2 = (ulong)(byte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + unsigned2 = (ulong)(ushort)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + unsigned2 = (ulong)(uint)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is UIntPtr); + unsigned2 = (ulong)(UIntPtr)operand2.Value; + doUnsigned2: + return unsigned1 >= unsigned2; + default: + return false; + } + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var d1 = (double)(float)operand1.Value; + goto doFloat1; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d1 = (double)operand1.Value; + doFloat1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var d2 = (double)(float)operand2.Value; + goto doFloat2; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doFloat2: + return d1 >= d2; + default: + return false; + } + } + return false; + } + + /// + /// + /// + /// + /// + /// + public static bool IsNumericallyLessThan(IMetadataConstant/*?*/ operand1, IMetadataConstant/*?*/ operand2) { + if (operand1 == null || operand2 == null) return false; + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return false; + + long signed1 = 0; + ulong unsigned1 = 0; + switch (operand1.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + signed1 = (long)(sbyte)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + signed1 = (long)(short)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + signed1 = (long)(int)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + signed1 = (long)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed1 = (long)(IntPtr)operand1.Value; + doSigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + var signed2 = (long)(sbyte)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + signed2 = (long)(short)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + signed2 = (long)(int)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + signed2 = (long)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + signed2 = (long)(IntPtr)operand2.Value; + doSigned2: + return signed1 < signed2; + default: + unsigned1 = (ulong)signed1; + goto doUnsigned1; + } + + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + unsigned1 = ((bool)operand1.Value) ? 1UL : 0UL; + goto doUnsigned1; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + unsigned1 = (ulong)(char)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + unsigned1 = (ulong)(byte)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + unsigned1 = (ulong)(ushort)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + unsigned1 = (ulong)(uint)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + unsigned1 = (ulong)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is UIntPtr); + unsigned1 = (ulong)(UIntPtr)operand1.Value; + doUnsigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var unsigned2 = ((bool)operand2.Value) ? 1UL : 0UL; + goto doUnsigned2; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + unsigned2 = (ulong)(char)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + unsigned2 = (ulong)(sbyte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + unsigned2 = (ulong)(short)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + unsigned2 = (ulong)(int)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + unsigned2 = (ulong)(IntPtr)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + unsigned2 = (ulong)(byte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + unsigned2 = (ulong)(ushort)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + unsigned2 = (ulong)(uint)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is UIntPtr); + unsigned2 = (ulong)(UIntPtr)operand2.Value; + doUnsigned2: + return unsigned1 < unsigned2; + default: + return false; + } + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var d1 = (double)(float)operand1.Value; + goto doFloat1; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d1 = (double)operand1.Value; + doFloat1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var d2 = (double)(float)operand2.Value; + goto doFloat2; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doFloat2: + return d1 < d2; + default: + return false; + } + } + return false; + } + + /// + /// + /// + /// + /// + /// + public static bool IsNumericallyLessThanOrEqualTo(IMetadataConstant/*?*/ operand1, IMetadataConstant/*?*/ operand2) { + if (operand1 == null || operand2 == null) return false; + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return false; + + long signed1 = 0; + ulong unsigned1 = 0; + switch (operand1.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + signed1 = (long)(sbyte)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + signed1 = (long)(short)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + signed1 = (long)(int)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + signed1 = (long)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed1 = (long)(IntPtr)operand1.Value; + doSigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + var signed2 = (long)(sbyte)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + signed2 = (long)(short)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + signed2 = (long)(int)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + signed2 = (long)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + signed2 = (long)(IntPtr)operand2.Value; + doSigned2: + return signed1 <= signed2; + default: + unsigned1 = (ulong)signed1; + goto doUnsigned1; + } + + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + unsigned1 = ((bool)operand1.Value) ? 1UL : 0UL; + goto doUnsigned1; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + unsigned1 = (ulong)(char)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + unsigned1 = (ulong)(byte)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + unsigned1 = (ulong)(ushort)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + unsigned1 = (ulong)(uint)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + unsigned1 = (ulong)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is UIntPtr); + unsigned1 = (ulong)(UIntPtr)operand1.Value; + doUnsigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var unsigned2 = ((bool)operand2.Value) ? 1UL : 0UL; + goto doUnsigned2; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + unsigned2 = (ulong)(char)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + unsigned2 = (ulong)(sbyte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + unsigned2 = (ulong)(short)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + unsigned2 = (ulong)(int)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + unsigned2 = (ulong)(IntPtr)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + unsigned2 = (ulong)(byte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + unsigned2 = (ulong)(ushort)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + unsigned2 = (ulong)(uint)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is UIntPtr); + unsigned2 = (ulong)(UIntPtr)operand2.Value; + doUnsigned2: + return unsigned1 <= unsigned2; + default: + return false; + } + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var d1 = (double)(float)operand1.Value; + goto doFloat1; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d1 = (double)operand1.Value; + doFloat1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var d2 = (double)(float)operand2.Value; + goto doFloat2; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doFloat2: + return d1 <= d2; + default: + return false; + } + } + return false; + } + + /// + /// + /// + /// + /// + public static bool IsNegative(IMetadataConstant operand) { + Contract.Requires(operand != null); + + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + return 0 > (sbyte)operand.Value; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + return 0 > (short)operand.Value; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + return 0 > (int)operand.Value; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + return 0 > (long)operand.Value; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + return 0 > (long)(IntPtr)operand.Value; + case PrimitiveTypeCode.Float32: + Contract.Assume(operand.Value is float); + return 0 > (float)operand.Value; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand.Value is double); + return 0 > (double)operand.Value; + default: + return false; + } + } + + /// + /// + /// + /// + /// + public static bool IsNonNegative(IMetadataConstant operand) { + Contract.Requires(operand != null); + + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + return 0 <= (sbyte)operand.Value; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + return 0 <= (short)operand.Value; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + return 0 <= (int)operand.Value; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + return 0 <= (long)operand.Value; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + return 0 <= (long)(IntPtr)operand.Value; + case PrimitiveTypeCode.Float32: + Contract.Assume(operand.Value is float); + return 0 <= (float)operand.Value; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand.Value is double); + return 0 <= (double)operand.Value; + default: + return true; + } + } + + /// + /// + /// + /// + /// + public static bool IsPositive(IMetadataConstant operand) { + Contract.Requires(operand != null); + + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + return 0 < (sbyte)operand.Value; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + return 0 < (short)operand.Value; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + return 0 < (int)operand.Value; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + return 0 < (long)operand.Value; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + return 0 < (long)(IntPtr)operand.Value; + case PrimitiveTypeCode.Float32: + Contract.Assume(operand.Value is float); + return 0 < (float)operand.Value; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand.Value is double); + return 0 < (double)operand.Value; + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand.Value is bool); + return (bool)operand.Value; + case PrimitiveTypeCode.Char: + Contract.Assume(operand.Value is char); + return 0 < (char)operand.Value; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand.Value is byte); + return 0 < (byte)operand.Value; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand.Value is ushort); + return 0 < (ushort)operand.Value; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand.Value is uint); + return 0 < (uint)operand.Value; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand.Value is ulong); + return 0 < (ulong)operand.Value; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand.Value is UIntPtr); + return 0 < (ulong)(UIntPtr)operand.Value; + default: + return false; + } + } + + /// + /// + /// + /// + /// + public static bool IsSmallerThanMinusOne(IMetadataConstant operand) { + Contract.Requires(operand != null); + + switch (operand.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand.Value is sbyte); + return -1 > (sbyte)operand.Value; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand.Value is short); + return -1 > (short)operand.Value; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand.Value is int); + return -1 > (int)operand.Value; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand.Value is long); + return -1 > (long)operand.Value; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand.Value is IntPtr); + return -1 > (long)(IntPtr)operand.Value; + case PrimitiveTypeCode.Float32: + Contract.Assume(operand.Value is float); + return -1 > (float)operand.Value; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand.Value is double); + return -1 > (double)operand.Value; + default: + return false; + } + } + + /// + /// + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Max(IMetadataConstant/*?*/ operand1, IMetadataConstant/*?*/ operand2) { + if (operand1 == null || operand2 == null) return null; + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return Dummy.Constant; + + long signed1 = 0; + ulong unsigned1 = 0; + switch (operand1.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + signed1 = (long)(sbyte)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + signed1 = (long)(short)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + signed1 = (long)(int)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + signed1 = (long)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed1 = (long)(IntPtr)operand1.Value; + doSigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + var signed2 = (long)(sbyte)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + signed2 = (long)(short)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + signed2 = (long)(int)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + signed2 = (long)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + signed2 = (long)(IntPtr)operand2.Value; + doSigned2: + if (signed1 >= signed2) return operand1; + return operand2; + default: + unsigned1 = (ulong)signed1; + goto doUnsigned1; + } + + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + unsigned1 = ((bool)operand1.Value) ? 1UL : 0UL; + goto doUnsigned1; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + unsigned1 = (ulong)(char)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + unsigned1 = (ulong)(byte)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + unsigned1 = (ulong)(ushort)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + unsigned1 = (ulong)(uint)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + unsigned1 = (ulong)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is UIntPtr); + unsigned1 = (ulong)(UIntPtr)operand1.Value; + doUnsigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var unsigned2 = ((bool)operand2.Value) ? 1UL : 0UL; + goto doUnsigned2; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + unsigned2 = (ulong)(char)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + unsigned2 = (ulong)(sbyte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + unsigned2 = (ulong)(short)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + unsigned2 = (ulong)(int)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + unsigned2 = (ulong)(IntPtr)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + unsigned2 = (ulong)(byte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + unsigned2 = (ulong)(ushort)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + unsigned2 = (ulong)(uint)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is UIntPtr); + unsigned2 = (ulong)(UIntPtr)operand2.Value; + doUnsigned2: + if (unsigned1 >= unsigned2) return operand1; + return operand2; + default: + return null; + } + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var d1 = (double)(float)operand1.Value; + goto doFloat1; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d1 = (double)operand1.Value; + doFloat1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var d2 = (double)(float)operand2.Value; + goto doFloat2; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doFloat2: + if (d1 >= d2) return operand1; + return operand2; + default: + return null; + } + } + return null; + + } + + /// + /// + /// + /// + /// + /// + public static IMetadataConstant/*?*/ Min(IMetadataConstant/*?*/ operand1, IMetadataConstant/*?*/ operand2) { + if (operand1 == null || operand2 == null) return null; + if (operand1 == Dummy.Constant || operand2 == Dummy.Constant) return Dummy.Constant; + + long signed1 = 0; + ulong unsigned1 = 0; + switch (operand1.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand1.Value is sbyte); + signed1 = (long)(sbyte)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand1.Value is short); + signed1 = (long)(short)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand1.Value is int); + signed1 = (long)(int)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand1.Value is long); + signed1 = (long)operand1.Value; + goto doSigned1; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand1.Value is IntPtr); + signed1 = (long)(IntPtr)operand1.Value; + doSigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + var signed2 = (long)(sbyte)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + signed2 = (long)(short)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + signed2 = (long)(int)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + signed2 = (long)operand2.Value; + goto doSigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + signed2 = (long)(IntPtr)operand2.Value; + doSigned2: + if (signed1 <= signed2) return operand1; + return operand2; + default: + unsigned1 = (ulong)signed1; + goto doUnsigned1; + } + + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand1.Value is bool); + unsigned1 = ((bool)operand1.Value) ? 1UL : 0UL; + goto doUnsigned1; + case PrimitiveTypeCode.Char: + Contract.Assume(operand1.Value is char); + unsigned1 = (ulong)(char)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand1.Value is byte); + unsigned1 = (ulong)(byte)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand1.Value is ushort); + unsigned1 = (ulong)(ushort)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand1.Value is uint); + unsigned1 = (ulong)(uint)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand1.Value is ulong); + unsigned1 = (ulong)operand1.Value; + goto doUnsigned1; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand1.Value is UIntPtr); + unsigned1 = (ulong)(UIntPtr)operand1.Value; + doUnsigned1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Boolean: + Contract.Assume(operand2.Value is bool); + var unsigned2 = ((bool)operand2.Value) ? 1UL : 0UL; + goto doUnsigned2; + case PrimitiveTypeCode.Char: + Contract.Assume(operand2.Value is char); + unsigned2 = (ulong)(char)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int8: + Contract.Assume(operand2.Value is sbyte); + unsigned2 = (ulong)(sbyte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int16: + Contract.Assume(operand2.Value is short); + unsigned2 = (ulong)(short)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int32: + Contract.Assume(operand2.Value is int); + unsigned2 = (ulong)(int)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.Int64: + Contract.Assume(operand2.Value is long); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.IntPtr: + Contract.Assume(operand2.Value is IntPtr); + unsigned2 = (ulong)(IntPtr)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt8: + Contract.Assume(operand2.Value is byte); + unsigned2 = (ulong)(byte)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt16: + Contract.Assume(operand2.Value is ushort); + unsigned2 = (ulong)(ushort)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt32: + Contract.Assume(operand2.Value is uint); + unsigned2 = (ulong)(uint)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UInt64: + Contract.Assume(operand2.Value is ulong); + unsigned2 = (ulong)operand2.Value; + goto doUnsigned2; + case PrimitiveTypeCode.UIntPtr: + Contract.Assume(operand2.Value is UIntPtr); + unsigned2 = (ulong)(UIntPtr)operand2.Value; + doUnsigned2: + if (unsigned1 <= unsigned2) return operand1; + return operand2; + default: + return null; + } + + case PrimitiveTypeCode.Float32: + Contract.Assume(operand1.Value is float); + var d1 = (double)(float)operand1.Value; + goto doFloat1; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand1.Value is double); + d1 = (double)operand1.Value; + doFloat1: + switch (operand2.Type.TypeCode) { + case PrimitiveTypeCode.Float32: + Contract.Assume(operand2.Value is float); + var d2 = (double)(float)operand2.Value; + goto doFloat2; + case PrimitiveTypeCode.Float64: + Contract.Assume(operand2.Value is double); + d2 = (double)operand2.Value; + doFloat2: + if (d1 <= d2) return operand1; + return operand2; + default: + return null; + } + } + return null; + + } + + + } + +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/ExpressionCanonicalizer.cs b/Metadata/Sources/AnalyisUtilities/ExpressionCanonicalizer.cs new file mode 100644 index 0000000..169ca4f --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/ExpressionCanonicalizer.cs @@ -0,0 +1,300 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; +using Microsoft.Cci.MutableCodeModel; +using System; + +namespace Microsoft.Cci.Analysis { + + internal class ExpressionCanonicalizer + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + internal ExpressionCanonicalizer(ValueMappings mappings) { + Contract.Requires(mappings != null); + this.mappings = mappings; + } + + ValueMappings mappings; + Dictionary cache = new Dictionary(new ExpressionComparer()); + Instruction dummyInstruction = new Instruction(); + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.mappings != null); + Contract.Invariant(this.cache != null); + Contract.Invariant(this.dummyInstruction != null); + } + + internal class ExpressionComparer : IEqualityComparer { + + [ContractVerification(false)] + public bool Equals(Instruction x, Instruction y) { + if (x == null) return y == null; + if (y == null) return false; + if (object.ReferenceEquals(x, y)) return true; + if (x.Operation.OperationCode != y.Operation.OperationCode) return false; + if (x.Operation.Value == null) { + if (y.Operation.Value != null) return false; + } else { + if (!x.Operation.Value.Equals(y.Operation.Value)) return false; + } + if (x.Operand1 == null) return y.Operand1 == null; + bool result = this.Equals((Instruction)x.Operand1, (Instruction)y.Operand1); + if (x.Operand2 == null) return result && y.Operand2 == null; + var operand2x = x.Operand2 as Instruction; + var operand2y = y.Operand2 as Instruction; + if (operand2x != null) { + if (operand2y == null) return false; + if (this.Equals(operand2x, operand2y)) return result; + if (result) return false; + if (OperationIsCummutative(x.Operation.OperationCode)) { + return (this.Equals((Instruction)x.Operand1, operand2y) && this.Equals((Instruction)y.Operand1, operand2x)); + } + return false; + } + if (!result) return false; + var operandsx = x.Operand2 as Instruction[]; + var operandsy = y.Operand2 as Instruction[]; + Contract.Assume(operandsx != null); + var n = operandsx.Length; + if (operandsy == null || operandsy.Length != n) return false; + for (int i = 0; i < n; i++) { + var opx = operandsx[i]; + var opy = operandsy[i]; + if (!this.Equals(opx, opy)) return false; + } + return true; + } + + private static bool OperationIsCummutative(OperationCode operationCode) { + switch (operationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + case OperationCode.And: + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + case OperationCode.Ceq: + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + case OperationCode.Or: + case OperationCode.Xor: + return true; + } + return false; + } + + [ContractVerification(false)] + public int GetHashCode(Instruction instruction) { + if (instruction == null) return 0; + int result = (int)instruction.Operation.OperationCode; + if (instruction.Operation.Value != null) + result = (result << 2) ^ instruction.Operation.Value.GetHashCode(); + if (instruction.Operand1 == null) return result; + var hash1 = this.GetHashCode((Instruction)instruction.Operand1); + if (instruction.Operand2 == null) return (result << 2) ^ hash1; + var operand2 = instruction.Operand2 as Instruction; + if (operand2 != null) { + var hash2 = this.GetHashCode(operand2); + return (result << 3) ^ (hash1 ^ hash2); + } + var operands = instruction.Operand2 as Instruction[]; + Contract.Assume(operands != null); + for (int i = 0, n = operands.Length; i < n; i++) { + result = (result << 2) ^ this.GetHashCode(operands[i]); + } + return result; + } + + } + + /// + /// Returns the canonical form of an expression that results in the given constant at runtime. + /// + /// The compile time constant that should be equal to the value the resulting expression will evaluate to at runtime. + /// An instruction that will result in the given constant at runtime. The result of this method is a canonical form of this instruction. + internal Instruction GetAsCanonicalizedLoadConstant(IMetadataConstant compileTimeConstant, Instruction originalInstruction) { + Contract.Requires(compileTimeConstant != null); + Contract.Requires(originalInstruction != null); + Contract.Ensures(Contract.Result() != null); + + var ic = compileTimeConstant.Value as IConvertible; + if (ic != null) + return this.GetAsLoadConstant(ic, originalInstruction); + else if (compileTimeConstant.Value == null) + return new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldnull, Location = originalInstruction.Operation.Location }, Type = compileTimeConstant.Type }; + else + return originalInstruction; + } + + /// + /// Returns the canonical form of an expression that results in the given constant at runtime. + /// + /// The value that the resulting expression must evaluate to at runtime. + /// An instruction that will result in the given constant at runtime. The result of this method is a canonical form of this instruction. + private Instruction GetAsLoadConstant(IConvertible convertible, Instruction originalInstruction) { + Contract.Requires(convertible != null); + Contract.Requires(originalInstruction != null); + Contract.Ensures(Contract.Result() != null); + + Instruction result = originalInstruction; + var operation = originalInstruction.Operation; + var location = originalInstruction.Operation.Location; + TypeCode tc = convertible.GetTypeCode(); + switch (tc) { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + long n = convertible.ToInt64(null); + if (int.MinValue <= n && n <= int.MaxValue) + result = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldc_I4, Value = (int)n, Location = location } }; + else + result = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldc_I8, Value = n, Location = location } }; + break; + + case TypeCode.UInt64: + result = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldc_I8, Value = (long)convertible.ToUInt64(null), Location = location } }; + break; + + case TypeCode.Single: + result = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldc_R4, Value = convertible.ToSingle(null), Location = location } }; + break; + + case TypeCode.Double: + result = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldc_R8, Value = convertible.ToDouble(null), Location = location } }; + break; + + case TypeCode.String: + result = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldstr, Value = convertible.ToString(null), Location = location } }; + break; + } + result.Type = originalInstruction.Type; + return this.GetCanonicalExpression(result); + } + + /// + /// If an expression equivalent to the given expression can be found in the cache, the result is that expression. + /// Otherwise the result is the given expression and it is added to the cache as well. + /// + /// An instruction that computes a value. + internal Instruction GetCanonicalExpression(Instruction expression) { + Contract.Requires(expression != null); + Contract.Ensures(Contract.Result() != null); + + Instruction result; + if (!this.cache.TryGetValue(expression, out result)) { + this.cache[expression] = result = expression; + } + Contract.Assume(result != null); + return result; + } + + /// + /// If the cache contains an expression with an Operation structurally equivalent to unaryInstruction.Operation and Operand1 structurally equivalent to + /// operand1, then return that expression. Otherwise construct such an expression, add it to the cache and return it. + /// + /// An instruction with a single operand. + /// The already canonicalized version of unaryInstruction.Operand1, if available, otherwise unaryInstruction.Operand1. + internal Instruction GetCanonicalExpression(Instruction unaryInstruction, Instruction operand1) { + Contract.Requires(unaryInstruction != null); + Contract.Requires(operand1 != null); + Contract.Ensures(Contract.Result() != null); + + var expression = this.dummyInstruction; + expression.Operation = unaryInstruction.Operation; + expression.Operand1 = operand1; + expression.Operand2 = null; + expression.Type = unaryInstruction.Type; + + Instruction result; + if (!this.cache.TryGetValue(expression, out result)) { + result = this.GetCanonicalExpression(Simplifier.SimplifyUnary(expression, this.mappings, this)); + this.cache[expression] = result; + this.dummyInstruction = new Instruction(); + } + Contract.Assume(result != null); + return result; + } + + /// + /// If the cache contains an expression with an Operation structurally equivalent to binaryInstruction.Operation, Operand1 structurally equivalent to + /// operand1 and Operand2 structurally equivalent to the operand2, then return that expression. + /// Otherwise construct such an expression, simplify it and then add it to the cache and return it. + /// + /// An instruction with a two operands. + /// The already canonicalized version of binaryInstruction.Operand1, if available, otherwise binaryInstruction.Operand1. + /// The already canonicalized version of binaryInstruction.Operand2, if available, otherwise binaryInstruction.Operand2. + internal Instruction GetCanonicalExpression(Instruction binaryInstruction, Instruction operand1, Instruction operand2) { + Contract.Requires(binaryInstruction != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Ensures(Contract.Result() != null); + + var expression = this.dummyInstruction; + expression.Operation = binaryInstruction.Operation; + expression.Operand1 = operand1; + expression.Operand2 = operand2; + expression.Type = binaryInstruction.Type; + + Instruction result; + if (!this.cache.TryGetValue(expression, out result)) { + result = this.GetCanonicalExpression(Simplifier.SimplifyBinary(expression, this.mappings, this)); + this.cache[expression] = result; + this.dummyInstruction = new Instruction(); + } + Contract.Assume(result != null); + return result; + } + + internal Instruction GetCanonicalExpression(Instruction naryInstruction, Instruction operand1, Instruction/*?*/ operand2, Instruction[]/*?*/ operands2andBeyond) { + Contract.Requires(naryInstruction != null); + Contract.Requires(operand1 != null); + + var expression = this.dummyInstruction; + expression.Operation = naryInstruction.Operation; + expression.Operand1 = this.GetCanonicalExpression(Simplifier.Simplify(operand1, this.mappings, this)); + if (operand2 != null) + expression.Operand2 = this.GetCanonicalExpression(Simplifier.Simplify(operand2, this.mappings, this)); + else if (operands2andBeyond != null) { + var n = operands2andBeyond.Length; + var canonOps = new Instruction[n]; + for (int i = 0; i < n; i++) { + Contract.Assume(operands2andBeyond[i] != null); + canonOps[i] = this.GetCanonicalExpression(Simplifier.Simplify(operands2andBeyond[i], this.mappings, this)); + } + expression.Operand2 = canonOps; + } + expression.Type = naryInstruction.Type; + + Instruction result; + if (!this.cache.TryGetValue(expression, out result)) { + this.cache[expression] = result = expression; + this.dummyInstruction = new Instruction(); + } + Contract.Assume(result != null); + return result; + } + + } + + +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/Properties/AssemblyInfo.cs b/Metadata/Sources/AnalyisUtilities/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d58bacf --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Cci.Analysis.AnalysisUtilities")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c7078b32-c394-49a1-9946-c0306c238aa8")] diff --git a/Metadata/Sources/AnalyisUtilities/Purger.cs b/Metadata/Sources/AnalyisUtilities/Purger.cs new file mode 100644 index 0000000..7c29599 --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/Purger.cs @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; +using Microsoft.Cci.MutableCodeModel; +using System; + +namespace Microsoft.Cci.Analysis { + + /// + /// Rewrites Boolean expressions to exclude references to variables that have been updated. + /// + public static class Purger { + + /// + /// Rewrites Boolean expressions to exclude references to variables that have been updated. + /// + /// + /// + /// + /// + /// + internal static Instruction/*?*/ Purge(Instruction instruction, INamedEntity variable, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(variable != null); + Contract.Requires(canonicalizer != null); + + var operand1 = instruction.Operand1 as Instruction; + if (operand1 == null) + return PurgeNullary(instruction, variable); + var operand2 = instruction.Operand2 as Instruction; + if (operand2 != null) + return PurgeBinary(instruction, variable, canonicalizer); + else if (instruction.Operand2 == null) + return PurgeUnary(instruction, variable, canonicalizer); + else + return PurgeNary(instruction, variable, canonicalizer); + } + + private static Instruction/*?*/ PurgeNary(Instruction instruction, INamedEntity variable, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(variable != null); + Contract.Requires(canonicalizer != null); + + Contract.Assume(instruction.Operand1 is Instruction); + var operand1 = Purge((Instruction)instruction.Operand1, variable, canonicalizer); + if (operand1 == null) return null; + var operand2andBeyond = instruction.Operand2 as Instruction[]; + Contract.Assume(operand2andBeyond != null); + var n = operand2andBeyond.Length; + Instruction[] copy = null; + for (int i = 0; i < n; i++) { + Contract.Assume(operand2andBeyond[i] != null); + var opi = Purge(operand2andBeyond[i], variable, canonicalizer); + if (opi == null) return null; + if (opi != operand2andBeyond[i]) { + if (copy == null) { + copy = new Instruction[n]; + for (int j = 0; j < i; j++) copy[j] = operand2andBeyond[j]; + } + Contract.Assume(copy.Length == n); + copy[i] = opi; + } + } + if (operand1 != instruction.Operand1 || copy != null) + return canonicalizer.GetCanonicalExpression(instruction, operand1, null, copy); + return instruction; + } + + private static Instruction/*?*/ PurgeNullary(Instruction instruction, INamedEntity variable) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(variable != null); + + if (instruction.Operation.Value == variable) return null; + return instruction; + } + + /// + /// Uses Arithmetic and Boolean laws to simplify expressions. + /// + /// + /// + /// + /// + /// + internal static Instruction/*?*/ PurgeBinary(Instruction instruction, INamedEntity variable, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(variable != null); + Contract.Requires(canonicalizer != null); + + var operation = instruction.Operation; + Contract.Assume(instruction.Operand1 is Instruction); + var operand1 = (Instruction)instruction.Operand1; + Contract.Assume(instruction.Operand2 is Instruction); + var operand2 = (Instruction)instruction.Operand2; + operand1 = Purge(operand1, variable, canonicalizer); + operand2 = Purge(operand2, variable, canonicalizer); + + switch (operation.OperationCode) { + case OperationCode.And: + case OperationCode.Or: + if (operand1 == null) return operand2; + if (operand2 == null) return operand1; + if (operand1 != instruction.Operand1 || operand2 != instruction.Operand2) + return canonicalizer.GetCanonicalExpression(instruction, operand1, operand2); + break; + } + if (operand1 != instruction.Operand1 || operand2 != instruction.Operand2) return null; + return instruction; + } + + /// + /// + /// + /// + /// + /// + /// + /// + internal static Instruction/*?*/ PurgeUnary(Instruction instruction, INamedEntity variable, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(variable != null); + Contract.Requires(canonicalizer != null); + + var operation = instruction.Operation; + Contract.Assume(instruction.Operand1 is Instruction); + var operand1 = (Instruction)instruction.Operand1; + var operand = Purge(operand1, variable, canonicalizer); + if (operand != operand1) return null; + return instruction; + } + + } +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/SatSolver.cs b/Metadata/Sources/AnalyisUtilities/SatSolver.cs new file mode 100644 index 0000000..97f0339 --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/SatSolver.cs @@ -0,0 +1,318 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; +using System.Collections.Generic; +using Microsoft.Cci.MutableCodeModel; + +namespace Microsoft.Cci.Analysis { + + /// + /// Implemented by an object that implements a Boolean Satisfibility Solver. + /// + public interface ISatSolver { + + /// + /// An expression for which ISatSolverContext.Check always returns null. + /// + ISatExpressionWrapper Dummy { get; } + + /// + /// An expression for which ISatSolverContext.Check always returns false. + /// + ISatExpressionWrapper False { get; } + + /// + /// Provides a context that can hold a number of boolean expressions. + /// The solver is used to see if there exists an assignment of values to variables that will make all of the Boolean expressions true. + /// + ISatSolverContext GetNewContext(); + + /// + /// Returns an ISatExpressionWrapper instance that corresponds to the given operation. The operation takes no arguments, so it is expected + /// to be a constant value or a variable reference. May return null if operation cannot be mapped to an expression. + /// + ISatExpressionWrapper/*?*/ MakeExpression(IOperation operation, ITypeReference expressionType); + + /// + /// Returns an ISatExpressionWrapper instance that corresponds to the given operation. The operation takes a single argument, so it is expected + /// to involve a unary operator, such as - or ! or ~. May return null if operation cannot be mapped to an expression. + /// + ISatExpressionWrapper/*?*/ MakeExpression(IOperation operation, ITypeReference expressionType, ISatExpressionWrapper operand1); + + /// + /// Returns an ISatExpressionWrapper instance that corresponds to the given operation. The operation takes two arguments, so it is expected + /// to involve a binary operator, such as * or / or ^. May return null if operation cannot be mapped to an expression. + /// + ISatExpressionWrapper/*?*/ MakeExpression(IOperation operation, ITypeReference expressionType, ISatExpressionWrapper operand1, ISatExpressionWrapper operand2); + + /// + /// Returns an ISatExpressionWrapper instance that corresponds to operand1 => operand2. + /// + ISatExpressionWrapper MakeImplication(ISatExpressionWrapper operand1, ISatExpressionWrapper operand2); + + /// + /// An expression for which ISatSolverContext.Check always returns true. + /// + ISatExpressionWrapper True { get; } + + //TODO: add a way to make predicates that check for overflow. I.e. like MakeExpression, but the result is a boolean expression that indicates overflow can happen if it is satisfiable. + + } + + /// + /// A context that can hold a number of boolean expressions. + /// The solver is used to see if there exists an assignment of values to variables that will make all of the Boolean expressions true. + /// + public interface ISatSolverContext { + + /// + /// Adds a boolean expression to the context. + /// + void Add(ISatExpressionWrapper expression); + + /// + /// Adds the inverse of the given boolean expression to the context. + /// + void AddInverse(ISatExpressionWrapper expression); + + /// + /// Checks if there exists an assigment of values to variables that will make all of the Boolean expressions in the context true. + /// Since this problem is not decidable, the solver may not be able to return an answer, in which case the return result is null + /// rather than false or true. + /// + bool? Check(); + + /// + /// Creates a check point from the expressions currently in the context. Any expressions added to the context after this call will be + /// discarded when a corresponding call is made to RestoreCheckPoint. + /// + void MakeCheckPoint(); + + /// + /// The number of check points that have been created. + /// + uint NumberOfCheckPoints { get; } + + /// + /// Discards any expressions added to the context since the last call to MakeCheckPoint. At least one check point must exist. + /// + void RestoreCheckPoint(); + } + + /// + /// A wrapper for an expression encoded in a format understood by the SAT solver. + /// + public interface ISatExpressionWrapper { + + /// + /// The type of value this expression results in. + /// + ITypeReference Type { get; } + + /// + /// Unwraps the wrapped expression, returning a value of the type expected by the SAT solver. + /// + T Unwrap(); + } + + internal class SatSolverHelper where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + internal SatSolverHelper(ISatSolver satSolver, ValueMappings mappings) { + Contract.Requires(satSolver != null); + Contract.Requires(mappings != null); + this.satSolver = satSolver; + this.mappings = mappings; + this.expressionMap = new Hashtable(); + this.andOp = new Operation() { OperationCode = OperationCode.And }; + this.orOp = new Operation() { OperationCode = OperationCode.Or }; + } + + IOperation andOp; + IOperation orOp; + ISatSolver satSolver; + Hashtable expressionMap; + ValueMappings mappings; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.satSolver != null); + Contract.Invariant(this.expressionMap != null); + Contract.Invariant(this.mappings != null); + } + + internal void AddPhiNodeConstraints(Instruction expression, ISatSolverContext context) { + Contract.Requires(expression != null); + Contract.Requires(context != null); + + if (expression.Operation.OperationCode == OperationCode.Nop) { + var phiVar = expression.Operation.Value as INamedEntity; + if (phiVar != null) { + var operand1 = expression.Operand1 as Instruction; + if (operand1 == null) return; + var loadPhiVar = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ldloc, Value = phiVar }, Type = operand1.Type }; + var constraint = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ceq }, Operand1 = loadPhiVar, Operand2 = operand1, Type = operand1.Type.PlatformType.SystemBoolean }; + var operand2 = expression.Operand2 as Instruction; + if (operand2 != null) { + var eq2 = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ceq }, Operand1 = loadPhiVar, Operand2 = operand2, Type = constraint.Type }; + constraint = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Or }, Operand1 = constraint, Operand2 = eq2, Type = constraint.Type }; + } else { + var operands2toN = expression.Operand2 as Instruction[]; + if (operands2toN != null) { + for (int i = 0; i < operands2toN.Length; i++) { + var operandi = operands2toN[i]; + Contract.Assume(operandi != null); + var eqi = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Ceq }, Operand1 = loadPhiVar, Operand2 = operandi, Type = constraint.Type }; + constraint = new Instruction() { Operation = new Operation() { OperationCode = OperationCode.Or }, Operand1 = constraint, Operand2 = eqi, Type = constraint.Type }; + } + } + } + context.Add(this.GetSolverExpressionFor(constraint)); + } + } else { + var operand1 = expression.Operand1 as Instruction; + if (operand1 != null) { + this.AddPhiNodeConstraints(operand1, context); + var operand2 = expression.Operand2 as Instruction; + if (operand2 != null) { + this.AddPhiNodeConstraints(operand2, context); + } else { + var operands2toN = expression.Operand2 as Instruction[]; + if (operands2toN != null) { + for (int i = 0; i < operands2toN.Length; i++) { + var operandi = operands2toN[i]; + Contract.Assume(operandi != null); + this.AddPhiNodeConstraints(operandi, context); + } + } + } + } + } + } + + internal ISatExpressionWrapper GetSolverExpressionFor(Instruction expression, List> listOfConjunctions = null) { + Contract.Requires(expression != null); + Contract.Ensures(Contract.Result() != null); + + var wrapper = this.expressionMap[expression] as ISatExpressionWrapper; + if (wrapper == null) { + if (!this.mappings.IsRecursive(expression)) + expression = Simplifier.HoistPhiNodes(expression); + if (listOfConjunctions != null && expression.Type.TypeCode == PrimitiveTypeCode.Boolean && expression.Operation.OperationCode == OperationCode.Nop && expression.Operation.Value is INamedEntity) { + wrapper = this.GetSolverExpressionForBooleanPhiNode(expression, listOfConjunctions); + } else { + var operand1 = expression.Operand1 as Instruction; + if (operand1 == null) { + wrapper = this.satSolver.MakeExpression(expression.Operation, expression.Type); + } else { + var operand1wrapper = this.GetSolverExpressionFor(operand1, listOfConjunctions); + var operand2 = expression.Operand2 as Instruction; + if (operand2 == null) { + wrapper = this.satSolver.MakeExpression(expression.Operation, expression.Type, operand1wrapper); + } else { + var operand2wrapper = this.GetSolverExpressionFor(operand2, listOfConjunctions); + wrapper = this.satSolver.MakeExpression(expression.Operation, expression.Type, operand1wrapper, operand2wrapper); + } + } + } + if (wrapper == null) wrapper = this.satSolver.Dummy; + Contract.Assume(wrapper != null); + this.expressionMap[expression] = wrapper; + } + return wrapper; + } + + private ISatExpressionWrapper/*?*/ GetSolverExpressionForBooleanPhiNode(Instruction expression, List> listOfConjunctions) { + Contract.Requires(expression != null); + Contract.Requires(listOfConjunctions != null); + + var operand1 = expression.Operand1 as Instruction; + if (operand1 == null) return null; + var result = this.GetSolverExpressionFor(operand1); + Contract.Assume(listOfConjunctions.Count > 0); + if (listOfConjunctions[0] == null) return null; + var entryConstraint1 = this.GetSolverExpressionFor(listOfConjunctions[0]); + if (entryConstraint1 != null) + result = this.satSolver.MakeImplication(entryConstraint1, result); + var operand2 = expression.Operand2 as Instruction; + if (operand2 != null) { + var wrappedOperand2 = this.GetSolverExpressionFor(operand2); + Contract.Assume(listOfConjunctions.Count > 1); + if (listOfConjunctions[1] == null) return null; + var entryConstraint2 = this.GetSolverExpressionFor(listOfConjunctions[1]); + if (entryConstraint2 != null) + result = this.satSolver.MakeImplication(entryConstraint2, wrappedOperand2); + result = this.satSolver.MakeExpression(this.andOp, expression.Type, result, wrappedOperand2); + } else { + var operand2toN = expression.Operand2 as Instruction[]; + if (operand2toN != null) { + for (int i = 0; i < operand2toN.Length; i++) { + Contract.Assume(operand2toN[i] != null); + var wrappedOperandi = this.GetSolverExpressionFor(operand2toN[i]); + Contract.Assume(listOfConjunctions.Count > i+1); + if (listOfConjunctions[i+1] == null) return null; + var entryConstrainti = this.GetSolverExpressionFor(listOfConjunctions[i+1]); + if (entryConstrainti != null) + result = this.satSolver.MakeImplication(entryConstrainti, wrappedOperandi); + result = this.satSolver.MakeExpression(this.andOp, expression.Type, result, wrappedOperandi); + } + } + } + return result; + } + + internal ISatExpressionWrapper/*?*/ GetSolverExpressionFor(List> listOfConjunctions) { + Contract.Requires(listOfConjunctions != null); + + var n = listOfConjunctions.Count; + if (n == 0) return null; + ISatExpressionWrapper/*?*/ disjunct = null; + for (int i = 0; i < n; i++) { + if (listOfConjunctions[i] == null) return null; + var conjunct = this.GetSolverExpressionFor(listOfConjunctions[i]); + if (conjunct == null) return null; + if (disjunct == null) + disjunct = conjunct; + else + disjunct = this.satSolver.MakeExpression(this.orOp, disjunct.Type, disjunct, conjunct); + } + return disjunct; + } + + private ISatExpressionWrapper/*?*/ GetSolverExpressionFor(List listOfExpressions) { + Contract.Requires(listOfExpressions != null); + + var n = listOfExpressions.Count; + if (n == 0) return null; + ISatExpressionWrapper/*?*/ conjunct = null; + for (int i = 0; i < n; i++) { + + if (listOfExpressions[i] == null) return null; + var expression = this.GetSolverExpressionFor(listOfExpressions[i]); + if (conjunct == null) + conjunct = expression; + else + conjunct = this.satSolver.MakeExpression(this.andOp, conjunct.Type, conjunct, expression); + } + return conjunct; + } + + internal ISatSolver SatSolver { + get { + Contract.Ensures(Contract.Result() != null); + return this.satSolver; + } + } + + } + +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/Simplifier.cs b/Metadata/Sources/AnalyisUtilities/Simplifier.cs new file mode 100644 index 0000000..672497a --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/Simplifier.cs @@ -0,0 +1,617 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci; +using Microsoft.Cci.UtilityDataStructures; +using Microsoft.Cci.MutableCodeModel; +using System; + +namespace Microsoft.Cci.Analysis { + + /// + /// Uses Arithmetic and Boolean laws to simplify expressions. + /// + public static class Simplifier { + + /// + /// Uses Arithmetic and Boolean laws to simplify expressions. + /// + /// + /// + /// + /// + /// + internal static Instruction Simplify(Instruction instruction, ValueMappings mappings, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(mappings != null); + Contract.Requires(canonicalizer != null); + Contract.Ensures(Contract.Result() != null); + + var operand1 = instruction.Operand1 as Instruction; + if (operand1 == null) + return SimplifyNullary(instruction, mappings); + var operand2 = instruction.Operand2 as Instruction; + if (operand2 != null) + return SimplifyBinary(instruction, mappings, canonicalizer); + else if (instruction.Operand2 == null) + return SimplifyUnary(instruction, mappings, canonicalizer); + else + return SimplifyNary(instruction, mappings, canonicalizer); + } + + private static Instruction SimplifyNary(Instruction instruction, ValueMappings mappings, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(mappings != null); + Contract.Requires(canonicalizer != null); + Contract.Ensures(Contract.Result() != null); + + Contract.Assume(instruction.Operand1 is Instruction); + instruction.Operand1 = Simplify((Instruction)instruction.Operand1, mappings, canonicalizer); + var operand2 = instruction.Operand2 as Instruction; + if (operand2 != null) { + instruction.Operand2 = Simplify(operand2, mappings, canonicalizer); + } else { + var operand2andBeyond = instruction.Operand2 as Instruction[]; + Contract.Assume(operand2andBeyond != null); + for (int i = 0, n = operand2andBeyond.Length; i < n; i++) { + Contract.Assume(operand2andBeyond[i] != null); + operand2andBeyond[i] = Simplify(operand2andBeyond[i], mappings, canonicalizer); + } + } + return instruction; + } + + private static Instruction SimplifyNullary(Instruction instruction, ValueMappings mappings) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(mappings != null); + var oldOp = instruction.Operation; + OperationCode newCode = OperationCode.Invalid; + switch (oldOp.OperationCode) { + case OperationCode.Ldarga_S: newCode = OperationCode.Ldarga; break; + case OperationCode.Ldloca_S: newCode = OperationCode.Ldloca; break; + case OperationCode.Br_S: newCode = OperationCode.Br; break; + case OperationCode.Leave_S: newCode = OperationCode.Leave; break; + case OperationCode.Ldc_I4_0: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_1: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_2: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_3: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_4: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_5: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_6: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_7: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_8: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_M1: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldc_I4_S: newCode = OperationCode.Ldc_I4; break; + case OperationCode.Ldarg_0: newCode = OperationCode.Ldarg; break; + case OperationCode.Ldarg_1: newCode = OperationCode.Ldarg; break; + case OperationCode.Ldarg_2: newCode = OperationCode.Ldarg; break; + case OperationCode.Ldarg_3: newCode = OperationCode.Ldarg; break; + case OperationCode.Ldarg_S: newCode = OperationCode.Ldarg; break; + case OperationCode.Ldloc_0: newCode = OperationCode.Ldloc; break; + case OperationCode.Ldloc_1: newCode = OperationCode.Ldloc; break; + case OperationCode.Ldloc_2: newCode = OperationCode.Ldloc; break; + case OperationCode.Ldloc_3: newCode = OperationCode.Ldloc; break; + case OperationCode.Ldloc_S: newCode = OperationCode.Ldloc; break; + } + switch (newCode) { + case OperationCode.Ldarg: + case OperationCode.Ldloc: + var localOrParameter = oldOp.Value as INamedEntity; + if (localOrParameter == null) break; + var definingExpression = mappings.GetDefiningExpressionFor(localOrParameter); + if (definingExpression != null) return definingExpression; + break; + } + if (newCode == OperationCode.Invalid) return instruction; + var newOp = new Operation() { OperationCode = newCode, Location = oldOp.Location, Offset = oldOp.Offset, Value = oldOp.Value }; + return new Instruction() { Operation = newOp, Operand1 = instruction.Operand1, Operand2 = instruction.Operand2, Type = instruction.Type }; + } + + /// + /// Uses Arithmetic and Boolean laws to simplify expressions. + /// + /// + /// + /// + /// + /// + internal static Instruction SimplifyBinary(Instruction instruction, ValueMappings mappings, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(mappings != null); + Contract.Requires(canonicalizer != null); + Contract.Ensures(Contract.Result() != null); + + var operation = instruction.Operation; + Contract.Assume(instruction.Operand1 is Instruction); + var operand1 = (Instruction)instruction.Operand1; + Contract.Assume(instruction.Operand2 is Instruction); + var operand2 = (Instruction)instruction.Operand2; + IMetadataConstant constantResult = null; + var compileTimeConstant1 = mappings.GetCompileTimeConstantValueFor(operand1); + var compileTimeConstant2 = mappings.GetCompileTimeConstantValueFor(operand2); + if (compileTimeConstant1 != null) { + if (compileTimeConstant2 != null) + constantResult = Evaluator.Evaluate(instruction.Operation, compileTimeConstant1, compileTimeConstant2); + else + constantResult = Evaluator.Evaluate(instruction.Operation, compileTimeConstant1, operand2, mappings); + } else if (compileTimeConstant2 != null) { + constantResult = Evaluator.Evaluate(instruction.Operation, operand1, compileTimeConstant2, mappings); + } else { + constantResult = Evaluator.Evaluate(instruction.Operation, operand1, operand2, mappings); + } + if (constantResult != null) return canonicalizer.GetAsCanonicalizedLoadConstant(constantResult, instruction); + + //If we get here, the instruction does not simplify to a constant, but it could still simplify to a simpler expression. + bool operand1IsZero = compileTimeConstant1 != null && MetadataExpressionHelper.IsIntegralZero(compileTimeConstant1); + bool operand1IsOne = compileTimeConstant1 != null && (operand1IsZero ? false : MetadataExpressionHelper.IsIntegralOne(compileTimeConstant1)); + bool operand1IsMinusOne = compileTimeConstant1 != null && ((operand1IsZero || operand1IsOne) ? false : MetadataExpressionHelper.IsIntegralMinusOne(compileTimeConstant1)); + bool operand2IsZero = compileTimeConstant2 != null && MetadataExpressionHelper.IsIntegralZero(compileTimeConstant2); + bool operand2IsOne = compileTimeConstant2 != null && (operand1IsZero ? false : MetadataExpressionHelper.IsIntegralOne(compileTimeConstant2)); + bool operand2IsMinusOne = compileTimeConstant2 != null && ((operand2IsZero || operand2IsOne) ? false : MetadataExpressionHelper.IsIntegralMinusOne(compileTimeConstant2)); + operand1 = Simplify(operand1, mappings, canonicalizer); + operand2 = Simplify(operand2, mappings, canonicalizer); + + switch (operation.OperationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + if (operand1IsZero) return operand2; + if (operand2IsZero) return operand1; + //TODO: factor out common mults/divs/etc (subject to overflow checks). + break; + case OperationCode.And: + if (operand1IsZero) return operand1; + if (operand2IsZero) return operand2; + if (operand1IsMinusOne) return operand2; + if (operand2IsMinusOne) return operand1; + if (operand1.Operation.OperationCode == OperationCode.Not && operand2.Operation.OperationCode == OperationCode.Not) { + var opnd11 = operand1.Operand1 as Instruction; + var opnd21 = operand2.Operand1 as Instruction; + Contract.Assume(opnd11 != null && opnd21 != null); + var or = new Operation() { OperationCode = OperationCode.Or, Location = operation.Location, Offset = operation.Offset }; + var orInst = new Instruction() { Operation = or, Operand1 = opnd11, Operand2 = opnd21, Type = instruction.Type }; + var not = new Operation { OperationCode = OperationCode.Not, Location = operation.Location, Offset = operation.Offset }; + return new Instruction() { Operation = not, Operand1 = orInst, Type = instruction.Type }; + } + break; + case OperationCode.Ceq: + //If one of the operands is const 0 and the other is a boolean expression, invert the boolean expression + if (operand2IsZero && operand1.Type.TypeCode == PrimitiveTypeCode.Boolean) { + var not = new Operation() { Location = instruction.Operation.Location, Offset = instruction.Operation.Offset, OperationCode = OperationCode.Not }; + instruction = new Instruction() { Operation = not, Operand1 = operand1, Type = instruction.Type }; + return SimplifyUnary(instruction, mappings, canonicalizer); + } else if (operand1IsZero && operand2.Type.TypeCode == PrimitiveTypeCode.Boolean) { + var not = new Operation() { Location = instruction.Operation.Location, Offset = instruction.Operation.Offset, OperationCode = OperationCode.Not }; + instruction = new Instruction() { Operation = not, Operand1 = operand2, Type = instruction.Type }; + return SimplifyUnary(instruction, mappings, canonicalizer); + } else { + operation = new Operation() { Location = instruction.Operation.Location, Offset = instruction.Operation.Offset, OperationCode = OperationCode.Beq }; + } + break; + case OperationCode.Cgt: + operation = new Operation() { Location = instruction.Operation.Location, Offset = instruction.Operation.Offset, OperationCode = OperationCode.Bgt }; + break; + case OperationCode.Cgt_Un: + operation = new Operation() { Location = instruction.Operation.Location, Offset = instruction.Operation.Offset, OperationCode = OperationCode.Bgt_Un }; + break; + case OperationCode.Clt: + operation = new Operation() { Location = instruction.Operation.Location, Offset = instruction.Operation.Offset, OperationCode = OperationCode.Blt }; + break; + case OperationCode.Clt_Un: + operation = new Operation() { Location = instruction.Operation.Location, Offset = instruction.Operation.Offset, OperationCode = OperationCode.Blt_Un }; + break; + case OperationCode.Div: + case OperationCode.Div_Un: + if (operand2IsOne) return operand1; + break; + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + if (operand1IsOne) return operand2; + if (operand2IsOne) return operand1; + break; + case OperationCode.Or: + if (operand1IsZero) return operand2; + if (operand2IsZero) return operand1; + if (operand1.Operation.OperationCode == OperationCode.Not && operand2.Operation.OperationCode == OperationCode.Not) { + var opnd11 = operand1.Operand1 as Instruction; + var opnd21 = operand2.Operand1 as Instruction; + Contract.Assume(opnd11 != null && opnd21 != null); + var and = new Operation() { OperationCode = OperationCode.And, Location = operation.Location, Offset = operation.Offset }; + var andInst = new Instruction() { Operation = and, Operand1 = opnd11, Operand2 = opnd21, Type = instruction.Type }; + var not = new Operation { OperationCode = OperationCode.Not, Location = operation.Location, Offset = operation.Offset }; + return new Instruction() { Operation = not, Operand1 = andInst, Type = instruction.Type }; + } + if (operand1.Operand1 == operand2.Operand1 && operand1.Operand2 == operand2.Operand2 && + operand1.Operation.OperationCode != operand2.Operation.OperationCode && operand2.Operand1 != null && + operand1.Operation.OperationCode == GetInverse(operand2.Operation.OperationCode, + operand2.Operand1.Type.TypeCode == PrimitiveTypeCode.Float32 || operand2.Operand1.Type.TypeCode == PrimitiveTypeCode.Float64)) { + return canonicalizer.GetAsCanonicalizedLoadConstant(new MetadataConstant() { Value = true, Type = instruction.Type }, instruction); + } + break; + case OperationCode.Rem: + case OperationCode.Rem_Un: + break; + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + if (operand2IsZero) return operand1; + break; + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + if (operand2IsZero) return operand1; + break; + case OperationCode.Xor: + break; + case OperationCode.Beq: + case OperationCode.Beq_S: + if (operand1IsZero && operand2.Type.TypeCode == PrimitiveTypeCode.Boolean) { + var operand2inv = TryToGetSimplerLogicalInverse(operand2); + if (operand2inv != null) return operand2inv; + } else if (operand2IsZero && operand1.Type.TypeCode == PrimitiveTypeCode.Boolean) { + var operand1inv = TryToGetSimplerLogicalInverse(operand1); + if (operand1inv != null) return operand1inv; + } + goto case OperationCode.Bge_S; + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + if (operand1IsZero && operand2.Type.TypeCode == PrimitiveTypeCode.Boolean) return operand2; + if (operand2IsZero && operand1.Type.TypeCode == PrimitiveTypeCode.Boolean) return operand1; + goto case OperationCode.Bge_S; + case OperationCode.Bge_S: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble_S: + case OperationCode.Ble_Un_S: + case OperationCode.Blt_S: + case OperationCode.Blt_Un_S: + operation = new Operation() { + Location = operation.Location, Offset = operation.Offset, + OperationCode = LongVersionOf(operation.OperationCode), Value = operation.Value + }; + break; + } + if (operation != instruction.Operation || operand1 != instruction.Operand1 || operand2 != instruction.Operand2) + return new Instruction() { Operation = operation, Operand1 = operand1, Operand2 = operand2, Type = instruction.Type }; + return instruction; + } + + /// + /// + /// + /// + /// + /// + public static Instruction HoistPhiNodes(Instruction instruction) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + var operand1 = instruction.Operand1 as Instruction; + if (operand1 == null) return instruction; + var operand2 = instruction.Operand2 as Instruction; + if (operand2 == null) return instruction; //TODO: unary ops + + bool operand1IsPhiNode = operand1.Operation.OperationCode == OperationCode.Nop && operand1.Operation.Value is INamedEntity; + bool operand2IsPhiNode = operand2.Operation.OperationCode == OperationCode.Nop && operand2.Operation.Value is INamedEntity; + if (operand1IsPhiNode) { + if (operand2IsPhiNode) + return HoistPhiPhi(instruction, operand1, operand2); + else + return HoistPhiOp(instruction, operand1, operand2); + } else { + if (operand2IsPhiNode) + return HoistOpPhi(instruction, operand1, operand2); + } + return instruction; + } + + private static Instruction HoistPhiPhi(Instruction instruction, Instruction operand1, Instruction operand2) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Ensures(Contract.Result() != null); + + var result = new Instruction() { Operation = new Operation() { Value = Dummy.LocalVariable }, Type = instruction.Type }; + var operand11 = operand1.Operand1 as Instruction; + var operand21 = operand2.Operand1 as Instruction; + if (operand11 == null) { + Contract.Assume(operand21 == null); + return result; + } + result.Operand1 = new Instruction() { Operation = instruction.Operation, Operand1 = operand11, Operand2 = operand21, Type = instruction.Type }; + var operand12 = operand1.Operand2 as Instruction; + var operand22 = operand2.Operand2 as Instruction; + if (operand12 != null && operand22 != null) { + result.Operand2 = new Instruction() { Operation = instruction.Operation, Operand1 = operand12, Operand2 = operand22, Type = instruction.Type }; + } else { + var operand12toN = operand1.Operand2 as Instruction[]; + var operand22toN = operand2.Operand2 as Instruction[]; + if (operand12toN != null && operand22toN != null) { + var n = operand12toN.Length; + Contract.Assume(n == operand22toN.Length); + var resultOperands2ToN = new Instruction[n]; + result.Operand2 = resultOperands2ToN; + for (int i = 0; i < n; i++) { + var operand1i = operand12toN[i]; + var operand2i = operand22toN[i]; + resultOperands2ToN[i] = new Instruction() { Operation = instruction.Operation, Operand1 = operand1i, Operand2 = operand2i, Type = instruction.Type }; + } + } + } + return result; + } + + private static Instruction HoistPhiOp(Instruction instruction, Instruction operand1, Instruction operand2) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Ensures(Contract.Result() != null); + + var result = new Instruction() { Operation = new Operation() { Value = Dummy.LocalVariable }, Type = instruction.Type }; + var operand11 = operand1.Operand1 as Instruction; + if (operand11 == null) { + Contract.Assume(false); + return result; + } + result.Operand1 = new Instruction() { Operation = instruction.Operation, Operand1 = operand11, Operand2 = operand2, Type = instruction.Type }; + var operand12 = operand1.Operand2 as Instruction; + if (operand12 != null) { + result.Operand2 = new Instruction() { Operation = instruction.Operation, Operand1 = operand12, Operand2 = operand2, Type = instruction.Type }; + } else { + var operand12toN = operand1.Operand2 as Instruction[]; + if (operand12toN != null) { + var n = operand12toN.Length; + var resultOperands2ToN = new Instruction[n]; + result.Operand2 = resultOperands2ToN; + for (int i = 0; i < n; i++) { + var operand1i = operand12toN[i]; + resultOperands2ToN[i] = new Instruction() { Operation = instruction.Operation, Operand1 = operand1i, Operand2 = operand2, Type = instruction.Type }; + } + } + } + return result; + } + + private static Instruction HoistOpPhi(Instruction instruction, Instruction operand1, Instruction operand2) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(operand1 != null); + Contract.Requires(operand2 != null); + Contract.Ensures(Contract.Result() != null); + + var result = new Instruction() { Operation = new Operation() { Value = Dummy.LocalVariable }, Type = instruction.Type }; + var operand21 = operand2.Operand1 as Instruction; + if (operand21 == null) { + Contract.Assume(false); + return result; + } + result.Operand1 = new Instruction() { Operation = instruction.Operation, Operand1 = operand1, Operand2 = operand21, Type = instruction.Type }; + var operand22 = operand2.Operand2 as Instruction; + if (operand22 != null) { + result.Operand2 = new Instruction() { Operation = instruction.Operation, Operand1 = operand1, Operand2 = operand22, Type = instruction.Type }; + } else { + var operand22toN = operand2.Operand2 as Instruction[]; + if (operand22toN != null) { + var n = operand22toN.Length; + var resultOperands2ToN = new Instruction[n]; + result.Operand2 = resultOperands2ToN; + for (int i = 0; i < n; i++) { + var operand2i = operand22toN[i]; + resultOperands2ToN[i] = new Instruction() { Operation = instruction.Operation, Operand1 = operand1, Operand2 = operand2i, Type = instruction.Type }; + } + } + } + return result; + } + + /// + /// If the given operation code is a short branch, return the corresponding long branch. Otherwise return the given operation code. + /// + /// An operation code. + public static OperationCode LongVersionOf(OperationCode operationCode) { + switch (operationCode) { + case OperationCode.Beq_S: return OperationCode.Beq; + case OperationCode.Bge_S: return OperationCode.Bge; + case OperationCode.Bge_Un_S: return OperationCode.Bge_Un; + case OperationCode.Bgt_S: return OperationCode.Bgt; + case OperationCode.Bgt_Un_S: return OperationCode.Bgt_Un; + case OperationCode.Ble_S: return OperationCode.Ble; + case OperationCode.Ble_Un_S: return OperationCode.Ble_Un; + case OperationCode.Blt_S: return OperationCode.Blt; + case OperationCode.Blt_Un_S: return OperationCode.Blt_Un; + case OperationCode.Bne_Un_S: return OperationCode.Bne_Un; + case OperationCode.Br_S: return OperationCode.Br; + case OperationCode.Brfalse_S: return OperationCode.Brfalse; + case OperationCode.Brtrue_S: return OperationCode.Brtrue; + case OperationCode.Leave_S: return OperationCode.Leave; + default: return operationCode; + } + } + + /// + /// + /// + /// + /// + /// + /// + /// + internal static Instruction SimplifyUnary(Instruction instruction, ValueMappings mappings, ExpressionCanonicalizer canonicalizer) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + Contract.Requires(mappings != null); + Contract.Requires(canonicalizer != null); + Contract.Ensures(Contract.Result() != null); + + var operation = instruction.Operation; + Contract.Assume(instruction.Operand1 is Instruction); + var operand1 = (Instruction)instruction.Operand1; + var operand = Simplify(operand1, mappings, canonicalizer); + var compileTimeConstant = mappings.GetCompileTimeConstantValueFor(operand); + if (compileTimeConstant != null) { + var constantResult = Evaluator.Evaluate(instruction.Operation, compileTimeConstant); + if (constantResult != null) return canonicalizer.GetAsCanonicalizedLoadConstant(constantResult, instruction); + } + + switch (operation.OperationCode) { + case OperationCode.Neg: + if (operand.Operation.OperationCode == OperationCode.Neg) { + Contract.Assume(operand.Operand1 is Instruction); + return (Instruction)operand.Operand1; + } + //TODO: if the operand is a binary operation with arithmetic operands where one of them is a Neg + //distribute the neg over the binary operation, if doing so is safe w.r.t. overflow. + break; + case OperationCode.Not: + var simplerInverse = TryToGetSimplerLogicalInverse(operand); + if (simplerInverse != null) return simplerInverse; + if (operand != operand1) { + var operation1 = operand1.Operation; + switch (operation1.OperationCode) { + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + case OperationCode.Beq: + case OperationCode.Beq_S: + OperationCode newOpcode = GetInverse(operation1.OperationCode, operand1.Type.TypeCode == PrimitiveTypeCode.Float32 || operand1.Type.TypeCode == PrimitiveTypeCode.Float64); + return new Instruction() { + Operation = new Operation() { OperationCode = newOpcode, Offset = operation.Offset, Location = operation.Location }, + Operand1 = operand1.Operand1, + Operand2 = operand1.Operand2, + Type = instruction.Type + }; + } + } + return new Instruction() { Operation = operation, Operand1 = operand, Type = instruction.Type }; + } + return instruction; + } + + private static Instruction/*?*/ TryToGetSimplerLogicalInverse(Instruction instruction) + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + Contract.Requires(instruction != null); + switch (instruction.Operation.OperationCode) { + case OperationCode.Not: + Contract.Assume(instruction.Operand1 is Instruction); + return (Instruction)instruction.Operand1; + case OperationCode.And: { + var opnd1 = instruction.Operand1 as Instruction; + var opnd2 = instruction.Operand2 as Instruction; + Contract.Assume(opnd1 != null && opnd2 != null); + var opnd1inv = TryToGetSimplerLogicalInverse(opnd1); + var opnd2inv = TryToGetSimplerLogicalInverse(opnd2); + if (opnd1inv == null) { + if (opnd2inv == null) return null; + var not = new Operation() { OperationCode = OperationCode.Not, Location = opnd1.Operation.Location, Offset = opnd1.Operation.Offset }; + opnd1inv = new Instruction() { Operation = not, Operand1 = opnd1, Type = instruction.Type }; + } else if (opnd2inv == null) { + var not = new Operation() { OperationCode = OperationCode.Not, Location = opnd2.Operation.Location, Offset = opnd2.Operation.Offset }; + opnd2inv = new Instruction() { Operation = not, Operand1 = opnd2, Type = instruction.Type }; + } + var or = new Operation() { OperationCode = OperationCode.Or, Location = instruction.Operation.Location, Offset = instruction.Operation.Offset }; + return new Instruction() { Operation = or, Operand1 = opnd1inv, Operand2 = opnd2inv, Type = instruction.Type }; + } + case OperationCode.Or: { + var opnd1 = instruction.Operand1 as Instruction; + var opnd2 = instruction.Operand2 as Instruction; + Contract.Assume(opnd1 != null && opnd2 != null); + var opnd1inv = TryToGetSimplerLogicalInverse(opnd1); + var opnd2inv = TryToGetSimplerLogicalInverse(opnd2); + if (opnd1inv == null) { + if (opnd2inv == null) return null; + var not = new Operation() { OperationCode = OperationCode.Not, Location = opnd1.Operation.Location, Offset = opnd1.Operation.Offset }; + opnd1inv = new Instruction() { Operation = not, Operand1 = opnd1, Type = instruction.Type }; + } else if (opnd2inv == null) { + var not = new Operation() { OperationCode = OperationCode.Not, Location = opnd2.Operation.Location, Offset = opnd2.Operation.Offset }; + opnd2inv = new Instruction() { Operation = not, Operand1 = opnd2, Type = instruction.Type }; + } + var and = new Operation() { OperationCode = OperationCode.And, Location = instruction.Operation.Location, Offset = instruction.Operation.Offset }; + return new Instruction() { Operation = and, Operand1 = opnd1inv, Operand2 = opnd2inv, Type = instruction.Type }; + } + case OperationCode.Beq: + case OperationCode.Bge: + case OperationCode.Bge_Un: + case OperationCode.Bgt: + case OperationCode.Bgt_Un: + case OperationCode.Ble: + case OperationCode.Ble_Un: + case OperationCode.Blt: + case OperationCode.Blt_Un: + case OperationCode.Bne_Un: + case OperationCode.Ceq: + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + case OperationCode.Clt: + case OperationCode.Clt_Un: { + var opnd1 = instruction.Operand1 as Instruction; + var opnd2 = instruction.Operand2 as Instruction; + Contract.Assume(opnd1 != null && opnd2 != null); + OperationCode newOpcode = GetInverse(instruction.Operation.OperationCode, opnd1.Type.TypeCode == PrimitiveTypeCode.Float32 || opnd1.Type.TypeCode == PrimitiveTypeCode.Float64); + if (newOpcode != instruction.Operation.OperationCode) { + var cmp = new Operation() { OperationCode = newOpcode, Location = instruction.Operation.Location, Offset = instruction.Operation.Offset }; + return new Instruction() { Operation = cmp, Operand1 = opnd1, Operand2 = opnd2, Type = instruction.Type }; + } + break; + } + } + return null; + } + + private static OperationCode GetInverse(OperationCode operationCode, bool isFloatingPointOperation) { + if (isFloatingPointOperation) { + switch (operationCode) { + case OperationCode.Beq: return OperationCode.Bne_Un; + case OperationCode.Bge: return OperationCode.Blt_Un; + case OperationCode.Bge_Un: return OperationCode.Blt; + case OperationCode.Bgt: return OperationCode.Ble_Un; + case OperationCode.Bgt_Un: return OperationCode.Ble; + case OperationCode.Ble: return OperationCode.Bgt_Un; + case OperationCode.Ble_Un: return OperationCode.Bgt; + case OperationCode.Blt: return OperationCode.Bge_Un; + case OperationCode.Blt_Un: return OperationCode.Bge; + case OperationCode.Bne_Un: return OperationCode.Beq; + case OperationCode.Ceq: return OperationCode.Bne_Un; + case OperationCode.Cgt: return OperationCode.Ble_Un; + case OperationCode.Cgt_Un: return OperationCode.Ble; + case OperationCode.Clt: return OperationCode.Bge_Un; + case OperationCode.Clt_Un: return OperationCode.Bge; + } + } else { + switch (operationCode) { + case OperationCode.Beq: return OperationCode.Bne_Un; + case OperationCode.Bge: return OperationCode.Blt; + case OperationCode.Bge_Un: return OperationCode.Blt_Un; + case OperationCode.Bgt: return OperationCode.Ble; + case OperationCode.Bgt_Un: return OperationCode.Ble_Un; + case OperationCode.Ble: return OperationCode.Bgt; + case OperationCode.Ble_Un: return OperationCode.Bgt_Un; + case OperationCode.Blt: return OperationCode.Bge; + case OperationCode.Blt_Un: return OperationCode.Bge_Un; + case OperationCode.Bne_Un: return OperationCode.Beq; + case OperationCode.Ceq: return OperationCode.Bne_Un; + case OperationCode.Cgt: return OperationCode.Ble; + case OperationCode.Cgt_Un: return OperationCode.Ble; + case OperationCode.Clt: return OperationCode.Bge; + case OperationCode.Clt_Un: return OperationCode.Bge_Un; + } + } + return operationCode; + } + + } +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/SingleAssignment.cs b/Metadata/Sources/AnalyisUtilities/SingleAssignment.cs new file mode 100644 index 0000000..9314366 --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/SingleAssignment.cs @@ -0,0 +1,967 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; + +namespace Microsoft.Cci.Analysis { + + /// + /// Provides a static method that modifies a suitable control and data flow graph into a form where every local is assigned to in a single location. + /// That is, the graph is put into Static Single Assignment (SSA) form. (The "Static" is an attempt to make it clear that a local can be assigned + /// to many times dynamically (during execution) even though there is a single assignment instruction for it in the graph.) + /// + /// A type that is a subtype of Microsoft.Cci.Analysis.SSABasicBlock. + /// A type that is a subtype of Microsoft.Cci.Analysis.Instruction and that has a default constructor. + public class SingleAssigner + where BasicBlock : SSABasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + /// + /// Initializes an instance of SingleAssigner. + /// + /// + /// A set of basic blocks, each of which has a list of successor blocks and some other information. + /// Each block consists of a list of instructions, each of which can point to previous instructions that compute the operands it consumes. + /// + /// + /// An extensible collection of IName instances that represent names that are commonly used during compilation. + /// + /// + /// An object that can map some kinds of ILocation objects to IPrimarySourceLocation objects. May be null. + private SingleAssigner(INameTable nameTable, ControlAndDataFlowGraph cdfg, + ControlGraphQueries cfgQueries, ISourceLocationProvider sourceLocationProvider) { + Contract.Requires(nameTable != null); + Contract.Requires(cdfg != null); + Contract.Requires(cfgQueries != null); + + this.nameTable = nameTable; + this.cdfg = cdfg; + this.cfgQueries = cfgQueries; + this.sourceLocationProvider = sourceLocationProvider; + } + + /// + /// An extensible collection of IName instances that represent names that are commonly used during compilation. + /// + INameTable nameTable; + /// + /// An object that can map some kinds of ILocation objects to IPrimarySourceLocation objects. May be null. + /// + ISourceLocationProvider sourceLocationProvider; + /// + /// Used to make up unique names for the new locals introduced to make all assignments unique. + /// + uint localCounter; + /// + /// A set of basic blocks, each of which has a list of successor blocks and some other information. + /// Each block consists of a list of instructions, each of which can point to previous instructions that compute the operands it consumes. + /// + ControlAndDataFlowGraph cdfg; + /// + /// Presents information derived from a simple control flow graph. For example, traversal orders, predecessors, dominators and dominance frontiers. + /// + ControlGraphQueries cfgQueries; + /// + /// A list of all of the reads (join points or phi nodes) in this.cdfg. Used to avoid allocating new List objects for every block. + /// + List allReads = new List(); + /// + /// Keeps track of all of the blocks that have already been visited. Used to break cycles during visitation. + /// + SetOfObjects blocksAlreadyVisited = new SetOfObjects(); + /// + /// A map from the local (or parameter) used in the original IL to the new local written by the most recent assignment to the original local. + /// + Hashtable ssaVariableFor = new Hashtable(); + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.nameTable != null); + Contract.Invariant(this.cdfg != null); + Contract.Invariant(this.cfgQueries != null); + Contract.Invariant(this.allReads != null); + Contract.Invariant(this.blocksAlreadyVisited != null); + Contract.Invariant(this.ssaVariableFor != null); + } + + /// + /// Rewrites the blocks in the given cdfg so that every assignment to a local or parameter is to a new local (and thus each local is just + /// assigned to in exactly one place in the graph). The new names introduced by the writes are connected to the reads in successor blocks + /// by means of join points (a.k.a. Phi nodes) that are found in the Reads property of an SSABasicBlock. + /// + /// + /// A set of basic blocks, each of which has a list of successor blocks and some other information. + /// Each block consists of a list of instructions, each of which can point to previous instructions that compute the operands it consumes. + /// + /// + /// Presents information derived from a simple control flow graph. For example, traversal orders, predecessors, dominators and dominance frontiers. + /// + /// + /// An extensible collection of IName instances that represent names that are commonly used during compilation. + /// + /// + public static void GetInSingleAssignmentForm(INameTable nameTable, ControlAndDataFlowGraph cdfg, + ControlGraphQueries cfgQueries, ISourceLocationProvider sourceLocationProvider) { + Contract.Requires(nameTable != null); + Contract.Requires(cdfg != null); + Contract.Requires(cfgQueries != null); + + var singleAssigner = new SingleAssigner(nameTable, cdfg, cfgQueries, sourceLocationProvider); + singleAssigner.GetInSingleAssignmentForm(); + } + + /// + /// Rewrites the blocks in the given cdfg so that every assignment to a local or parameter is to a new local (and thus each local is just + /// assigned to in exactly one place in the graph). The new names introduced by the writes are connected to the reads in successor blocks + /// by means of join points (a.k.a. Phi nodes) that are found in the Reads property of an SSABasicBlock. + /// + private void GetInSingleAssignmentForm() { + foreach (var block in this.cdfg.AllBlocks) { + Contract.Assume(block != null); + this.CreateSSAVariablesAndJoinInformation(block); + } + foreach (var block in this.cdfg.RootBlocks) { + Contract.Assume(block != null); + this.ssaVariableFor.Clear(); + this.ReplaceLocalsWithSSALocals(block, this.ssaVariableFor); + } + } + + private void ReplaceLocalsWithSSALocals(BasicBlock block, Hashtable ssaVariableFor) { + Contract.Requires(block != null); + Contract.Requires(ssaVariableFor != null); + + if (block.Joins != null) { + foreach (var join in block.Joins) { + Contract.Assume(join.OriginalLocal != null); + ssaVariableFor[join.OriginalLocal] = join.NewLocal; + } + } + + foreach (var instruction in block.Instructions) { + Contract.Assume(instruction != null); + var operation = instruction.Operation; + switch (operation.OperationCode) { + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + case OperationCode.Starg: + case OperationCode.Starg_S: + Contract.Assume(operation.Value is SSAParameterDefinition); + var ssaParam = (SSAParameterDefinition)operation.Value; + ssaVariableFor[ssaParam.OriginalParameter] = ssaParam; + break; + + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + case OperationCode.Stloc: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + Contract.Assume(operation.Value is SSALocalDefinition); + var ssaLocal = (SSALocalDefinition)operation.Value; + ssaVariableFor[ssaLocal.OriginalLocal] = ssaLocal; + break; + + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + var ssaVar = ssaVariableFor[operation.Value??Dummy.ParameterDefinition]; + if (ssaVar != null) + instruction.Operation = new SSAOperation(operation, ssaVar); + break; + } + } + + var successors = this.cdfg.SuccessorsFor(block); + var n = successors.Count; + if (n == 0) return; + for (var i = 0; i < n; i++) { + var succ = successors[i]; + if (this.cfgQueries.ImmediateDominator(succ) == block) { + //Add join information, if necessary + foreach (var pair in this.ssaVariableFor) { + if (succ.Joins != null) { + foreach (var join in succ.Joins) { + if (join.OriginalLocal == pair.key) { + if (join.Join2 == null) { + join.Join2 = (INamedEntity)pair.value; + join.Block2 = block; + } else { + var otherJoins = join.OtherJoins; + if (otherJoins == null) join.OtherJoins = otherJoins = new List(); + otherJoins.Add((INamedEntity)pair.value); + var otherBlocks = join.OtherBlocks; + if (otherBlocks == null) join.OtherBlocks = otherBlocks = new List(); + otherBlocks.Add(block); + } + break; + } + } + } + } + } + } + for (var i = 0; i < n-1; i++) { + var succ = successors[i]; + if (!this.blocksAlreadyVisited.Add(succ)) continue; + var copyOfssaVariableFor = new Hashtable(ssaVariableFor); + this.ReplaceLocalsWithSSALocals(succ, copyOfssaVariableFor); + } + var lastSuccessor = successors[n-1]; + //Contract.Assume(lastSuccessor != null); + if (this.blocksAlreadyVisited.Add(lastSuccessor)) + this.ReplaceLocalsWithSSALocals(lastSuccessor, ssaVariableFor); + } + + /// + /// Runs through the instructions of the given block and updates any instruction that references a local or parameter + /// to instead reference a SSA local or parameter. + /// + private void CreateSSAVariablesAndJoinInformation(BasicBlock block) { + Contract.Requires(block != null); + + this.ssaVariableFor.Clear(); + foreach (var instruction in block.Instructions) { + Contract.Assume(instruction != null); + var operation = instruction.Operation; + switch (operation.OperationCode) { + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + var ssaVariable = this.ssaVariableFor[operation.Value??Dummy.ParameterDefinition]; + if (ssaVariable != null) + //The variable has already been defined in this block, use its new identity. + instruction.Operation = new SSAOperation(operation, ssaVariable); + else + //Create a new identity for this variable. + this.ReplaceWithNewSSAValue(instruction); + //Now replace the value one more time because this instruction represents both a read (the above replacement) and a write (the replacement below). + this.ReplaceWithNewSSAValue(instruction); + break; + + case OperationCode.Starg: + case OperationCode.Starg_S: + case OperationCode.Stloc: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + //Assign to new variable + this.ReplaceWithNewSSAValue(instruction); + break; + + //case OperationCode.Stind_I: + //case OperationCode.Stind_I1: + //case OperationCode.Stind_I2: + //case OperationCode.Stind_I4: + //case OperationCode.Stind_I8: + //case OperationCode.Stind_R4: + //case OperationCode.Stind_R8: + //case OperationCode.Stind_Ref: + //These could potentially write to locals. In that case the SSA constructed by this algorithm is inaccurate. + //Such coding patterns are very rare and fixing the inaccuracy at this level is very expensive. + //Consequently we leave it to the client to either fix the inaccuracy itself, or to detect situations where + //a stind could write to a local and to report them as errors. + + } + } + + foreach (var successor in this.cfgQueries.DominanceFrontierFor(block)) { + Contract.Assume(successor != null); + foreach (var pair in this.ssaVariableFor) { + bool joinedWriteWithRead = false; + if (successor.Joins != null) { + foreach (var join in successor.Joins) { + if (join.OriginalLocal == pair.key) { + if (join.Join2 == null) { + join.Join2 = (INamedEntity)pair.value; + join.Block2 = block; + } else { + var otherJoins = join.OtherJoins; + if (otherJoins == null) join.OtherJoins = otherJoins = new List(); + otherJoins.Add((INamedEntity)pair.value); + var otherBlocks = join.OtherBlocks; + if (otherBlocks == null) join.OtherBlocks = otherBlocks = new List(); + otherBlocks.Add(block); + } + joinedWriteWithRead = true; + break; + } + } + } + if (!joinedWriteWithRead) { + successor.Joins = new Join() { + OriginalLocal = pair.key, NewLocal = this.GetNewLocal(pair.key), + Join1 = (INamedEntity)pair.value, Block1 = block, Next = successor.Joins + }; + } + } + } + } + + /// + /// Makes up a new LocalDefinition or ParameterDefinition corresponding to the one in instruction.Operation.Value and + /// then updates instruction.Operation.Value with the new definition. The new definition will be an instance of + /// SSALocalDefinition or SSAParameterDefinition, both of which retain a reference to the original definition + /// so that the original IL can be recovered from the control flow graph even after it has been put into SSA form. + /// + /// The instruction to update. + private void ReplaceWithNewSSAValue(Instruction instruction) { + Contract.Requires(instruction != null); + + var operation = instruction.Operation; + var local = operation.Value as ILocalDefinition; + if (local != null) { + var newLocal = this.GetNewLocal(local); + this.ssaVariableFor[local] = newLocal; + instruction.Operation = new SSAOperation(operation, newLocal); + } else { + var par = operation.Value as IParameterDefinition; + var newPar = this.GetNewLocal(par); + if (par == null) par = Dummy.ParameterDefinition; + this.ssaVariableFor[par] = newPar; + instruction.Operation = new SSAOperation(operation, newPar); + } + } + + /// + /// Returns a new SSALocalDefinition if the argument is a local or a new SSAParameterDefinition if the argument is a parameter. + /// + private object GetNewLocal(object localOrParameter) { + Contract.Ensures(Contract.Result() != null); + + var local = localOrParameter as ILocalDefinition; + if (local != null) { + var localName = this.GetLocalName(local); + IName newName = this.GetNewName(localName); + return new SSALocalDefinition(local, newName); + } else { + var par = (localOrParameter as IParameterDefinition)??Dummy.ParameterDefinition; + IName newName = this.GetNewName(par.Name.Value); + return new SSAParameterDefinition(par, newName, this.cdfg.MethodBody.MethodDefinition.ContainingTypeDefinition); + } + } + + /// + /// Looks up a source provided name for the local using this.sourceLocationProvider, if there is one. + /// + /// + /// + private string GetLocalName(ILocalDefinition local) { + Contract.Requires(local != null); + Contract.Ensures(Contract.Result() != null); + + if (this.sourceLocationProvider != null) { + bool isCompilerGenerated; + var result = this.sourceLocationProvider.GetSourceNameFor(local, out isCompilerGenerated); + if (!string.IsNullOrEmpty(result)) return result; + } + return local.Name.Value; + } + + /// + /// Makes up a new name that is derived from the given name, but distinct from all other local names in this graph. + /// + /// Since all local names are going to get rewritten like this, they should remain unique if they started out that way. + private IName GetNewName(string name) { + Contract.Requires(name != null); + Contract.Ensures(Contract.Result() != null); + + if (name.Length == 0) name = "this"; + return this.nameTable.GetNameFor(name+"_"+this.localCounter++); + } + + } + + /// + /// A basic block in a control flow graph, enhanced with information to help create and represent a Static Single Assignment (SSA) form + /// of the control flow graph. + /// + /// + public class SSABasicBlock : EnhancedBasicBlock where Instruction : Microsoft.Cci.Analysis.Instruction { + /// + /// A potentially null (empty) list of locals + /// + public Join/*?*/ Joins; + + } + + /// + /// Records information about a local (or parameter) whose value can come from more than one SSA variable defined in ancestor blocks. + /// Corresponds to a "Phi node" in SSA literature. + /// + public class Join { + /// + /// The block from which control flowed to create Join1. + /// + public object Block1; + /// + /// The block from which control flowed to create Join2. + /// + public object Block2; + /// + /// A potentially null (empty) list of blocks from which control flowed to create OtherJoins. The order is the same, so OtherBlocks[i] will be the block that provided OtherJoins[i]. + /// + public List/*?*/ OtherBlocks; + /// + /// The local (or parameter) that appears in the original IL. + /// + public object OriginalLocal; + /// + /// The "SSA" local (or parameter) that is "written" by this join point (a.k.a. "Phi node") + /// + public object NewLocal; + /// + /// A local written by an ancestor block that flows into this join point without an intervening write to OriginalLocal. + /// + public INamedEntity Join1; + /// + /// A local written by another ancestor block that flows into this join point without an intervening write to OriginalLocal. + /// + public INamedEntity/*?*/ Join2; + /// + /// A potentially null (empty) set of locals written by other ancestor blocks that flows into this join point without an intervening write to OriginalLocal. + /// + public List/*?*/ OtherJoins; + /// + /// The type of the local (or parameter). + /// + public ITypeReference Type { + get { + Contract.Ensures(Contract.Result() != null); + var local = this.NewLocal as ILocalDefinition; + if (local != null) return local.Type; + var par = this.NewLocal as IParameterDefinition; + Contract.Assume(par != null); + return par.Type; + } + } + /// + /// The next local. + /// + internal Join/*?*/ Next; + + /// + /// + /// + /// + public ReadLocalEnumerator GetEnumerator() { + return new ReadLocalEnumerator(this); + } + + /// + /// + /// + public struct ReadLocalEnumerator { + + internal ReadLocalEnumerator(Join head) { + Contract.Requires(head != null); + this.current = head; + this.next = head; + } + + /// + /// + /// + public Join Current { + get { + Contract.Ensures(Contract.Result() != null); + Contract.Assume(this.current != null); + return this.current; + } + } + Join/*?*/ current; + + Join/*?*/ next; + + /// + /// + /// + /// + public bool MoveNext() { + if (this.next == null) return false; + this.current = this.next; + this.next = this.current.Next; + return true; + } + + } + } + + /// + /// A local definition that is just a new name (and object identity) for an existing local. The existing local + /// can be recovered via the OriginalLocal property. + /// + public class SSALocalDefinition : ILocalDefinition { + + /// + /// A local definition that is just a new name (and object identity) for an existing local. The existing local + /// can be recovered via the OriginalLocal property. + /// + /// The local for which the new local provides a new name and new object identity. + /// The name of the new local. + public SSALocalDefinition(ILocalDefinition originalLocal, IName name) { + Contract.Requires(originalLocal != null); + Contract.Requires(name != null); + this.originalLocal = originalLocal; + this.name = name; + } + + /// + /// The name of this local. + /// + IName name; + /// + /// The local for which this local provides a new name and new object identity. + /// + ILocalDefinition originalLocal; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.originalLocal != null); + Contract.Invariant(this.name != null); + } + + /// + /// The local for which this local provides a new name and new object identity. + /// + public ILocalDefinition OriginalLocal { + get { + Contract.Ensures(Contract.Result() != null); + return this.originalLocal; + } + } + + /// + /// Return the name of the local. + /// + public override string ToString() { + return this.name.Value; + } + + #region ILocalDefinition Members + + /// + /// The compile time value of the definition, if it is a local constant. + /// + public IMetadataConstant CompileTimeValue { + get { + Contract.Assume(this.originalLocal.IsConstant); + return this.originalLocal.CompileTimeValue; + } + } + + /// + /// Custom modifiers associated with local variable definition. + /// + public IEnumerable CustomModifiers { + get { + Contract.Assume(this.originalLocal.IsModified); + return this.originalLocal.CustomModifiers; + } + } + + /// + /// True if this local definition is readonly and initialized with a compile time constant value. + /// + public bool IsConstant { + get { return this.originalLocal.IsConstant; } + } + + /// + /// The local variable has custom modifiers. + /// + public bool IsModified { + get { return this.originalLocal.IsModified; } + } + + /// + /// True if the value referenced by the local must not be moved by the actions of the garbage collector. + /// + public bool IsPinned { + get { return this.originalLocal.IsPinned; } + } + + /// + /// True if the local contains a managed pointer (for example a reference to a local variable or a reference to a field of an object). + /// + public bool IsReference { + get { return this.originalLocal.IsReference; } + } + + /// + /// The definition of the method in which this local is defined. + /// + public IMethodDefinition MethodDefinition { + get { return this.originalLocal.MethodDefinition; } + } + + /// + /// The type of the local. + /// + public ITypeReference Type { + get { return this.originalLocal.Type; } + } + + #endregion + + #region INamedEntity Members + + /// + /// The name of the entity. + /// + public IName Name { + get { return this.name; } + } + + #endregion + + #region IObjectWithLocations Members + + /// + /// A potentially empty collection of locations that correspond to this instance. + /// + public IEnumerable Locations { + get { return this.originalLocal.Locations; } + } + + #endregion + } + + /// + /// A parameter definition that is just a new name (and object identity) for an existing parameter. The existing parameter + /// can be recovered via the OriginalParameter property. + /// + public class SSAParameterDefinition : IParameterDefinition { + + /// + /// A parameter definition that is just a new name (and object identity) for an existing parameter. The existing parameter + /// can be recovered via the OriginalParameter property. + /// + /// The parameter for which the new parameter provides a new name and new object identity. + /// + /// + public SSAParameterDefinition(IParameterDefinition originalParameter, IName name, ITypeDefinition containingType) { + Contract.Requires(originalParameter != null); + Contract.Requires(name != null); + Contract.Requires(containingType != null); + + this.originalParameter = originalParameter; + this.name = name; + if (originalParameter.Type is Dummy) + this.type = containingType; //should only happen if the parameter is the this parameter. + else + this.type = originalParameter.Type; + } + + /// + /// The name of this parameter. + /// + IName name; + /// + /// The parameter for which this object provides a new name and new object identity. + /// + IParameterDefinition originalParameter; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.originalParameter != null); + Contract.Invariant(this.name != null); + Contract.Invariant(this.type != null); + } + + /// + /// The parameter for which this object provides a new name and new object identity. + /// + public IParameterDefinition OriginalParameter { + get { + Contract.Ensures(Contract.Result() != null); + return this.originalParameter; + } + } + + /// + /// Returns the name of the parameter. + /// + public override string ToString() { + return this.name.Value; + } + + #region IParameterDefinition Members + + /// + /// A compile time constant value that should be supplied as the corresponding argument value by callers that do not explicitly specify an argument value for this parameter. + /// + public IMetadataConstant DefaultValue { + get { return this.originalParameter.DefaultValue; } + } + + /// + /// True if the parameter has a default value that should be supplied as the argument value by a caller for which the argument value has not been explicitly specified. + /// + public bool HasDefaultValue { + get { return this.originalParameter.HasDefaultValue; } + } + + /// + /// True if the argument value must be included in the marshalled arguments passed to a remote callee. + /// + public bool IsIn { + get { return this.originalParameter.IsIn; } + } + + /// + /// This parameter has associated marshalling information. + /// + public bool IsMarshalledExplicitly { + get { return this.originalParameter.IsMarshalledExplicitly; } + } + + /// + /// True if the argument value must be included in the marshalled arguments passed to a remote callee only if it is different from the default value (if there is one). + /// + public bool IsOptional { + get { return this.originalParameter.IsOptional; } + } + + /// + /// True if the final value assigned to the parameter will be marshalled with the return values passed back from a remote callee. + /// + public bool IsOut { + get { return this.originalParameter.IsOut; } + } + + /// + /// True if the parameter has the ParamArrayAttribute custom attribute. + /// + public bool IsParameterArray { + get { return this.originalParameter.IsParameterArray; } + } + + /// + /// Specifies how this parameter is marshalled when it is accessed from unmanaged code. + /// + public IMarshallingInformation MarshallingInformation { + get { return this.originalParameter.MarshallingInformation; } + } + + /// + /// The element type of the parameter array. + /// + public ITypeReference ParamArrayElementType { + get { return this.originalParameter.ParamArrayElementType; } + } + + #endregion + + #region IReference Members + + /// + /// A collection of metadata custom attributes that are associated with this definition. + /// + public IEnumerable Attributes { + get { return this.originalParameter.Attributes; } + } + + /// + /// Calls visitor.Visit(IParameterDefinition). + /// + public void Dispatch(IMetadataVisitor visitor) { + visitor.Visit(this); + } + + /// + /// Calls visitor.VisitReference(IParameterDefinition). + /// + public void DispatchAsReference(IMetadataVisitor visitor) { + visitor.VisitReference(this); + } + + #endregion + + #region IObjectWithLocations Members + + /// + /// A potentially empty collection of locations that correspond to this instance. + /// + public IEnumerable Locations { + get { return this.originalParameter.Locations; } + } + + #endregion + + #region INamedEntity Members + + /// + /// The name of the entity. + /// + public IName Name { + get { return this.name; } + } + + #endregion + + #region IParameterTypeInformation Members + + /// + /// The method or property that defines this parameter. + /// + public ISignature ContainingSignature { + get { return this.originalParameter.ContainingSignature; } + } + + /// + /// The list of custom modifiers, if any, associated with the parameter. Evaluate this property only if IsModified is true. + /// + public IEnumerable CustomModifiers { + get { + Contract.Assume(this.originalParameter.IsModified); + return this.originalParameter.CustomModifiers; + } + } + + /// + /// True if the parameter is passed by reference (using a managed pointer). + /// + public bool IsByReference { + get { return this.originalParameter.IsByReference; } + } + + /// + /// This parameter has one or more custom modifiers associated with it. + /// + public bool IsModified { + get { return this.originalParameter.IsModified; } + } + + /// + /// The type of argument value that corresponds to this parameter. + /// + public ITypeReference Type { + get { return this.type; } + } + ITypeReference type; + + #endregion + + #region IParameterListEntry Members + + /// + /// The position in the parameter list where this instance can be found. + /// + public ushort Index { + get { return this.originalParameter.Index; } + } + + #endregion + + #region IMetadataConstantContainer Members + + /// + /// The constant value associated with this metadata object. For example, the default value of a parameter. + /// + public IMetadataConstant Constant { + get { return this.originalParameter.Constant; } + } + + #endregion + } + + /// + /// A copy of an existing operation, whose Value property has been updated to reference a SSALocalDefinition or SSAParameterDefinition. + /// The original operation can be recovered via the OriginalOperation property. + /// + internal class SSAOperation : IOperation { + + /// + /// A copy of an existing operation, whose Value property has been updated to reference a SSALocalDefinition or SSAParameterDefinition. + /// The original operation can be recovered via the OriginalOperation property. + /// + /// The operation to copy into the new operation. + /// The object to use as the Value property of the new operation. + internal SSAOperation(IOperation originalOperation, object value) { + Contract.Requires(originalOperation != null); + + this.originalOperation = originalOperation; + this.value = value; + } + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.originalOperation != null); + } + + /// + /// The operation that was replaced by this object. It is the same as this object except for this.Value. + /// + IOperation originalOperation; + /// + /// The object to use as the Value property of this object. Usually an SSALocalDefinition or an SSAParameterDefinition + /// but could be a Dummy.ParameterDefinition of the operation references the this value of an instance method. + /// + object value; + + /// + /// The operation that was replaced by this object. It is the same as this object except for this.Value. + /// + public IOperation OriginalOperation { + get { + return this.originalOperation; + } + } + + #region IOperation Members + + /// + /// The actual value of the operation code + /// + public OperationCode OperationCode { + get { return this.originalOperation.OperationCode; } + } + + /// + /// The offset from the start of the operation stream of a method + /// + public uint Offset { + get { return this.originalOperation.Offset; } + } + + /// + /// The location that corresponds to this instruction. + /// + public ILocation Location { + get { return this.originalOperation.Location; } + } + + /// + /// Immediate data such as a string, the address of a branch target, or a metadata reference, such as a Field + /// + public object Value { + get { return this.value; } + } + + #endregion + } +} \ No newline at end of file diff --git a/Metadata/Sources/AnalyisUtilities/ValueMappings.cs b/Metadata/Sources/AnalyisUtilities/ValueMappings.cs new file mode 100644 index 0000000..f66bdbc --- /dev/null +++ b/Metadata/Sources/AnalyisUtilities/ValueMappings.cs @@ -0,0 +1,340 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; +using Microsoft.Cci.MutableCodeModel; + +namespace Microsoft.Cci.Analysis { + + /// + /// Provides several maps from expressions to concrete and abstract values. + /// + /// An instruction that results in value. + public class ValueMappings + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + /// + /// Provides several maps from expressions to concrete and abstract values. + /// + public ValueMappings(IPlatformType platformType, ISatSolver/*?*/ satSolver = null) { + Contract.Requires(platformType != null); + if (satSolver != null) + this.satSolverHelper = new SatSolverHelper(satSolver, this); + this.Int8Interval = new Interval(new MetadataConstant() { Value = sbyte.MinValue, Type = platformType.SystemInt8 }, + new MetadataConstant() { Value = sbyte.MaxValue, Type = platformType.SystemInt8 }); + this.Int16Interval = new Interval(new MetadataConstant() { Value = short.MinValue, Type = platformType.SystemInt16 }, + new MetadataConstant() { Value = short.MaxValue, Type = platformType.SystemInt16 }); + this.Int32Interval = new Interval(new MetadataConstant() { Value = int.MinValue, Type = platformType.SystemInt32 }, + new MetadataConstant() { Value = int.MaxValue, Type = platformType.SystemInt32 }); + this.Int64Interval = new Interval(new MetadataConstant() { Value = long.MinValue, Type = platformType.SystemInt64 }, + new MetadataConstant() { Value = long.MaxValue, Type = platformType.SystemInt64 }); + this.UInt8Interval = new Interval(new MetadataConstant() { Value = byte.MinValue, Type = platformType.SystemUInt8 }, + new MetadataConstant() { Value = byte.MaxValue, Type = platformType.SystemUInt8 }); + this.UInt16Interval = new Interval(new MetadataConstant() { Value = ushort.MinValue, Type = platformType.SystemUInt16 }, + new MetadataConstant() { Value = ushort.MaxValue, Type = platformType.SystemUInt16 }); + this.UInt32Interval = new Interval(new MetadataConstant() { Value = uint.MinValue, Type = platformType.SystemUInt32 }, + new MetadataConstant() { Value = uint.MaxValue, Type = platformType.SystemUInt32 }); + this.UInt64Interval = new Interval(new MetadataConstant() { Value = ulong.MinValue, Type = platformType.SystemUInt64 }, + new MetadataConstant() { Value = ulong.MaxValue, Type = platformType.SystemUInt64 }); + } + + Hashtable compileTimeConstantValueForExpression = new Hashtable(); + Hashtable compileTimeConstantForSSAVariable = new Hashtable(); + Hashtable> definingBlockForExpression = new Hashtable>(); + Hashtable definingExpressionForSSAVariable = new Hashtable(); + Hashtable definingJoinForSSAVariable = new Hashtable(); + Hashtable expressionForExpression = new Hashtable(); + Interval dummyInterval = new Interval(); + SatSolverHelper/*?*/ satSolverHelper; + HashtableForUintValues recursiveExpressions = new HashtableForUintValues(); + + internal Interval Int8Interval; + internal Interval Int16Interval; + internal Interval Int32Interval; + internal Interval Int64Interval; + internal Interval UInt8Interval; + internal Interval UInt16Interval; + internal Interval UInt32Interval; + internal Interval UInt64Interval; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.compileTimeConstantValueForExpression != null); + Contract.Invariant(this.compileTimeConstantForSSAVariable != null); + Contract.Invariant(this.definingBlockForExpression != null); + Contract.Invariant(this.definingExpressionForSSAVariable != null); + Contract.Invariant(this.definingJoinForSSAVariable != null); + Contract.Invariant(this.expressionForExpression != null); + Contract.Invariant(this.recursiveExpressions != null); + } + + /// + /// Returns a compile time constant value that is known to be the value of the given expression in all possible executions of the analyzed method. + /// If the value is Dummy.Constant, the expression is known to fail at runtime in all possible executions of the analyzed method. + /// + /// The expression for which a compile time constant value is desired. + /// A block providing the context for the interval. The entry contraints of the block are used to narrow the interval if possible. May be null. + [Pure] + public IMetadataConstant/*?*/ GetCompileTimeConstantValueFor(Instruction expression, AiBasicBlock/*?*/ block = null) { + Contract.Requires(expression != null); + + var result = this.compileTimeConstantValueForExpression[expression] as IMetadataConstant; + if (result != null) return result; + var ce = this.GetCanonicalExpressionFor(expression); + if (ce == null) return null; + result = this.compileTimeConstantValueForExpression[ce] as IMetadataConstant; + if (result != null || block == null) return result; + result = block.ConstantForExpression[expression] as IMetadataConstant; + if (result != null) { + if (result == Dummy.Constant) return null; + return result; + } + var operand1 = ce.Operand1 as Instruction; + if (operand1 == null) return null; + var operand2 = ce.Operand2 as Instruction; + if (operand2 != null) { + result = Evaluator.Evaluate(ce.Operation, operand1, operand2, this, block); + block.ConstantForExpression[expression] = result??Dummy.Constant; + if (result != null && result != Dummy.Constant) { + return result; + } + } else { + var operands2toN = ce.Operand2 as Instruction[]; + if (operands2toN != null) { + result = Evaluator.Evaluate(ce.Operation, operand1, operands2toN, this, block); + block.ConstantForExpression[expression] = result??Dummy.Constant; + if (result != null && result != Dummy.Constant) { + return result; + } + } + } + return null; + } + + /// + /// Returns a compile time constant that is known to be the value that is assigned to the given variable in all possible executions of the analyzed method. + /// + public IMetadataConstant/*?*/ GetCompileTimeConstantValueFor(INamedEntity variable) { + Contract.Requires(variable != null); + return this.compileTimeConstantForSSAVariable[(uint)variable.Name.UniqueKey]; + } + + /// + /// Returns an expression that results in the same value as the given expression in all possible executions of the analyzed method. + /// May be null if no such expression can be found. + /// + public Instruction/*?*/ GetCanonicalExpressionFor(Instruction expression) { + Contract.Requires(expression != null); + + return this.expressionForExpression[expression]; + } + + /// + /// Returns the expression which computes the value of the sole assignment to the given variable. + /// This can be a NOP instruction, which is a "phi" node in the SSA. Can be null. + /// + public Instruction/*?*/ GetDefiningExpressionFor(INamedEntity variable) { + Contract.Requires(variable != null); + return this.definingExpressionForSSAVariable[(uint)variable.Name.UniqueKey]; + } + + /// + /// Returns the block that defined the given "phi" node expression. This is useful because the entry contraints of this block might provide + /// additional information about the expression. If the block has not been defined earlier via SetDefiningBlockFor, the result will be null. + /// + internal AiBasicBlock/*?*/ GetDefiningBlockFor(Instruction expression) { + Contract.Requires(expression != null); + return this.definingBlockForExpression[expression]; + } + + /// + /// Return the Join information of the "phi" node that is the right hand side of the sole assignment to the given variable. Returns null if the variable is not + /// defined by a "phi" node. + /// + /// + /// + public Join/*?*/ GetDefiningJoinFor(INamedEntity variable) { + Contract.Requires(variable != null); + return this.definingJoinForSSAVariable[(uint)variable.Name.UniqueKey]; + } + + /// + /// Computes an inclusive numerical interval that contains the runtime value of the given expression. If no such interval can be found, the result is null. + /// + /// The expression for which a containing interval is desired. + /// A block providing the context for the interval. The entry contraints of the block are used to narrow the interval if possible. + public Interval/*?*/ GetIntervalFor(Instruction expression, AiBasicBlock block) { + Contract.Requires(expression != null); + Contract.Requires(block != null); + + var interval = block.IntervalForExpression[expression]; + if (interval == this.dummyInterval) return null; + if (interval == null) { + block.IntervalForExpression[expression] = this.dummyInterval; + interval = Interval.TryToGetAsInterval(expression, block, null, null, this); + block.IntervalForExpression[expression] = interval??this.dummyInterval; + } + return interval; + } + + /// + /// Returns true if the given expression is the value of a variable that is updated (inside of a loop) with a value that depends on the value of the variable in a an earlier iteration of the loop. + /// + internal bool IsRecursive(Instruction expression) { + Contract.Requires(expression != null); + + var tag = this.recursiveExpressions[expression]; + if (tag == 0) { + tag = 1; + var operand1 = expression.Operand1 as Instruction; + if (operand1 != null) { + if (this.IsRecursive(operand1)) + tag = 2; + else { + var operand2 = expression.Operand2 as Instruction; + if (operand2 != null) { + if (this.IsRecursive(operand2)) tag = 2; + } else { + var operand2toN = expression.Operand2 as Instruction[]; + if (operand2toN != null) { + for (int i = 0, n = operand2toN.Length; i < n; i++) { + var operandi = operand2toN[i]; + Contract.Assume(operandi != null); + if (this.IsRecursive(operandi)) { + tag = 2; + break; + } + } + } + } + } + } + this.recursiveExpressions[expression] = tag; + } + return tag == 2; + } + + /// + /// Uses the SAT solver, if supplied, to check if the given Boolean expression is true in the context of the given block. + /// Since this problem is not decidable, the solver may not be able to return an answer, in which case the return result is null + /// rather than false or true. Likewise, if no solver is available, the result is null. + /// + public bool? CheckIfExpressionIsTrue(Instruction expression, AiBasicBlock block) { + Contract.Requires(expression != null); + Contract.Requires(block != null); + + var satSolverHelper = this.satSolverHelper; + if (satSolverHelper == null) return null; + var context = block.SatSolverContext; + if (context == null) { + block.SatSolverContext = context = satSolverHelper.SatSolver.GetNewContext(); + Contract.Assume(context != null); + var constraintsAtEntry = satSolverHelper.GetSolverExpressionFor(block.ConstraintsAtEntry); + if (constraintsAtEntry != null) context.Add(constraintsAtEntry); + } + var solverExpression = this.satSolverHelper.GetSolverExpressionFor(expression, block.ConstraintsAtEntry); + context.MakeCheckPoint(); + if (!this.IsRecursive(expression)) + satSolverHelper.AddPhiNodeConstraints(expression, context); + //context.MakeCheckPoint(); + context.Add(solverExpression); + var result = context.Check(); + context.RestoreCheckPoint(); + if (result != null && !result.Value) { + //context.RestoreCheckPoint(); + return false; //The expression is never satisfied, so it is known to be false. + } + context.MakeCheckPoint(); + if (!this.IsRecursive(expression)) + satSolverHelper.AddPhiNodeConstraints(expression, context); + context.AddInverse(solverExpression); + result = context.Check(); + context.RestoreCheckPoint(); + //context.RestoreCheckPoint(); + + if (result != null && !result.Value) return true; //The inverse expression is never satisfied, so the expression is known to be true. + return null; + } + + /// + /// Associates the given expression with a canonical version that will always evaluate to the same value as the given expression. + /// + /// + /// + internal void SetCanonicalExpressionFor(Instruction expression, Instruction canonicalExpression) { + Contract.Requires(expression != null); + Contract.Requires(canonicalExpression != null); + + this.expressionForExpression[expression] = canonicalExpression; + } + + /// + /// Associates the given SSA variable with the compile time constant that is always the value of the right side of the single assignment to this variable. + /// + public void SetCompileTimeConstantValueFor(INamedEntity variable, IMetadataConstant compileTimeConstant) { + Contract.Requires(variable != null); + Contract.Requires(compileTimeConstant != null); + + this.compileTimeConstantForSSAVariable[(uint)variable.Name.UniqueKey] = compileTimeConstant; + } + + /// + /// Associates the given expression with a compile time constant value that is always its result when evaluated at runtime. + /// + internal void SetCompileTimeConstantValueFor(Instruction expression, IMetadataConstant compileTimeConstant) { + Contract.Requires(expression != null); + Contract.Requires(compileTimeConstant != null); + + this.compileTimeConstantValueForExpression[expression] = compileTimeConstant; + } + + /// + /// Keeps track of the block in which a "phi" node expression is defined. This is useful because the entry contraints of this block might provide + /// additional information about the expression. + /// + internal void SetDefininingBlockFor(Instruction expression, AiBasicBlock block) { + Contract.Requires(expression != null); + Contract.Requires(block != null); + + this.definingBlockForExpression[expression] = block; + } + + /// + /// Associates the given SSA variable with the expression (expected to be canonicalized) that is the right hand side of the single assignment to this variable. + /// + internal void SetDefininingExpressionFor(INamedEntity variable, Instruction expression) { + Contract.Requires(variable != null); + Contract.Requires(expression != null); + + this.definingExpressionForSSAVariable[(uint)variable.Name.UniqueKey] = expression; + } + + /// + /// Associates the given SSA variable with the Join information of the "phi" node that that is the right hand side of the single assignment to this variable. + /// + internal void SetDefininingJoinFor(INamedEntity variable, Join join) { + Contract.Requires(variable != null); + Contract.Requires(join != null); + + this.definingJoinForSSAVariable[(uint)variable.Name.UniqueKey] = join; + } + + /// + /// Records that the given expression is the value of a variable that is updated (inside of a loop) with a value that depends on the value of the variable in a an earlier iteration of the loop. + /// + internal void SetIsRecursive(Instruction expression) { + Contract.Requires(expression != null); + this.recursiveExpressions[expression] = 2; + } + } + +} \ No newline at end of file diff --git a/Metadata/Sources/Common/Include/Version.cs b/Metadata/Sources/Common/Include/Version.cs new file mode 100644 index 0000000..3a325f2 --- /dev/null +++ b/Metadata/Sources/Common/Include/Version.cs @@ -0,0 +1,25 @@ +// ==++== +// +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +// ==--== +// Warning: Automatically generated file. DO NOT EDIT +// Generated at + +using System.Reflection; +[assembly: AssemblyVersion("1.0.13.0")] +[assembly: AssemblyFileVersion("1.0.13.0")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyTrademark("Microsoft")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyProduct("CCI")] +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif diff --git a/Metadata/Sources/Common/InterimKey.snk b/Metadata/Sources/Common/InterimKey.snk new file mode 100644 index 0000000..8fc7950 Binary files /dev/null and b/Metadata/Sources/Common/InterimKey.snk differ diff --git a/Metadata/Sources/ControlAndDataFlowGraph/ControlAndDataFlowGraph.csproj b/Metadata/Sources/ControlAndDataFlowGraph/ControlAndDataFlowGraph.csproj new file mode 100644 index 0000000..b4504e3 --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/ControlAndDataFlowGraph.csproj @@ -0,0 +1,107 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2596EFB0-87AE-42CE-89EB-84F35D6350D2} + Library + Properties + Microsoft.Cci.Analysis + Microsoft.Cci.Analysis.ControlAndDataFlowGraph + v4.0 + 512 + 0 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + False + False + True + False + False + False + True + True + True + True + False + True + True + True + False + True + + + + + + + + True + Full + Build + 2 + bin\Debug\Microsoft.Cci.Analysis.ControlAndDataFlowGraph.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Microsoft.Cci.Analysis.ControlAndDataFlowGraph.XML + + + true + + + ..\Common\InterimKey.snk + + + + Build\Version.cs + + + + + + + + + + + + {4A34A3C5-6176-49D7-A4C5-B2B671247F8F} + MetadataHelper + + + {33CAB640-0D03-43DF-81BD-22CDC6C0A597} + MetadataModel + + + {4B0054FD-124A-4037-9965-BDB55E6BF389} + SourceModel + + + + + + + + \ No newline at end of file diff --git a/Metadata/Sources/ControlAndDataFlowGraph/ControlFlowInferencer.cs b/Metadata/Sources/ControlAndDataFlowGraph/ControlFlowInferencer.cs new file mode 100644 index 0000000..e6148c6 --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/ControlFlowInferencer.cs @@ -0,0 +1,323 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; + +namespace Microsoft.Cci.Analysis { + + internal class ControlFlowInferencer + where BasicBlock : Microsoft.Cci.Analysis.BasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + private ControlFlowInferencer(IMetadataHost host, IMethodBody methodBody, ILocalScopeProvider/*?*/ localScopeProvider = null) { + Contract.Requires(host != null); + Contract.Requires(methodBody != null); + + this.platformType = host.PlatformType; + this.internFactory = host.InternFactory; + this.methodBody = methodBody; + this.localScopeProvider = localScopeProvider; + + int size = 1024; + var ops = methodBody.Operations as ICollection; + if (ops != null) size = ops.Count; + Hashtable blockFor = new Hashtable((uint)size); + List allBlocks = new List(size); + List rootBlocks = new List(1+(int)IteratorHelper.EnumerableCount(methodBody.OperationExceptionInformation)); + this.successorEdges = new List(size); + this.cdfg = new ControlAndDataFlowGraph(methodBody, this.successorEdges, allBlocks, rootBlocks, blockFor); + this.instructions = new List(size); + this.blocksThatTarget = new MultiHashtable((uint)size); + } + + IPlatformType platformType; + IInternFactory internFactory; + IMethodBody methodBody; + ILocalScopeProvider/*?*/ localScopeProvider; + ControlAndDataFlowGraph cdfg; + List successorEdges; + List instructions; + MultiHashtable blocksThatTarget; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.platformType != null); + Contract.Invariant(this.internFactory != null); + Contract.Invariant(this.methodBody != null); + Contract.Invariant(this.cdfg != null); + Contract.Invariant(this.successorEdges != null); + Contract.Invariant(this.instructions != null); + Contract.Invariant(this.blocksThatTarget != null); + } + + /// + /// + /// + internal static ControlAndDataFlowGraph SetupControlFlow(IMetadataHost host, IMethodBody methodBody, ILocalScopeProvider/*?*/ localScopeProvider = null) { + Contract.Requires(host != null); + Contract.Requires(methodBody != null); + Contract.Ensures(Contract.Result>() != null); + + var inferencer = new ControlFlowInferencer(host, methodBody, localScopeProvider); + return inferencer.CreateBlocksAndEdges(); + } + + private ControlAndDataFlowGraph CreateBlocksAndEdges() { + Contract.Ensures(Contract.Result>() != null); + + var firstBlock = new BasicBlock(); + this.cdfg.BlockFor[0] = firstBlock; + this.cdfg.RootBlocks.Add(firstBlock); + this.CreateBlocksForLocalScopes(); + this.CreateBlocksForBranchTargetsAndFallthroughs(); + this.CreateBlocksForExceptionHandlers(); + this.CreateSuccessorEdges(firstBlock); + + this.successorEdges.TrimExcess(); + this.instructions.TrimExcess(); + this.cdfg.AllBlocks.TrimExcess(); + this.cdfg.RootBlocks.TrimExcess(); + return this.cdfg; + } + + private void CreateBlocksForLocalScopes() { + if (this.localScopeProvider == null) return; + foreach (var scope in this.localScopeProvider.GetLocalScopes(this.methodBody)) { + Contract.Assume(scope != null); + this.CreateBlock(scope.Offset); + this.CreateBlock(scope.Offset+scope.Length); + } + } + + private void CreateBlocksForBranchTargetsAndFallthroughs() { + bool lastInstructionWasBranch = false; + foreach (var ilOperation in this.methodBody.Operations) { + if (lastInstructionWasBranch) { + this.CreateBlock(ilOperation.Offset); + lastInstructionWasBranch = false; + } + switch (ilOperation.OperationCode) { + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + case OperationCode.Br: + case OperationCode.Br_S: + case OperationCode.Brfalse: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue: + case OperationCode.Brtrue_S: + case OperationCode.Leave: + case OperationCode.Leave_S: + Contract.Assume(ilOperation.Value is uint); //This is an informally specified property of the Metadata model. + this.CreateBlock((uint)ilOperation.Value); + lastInstructionWasBranch = true; + break; + case OperationCode.Ret: + case OperationCode.Throw: + case OperationCode.Jmp: + //The code following these instructions will be dead unless its a branch target, but we may as well end the basic block with the transfer. + lastInstructionWasBranch = true; + break; + case OperationCode.Switch: { + Contract.Assume(ilOperation.Value is uint[]); //This is an informally specified property of the Metadata model. + uint[] branches = (uint[])ilOperation.Value; + foreach (uint targetAddress in branches) + this.CreateBlock(targetAddress); + } + lastInstructionWasBranch = true; + break; + default: + break; + } + } + } + + private void CreateBlocksForExceptionHandlers() { + foreach (IOperationExceptionInformation exinfo in this.methodBody.OperationExceptionInformation) { + this.CreateBlock(exinfo.TryStartOffset); + var block = CreateBlock(exinfo.HandlerStartOffset); + this.cdfg.RootBlocks.Add(block); + if (exinfo.HandlerKind == HandlerKind.Filter) { + block = this.CreateBlock(exinfo.FilterDecisionStartOffset); + this.cdfg.RootBlocks.Add(block); + } + this.CreateBlock(exinfo.HandlerEndOffset); + } + } + + private BasicBlock CreateBlock(uint targetAddress) { + var result = this.cdfg.BlockFor[targetAddress]; + if (result == null) { + result = new BasicBlock() { Offset = targetAddress }; + this.cdfg.BlockFor[targetAddress] = result; + } + return result; + } + + private void CreateSuccessorEdges(BasicBlock currentBlock) { + Contract.Requires(currentBlock != null); + + this.cdfg.AllBlocks.Add(currentBlock); + int startingEdge = 0; + int startingInstruction = 0; + bool lastInstructionWasUnconditionalTransfer = false; + foreach (var ilOperation in this.methodBody.Operations) { + Contract.Assert(ilOperation != null); //This is formally specified in the Metadata model, but the checker does not yet understand it well enough to prove this. + Contract.Assume(startingInstruction <= instructions.Count); //due to the limitations of the contract language and checker + Contract.Assume(startingEdge <= successorEdges.Count); //due to the limitations of the contract language and checker + var newBlock = this.cdfg.BlockFor.Find(ilOperation.Offset); + if (newBlock != null && currentBlock != newBlock) { + this.cdfg.AllBlocks.Add(newBlock); + currentBlock.Instructions = new Sublist(instructions, startingInstruction, instructions.Count-startingInstruction); + if (!lastInstructionWasUnconditionalTransfer) + this.AddToSuccessorListIfNotAlreadyInIt(successorEdges, startingEdge, newBlock, currentBlock); + currentBlock.firstSuccessorEdge = startingEdge; + currentBlock.successorCount = successorEdges.Count-startingEdge; + startingEdge = successorEdges.Count; + startingInstruction = instructions.Count; + currentBlock = newBlock; + } + instructions.Add(this.GetInstruction(ilOperation, currentBlock, successorEdges, out lastInstructionWasUnconditionalTransfer)); + } + if (instructions.Count > startingInstruction) + currentBlock.Instructions = new Sublist(instructions, startingInstruction, instructions.Count-startingInstruction); + if (successorEdges.Count > startingEdge) { + currentBlock.firstSuccessorEdge = startingEdge; + currentBlock.successorCount = successorEdges.Count-startingEdge; + } + } + + private void AddToSuccessorListIfNotAlreadyInIt(List edges, int startingEdge, BasicBlock target, BasicBlock current) { + Contract.Requires(edges != null); + Contract.Requires(startingEdge >= 0); + Contract.Requires(target != null); + Contract.Requires(current != null); + Contract.Ensures(Contract.OldValue(edges.Count) <= edges.Count); + + for (int i = startingEdge, n = edges.Count; i < n; i++) { + if (edges[i] == target) return; + } + edges.Add(target); + this.blocksThatTarget.Add(target.Offset, current); + } + + private Instruction GetInstruction(IOperation ilOperation, BasicBlock currentBlock, List edges, out bool isUnconditionalTransfer) { + Contract.Requires(ilOperation != null); + Contract.Requires(currentBlock != null); + Contract.Requires(edges != null); + + isUnconditionalTransfer = false; + var instruction = new Instruction() { Operation = ilOperation }; + switch (ilOperation.OperationCode) { + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + Contract.Assume(ilOperation.Value is uint); //This is an informally specified property of the Metadata model. + var targetOffset = (uint)ilOperation.Value; + this.blocksThatTarget.Add(targetOffset, currentBlock); + edges.Add(this.cdfg.BlockFor[targetOffset]); + break; + + case OperationCode.Br: + case OperationCode.Br_S: + case OperationCode.Leave: + case OperationCode.Leave_S: + Contract.Assume(ilOperation.Value is uint); //This is an informally specified property of the Metadata model. + targetOffset = (uint)ilOperation.Value; + this.blocksThatTarget.Add(targetOffset, currentBlock); + edges.Add(this.cdfg.BlockFor[targetOffset]); + isUnconditionalTransfer = true; + break; + + case OperationCode.Brfalse: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue: + case OperationCode.Brtrue_S: + Contract.Assume(ilOperation.Value is uint); //This is an informally specified property of the Metadata model. + targetOffset = (uint)ilOperation.Value; + this.blocksThatTarget.Add(targetOffset, currentBlock); + edges.Add(this.cdfg.BlockFor[targetOffset]); + break; + + case OperationCode.Endfilter: + case OperationCode.Endfinally: + case OperationCode.Jmp: + case OperationCode.Ret: + case OperationCode.Rethrow: + case OperationCode.Throw: + isUnconditionalTransfer = true; + break; + + case OperationCode.Switch: + this.AddEdgesForSwitch(ilOperation, currentBlock, edges, instruction); + break; + } + return instruction; + } + + private void AddEdgesForSwitch(IOperation ilOperation, BasicBlock currentBlock, List edges, Instruction instruction) { + Contract.Requires(ilOperation != null); + Contract.Requires(currentBlock != null); + Contract.Requires(ilOperation.OperationCode == OperationCode.Switch); + Contract.Requires(edges != null); + Contract.Requires(instruction != null); + + Contract.Assume(ilOperation.Value is uint[]); //This is an informally specified property of the Metadata model. + uint[] branches = (uint[])ilOperation.Value; + SetOfObjects currentSuccesors = new SetOfObjects((uint)branches.Length); + foreach (uint targetAddress in branches) { + this.blocksThatTarget.Add(targetAddress, currentBlock); + var target = this.cdfg.BlockFor[targetAddress]; + Contract.Assume(target != null); //All branch targets must have blocks, but we can't put that in a contract that satisfies the checker. + if (currentSuccesors.Contains(target)) continue; + currentSuccesors.Add(target); + edges.Add(target); + } + } + + + } +} diff --git a/Metadata/Sources/ControlAndDataFlowGraph/ControlFlowQueries.cs b/Metadata/Sources/ControlAndDataFlowGraph/ControlFlowQueries.cs new file mode 100644 index 0000000..c2dee64 --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/ControlFlowQueries.cs @@ -0,0 +1,368 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; +using Microsoft.Cci.UtilityDataStructures; + +namespace Microsoft.Cci.Analysis { + + /// + /// Presents information derived from a simple control flow graph. For example, traversal orders, predecessors, dominators and dominance frontiers. + /// + public class ControlGraphQueries + where BasicBlock : Microsoft.Cci.Analysis.EnhancedBasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + /// + /// Presents information derived from a simple control flow graph. For example, traversal orders, predecessors, dominators and dominance frontiers. + /// + /// The simple control flow graph from which to derive the information. + public ControlGraphQueries(ControlAndDataFlowGraph controlFlowGraph) { + Contract.Requires(controlFlowGraph != null); + + this.cfg = controlFlowGraph; + } + + ControlAndDataFlowGraph cfg; + BasicBlock[] preOrder; + BasicBlock[] postOrder; + List predecessorEdges; + List dominanceFrontier; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.cfg != null); + } + + /// + /// Contains the same nodes as the AllBlocks property of the control flow graph, but in the order they will be visited by a depth first, post order traversal of successor nodes. + /// + public BasicBlock[] BlocksInPostorder { + get { + Contract.Ensures(Contract.Result() != null); + if (this.postOrder == null) + this.SetupTraversalOrders(); + return this.postOrder; + } + } + + /// + /// Contains the same nodes as the AllBlocks property of the control flow graph, but in the order they will be visited by a depth first, pre order traversal of successor nodes. + /// + public BasicBlock[] BlocksInPreorder { + get { + Contract.Ensures(Contract.Result() != null); + if (this.preOrder == null) + this.SetupTraversalOrders(); + return this.preOrder; + } + } + + /// + /// Returns zero or more nodes that are reachable from the given basic block, but are not dominated by the given basic block. + /// + public Sublist DominanceFrontierFor(BasicBlock basicBlock) { + Contract.Requires(basicBlock != null); + if (this.dominanceFrontier == null) + this.SetupDominanceFrontier(); + + if (basicBlock.firstDominanceFrontierNode+basicBlock.dominanceFrontierCount > this.dominanceFrontier.Count) + throw new InvalidOperationException(); //can only happen if the basic block does not belong to this graph. + Contract.Assume(basicBlock.firstDominanceFrontierNode >= 0); + Contract.Assume(basicBlock.dominanceFrontierCount >= 0); + return new Sublist(this.dominanceFrontier, basicBlock.firstDominanceFrontierNode, basicBlock.dominanceFrontierCount); + } + + /// + /// Returns true if the first block dominates the second block. That is, if all control paths from the applicable root node + /// lead to the second block only via the first block. + /// + public bool Dominates(BasicBlock block1, BasicBlock block2) { + Contract.Requires(block1 != null); + Contract.Requires(block2 != null); + + if (block1 == block2) return true; + var block2dominator = ImmediateDominator(block2); + while (true) { + if (block1 == block2dominator) return true; + if (block2 == block2dominator) return false; + block2 = block2dominator; + block2dominator = ImmediateDominator(block2); + } + } + + /// + /// Returns the last block through which all control flows from a root must pass in order to reach the given block. + /// This block can be a root, however, it will not be the given block, except when the given block is a root. + /// + public BasicBlock ImmediateDominator(BasicBlock basicBlock) { + Contract.Requires(basicBlock != null); + Contract.Ensures(Contract.Result() != null); + + if (!this.immediateDominatorsAreInitialized) + this.SetupImmediateDominators(); + Contract.Assume(basicBlock.immediateDominator is BasicBlock); + return (BasicBlock)basicBlock.immediateDominator; + } + bool immediateDominatorsAreInitialized; + + /// + /// All basic blocks from which control can flow to the given basic block. + /// + public Sublist PredeccessorsFor(BasicBlock basicBlock) { + Contract.Requires(basicBlock != null); + if (this.predecessorEdges == null) + this.SetupPredecessorEdges(); + + if (basicBlock.firstPredecessorEdge+basicBlock.predeccessorCount > this.predecessorEdges.Count) + throw new InvalidOperationException(); //can only happen if the basic block does not belong to this graph. + Contract.Assume(basicBlock.firstPredecessorEdge >= 0); + Contract.Assume(basicBlock.predeccessorCount >= 0); + return new Sublist(this.predecessorEdges, basicBlock.firstPredecessorEdge, basicBlock.predeccessorCount); + } + + private void SetupDominanceFrontier() { + Contract.Ensures(this.dominanceFrontier != null); + MultiHashtable frontierFor = new MultiHashtable(); + + if (!this.immediateDominatorsAreInitialized) + this.SetupImmediateDominators(); + var predecessorEdges = this.predecessorEdges; + Contract.Assume(predecessorEdges != null); + + var dominanceFrontier = this.dominanceFrontier = new List(this.cfg.AllBlocks.Count*2); + foreach (var block in this.cfg.AllBlocks) { + Contract.Assume(block != null); + var n = block.predeccessorCount; + if (n < 2) continue; + for (int i = 0; i < n; i++) { + Contract.Assume(block.firstPredecessorEdge+i >= 0); + Contract.Assume(block.firstPredecessorEdge+i < predecessorEdges.Count); + var pred = predecessorEdges[block.firstPredecessorEdge+i]; + Contract.Assume(pred != null); + var a = pred; + while (true) { + if (a == block.immediateDominator) break; //Any node that dominates node a will also dominate node block and hence block will not be in its dominance frontier. + frontierFor.Add(a.Offset, block); + if (a == a.immediateDominator) break; //Since there are multiple roots, block can be its own immediate dominator while still having predecessors. + a = (BasicBlock)a.immediateDominator; + Contract.Assume(a != null); + } + } + } + foreach (var block in this.cfg.AllBlocks) { + Contract.Assume(block != null); + block.firstDominanceFrontierNode = dominanceFrontier.Count; + foreach (var frontierNode in frontierFor.GetValuesFor(block.Offset)) { + dominanceFrontier.Add(frontierNode); + } + block.dominanceFrontierCount = dominanceFrontier.Count-block.firstDominanceFrontierNode; + } + dominanceFrontier.TrimExcess(); + } + + private void SetupImmediateDominators() { + Contract.Ensures(this.immediateDominatorsAreInitialized); + //Note this is an adaptation of the algorithm in Cooper, Keith D.; Harvey, Timothy J.; and Kennedy, Ken (2001). A Simple, Fast Dominance Algorithm + //The big difference is that we deal with multiple roots at the same time. + + if (this.postOrder == null) + this.SetupTraversalOrders(); + var postOrder = this.postOrder; + if (this.predecessorEdges == null) + this.SetupPredecessorEdges(); + foreach (var rootBlock in this.cfg.RootBlocks) { + Contract.Assume(rootBlock != null); + rootBlock.immediateDominator = rootBlock; + } + var predecessorEdges = this.predecessorEdges; + var n = postOrder.Length; + var changed = true; + while (changed) { + changed = false; + for (int i = n-1; i >= 0; i--) { //We iterate in reverse post order so that a block always has its immediateDominator field filled in before we get to any of its successors. + var b = postOrder[i]; + Contract.Assume(b != null); + if (b.immediateDominator == b) continue; + if (b.predeccessorCount == 0) { + b.immediateDominator = b; + continue; + } + Contract.Assume(b.firstPredecessorEdge >= 0); + Contract.Assume(b.firstPredecessorEdge < predecessorEdges.Count); + var predecessors = new HashSet(); + for (int j = 0, m = b.predeccessorCount; j < m; j++) { + predecessors.Add(predecessorEdges[b.firstPredecessorEdge + j]); + } + // newIDom <- first (processed) predecessor of b + BasicBlock newIDom = null; + foreach (var p in predecessors) { + if (p.immediateDominator != null) { + newIDom = p; + break; + } + } + Contract.Assume(newIDom != null); + predecessors.Remove(newIDom); + foreach (var predecessor in predecessors) { + Contract.Assume(predecessor != null); + if (predecessor.immediateDominator != null) { + var intersection = Intersect(predecessor, newIDom); + if (intersection != null) { + newIDom = intersection; + } else { + //This can happen when predecessor and newIDom are only reachable via distinct roots. + //We now have two distinct paths from a root to b. This means b is its own dominator. + b.immediateDominator = newIDom = b; + break; + } + } + } + if (b.immediateDominator != newIDom) { + b.immediateDominator = newIDom; + changed = true; + } + } + } + this.immediateDominatorsAreInitialized = true; + } + + private BasicBlock/*?*/ Intersect(BasicBlock block1, BasicBlock block2) { + Contract.Requires(block1 != null); + Contract.Requires(block2 != null); + + while (block1 != block2) { + while (block1.postOrderNumber < block2.postOrderNumber) { + var block1dominator = block1.immediateDominator; + if (block1dominator == block1) return null; //block2 is its own dominator, which means it has no predecessors + block1 = (BasicBlock)block1dominator; //The block with the smaller post order number cannot be a predecessor of the other block. + if (block1 == null) return null; + } + while (block2.postOrderNumber < block1.postOrderNumber) { + var block2dominator = block2.immediateDominator; + if (block2dominator == block2) return null; //block2 is its own dominator, which means it has no predecessors + block2 = (BasicBlock)block2dominator; //The block with the smaller post order number cannot be a predecessor of the other block. + if (block2 == null) return null; + } + } + return block1; + } + + private void SetupPredecessorEdges() { + Contract.Ensures(this.predecessorEdges != null); + + var predecessorEdges = this.predecessorEdges = new List(this.cfg.SuccessorEdges.Count); + MultiHashtable blocksThatTarget = new MultiHashtable(); + foreach (var block in this.cfg.AllBlocks) { + Contract.Assume(block != null); + foreach (var successor in this.cfg.SuccessorsFor(block)) { + blocksThatTarget.Add(successor.Offset, block); + } + } + foreach (var block in this.cfg.AllBlocks) { + Contract.Assume(block != null); + block.firstPredecessorEdge = predecessorEdges.Count; + foreach (var predecessor in blocksThatTarget.GetValuesFor(block.Offset)) { + predecessorEdges.Add(predecessor); + } + block.predeccessorCount = predecessorEdges.Count-block.firstPredecessorEdge; + } + } + + private void SetupTraversalOrders() { + Contract.Ensures(this.postOrder != null); + Contract.Ensures(this.preOrder != null); + + var n = cfg.AllBlocks.Count; + this.postOrder = new BasicBlock[n]; + this.preOrder = new BasicBlock[n]; + uint preorderCounter = 0; + uint postorderCounter = 0; + var alreadyTraversed = new SetOfObjects((uint)n); + foreach (var rootBlock in cfg.RootBlocks) { + Contract.Assume(rootBlock != null); + this.SetupTraversalOrders(rootBlock, alreadyTraversed, ref preorderCounter, ref postorderCounter); + } + Contract.Assume(preorderCounter == postorderCounter); + if (preorderCounter != n) { + //Add unreachable blocks to traversal order, treating them as if they were roots. + foreach (var block in cfg.AllBlocks) { + Contract.Assume(block != null); + if (alreadyTraversed.Contains(block)) continue; + this.SetupTraversalOrders(block, alreadyTraversed, ref preorderCounter, ref postorderCounter); + } + } + Contract.Assume(this.postOrder != null); + Contract.Assume(this.preOrder != null); + } + + private void SetupTraversalOrders(BasicBlock root, SetOfObjects alreadyTraversed, ref uint preOrderIndex, ref uint postOrderIndex) { + Contract.Requires(root != null); + Contract.Requires(alreadyTraversed != null); + + if (!alreadyTraversed.Add(root)) return; + Contract.Assume(this.preOrder != null); + Contract.Assume(this.postOrder != null); + Contract.Assume(preOrderIndex < this.preOrder.Length); + this.preOrder[preOrderIndex++] = root; + foreach (var successor in this.cfg.SuccessorsFor(root)) { + Contract.Assume(successor != null); + this.SetupTraversalOrders(successor, alreadyTraversed, ref preOrderIndex, ref postOrderIndex); + } + Contract.Assume(this.postOrder != null); + Contract.Assume(postOrderIndex < this.postOrder.Length); + root.postOrderNumber = postOrderIndex; + this.postOrder[postOrderIndex++] = root; + } + + + } + + /// + /// A basic block with additional fields to help compute things such as predecessor edges, dominance and dominance frontiers. + /// + /// + public class EnhancedBasicBlock : BasicBlock where Instruction : Microsoft.Cci.Analysis.Instruction { + /// + /// The first block in a list of blocks that are reachable from, but not dominated by this block. + /// + internal int firstDominanceFrontierNode; + + /// + /// The number of blocks that are reachable from, but not dominated by this block. + /// + internal int dominanceFrontierCount; + + /// + /// The first edge that enters this block. The edges are a contiguous sublist of the the PredeccessorEdges list of the ControlAndDataFlowGraph that contains this block. + /// + internal int firstPredecessorEdge; + + /// + /// The number of edges that enter this block. The edges are a contiguous sublist of the the PredeccessorEdges list of the ControlAndDataFlowGraph that contains this block. + /// + internal int predeccessorCount; + + /// + /// The block through which all control flows from a root must pass in order to reach this block. Can be a root. Will not be the block itself, except when the block is a root. + /// + internal EnhancedBasicBlock immediateDominator; + + /// + /// The position of the node in a depth first, post order traversal of successor edges. + /// + internal uint postOrderNumber; + + } + +} diff --git a/Metadata/Sources/ControlAndDataFlowGraph/DataFlowInferencer.cs b/Metadata/Sources/ControlAndDataFlowGraph/DataFlowInferencer.cs new file mode 100644 index 0000000..1cd21f9 --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/DataFlowInferencer.cs @@ -0,0 +1,565 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; + +namespace Microsoft.Cci.Analysis { + + internal class DataFlowInferencer + where BasicBlock : Microsoft.Cci.Analysis.BasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + private DataFlowInferencer(IMetadataHost host, ControlAndDataFlowGraph cdfg) { + Contract.Requires(host != null); + Contract.Requires(cdfg != null); + + var numberOfBlocks = cdfg.BlockFor.Count; + this.platformType = host.PlatformType; + this.cdfg = cdfg; + this.operandStackSetupInstructions = new List(cdfg.MethodBody.MaxStack); + this.stack = new Stack(cdfg.MethodBody.MaxStack); + this.blocksToVisit = new Queue((int)numberOfBlocks); + this.blocksAlreadyVisited = new SetOfObjects(numberOfBlocks); ; + this.internFactory = host.InternFactory; + } + + IPlatformType platformType; + ControlAndDataFlowGraph cdfg; + Stack stack; + List operandStackSetupInstructions; + Queue blocksToVisit; + SetOfObjects blocksAlreadyVisited; + IInternFactory internFactory; + bool codeIsUnreachable; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.platformType != null); + Contract.Invariant(this.cdfg != null); + Contract.Invariant(this.stack != null); + Contract.Invariant(this.operandStackSetupInstructions != null); + Contract.Invariant(this.blocksToVisit != null); + Contract.Invariant(this.blocksAlreadyVisited != null); + Contract.Invariant(this.internFactory != null); + } + + /// + /// + /// + internal static void SetupDataFlow(IMetadataHost host, IMethodBody methodBody, ControlAndDataFlowGraph cdfg) { + Contract.Requires(host != null); + Contract.Requires(methodBody != null); + Contract.Requires(cdfg != null); + + var dataFlowInferencer = new DataFlowInferencer(host, cdfg); + dataFlowInferencer.SetupDataFlowFor(methodBody); + } + + private void SetupDataFlowFor(IMethodBody methodBody) { + Contract.Requires(methodBody != null); + + //If this is a dummy body, do nothing. + if (this.cdfg.AllBlocks.Count == 1 && this.cdfg.AllBlocks[0] != null && this.cdfg.AllBlocks[0].Instructions.Count <= 1) return; + + this.AddStackSetupForExceptionHandlers(methodBody); + foreach (var root in this.cdfg.RootBlocks) { + this.blocksToVisit.Enqueue(root); + while (this.blocksToVisit.Count != 0) + this.DequeueBlockAndSetupDataFlow(); + } + //At this point, all reachable code blocks have had their data flow inferred. Now look for unreachable blocks. + this.codeIsUnreachable = true; //unreachable code might not satisfy invariants. + foreach (var block in this.cdfg.AllBlocks) { + if (this.blocksAlreadyVisited.Contains(block)) continue; + blocksToVisit.Enqueue(block); + while (blocksToVisit.Count != 0) + this.DequeueBlockAndSetupDataFlow(); + } + this.operandStackSetupInstructions.TrimExcess(); + } + + private void AddStackSetupForExceptionHandlers(IMethodBody methodBody) { + Contract.Requires(methodBody != null); + + foreach (var exinfo in methodBody.OperationExceptionInformation) { + Contract.Assert(exinfo != null); //The checker can't work out that all collection elements are non null, even though there is a contract to that effect + if (exinfo.HandlerKind == HandlerKind.Filter) { + var block = this.cdfg.BlockFor[exinfo.FilterDecisionStartOffset]; + Contract.Assume(block != null); //All branch targets must have blocks, but we can't put that in a contract that satisfies the checker. + this.AddStackSetup(block, exinfo.ExceptionType); + block = this.cdfg.BlockFor[exinfo.HandlerStartOffset]; + Contract.Assume(block != null); //All branch targets must have blocks, but we can't put that in a contract that satisfies the checker. + this.AddStackSetup(block, exinfo.ExceptionType); + } else if (exinfo.HandlerKind == HandlerKind.Catch) { + var block = this.cdfg.BlockFor[exinfo.HandlerStartOffset]; + Contract.Assume(block != null); //All branch targets must have blocks, but we can't put that in a contract that satisfies the checker. + this.AddStackSetup(block, exinfo.ExceptionType); + } + } + } + + private void AddStackSetup(BasicBlock block, ITypeReference operandType) { + Contract.Requires(block != null); + Contract.Requires(operandType != null); + + this.operandStackSetupInstructions.Add(new Instruction() { Type = operandType }); + block.OperandStack = new Sublist(this.operandStackSetupInstructions, this.operandStackSetupInstructions.Count-1, 1); + } + + private void DequeueBlockAndSetupDataFlow() { + var block = this.blocksToVisit.Dequeue(); + Contract.Assume(block != null); //this.blocksToVisit only has non null elements, but we can't put that in a contract that satisfies the checker + if (!this.blocksAlreadyVisited.Add(block)) return; //The same block can be added multiple times to the queue. + + foreach (var instruction in block.OperandStack) { + Contract.Assume(instruction != null); //block.OperandStack only has non null elements, but we can't put that in a contract that satisfies the checker + this.stack.Push(instruction); + } + + foreach (var instruction in block.Instructions) { + Contract.Assume(instruction != null); //block.Instructions only has non null elements, but we can't put that in a contract that satisfies the checker + this.SetupDataFlowFor(instruction); + } + + foreach (var successor in this.cdfg.SuccessorsFor(block)) { + Contract.Assume(successor != null); //block.Successors only has non null elements, but we can't put that in a contract that satisfies the checker + this.SetupStackFor(successor); + if (blocksAlreadyVisited.Contains(successor)) continue; + blocksToVisit.Enqueue(successor); //The block might already be in the queue, but we can deal with this more efficiently by checking blocksAlreadyVisited when dequeueing. + } + + this.stack.Clear(); + + } + + private void SetupStackFor(BasicBlock successor) { + Contract.Requires(successor != null); + + if (successor.OperandStack.Count == 0) { + int n = this.stack.Top; + if (n < 0) return; + int startingCount = this.operandStackSetupInstructions.Count; + for (int i = 0; i <= n; i++) { + var pushInstruction = this.stack.Peek(i); + this.operandStackSetupInstructions.Add(new Instruction() { Operand1 = pushInstruction }); + } + successor.OperandStack = new Sublist(this.operandStackSetupInstructions, startingCount, operandStackSetupInstructions.Count-startingCount); + } else { + int n = this.stack.Top; + Contract.Assume(n == successor.OperandStack.Count-1); //This is an optimistic assumption. It should be true for any well formed PE file. We are content to crash given bad input. + for (int i = 0; i <= n; i++) { + var pushInstruction = this.stack.Peek(i); + var setupInstruction = successor.OperandStack[i]; + if (setupInstruction.Operand2 == null) + setupInstruction.Operand2 = pushInstruction; + else { + var list = setupInstruction.Operand2 as List; + if (list == null) { + Contract.Assume(setupInstruction.Operand2 is Instruction); + list = new List(4); + list.Add((Instruction)setupInstruction.Operand2); + } + list.Add(pushInstruction); + } + } + } + } + + private void SetupDataFlowFor(Instruction instruction) { + Contract.Requires(instruction != null); + + switch (instruction.Operation.OperationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Add_Ovf_Un: + case OperationCode.And: + case OperationCode.Ceq: + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + case OperationCode.Clt: + case OperationCode.Clt_Un: + case OperationCode.Div: + case OperationCode.Div_Un: + case OperationCode.Ldelema: + case OperationCode.Ldelem: + case OperationCode.Ldelem_I: + case OperationCode.Ldelem_I1: + case OperationCode.Ldelem_I2: + case OperationCode.Ldelem_I4: + case OperationCode.Ldelem_I8: + case OperationCode.Ldelem_R4: + case OperationCode.Ldelem_R8: + case OperationCode.Ldelem_Ref: + case OperationCode.Ldelem_U1: + case OperationCode.Ldelem_U2: + case OperationCode.Ldelem_U4: + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Mul_Ovf_Un: + case OperationCode.Or: + case OperationCode.Rem: + case OperationCode.Rem_Un: + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + case OperationCode.Sub_Ovf_Un: + case OperationCode.Xor: + instruction.Operand2 = this.stack.Pop(); + instruction.Operand1 = this.stack.Pop(); + this.stack.Push(instruction); + break; + + case OperationCode.Arglist: + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + case OperationCode.Ldsfld: + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + case OperationCode.Ldsflda: + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + case OperationCode.Ldftn: + case OperationCode.Ldc_I4: + case OperationCode.Ldc_I4_0: + case OperationCode.Ldc_I4_1: + case OperationCode.Ldc_I4_2: + case OperationCode.Ldc_I4_3: + case OperationCode.Ldc_I4_4: + case OperationCode.Ldc_I4_5: + case OperationCode.Ldc_I4_6: + case OperationCode.Ldc_I4_7: + case OperationCode.Ldc_I4_8: + case OperationCode.Ldc_I4_M1: + case OperationCode.Ldc_I4_S: + case OperationCode.Ldc_I8: + case OperationCode.Ldc_R4: + case OperationCode.Ldc_R8: + case OperationCode.Ldnull: + case OperationCode.Ldstr: + case OperationCode.Ldtoken: + case OperationCode.Sizeof: + this.stack.Push(instruction); + break; + + case OperationCode.Array_Addr: + case OperationCode.Array_Get: + Contract.Assume(instruction.Operation.Value is IArrayTypeReference); //This is an informally specified property of the Metadata model. + InitializeArrayIndexerInstruction(instruction, this.stack, (IArrayTypeReference)instruction.Operation.Value); + break; + + case OperationCode.Array_Create: + case OperationCode.Array_Create_WithLowerBound: + case OperationCode.Newarr: + InitializeArrayCreateInstruction(instruction, this.stack, instruction.Operation); + break; + + case OperationCode.Array_Set: + Contract.Assume(instruction.Operation.Value is IArrayTypeReference); //This is an informally specified property of the Metadata model. + InitializeArraySetInstruction(instruction, this.stack, (IArrayTypeReference)instruction.Operation.Value); + break; + + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + instruction.Operand2 = this.stack.Pop(); + instruction.Operand1 = this.stack.Pop(); + break; + + case OperationCode.Box: + case OperationCode.Castclass: + case OperationCode.Ckfinite: + case OperationCode.Conv_I: + case OperationCode.Conv_I1: + case OperationCode.Conv_I2: + case OperationCode.Conv_I4: + case OperationCode.Conv_I8: + case OperationCode.Conv_Ovf_I: + case OperationCode.Conv_Ovf_I_Un: + case OperationCode.Conv_Ovf_I1: + case OperationCode.Conv_Ovf_I1_Un: + case OperationCode.Conv_Ovf_I2: + case OperationCode.Conv_Ovf_I2_Un: + case OperationCode.Conv_Ovf_I4: + case OperationCode.Conv_Ovf_I4_Un: + case OperationCode.Conv_Ovf_I8: + case OperationCode.Conv_Ovf_I8_Un: + case OperationCode.Conv_Ovf_U: + case OperationCode.Conv_Ovf_U_Un: + case OperationCode.Conv_Ovf_U1: + case OperationCode.Conv_Ovf_U1_Un: + case OperationCode.Conv_Ovf_U2: + case OperationCode.Conv_Ovf_U2_Un: + case OperationCode.Conv_Ovf_U4: + case OperationCode.Conv_Ovf_U4_Un: + case OperationCode.Conv_Ovf_U8: + case OperationCode.Conv_Ovf_U8_Un: + case OperationCode.Conv_R_Un: + case OperationCode.Conv_R4: + case OperationCode.Conv_R8: + case OperationCode.Conv_U: + case OperationCode.Conv_U1: + case OperationCode.Conv_U2: + case OperationCode.Conv_U4: + case OperationCode.Conv_U8: + case OperationCode.Isinst: + case OperationCode.Ldind_I: + case OperationCode.Ldind_I1: + case OperationCode.Ldind_I2: + case OperationCode.Ldind_I4: + case OperationCode.Ldind_I8: + case OperationCode.Ldind_R4: + case OperationCode.Ldind_R8: + case OperationCode.Ldind_Ref: + case OperationCode.Ldind_U1: + case OperationCode.Ldind_U2: + case OperationCode.Ldind_U4: + case OperationCode.Ldobj: + case OperationCode.Ldflda: + case OperationCode.Ldfld: + case OperationCode.Ldlen: + case OperationCode.Ldvirtftn: + case OperationCode.Localloc: + case OperationCode.Mkrefany: + case OperationCode.Neg: + case OperationCode.Not: + case OperationCode.Refanytype: + case OperationCode.Refanyval: + case OperationCode.Unbox: + case OperationCode.Unbox_Any: + instruction.Operand1 = this.stack.Pop(); + this.stack.Push(instruction); + break; + + case OperationCode.Brfalse: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue: + case OperationCode.Brtrue_S: + instruction.Operand1 = this.stack.Pop(); + break; + + case OperationCode.Call: + case OperationCode.Callvirt: + var signature = instruction.Operation.Value as ISignature; + Contract.Assume(signature != null); //This is an informally specified property of the Metadata model. + InitializeArgumentsAndPushReturnResult(instruction, this.stack, signature); + break; + + case OperationCode.Calli: + var funcPointer = instruction.Operation.Value as IFunctionPointerTypeReference; + Contract.Assume(funcPointer != null); //This is an informally specified property of the Metadata model. + InitializeArgumentsAndPushReturnResult(instruction, this.stack, funcPointer); + break; + + case OperationCode.Cpobj: + case OperationCode.Stfld: + case OperationCode.Stind_I: + case OperationCode.Stind_I1: + case OperationCode.Stind_I2: + case OperationCode.Stind_I4: + case OperationCode.Stind_I8: + case OperationCode.Stind_R4: + case OperationCode.Stind_R8: + case OperationCode.Stind_Ref: + case OperationCode.Stobj: + instruction.Operand2 = this.stack.Pop(); + instruction.Operand1 = this.stack.Pop(); + break; + + case OperationCode.Cpblk: + case OperationCode.Initblk: + case OperationCode.Stelem: + case OperationCode.Stelem_I: + case OperationCode.Stelem_I1: + case OperationCode.Stelem_I2: + case OperationCode.Stelem_I4: + case OperationCode.Stelem_I8: + case OperationCode.Stelem_R4: + case OperationCode.Stelem_R8: + case OperationCode.Stelem_Ref: + var indexAndValue = new Instruction[2]; + indexAndValue[1] = this.stack.Pop(); + indexAndValue[0] = this.stack.Pop(); + instruction.Operand2 = indexAndValue; + instruction.Operand1 = this.stack.Pop(); + break; + + case OperationCode.Dup: + var dupop = this.stack.Pop(); + instruction.Operand1 = dupop; + this.stack.Push(instruction); + this.stack.Push(instruction); + break; + + case OperationCode.Endfilter: + case OperationCode.Initobj: + case OperationCode.Pop: + case OperationCode.Starg: + case OperationCode.Starg_S: + case OperationCode.Stloc: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + case OperationCode.Stsfld: + case OperationCode.Throw: + case OperationCode.Switch: + instruction.Operand1 = this.stack.Pop(); + break; + + case OperationCode.Leave: + case OperationCode.Leave_S: + this.stack.Clear(); + break; + + case OperationCode.Newobj: + Contract.Assume(instruction.Operation.Value is ISignature); //This is an informally specified property of the Metadata model. + signature = (ISignature)instruction.Operation.Value; + var numArguments = (int)IteratorHelper.EnumerableCount(signature.Parameters); + if (numArguments > 0) { + if (numArguments > 1) { + numArguments--; + var arguments = new Instruction[numArguments]; + instruction.Operand2 = arguments; + for (var i = numArguments-1; i >= 0; i--) + arguments[i] = stack.Pop(); + } + instruction.Operand1 = stack.Pop(); + } + this.stack.Push(instruction); + break; + + case OperationCode.Ret: + if (this.codeIsUnreachable && this.stack.Top < 0) break; + if (this.cdfg.MethodBody.MethodDefinition.Type.TypeCode != PrimitiveTypeCode.Void) + instruction.Operand1 = this.stack.Pop(); + break; + } + } + + private static void InitializeArgumentsAndPushReturnResult(Instruction instruction, Stack stack, ISignature signature) { + Contract.Requires(instruction != null); + Contract.Requires(stack != null); + Contract.Requires(signature != null); + + var methodRef = signature as IMethodReference; + uint numArguments = IteratorHelper.EnumerableCount(signature.Parameters); + if (methodRef != null && methodRef.AcceptsExtraArguments) numArguments += IteratorHelper.EnumerableCount(methodRef.ExtraParameters); + if (!signature.IsStatic) numArguments++; + if (numArguments > 0) { + numArguments--; + if (numArguments > 0) { + var arguments = new Instruction[numArguments]; + instruction.Operand2 = arguments; + for (var i = numArguments; i > 0; i--) + arguments[i-1] = stack.Pop(); + } + instruction.Operand1 = stack.Pop(); + } + if (signature.Type.TypeCode != PrimitiveTypeCode.Void) + stack.Push(instruction); + } + + private static void InitializeArgumentsAndPushReturnResult(Instruction instruction, Stack stack, IFunctionPointerTypeReference funcPointer) { + Contract.Requires(instruction != null); + Contract.Requires(stack != null); + Contract.Requires(funcPointer != null); + + instruction.Operand1 = stack.Pop(); //the function pointer + var numArguments = IteratorHelper.EnumerableCount(funcPointer.Parameters); + if (!funcPointer.IsStatic) numArguments++; + var arguments = new Instruction[numArguments]; + instruction.Operand2 = arguments; + for (var i = numArguments; i > 0; i--) + arguments[i-1] = stack.Pop(); + if (funcPointer.Type.TypeCode != PrimitiveTypeCode.Void) + stack.Push(instruction); + } + + private static void InitializeArrayCreateInstruction(Instruction instruction, Stack stack, IOperation currentOperation) { + Contract.Requires(instruction != null); + Contract.Requires(stack != null); + Contract.Requires(currentOperation != null); + IArrayTypeReference arrayType = (IArrayTypeReference)currentOperation.Value; + Contract.Assume(arrayType != null); //This is an informally specified property of the Metadata model. + var rank = arrayType.Rank; + if (rank > 0) { + if (currentOperation.OperationCode == OperationCode.Array_Create_WithLowerBound) rank *= 2; + rank--; + if (rank > 0) { + var indices = new Instruction[rank]; + instruction.Operand2 = indices; + for (var i = rank; i > 0; i--) + indices[i-1] = stack.Pop(); + } + instruction.Operand1 = stack.Pop(); + } + stack.Push(instruction); + } + + private static void InitializeArrayIndexerInstruction(Instruction instruction, Stack stack, IArrayTypeReference arrayType) { + Contract.Requires(instruction != null); + Contract.Requires(stack != null); + Contract.Requires(arrayType != null); + var rank = arrayType.Rank; + var indices = new Instruction[rank]; + instruction.Operand2 = indices; + for (var i = rank; i > 0; i--) + indices[i-1] = stack.Pop(); + instruction.Operand1 = stack.Pop(); + stack.Push(instruction); + } + + private static void InitializeArraySetInstruction(Instruction instruction, Stack stack, IArrayTypeReference arrayType) { + Contract.Requires(instruction != null); + Contract.Requires(stack != null); + Contract.Requires(arrayType != null); + var rank = arrayType.Rank; + var indices = new Instruction[rank+1]; + instruction.Operand2 = indices; + for (var i = rank+1; i > 0; i--) + indices[i-1] = stack.Pop(); + instruction.Operand1 = stack.Pop(); + } + + } + +} \ No newline at end of file diff --git a/Metadata/Sources/ControlAndDataFlowGraph/Graph.cs b/Metadata/Sources/ControlAndDataFlowGraph/Graph.cs new file mode 100644 index 0000000..ff97240 --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/Graph.cs @@ -0,0 +1,765 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; +using Microsoft.Cci.UtilityDataStructures; + +namespace Microsoft.Cci.Analysis { + + /// + /// A set of basic blocks, each of which has a list of successor blocks and some other information. + /// Each block consists of a list of instructions, each of which can point to previous instructions that compute the operands it consumes. + /// + public class ControlAndDataFlowGraph + where BasicBlock : Microsoft.Cci.Analysis.BasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + internal ControlAndDataFlowGraph(IMethodBody body, List successorEdges, List allBlocks, List rootBlocks, Hashtable blockFor) { + Contract.Requires(body != null); + Contract.Requires(successorEdges != null); + Contract.Requires(allBlocks != null); + Contract.Requires(rootBlocks != null); + Contract.Requires(blockFor != null); + + this.methodBody = body; + this.successorEdges = successorEdges; + this.allBlocks = allBlocks; + this.rootBlocks = rootBlocks; + this.blockFor = blockFor; + } + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.methodBody != null); + Contract.Invariant(this.successorEdges != null); + Contract.Invariant(this.allBlocks != null); + Contract.Invariant(this.rootBlocks != null); + Contract.Invariant(this.blockFor != null); + } + + /// + /// The method body for which this instance is a Control and Data Flow Graph. + /// + public IMethodBody MethodBody { + get { + Contract.Ensures(Contract.Result() != null); + return this.methodBody; + } + set { + Contract.Requires(value != null); + this.methodBody = value; + } + } + private IMethodBody methodBody; + + /// + /// The first block in the method as well as the first blocks of any exception handlers, fault handlers and finally clauses. + /// + public List RootBlocks { + get { + Contract.Ensures(Contract.Result>() != null); + return this.rootBlocks; + } + set { + Contract.Requires(value != null); + this.rootBlocks = value; + } + } + List rootBlocks; + + /// + /// A list of all basic blocks in the graph, ordered so that any block that ends on a conditional branch immediately precedes the block + /// to which it falls through and so that all blocks that make up a try body or handler are contiguous. + /// + public List AllBlocks { + get { + Contract.Ensures(Contract.Result>() != null); + return this.allBlocks; + } + set { + Contract.Requires(value != null); + this.allBlocks = value; + } + } + List allBlocks; + + /// + /// A map from IL offset to corresponding basic block. + /// + public Hashtable BlockFor { + get { + Contract.Ensures(Contract.Result>() != null); + return this.blockFor; + } + set { + Contract.Requires(value != null); + this.blockFor = value; + } + } + private Hashtable blockFor; + + /// + /// The master list of all successor edges. The successor list for each basic block is a sublist of this list. + /// + public List SuccessorEdges { + get { + Contract.Ensures(Contract.Result>() != null); + return this.successorEdges; + } + set { + Contract.Requires(value != null); + this.successorEdges = value; + } + } + List successorEdges; + + /// + /// All basic blocks that can be reached via control flow out of the given basic block. + /// + public Sublist SuccessorsFor(BasicBlock basicBlock) { + Contract.Requires(basicBlock != null); + + if (basicBlock.firstSuccessorEdge+basicBlock.successorCount > this.SuccessorEdges.Count) + throw new InvalidOperationException(); //can only happen if the basic block does not belong to this graph. + Contract.Assume(basicBlock.firstSuccessorEdge >= 0); + Contract.Assume(basicBlock.successorCount >= 0); + return new Sublist(this.SuccessorEdges, basicBlock.firstSuccessorEdge, basicBlock.successorCount); + } + + /// + /// Returns the pc for the first block + /// + public BlockPC StartPC { get { return new BlockPC(0u.Singleton()); } } + + /// + /// Returns the successor BlockPCs from the given BlockPC, properly taking into account finally blocks + /// + public IEnumerable Successors(BlockPC pc) + { + var current = this.BlockFor[pc.Current]; + var succs = this.SuccessorsFor(current); + if (succs.Count > 0) + { + foreach (var succ in succs) + { + var finallyBlocks = this.FinallyBlocksOnEdge(current, succ); + if (finallyBlocks != null) + { + var to = pc.Stack.Tail; // pop current + to = to.Cons(succ.Offset); // push ultimate successor + while (finallyBlocks != null) + { + to = to.Cons(finallyBlocks.Head.Offset); // push each finally block to execute + finallyBlocks = finallyBlocks.Tail; + } + yield return new BlockPC(to); + } + else + { + yield return pc.Replace(succ.Offset); + } + } + } + else + { + // no direct successors + + // find the next pending block + var to = pc.Stack.Tail; + if (to != null) yield return new BlockPC(to); + } + } + + /// + /// Return the Block corresponding to the offset on top of the execution stack + /// + public BasicBlock CurrentBlock(BlockPC pc) + { + var current = this.BlockFor[pc.Current]; + return current; + } + /// + /// Constructs a control and data flow graph for the given method body. + /// + public static ControlAndDataFlowGraph GetControlAndDataFlowGraphFor(IMetadataHost host, IMethodBody methodBody, ILocalScopeProvider/*?*/ localScopeProvider = null) { + Contract.Requires(host != null); + Contract.Requires(methodBody != null); + Contract.Ensures(Contract.Result>() != null); + + var cdfg = ControlFlowInferencer.SetupControlFlow(host, methodBody, localScopeProvider); + DataFlowInferencer.SetupDataFlow(host, methodBody, cdfg); + TypeInferencer.FillInTypes(host, cdfg); + HandlerInferencer.FillInHandlers(host, cdfg); + + return cdfg; + } + + /// + /// Returns the finally blocks on this control flow edge in reverse execution order on a forward traversal. + /// + public FList FinallyBlocksOnEdge(BasicBlock from, BasicBlock to) + { + Contract.Requires(from != null); + Contract.Requires(to != null); + + var fromHandlers = from.Handlers; + var toHandlers = to.Handlers; + var commonTail = fromHandlers.LongestCommonTail(toHandlers); + + var result = FList.Empty; + + while (fromHandlers != commonTail) + { + Contract.Assume(fromHandlers != null); + + var handler = fromHandlers.Head; + if (handler.HandlerKind == HandlerKind.Finally) + { + result = result.Cons(this.BlockFor[fromHandlers.Head.HandlerStartOffset]); + } + fromHandlers = fromHandlers.Tail; + } + + return result; + } + + /// + /// Given an instruction representing an address (byref), find the local or parameter it corresponds to or null + /// + public object LocalOrParameter(Analysis.Instruction address) + { + if (address == null) return null; + while (true) + { + if (address.IsMergeNode) { address = address.Operand1; } // all merges should be same address + else + { + switch (address.Operation.OperationCode) + { + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + return address.Operation.Value; // the parameter definition + + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + return address.Operation.Value; // the local definition + + default: + return null; + } + } + } + } + + } + + /// + /// A block of instructions of which only the first instruction can be reached via explicit control flow. + /// + public class BasicBlock where Instruction : Microsoft.Cci.Analysis.Instruction { + + /// + /// The first edge that leaves this block. The edges are a contiguous sublist of the the SuccessorEdges list of the ControlAndDataFlowGraph that contains this block. + /// + internal int firstSuccessorEdge; + + /// + /// The number of edges that leave this block. The edges are a contiguous sublist of the the SuccessorEdges list of the ControlAndDataFlowGraph that contains this block. + /// + internal int successorCount; + + /// + /// A list of pseudo instructions that initialize the operand stack when the block is entered. No actual code should be generated for these instructions + /// as the actual stack will be set up by the code transferring control to this block. + /// + public Sublist OperandStack; + + /// + /// The instructions making up this block. + /// + public Sublist Instructions; + + private uint offset; + + /// + /// The IL offset of the first instruction in this basic block. If the block is empty, it is the same as the Offset of the following block. If there is no following block, + /// it is the offset where the next instruction would have appeared. + /// + public uint Offset + { + get + { + if (this.offset == 0) + { + if (this.Instructions.Count == 0) return 0; else return this.Instructions[0].Operation.Offset; + } + return this.offset; + } + set + { + this.offset = value; + } + } + + /// + /// The enclosing handlers of this block in innermost to outermost order + /// + public FList Handlers; + + /// + /// If this block is physically inside a catch, fault, finally, or filter handler, then + /// ContainingHandler points to the closest enclosing such handler. + /// + public IOperationExceptionInformation/*?*/ ContainingHandler; + + private FMap localDefs; + + /// + /// Maps local variables at the beginning of the block to the corresponding defining instruction + /// + public FMap LocalDefs + { + get + { + Contract.Ensures(Contract.Result>() != null); + + if (this.localDefs == null) + { + this.localDefs = new FMap(l => l.GetHashCode()); + } + return this.localDefs; + } + set { + Contract.Requires(value != null); + this.localDefs = value; + } + + } + + private FMap paramDefs; + + /// + /// Maps parameters at the beginning of the block to the corresponding defining instruction + /// + public FMap ParamDefs + { + get + { + Contract.Ensures(Contract.Result>() != null); + if (this.paramDefs == null) + { + this.paramDefs = new FMap(l => l.Index); + } + return this.paramDefs; + } + set + { + Contract.Requires(value != null); + this.paramDefs = value; + } + } + + /// + /// If this instruction is a constrained call virt, return the corresponding preceeding type constraint. + /// + public ITypeReference CallConstraint(Instruction instruction) + { + Contract.Requires(instruction != null); + + if (instruction.Operation.OperationCode != OperationCode.Callvirt) return null; + + Instruction pred = null; + for (int i = 0; i < this.Instructions.Count; i++) + { + if (this.Instructions[i] == instruction) break; + pred = this.Instructions[i]; + } + if (pred != null && pred.Operation.OperationCode == OperationCode.Constrained_) + { + return pred.Operation.Value as ITypeReference; + } + return null; + } + + /// + /// Returns a string describing the basic block. + /// + /// + public override string ToString() { + if (this.Instructions.Count == 0) return "Empty BasicBlock"; + return "BasicBlock at "+this.Offset.ToString("x4"); + } + + } + + /// + /// A model of an IL operation, but with the implicit operand stack made explicit via the properties Operand1 and Operand2 + /// that point to the previous instructions that computed the operands, if any, that the instruction consumes. + /// + public class Instruction { + + /// + /// A model of an IL operation, but with the implicit operand stack made explicit via the properties Operand1 and Operand2 + /// that point to the previous instructions that computed the operands, if any, that the instruction consumes. + /// + public Instruction() { + this.operation = Dummy.Operation; + this.type = Dummy.Type; + } + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.operation != null); + Contract.Invariant(this.type != null); + } + + /// + /// The operation this instruction carries out. + /// + public IOperation Operation { + get { + Contract.Ensures(Contract.Result() != null); + return operation; + } + set { + Contract.Requires(value != null); + operation = value; + } + } + private IOperation operation; + + /// + /// The instruction that results in the first operand of the operation, if an operand is required. + /// + public Instruction/*?*/ Operand1; + + /// + /// The instruction that results in the second operand of the operation, if a second operand is required. + /// Could also be an array of instructions if the instruction is n-ary for n > 2. + /// + public object/*?*/ Operand2; + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() { + var stringBuilder = new StringBuilder(); + stringBuilder.Append(this.Operation.Offset.ToString("x4")); + stringBuilder.Append(", "); + stringBuilder.Append(this.Operation.OperationCode.ToString()); + + if (this.Operation.Value is uint) + stringBuilder.Append(" "+((uint)this.Operation.Value).ToString("x4")); + stringBuilder.Append(", "); + stringBuilder.Append(TypeHelper.GetTypeName(this.Type)); + if (this.Operand1 != null) { + stringBuilder.Append(", "); + this.AppendFlowFrom(this.Operand1, stringBuilder); + } + var i2 = this.Operand2 as Instruction; + if (i2 != null) { + stringBuilder.Append(", "); + this.AppendFlowFrom(i2, stringBuilder); + } else { + var i2a = this.Operand2 as Instruction[]; + if (i2a != null) { + foreach (var i2e in i2a) { + Contract.Assume(i2e != null); //Assumed because of the informal specification of the ControlFlowInferencer + stringBuilder.Append(", "); + this.AppendFlowFrom(i2e, stringBuilder); + } + } + } + return stringBuilder.ToString(); + } + + private void AppendFlowFrom(Instruction instruction, StringBuilder stringBuilder) { + Contract.Requires(instruction != null); + Contract.Requires(stringBuilder != null); + + if (instruction.Operation is Dummy) + stringBuilder.Append("stack"); + else + stringBuilder.Append(instruction.Operation.Offset.ToString("x4")); + } + + /// + /// The type of the result this instruction pushes onto the stack. Void if none. + /// + public ITypeReference Type { + get { + Contract.Ensures(Contract.Result() != null); + return type; + } + set { + Contract.Requires(value != null); + Contract.Assume(!(value is Dummy)); //It is a bit too onerous on the client code to prove this statically, but it does seem a very desirable check. + type = value; + } + } + private ITypeReference type; + + /// + /// Extra dataflow information for Ldloc, Ldarg, Ldind. It contains the actual result value that was loaded + /// + public Instruction Aux; + + /// + /// The local definitions after the instruction + /// + public FMap PostLocalDefs; + /// + /// The parameter definitions after the instruction + /// + public FMap PostParamDefs; + + /// + /// Return true if the instruction is a synthetic merge node (aka Phi node) at block entry + /// + public bool IsMergeNode { get { return this.operation is Dummy; } } + + /// + /// Returns the defining instruction of the local or parameter definition after this instruction or null + /// + public Instruction this[object localOrParameter] + { + get + { + Instruction result; + var local = localOrParameter as ILocalDefinition; + if (this.PostLocalDefs != null && local != null) + { + this.PostLocalDefs.TryGetValue(local, out result); + return result; + } + var param = localOrParameter as IParameterDefinition; + if (this.PostParamDefs != null && param != null) + { + this.PostParamDefs.TryGetValue(param, out result); + return result; + } + return null; + } + } + + /// + /// If the instruction is a merge node, then return all in-flowing defs + /// + /// + public IEnumerable InFlows() + { + if (!this.IsMergeNode) yield break; + if (this.Operand1 != null) + { + yield return this.Operand1; + } + var second = this.Operand2 as Instruction; + if (second != null) yield return second; + var rest = this.Operand2 as List; + if (rest != null) + { + for (int i = 0; i < rest.Count; i++) + { + yield return rest[i]; + } + } + } + + } + + internal class Stack where Instruction : class { + + internal Stack(int maxStack) { + if (maxStack <= 0) maxStack = 8; + this.elements = new Instruction[maxStack]; + this.top = -1; + } + + Instruction[] elements; + private int top; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.elements != null); + Contract.Invariant(this.elements.Length > 0); + Contract.Invariant(this.top < this.elements.Length); + Contract.Invariant(Contract.ForAll(0, this.top+1, (i) => this.elements[i] != null)); + Contract.Invariant(this.top >= -1); + } + + internal void Clear() { + this.top = -1; + } + + internal void Push(Instruction instruction) { + Contract.Requires(instruction != null); + + if (this.top >= this.elements.Length-1) { + Array.Resize(ref this.elements, this.elements.Length*2); + Contract.Assume(Contract.ForAll(0, this.top+1, (i) => this.elements[i] != null)); //this the expected behavior of Array.Resize + } + this.elements[++this.top] = instruction; + } + + internal Instruction Peek(int i) { + Contract.Requires(0 <= i); + Contract.Requires(i <= this.Top); + Contract.Ensures(Contract.Result() != null); + Contract.Ensures(this.Top == Contract.OldValue(this.Top)); + + return this.elements[i]; + } + + internal Instruction Pop() { + Contract.Ensures(Contract.Result() != null); + + Contract.Assume(this.top >= 0); //This is an optimistic assumption. Clients have to match their Pop and Push calls, but enforcing this convention via contracts is too verbose. + return this.elements[this.top--]; + } + + internal int Top { + get { return this.top; } + } + } + + /// + /// A generalized program counter that contains the current block (and blocks to execute after that) + /// + /// Equality and hashing is based on content (the blocks) rather than the list pointers, so they are value equal + /// + public struct BlockPC : IEquatable + { + /// + /// List of blocks (identified by start instruction offset) that are form a stack of execution contexts + /// + public readonly FList Stack; + + [ContractInvariantMethod] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")] + private void ObjectInvariant() + { + Contract.Invariant(this.Stack != null); + } + + /// + /// Produce a block PC with the given control stack + /// + public BlockPC(FList stack) + { + Contract.Requires(stack != null); + + this.Stack = stack; + } + + /// + /// Return the block offset of the current block in the PC + /// + public uint Current { get { return this.Stack.Head; } } + + /// + /// Produce a new BlockPC with the current block replaced by the given one. The rest of the stack is unchanged. + /// + public BlockPC Replace(uint newCurrent) + { + return new BlockPC(this.Stack.Tail.Cons(newCurrent)); + } + + /// + /// Produce a new BlockPC starting at the given Block. + /// + public static BlockPC For(Block b) + where Block : Analysis.BasicBlock + where Instruction : Analysis.Instruction + { + return new BlockPC(b.Offset.Singleton()); + } + + /// + /// Push the given block on top of the BlockPC call stack + /// + public BlockPC Push(Block b) + where Block : Analysis.BasicBlock + where Instruction : Analysis.Instruction + { + return new BlockPC(this.Stack.Cons(b.Offset)); + } + + #region IEquatable Members + + /// + /// Compare two BlockPCs for value equality + /// + public bool Equals(BlockPC other) + { + return EqualLists(this.Stack, other.Stack); + } + + private static bool EqualLists(FList tl, FList ol) + { + while (tl != null && ol != null) + { + if (tl.Head != ol.Head) return false; + tl = tl.Tail; + ol = ol.Tail; + } + if (ol != tl) return false; // both must be null here + + return true; + } + + /// + /// Return the hash code for this BlockPC + /// + public override int GetHashCode() + { + return HashList(0, this.Stack); + } + + private static int HashList(int hash, FList l) + { + while (l != null) { hash = 2 * hash + (int)l.Head; l = l.Tail; } + return hash; + } + + /// + /// Return a string representation of this BlockPC + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("Block "); + sb.AppendFormat("{0:x3}", this.Stack.Head); + sb.Append(" ("); + var to = this.Stack.Tail; + while (to != null) + { + sb.AppendFormat("{0:x3}", to.Head); + sb.Append(", "); + to = to.Tail; + } + sb.Append(")"); + return sb.ToString(); + } + #endregion + } + +} diff --git a/Metadata/Sources/ControlAndDataFlowGraph/HandlerInferencer.cs b/Metadata/Sources/ControlAndDataFlowGraph/HandlerInferencer.cs new file mode 100644 index 0000000..142d53d --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/HandlerInferencer.cs @@ -0,0 +1,474 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.Immutable; +using Microsoft.Cci.UtilityDataStructures; +using System; + +namespace Microsoft.Cci.Analysis +{ + /// + /// Infer protecting local handlers for each block. + /// + /// With that information we can then traverse finally and exception handler blocks in the proper order to infer data flow for locals and parameters + /// + internal class HandlerInferencer + where BasicBlock : Microsoft.Cci.Analysis.BasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() + { + internal static void FillInHandlers(IMetadataHost host, ControlAndDataFlowGraph cdfg) + { + var method = cdfg.MethodBody; + + var handlers = new List(method.OperationExceptionInformation).ToArray(); + + #region Compute enclosing handlers for each block + FList currentHandlers = null; + FList containingHandlers = null; + + foreach (var current in cdfg.AllBlocks) + { + //traceFile.WriteLine("Block at: {0:x3}", current.Offset); + currentHandlers = PopPushHandlers(current, currentHandlers, handlers, ref containingHandlers); + + } + #endregion + + // now compute extra block information: + // - local def maps + // - parameter def maps + + ComputeDataFlowThroughLocals(cdfg); + } + private static FList PopPushHandlers(BasicBlock block, FList currentHandlers, IOperationExceptionInformation[] handlers, ref FList containingHandlers) + { + Contract.Requires(block != null); + Contract.Requires(handlers != null); + Contract.Requires(Contract.ForAll(handlers, h => h != null)); + + // pop fault/finally subroutines off subroutine stack whose scope ends here + var blockOffset = block.Offset; + + #region Pop protecting handlers off stack whose scope ends here + for (int i = 0; i < handlers.Length; i++) + { + if (handlers[i].TryEndOffset == blockOffset) + { + // must be head + if (currentHandlers != null && Object.Equals(handlers[i], currentHandlers.Head)) + { + currentHandlers = currentHandlers.Tail; + } + else + { + throw new ApplicationException("bad order of handlers"); + } + } + } + #endregion + + #region Push protecting handlers on stack whose scope starts here + + // reverse order + for (int i = handlers.Length - 1; i >= 0; i--) + { + if (handlers[i].TryStartOffset == blockOffset) + { + // push this handler on top of current block enclosing handlers + currentHandlers = FList.Cons(handlers[i], currentHandlers); // Push handler + } + } + #endregion + + #region Pop containing handlers off containing handler stack whose scope ends here + for (int i = 0; i < handlers.Length; i++) + { + if (handlers[i].HandlerEndOffset == blockOffset) + { + // must be head + if (containingHandlers != null && Object.Equals(handlers[i], containingHandlers.Head)) + { + containingHandlers = containingHandlers.Tail; + } + else + { + throw new ApplicationException("bad order of handlers"); + } + } + } + #endregion + + #region Push containing handler on stack whose scope starts here + + // reverse order + for (int i = handlers.Length - 1; i >= 0; i--) + { + if (handlers[i].HandlerStartOffset == blockOffset) + { + // push this handler on top of containing handlers + containingHandlers = FList.Cons(handlers[i], containingHandlers); // Push handler + } + } + #endregion + + + // record handlers for this block + block.Handlers = currentHandlers; + + block.ContainingHandler = (containingHandlers != null) ? containingHandlers.Head : null; + + return currentHandlers; + } + + private static void ComputeDataFlowThroughLocals(ControlAndDataFlowGraph cdg) + { + FMap currentLocals; + + var todo = new Queue(); + var seen = new HashSet(); + var startBlock = cdg.RootBlocks[0]; + + FMap currentParameters = new FMap(k => k.Index); + + var initialLocation = GetStartLocation(startBlock); + + // push parameters onto start block + foreach (var arg in cdg.MethodBody.MethodDefinition.Parameters) + { + var initialOp = new InitialParameterAssignment(arg, initialLocation); + var initialDef = new Instruction() { Operation = initialOp }; + currentParameters = currentParameters.Insert(arg, initialDef); + } + startBlock.ParamDefs = currentParameters; + todo.Enqueue(new BlockPC(startBlock.Offset.Singleton())); + + while (todo.Count > 0) + { + var currentPC = todo.Dequeue(); + if (seen.Contains(currentPC)) continue; + seen.Add(currentPC); + + var block = cdg.CurrentBlock(currentPC); + Contract.Assume(block != null); + + currentLocals = block.LocalDefs; + currentParameters = block.ParamDefs; + + foreach (var instr in block.Instructions) + { + if (instr.IsMergeNode) continue; + switch (instr.Operation.OperationCode) + { + case OperationCode.Starg: + case OperationCode.Starg_S: + // without pdb we seem to have no parameter info. + var pdef = (IParameterDefinition)instr.Operation.Value; + if (pdef != null) + { + currentParameters = currentParameters.Insert(pdef, instr.Operand1); + } + break; + case OperationCode.Stloc: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + var ldef = (ILocalDefinition)instr.Operation.Value; + currentLocals = currentLocals.Insert(ldef, instr.Operand1); + break; + + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + // save the source in Aux + { + currentLocals.TryGetValue((ILocalDefinition)instr.Operation.Value, out instr.Aux); + break; + } + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + // save the source in Aux + var pdef2 = (IParameterDefinition)instr.Operation.Value; + if (pdef2 == null) + { + // this parameter. Assume it's never overwritten + } + else + { + currentParameters.TryGetValue(pdef2, out instr.Aux); + } + break; + + case OperationCode.Stind_I: + case OperationCode.Stind_I1: + case OperationCode.Stind_I2: + case OperationCode.Stind_I4: + case OperationCode.Stind_I8: + case OperationCode.Stind_R4: + case OperationCode.Stind_R8: + case OperationCode.Stind_Ref: + { + var location = cdg.LocalOrParameter(instr.Operand1); + UpdateLocation(ref currentLocals, ref currentParameters, location, (Analysis.Instruction)instr.Operand2); + + break; + } + case OperationCode.Ldind_I: + case OperationCode.Ldind_I1: + case OperationCode.Ldind_I2: + case OperationCode.Ldind_I4: + case OperationCode.Ldind_I8: + case OperationCode.Ldind_R4: + case OperationCode.Ldind_R8: + case OperationCode.Ldind_Ref: + case OperationCode.Ldind_U1: + case OperationCode.Ldind_U2: + case OperationCode.Ldind_U4: + { + // save the read value in Aux + var location = cdg.LocalOrParameter(instr.Operand1); + instr.Aux = ReadLocation(currentLocals, currentParameters, location); + break; + } + + case OperationCode.Call: + case OperationCode.Callvirt: + { + // update byref / out parameters + var methodRef = instr.Operation.Value as IMethodReference; + var args = instr.Operand2 as Instruction[]; + if (args != null && methodRef != null) + { + foreach (var p in methodRef.Parameters) + { + if (p.IsByReference && p.Index < args.Length) + { + var arg = args[p.Index]; + if (arg != null) + { + var loc = cdg.LocalOrParameter(arg); + var syntheticOp = new CallByRefAssignment(instr, p); + UpdateLocation(ref currentLocals, ref currentParameters, loc, new Instruction() { Operation = syntheticOp, Type = p.Type }); + } + } + } + } + break; + } + } + instr.PostLocalDefs = currentLocals; + instr.PostParamDefs = currentParameters; + } + foreach (var succ in cdg.Successors(currentPC)) + { + MergeLocalsAndParameters(cdg.CurrentBlock(succ), currentLocals, currentParameters); + todo.Enqueue(succ); + } + + } + } + + private static Analysis.Instruction ReadLocation(FMap currentLocals, FMap currentParameters, object localOrParameter) + { + Analysis.Instruction result; + var local = localOrParameter as ILocalDefinition; + if (local != null) + { + currentLocals.TryGetValue(local, out result); + return result; + } + var param = localOrParameter as IParameterDefinition; + if (param != null) + { + currentParameters.TryGetValue(param, out result); + return result; + } + return null; + } + + private static ILocation GetStartLocation(BasicBlock startBlock) + { + foreach (var i in startBlock.Instructions) + { + if (i.Operation.Location != null && !(i.Operation.Location is Dummy)) return i.Operation.Location; + } + return null; + } + + private static void UpdateLocation(ref FMap currentLocals, ref FMap currentParameters, object localOrParameter, Analysis.Instruction newValue) + { + Contract.Requires(currentParameters != null); + Contract.Requires(currentLocals != null); + Contract.Ensures(Contract.ValueAtReturn(out currentParameters) != null); + Contract.Ensures(Contract.ValueAtReturn(out currentLocals) != null); + + var local = localOrParameter as ILocalDefinition; + if (local != null) + { + currentLocals = currentLocals.Insert(local, newValue); + return; + } + var param = localOrParameter as IParameterDefinition; + if (param != null) + { + currentParameters = currentParameters.Insert(param, newValue); + return; + } + } + + + private static void MergeLocalsAndParameters(BasicBlock succ, FMap currentLocals, FMap currentParameters) + { + foreach (var p in currentLocals) + { + Microsoft.Cci.Analysis.Instruction existing; + if (succ.LocalDefs.TryGetValue(p.Key, out existing)) + { + PushMerge(existing, p.Value); + } + else + { + var mergeInstruction = new Instruction() { Operand1 = p.Value }; + succ.LocalDefs = succ.LocalDefs.Insert(p.Key, mergeInstruction); + } + } + foreach (var p in currentParameters) + { + Microsoft.Cci.Analysis.Instruction existing; + if (succ.ParamDefs.TryGetValue(p.Key, out existing)) + { + PushMerge(existing, p.Value); + } + else + { + var mergeInstruction = new Instruction() { Operand1 = p.Value }; + succ.ParamDefs = succ.ParamDefs.Insert(p.Key, mergeInstruction); + } + } + } + + private static void PushMerge(Microsoft.Cci.Analysis.Instruction target, Microsoft.Cci.Analysis.Instruction inflow) + { + if (target.Operand2 == null) + target.Operand2 = inflow; + else + { + var list = target.Operand2 as List; + if (list == null) + { + //Contract.Assume(target.Operand2 is Instruction); + list = new List(4); + list.Add((Microsoft.Cci.Analysis.Instruction)target.Operand2); + } + list.Add(inflow); + } + + } + + + } + + /// + /// Dummy instruction to give definition of initial parameter values + /// + public class InitialParameterAssignment : IOperation + { + private ILocation location; + private IParameterDefinition parameter; + + internal InitialParameterAssignment(IParameterDefinition p, ILocation locs) { + this.location = locs; + this.parameter = p; + } + + #region IOperation Members + + OperationCode IOperation.OperationCode + { + get { return Cci.OperationCode.Starg; } + } + + uint IOperation.Offset + { + get { return 0; } + } + + ILocation IOperation.Location + { + get { return this.location; } + } + + object IOperation.Value + { + get { return this.parameter; } + } + + #endregion + } + + /// + /// Dummy instruction to give definition of byref/out parameter values at a call + /// + public class CallByRefAssignment : IOperation + { + Instruction original; + + /// + /// The parameter assigned by this operation + /// + public readonly IParameterTypeInformation Parameter; + + internal CallByRefAssignment(Instruction original, IParameterTypeInformation p) + { + this.original = original; + this.Parameter = p; + } + + /// + /// The original call instruction giving rise to this by-ref assignment + /// + public Instruction Call { get { return this.original; } } + + #region IOperation Members + + OperationCode IOperation.OperationCode + { + get { return Cci.OperationCode.Starg; } + } + + uint IOperation.Offset + { + get { return original.Operation.Offset; } + } + + ILocation IOperation.Location + { + get { return this.original.Operation.Location; } + } + + object IOperation.Value + { + get { return this.original.Operation.Value; } + } + + #endregion + } + +} diff --git a/Metadata/Sources/ControlAndDataFlowGraph/Properties/AssemblyInfo.cs b/Metadata/Sources/ControlAndDataFlowGraph/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2e3c325 --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsof.Cci.ControlAndDataFlowGraph")] +[assembly: AssemblyDescription("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("29c3269c-6c6e-49fc-b471-181bb71ec938")] + diff --git a/Metadata/Sources/ControlAndDataFlowGraph/TypeInferencer.cs b/Metadata/Sources/ControlAndDataFlowGraph/TypeInferencer.cs new file mode 100644 index 0000000..07dc2c8 --- /dev/null +++ b/Metadata/Sources/ControlAndDataFlowGraph/TypeInferencer.cs @@ -0,0 +1,1035 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.Cci.Immutable; +using Microsoft.Cci.UtilityDataStructures; + +namespace Microsoft.Cci.Analysis { + /// + /// + /// + internal class TypeInferencer + where BasicBlock : Microsoft.Cci.Analysis.BasicBlock, new() + where Instruction : Microsoft.Cci.Analysis.Instruction, new() { + + private TypeInferencer(IMetadataHost host, ControlAndDataFlowGraph cfg, Stack stack, Queue blocksToVisit, SetOfObjects blocksAlreadyVisited) { + Contract.Requires(host != null); + Contract.Requires(cfg != null); + Contract.Requires(stack != null); + Contract.Requires(blocksToVisit != null); + Contract.Requires(blocksAlreadyVisited != null); + + this.platformType = host.PlatformType; + this.cfg = cfg; + this.stack = stack; + this.blocksToVisit = blocksToVisit; + this.blocksAlreadyVisited = blocksAlreadyVisited; + this.internFactory = host.InternFactory; + } + + IPlatformType platformType; + ControlAndDataFlowGraph cfg; + Stack stack; + Queue blocksToVisit; + SetOfObjects blocksAlreadyVisited; + IInternFactory internFactory; + bool codeIsUnreachable; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.platformType != null); + Contract.Invariant(this.cfg != null); + Contract.Invariant(this.stack != null); + Contract.Invariant(this.blocksToVisit != null); + Contract.Invariant(this.blocksAlreadyVisited != null); + Contract.Invariant(this.internFactory != null); + } + + /// + /// + /// + internal static void FillInTypes(IMetadataHost host, ControlAndDataFlowGraph cfg) { + Contract.Requires(host != null); + Contract.Requires(cfg != null); + + //If this is a dummy body, do nothing. + if (cfg.AllBlocks.Count == 1 && cfg.AllBlocks[0] != null && cfg.AllBlocks[0].Instructions.Count <= 1) return; + + var stack = new Stack(cfg.MethodBody.MaxStack); + var numberOfBlocks = cfg.BlockFor.Count; + var blocksToVisit = new Queue((int)numberOfBlocks); + var blocksAlreadyVisited = new SetOfObjects(numberOfBlocks); + var inferencer = new TypeInferencer(host, cfg, stack, blocksToVisit, blocksAlreadyVisited); + + foreach (var root in cfg.RootBlocks) { + blocksToVisit.Enqueue(root); + while (blocksToVisit.Count != 0) + inferencer.DequeueBlockAndFillInItsTypes(); + } + + //At this point, all reachable code blocks have had their types inferred. Now look for unreachable blocks. + inferencer.codeIsUnreachable = true; + foreach (var block in cfg.AllBlocks) { + if (blocksAlreadyVisited.Contains(block)) continue; + blocksToVisit.Enqueue(block); + while (blocksToVisit.Count != 0) + inferencer.DequeueBlockAndFillInItsTypes(); + } + } + + private void DequeueBlockAndFillInItsTypes() { + var block = this.blocksToVisit.Dequeue(); + Contract.Assume(block != null); //this.blocksToVisit only has non null elements, but we can't put that in a contract that satisfies the checker + if (!this.blocksAlreadyVisited.Add(block)) return; //The same block can be added multiple times to the queue. + + //The block either has no operand stack setup instructions, or we presume that a predecessor block has already assigned types to them. + foreach (var stackSetupInstruction in block.OperandStack) { + Contract.Assume(stackSetupInstruction != null); //block.OperandStack only has non null elements, but we can't put that in a contract that satisfies the checker + this.stack.Push(stackSetupInstruction); + } + + foreach (var instruction in block.Instructions) { + Contract.Assume(instruction != null); //block.Instructions only has non null elements, but we can't put that in a contract that satisfies the checker + this.InferTypeAndUpdateStack(instruction); + } + + foreach (var successor in this.cfg.SuccessorsFor(block)) { + Contract.Assume(successor != null); //block.Successors only has non null elements, but we can't put that in a contract that satisfies the checker + this.TransferTypesFromStackTo(successor); + if (blocksAlreadyVisited.Contains(successor)) continue; + blocksToVisit.Enqueue(successor); //The block might already be in the queue, but we can deal with this more efficiently by checking blocksAlreadyVisited when dequeueing. + } + + this.stack.Clear(); + } + + private void TransferTypesFromStackTo(BasicBlock successor) { + Contract.Requires(successor != null); + Contract.Assume(this.stack.Top+1 == successor.OperandStack.Count); //We assume that the DataFlowInferencer sets things up this way. + for (int i = 0, n = this.stack.Top; i <= n; i++) { + var producer = this.stack.Peek(i); + var consumer = successor.OperandStack[i]; + if (consumer.Type is Dummy) + consumer.Type = producer.Type; + else + consumer.Type = TypeHelper.MergedType(consumer.Type, producer.Type); + } + } + + private void InferTypeAndUpdateStack(Instruction instruction) { + Contract.Requires(instruction != null); + switch (instruction.Operation.OperationCode) { + case OperationCode.Add: + case OperationCode.Add_Ovf: + case OperationCode.Div: + case OperationCode.Mul: + case OperationCode.Mul_Ovf: + case OperationCode.Rem: + case OperationCode.Sub: + case OperationCode.Sub_Ovf: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.GetBinaryNumericOperationType(instruction); + this.stack.Push(instruction); + break; + case OperationCode.And: + case OperationCode.Or: + case OperationCode.Xor: + Contract.Assume(instruction.Operand1 != null); + Contract.Assume(instruction.Operand2 is Instruction); + if (instruction.Operand1.Type.TypeCode == PrimitiveTypeCode.Boolean && ((Instruction)instruction.Operand2).Type.TypeCode == PrimitiveTypeCode.Boolean) { + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemBoolean; + this.stack.Push(instruction); + break; + } + goto case OperationCode.Add; + + case OperationCode.Add_Ovf_Un: + case OperationCode.Div_Un: + case OperationCode.Mul_Ovf_Un: + case OperationCode.Rem_Un: + case OperationCode.Sub_Ovf_Un: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.GetUnsignedBinaryNumericOperationType(instruction); + this.stack.Push(instruction); + break; + case OperationCode.Arglist: + instruction.Type = this.platformType.SystemRuntimeArgumentHandle; + this.stack.Push(instruction); + break; + case OperationCode.Array_Addr: + var arrayType = instruction.Operation.Value as IArrayTypeReference; + Contract.Assume(arrayType != null); //This is an informally specified property of the Metadata model. + for (var i = arrayType.Rank; i > 0; i--) + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = ManagedPointerType.GetManagedPointerType(arrayType.ElementType, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Array_Create: + arrayType = instruction.Operation.Value as IArrayTypeReference; + Contract.Assume(arrayType != null); //This is an informally specified property of the Metadata model. + for (var i = arrayType.Rank; i > 0; i--) + this.stack.Pop(); + instruction.Type = arrayType; + this.stack.Push(instruction); + break; + case OperationCode.Array_Create_WithLowerBound: + arrayType = instruction.Operation.Value as IArrayTypeReference; + Contract.Assume(arrayType != null); //This is an informally specified property of the Metadata model. + for (var i = arrayType.Rank*2; i > 0; i--) + this.stack.Pop(); + instruction.Type = arrayType; + this.stack.Push(instruction); + break; + case OperationCode.Array_Get: + arrayType = instruction.Operation.Value as IArrayTypeReference; + Contract.Assume(arrayType != null); //This is an informally specified property of the Metadata model. + for (var i = arrayType.Rank; i > 0; i--) + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = arrayType.ElementType; + this.stack.Push(instruction); + break; + case OperationCode.Array_Set: + arrayType = instruction.Operation.Value as IArrayTypeReference; + Contract.Assume(arrayType != null); //This is an informally specified property of the Metadata model. + this.stack.Pop(); //The value to set + for (var i = arrayType.Rank; i > 0; i--) + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemVoid; + break; + case OperationCode.Beq: + case OperationCode.Beq_S: + case OperationCode.Bge: + case OperationCode.Bge_S: + case OperationCode.Bge_Un: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble: + case OperationCode.Ble_S: + case OperationCode.Ble_Un: + case OperationCode.Ble_Un_S: + case OperationCode.Blt: + case OperationCode.Blt_S: + case OperationCode.Blt_Un: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un: + case OperationCode.Bne_Un_S: + case OperationCode.Cpobj: + case OperationCode.Stfld: + case OperationCode.Stind_I: + case OperationCode.Stind_I1: + case OperationCode.Stind_I2: + case OperationCode.Stind_I4: + case OperationCode.Stind_I8: + case OperationCode.Stind_R4: + case OperationCode.Stind_R8: + case OperationCode.Stind_Ref: + case OperationCode.Stobj: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemVoid; + break; + case OperationCode.Box: + this.stack.Pop(); + var typeInBox = instruction.Operation.Value as ITypeReference; + Contract.Assume(typeInBox != null); + if (typeInBox.ResolvedType.IsReferenceType) + instruction.Type = typeInBox.ResolvedType; //This typically happens when typeInBox is a type parameter. + else + instruction.Type = this.platformType.SystemObject; + this.stack.Push(instruction); + break; + case OperationCode.Brfalse: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue: + case OperationCode.Brtrue_S: + case OperationCode.Endfilter: + case OperationCode.Initobj: + case OperationCode.Pop: + case OperationCode.Starg: + case OperationCode.Starg_S: + case OperationCode.Stloc: + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + case OperationCode.Stsfld: + case OperationCode.Throw: + case OperationCode.Switch: + this.stack.Pop(); + instruction.Type = this.platformType.SystemVoid; + break; + case OperationCode.Call: + case OperationCode.Callvirt: + var signature = instruction.Operation.Value as ISignature; + Contract.Assume(signature != null); //This is an informally specified property of the Metadata model. + var methodRef = signature as IMethodReference; + uint numArguments = IteratorHelper.EnumerableCount(signature.Parameters); + if (methodRef != null && methodRef.AcceptsExtraArguments) numArguments += IteratorHelper.EnumerableCount(methodRef.ExtraParameters); + for (var i = numArguments; i > 0; i--) + this.stack.Pop(); + if (!signature.IsStatic) + this.stack.Pop(); + instruction.Type = signature.Type; + if (signature.Type.TypeCode != PrimitiveTypeCode.Void) + this.stack.Push(instruction); + break; + case OperationCode.Calli: + var funcPointer = instruction.Operation.Value as IFunctionPointerTypeReference; + Contract.Assume(funcPointer != null); //This is an informally specified property of the Metadata model. + var fp = this.stack.Pop(); //The function pointer + fp.Type = funcPointer; + numArguments = IteratorHelper.EnumerableCount(funcPointer.Parameters); + for (var i = numArguments; i > 0; i--) + this.stack.Pop(); + if (!funcPointer.IsStatic) + this.stack.Pop(); + instruction.Type = funcPointer.Type; + if (funcPointer.Type.TypeCode != PrimitiveTypeCode.Void) + this.stack.Push(instruction); + break; + case OperationCode.Castclass: + case OperationCode.Isinst: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is ITypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = (ITypeReference)instruction.Operation.Value; + if (instruction.Type.ResolvedType.IsValueType || instruction.Type is IGenericParameterReference) + instruction.Type = this.platformType.SystemObject; + this.stack.Push(instruction); + break; + case OperationCode.Ceq: + case OperationCode.Cgt: + case OperationCode.Cgt_Un: + case OperationCode.Clt: + case OperationCode.Clt_Un: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemBoolean; + this.stack.Push(instruction); + break; + case OperationCode.Ckfinite: + case OperationCode.Neg: + case OperationCode.Not: + this.stack.Pop(); + Contract.Assume(instruction.Operand1 != null); //Assumed because of the informal specification of the DataFlowInferencer + instruction.Type = instruction.Operand1.Type; + this.stack.Push(instruction); + break; + case OperationCode.Conv_I: + case OperationCode.Conv_Ovf_I: + case OperationCode.Conv_Ovf_I_Un: + case OperationCode.Ldind_I: + case OperationCode.Localloc: + this.stack.Pop(); + instruction.Type = this.platformType.SystemIntPtr; + this.stack.Push(instruction); + break; + case OperationCode.Conv_I1: + case OperationCode.Conv_Ovf_I1: + case OperationCode.Conv_Ovf_I1_Un: + case OperationCode.Ldind_I1: + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt8; + this.stack.Push(instruction); + break; + case OperationCode.Conv_I2: + case OperationCode.Conv_Ovf_I2: + case OperationCode.Conv_Ovf_I2_Un: + case OperationCode.Ldind_I2: + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt16; + this.stack.Push(instruction); + break; + case OperationCode.Conv_I4: + case OperationCode.Conv_Ovf_I4: + case OperationCode.Conv_Ovf_I4_Un: + case OperationCode.Ldind_I4: + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt32; + this.stack.Push(instruction); + break; + case OperationCode.Conv_I8: + case OperationCode.Conv_Ovf_I8: + case OperationCode.Conv_Ovf_I8_Un: + case OperationCode.Ldind_I8: + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt64; + this.stack.Push(instruction); + break; + case OperationCode.Conv_Ovf_U: + case OperationCode.Conv_Ovf_U_Un: + case OperationCode.Conv_U: + case OperationCode.Ldlen: + this.stack.Pop(); + instruction.Type = this.platformType.SystemUIntPtr; + this.stack.Push(instruction); + break; + case OperationCode.Conv_Ovf_U1: + case OperationCode.Conv_Ovf_U1_Un: + case OperationCode.Conv_U1: + case OperationCode.Ldind_U1: + this.stack.Pop(); + instruction.Type = this.platformType.SystemUInt8; + this.stack.Push(instruction); + break; + case OperationCode.Conv_Ovf_U2: + case OperationCode.Conv_Ovf_U2_Un: + case OperationCode.Conv_U2: + case OperationCode.Ldind_U2: + this.stack.Pop(); + instruction.Type = this.platformType.SystemUInt16; + this.stack.Push(instruction); + break; + case OperationCode.Conv_Ovf_U4: + case OperationCode.Conv_Ovf_U4_Un: + case OperationCode.Conv_U4: + case OperationCode.Ldind_U4: + this.stack.Pop(); + instruction.Type = this.platformType.SystemUInt32; + this.stack.Push(instruction); + break; + case OperationCode.Conv_Ovf_U8: + case OperationCode.Conv_Ovf_U8_Un: + case OperationCode.Conv_U8: + this.stack.Pop(); + instruction.Type = this.platformType.SystemUInt64; + this.stack.Push(instruction); + break; + case OperationCode.Conv_R_Un: + this.stack.Pop(); + Contract.Assume(instruction.Operand1 != null); //Assumed because of the informal specification of the DataFlowInferencer + if (TypeHelper.SizeOfType(instruction.Operand1.Type) < 4) + instruction.Type = this.platformType.SystemFloat32; + else + instruction.Type = this.platformType.SystemFloat64; + this.stack.Push(instruction); + break; + case OperationCode.Conv_R4: + case OperationCode.Ldind_R4: + this.stack.Pop(); + instruction.Type = this.platformType.SystemFloat32; + this.stack.Push(instruction); + break; + case OperationCode.Conv_R8: + case OperationCode.Ldind_R8: + this.stack.Pop(); + instruction.Type = this.platformType.SystemFloat64; + this.stack.Push(instruction); + break; + case OperationCode.Cpblk: + case OperationCode.Initblk: + case OperationCode.Stelem: + case OperationCode.Stelem_I: + case OperationCode.Stelem_I1: + case OperationCode.Stelem_I2: + case OperationCode.Stelem_I4: + case OperationCode.Stelem_I8: + case OperationCode.Stelem_R4: + case OperationCode.Stelem_R8: + case OperationCode.Stelem_Ref: + this.stack.Pop(); + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemVoid; + break; + case OperationCode.Dup: + Contract.Assume(instruction.Operand1 != null); //Assumed because of the informal specification of the DataFlowInferencer + instruction.Type = instruction.Operand1.Type; + this.stack.Push(instruction); + break; + case OperationCode.Ldarg: + case OperationCode.Ldarg_0: + case OperationCode.Ldarg_1: + case OperationCode.Ldarg_2: + case OperationCode.Ldarg_3: + case OperationCode.Ldarg_S: + var parameter = instruction.Operation.Value as IParameterDefinition; + if (parameter == null) { //this arg + var containingType = this.cfg.MethodBody.MethodDefinition.ContainingTypeDefinition; + var namedType = containingType as INamedTypeDefinition; + if (namedType != null && namedType.IsGeneric) + instruction.Type = namedType.InstanceType; + else + instruction.Type = containingType; + if (instruction.Type.IsValueType) + instruction.Type = ManagedPointerType.GetManagedPointerType(instruction.Type, this.internFactory); + } else { + instruction.Type = parameter.Type; + if (parameter.IsByReference) + instruction.Type = ManagedPointerType.GetManagedPointerType(instruction.Type, this.internFactory); + } + this.stack.Push(instruction); + break; + case OperationCode.Ldarga: + case OperationCode.Ldarga_S: + parameter = instruction.Operation.Value as IParameterDefinition; + if (parameter == null) { //this arg + instruction.Type = ManagedPointerType.GetManagedPointerType(this.cfg.MethodBody.MethodDefinition.ContainingType, this.internFactory); + } else { + instruction.Type = ManagedPointerType.GetManagedPointerType(parameter.Type, this.internFactory); + if (parameter.IsByReference) + instruction.Type = ManagedPointerType.GetManagedPointerType(instruction.Type, this.internFactory); + } + this.stack.Push(instruction); + break; + case OperationCode.Ldc_I4: + case OperationCode.Ldc_I4_0: + case OperationCode.Ldc_I4_1: + case OperationCode.Ldc_I4_2: + case OperationCode.Ldc_I4_3: + case OperationCode.Ldc_I4_4: + case OperationCode.Ldc_I4_5: + case OperationCode.Ldc_I4_6: + case OperationCode.Ldc_I4_7: + case OperationCode.Ldc_I4_8: + case OperationCode.Ldc_I4_M1: + case OperationCode.Ldc_I4_S: + instruction.Type = this.platformType.SystemInt32; + this.stack.Push(instruction); + break; + case OperationCode.Ldc_I8: + instruction.Type = this.platformType.SystemInt64; + this.stack.Push(instruction); + break; + case OperationCode.Ldc_R4: + instruction.Type = this.platformType.SystemFloat32; + this.stack.Push(instruction); + break; + case OperationCode.Ldc_R8: + instruction.Type = this.platformType.SystemFloat64; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem: + this.stack.Pop(); + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is ITypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = (ITypeReference)instruction.Operation.Value; + this.stack.Push(instruction); + break; + case OperationCode.Ldobj: + case OperationCode.Unbox_Any: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is ITypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = (ITypeReference)instruction.Operation.Value; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_I: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemIntPtr; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_I1: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt8; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_I2: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt16; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_I4: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt32; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_I8: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemInt64; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_Ref: + this.stack.Pop(); + this.stack.Pop(); + Contract.Assume(instruction.Operand1 != null); //Assumed because of the informal specification of the DataFlowInferencer + arrayType = instruction.Operand1.Type as IArrayTypeReference; + if (arrayType != null) + instruction.Type = arrayType.ElementType; + else //Should only get here if the IL is bad. + instruction.Type = this.platformType.SystemObject; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_R4: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemFloat32; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_R8: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemFloat64; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_U1: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemUInt8; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_U2: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemUInt16; + this.stack.Push(instruction); + break; + case OperationCode.Ldelem_U4: + this.stack.Pop(); + this.stack.Pop(); + instruction.Type = this.platformType.SystemUInt32; + this.stack.Push(instruction); + break; + case OperationCode.Ldelema: + this.stack.Pop(); + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is ITypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = ManagedPointerType.GetManagedPointerType((ITypeReference)instruction.Operation.Value, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ldfld: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is IFieldReference); //This is an informally specified property of the Metadata model. + instruction.Type = ((IFieldReference)instruction.Operation.Value).Type; + this.stack.Push(instruction); + break; + case OperationCode.Ldsfld: + Contract.Assume(instruction.Operation.Value is IFieldReference); //This is an informally specified property of the Metadata model. + instruction.Type = ((IFieldReference)instruction.Operation.Value).Type; + this.stack.Push(instruction); + break; + case OperationCode.Ldflda: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is IFieldReference); //This is an informally specified property of the Metadata model. + instruction.Type = ManagedPointerType.GetManagedPointerType(((IFieldReference)instruction.Operation.Value).Type, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ldsflda: + Contract.Assume(instruction.Operation.Value is IFieldReference); //This is an informally specified property of the Metadata model. + instruction.Type = ManagedPointerType.GetManagedPointerType(((IFieldReference)instruction.Operation.Value).Type, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ldftn: + Contract.Assume(instruction.Operation.Value is IMethodReference); //This is an informally specified property of the Metadata model. + instruction.Type = new FunctionPointerType((IMethodReference)instruction.Operation.Value, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ldvirtftn: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is IMethodReference); //This is an informally specified property of the Metadata model. + instruction.Type = new FunctionPointerType((IMethodReference)instruction.Operation.Value, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ldind_Ref: + this.stack.Pop(); + Contract.Assume(instruction.Operand1 != null); //Assumed because of the informal specification of the DataFlowInferencer + var ptr = instruction.Operand1.Type as IPointerTypeReference; + if (ptr != null) + instruction.Type = ptr.TargetType; + else { + Contract.Assume(instruction.Operand1.Type is IManagedPointerTypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = ((IManagedPointerTypeReference)instruction.Operand1.Type).TargetType; + } + this.stack.Push(instruction); + break; + case OperationCode.Ldloc: + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + var local = instruction.Operation.Value as ILocalDefinition; + Contract.Assume(local != null); //This is an informally specified property of the Metadata model. + instruction.Type = local.Type; + if (local.IsReference) + instruction.Type = ManagedPointerType.GetManagedPointerType(instruction.Type, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ldloca: + case OperationCode.Ldloca_S: + local = instruction.Operation.Value as ILocalDefinition; + Contract.Assume(local != null); //This is an informally specified property of the Metadata model. + instruction.Type = ManagedPointerType.GetManagedPointerType(local.Type, this.internFactory); + if (local.IsReference) + instruction.Type = ManagedPointerType.GetManagedPointerType(instruction.Type, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ldnull: + instruction.Type = this.platformType.SystemObject; + this.stack.Push(instruction); + break; + case OperationCode.Ldstr: + instruction.Type = this.platformType.SystemString; + this.stack.Push(instruction); + break; + case OperationCode.Ldtoken: + if (instruction.Operation.Value is IMethodReference) + instruction.Type = this.platformType.SystemRuntimeMethodHandle; + else if (instruction.Operation.Value is ITypeReference) + instruction.Type = this.platformType.SystemRuntimeTypeHandle; + else if (instruction.Operation.Value is IFieldReference) + instruction.Type = this.platformType.SystemRuntimeFieldHandle; + else { + //this should never happen in well formed IL. + instruction.Type = this.platformType.SystemVoid; + } + this.stack.Push(instruction); + break; + case OperationCode.Leave: + case OperationCode.Leave_S: + this.stack.Clear(); + break; + case OperationCode.Mkrefany: + this.stack.Pop(); + instruction.Type = this.platformType.SystemTypedReference; + this.stack.Push(instruction); + break; + case OperationCode.Newarr: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is ITypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = (ITypeReference)instruction.Operation.Value; + this.stack.Push(instruction); + break; + case OperationCode.Newobj: + var constructorReference = instruction.Operation.Value as IMethodReference; + Contract.Assume(constructorReference != null); //This is an informally specified property of the Metadata model. + for (var i = constructorReference.ParameterCount; i > 0; i--) + this.stack.Pop(); + instruction.Type = constructorReference.ContainingType; + this.stack.Push(instruction); + break; + case OperationCode.Refanytype: + this.stack.Pop(); + instruction.Type = this.platformType.SystemRuntimeTypeHandle; + this.stack.Push(instruction); + break; + case OperationCode.Refanyval: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is ITypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = ManagedPointerType.GetManagedPointerType((ITypeReference)instruction.Operation.Value, this.internFactory); + this.stack.Push(instruction); + break; + case OperationCode.Ret: + if (this.codeIsUnreachable && this.stack.Top < 0) break; + if (this.cfg.MethodBody.MethodDefinition.Type.TypeCode != PrimitiveTypeCode.Void) + instruction.Operand1 = this.stack.Pop(); + instruction.Type = this.platformType.SystemVoid; + break; + case OperationCode.Shl: + case OperationCode.Shr: + case OperationCode.Shr_Un: + this.stack.Pop(); + this.stack.Pop(); + Contract.Assume(instruction.Operand1 != null); //Assumed because of the informal specification of the DataFlowInferencer + instruction.Type = instruction.Operand1.Type; + this.stack.Push(instruction); + break; + case OperationCode.Sizeof: + instruction.Type = this.platformType.SystemUInt32; + this.stack.Push(instruction); + break; + case OperationCode.Unbox: + this.stack.Pop(); + Contract.Assume(instruction.Operation.Value is ITypeReference); //This is an informally specified property of the Metadata model. + instruction.Type = ManagedPointerType.GetManagedPointerType((ITypeReference)instruction.Operation.Value, this.internFactory); + this.stack.Push(instruction); + break; + default: + instruction.Type = this.platformType.SystemVoid; + break; + } + } + + private ITypeReference GetUnsignedBinaryNumericOperationType(Instruction instruction) { + Contract.Requires(instruction != null); + return TypeHelper.UnsignedEquivalent(this.GetBinaryNumericOperationType(instruction)); + } + + private ITypeReference GetBinaryNumericOperationType(Instruction instruction) { + Contract.Requires(instruction != null); + Contract.Ensures(Contract.Result() != null); + + var leftOperand = instruction.Operand1; + var rightOperand = (Instruction)instruction.Operand2; + Contract.Assume(leftOperand != null); //Assumed because of the informal specification of the DataFlowInferencer + PrimitiveTypeCode leftTypeCode = leftOperand.Type.TypeCode; + PrimitiveTypeCode rightTypeCode = rightOperand.Type.TypeCode; + switch (leftTypeCode) { + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt8: + switch (rightTypeCode) { + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt8: + return this.platformType.SystemUInt32; + + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + return this.platformType.SystemUInt32; //code generators will tend to make both operands be of the same type. Assume this happened because the right operand is a polymorphic constant. + + //The cases below are not expected to happen in practice + case PrimitiveTypeCode.UInt64: + case PrimitiveTypeCode.Int64: + return this.platformType.SystemUInt64; + + case PrimitiveTypeCode.UIntPtr: + case PrimitiveTypeCode.IntPtr: + return this.platformType.SystemUIntPtr; + + case PrimitiveTypeCode.Float32: + return this.platformType.SystemFloat32; + + case PrimitiveTypeCode.Float64: + return this.platformType.SystemFloat64; + + default: + return rightOperand.Type; //code generators will tend to make both operands be of the same type. Assume this happened because the right operand is an enum. + } + + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + switch (rightTypeCode) { + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt8: + return this.platformType.SystemUInt32; //code generators will tend to make both operands be of the same type. Assume this happened because the left operand is a polymorphic constant. + + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + return this.platformType.SystemInt32; + + //The cases below are not expected to happen in practice + case PrimitiveTypeCode.UInt64: + return this.platformType.SystemUInt64; + + case PrimitiveTypeCode.Int64: + return this.platformType.SystemInt64; + + case PrimitiveTypeCode.UIntPtr: + return this.platformType.SystemUIntPtr; + + case PrimitiveTypeCode.IntPtr: + return this.platformType.SystemIntPtr; + + case PrimitiveTypeCode.Float32: + return this.platformType.SystemFloat32; + + case PrimitiveTypeCode.Float64: + return this.platformType.SystemFloat64; + + default: + return rightOperand.Type; //code generators will tend to make both operands be of the same type. Assume this happened because the right operand is an enum. + } + + case PrimitiveTypeCode.UInt64: + switch (rightTypeCode) { + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt8: + case PrimitiveTypeCode.UInt64: + return this.platformType.SystemUInt64; + + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + case PrimitiveTypeCode.Int64: + return this.platformType.SystemUInt64; //code generators will tend to make both operands be of the same type. Assume this happened because the right operand is a polymorphic constant. + + case PrimitiveTypeCode.UIntPtr: + return this.platformType.SystemUIntPtr; + + case PrimitiveTypeCode.IntPtr: + return this.platformType.SystemIntPtr; + + case PrimitiveTypeCode.Float32: + return this.platformType.SystemFloat32; + + case PrimitiveTypeCode.Float64: + return this.platformType.SystemFloat64; + + default: + return rightOperand.Type; //code generators will tend to make both operands be of the same type. Assume this happened because the right operand is an enum. + } + + case PrimitiveTypeCode.Int64: + switch (rightTypeCode) { + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt8: + case PrimitiveTypeCode.UInt64: + return this.platformType.SystemUInt64; //code generators will tend to make both operands be of the same type. Assume this happened because the left operand is a polymorphic constant. + + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + case PrimitiveTypeCode.Int64: + return this.platformType.SystemInt64; + + case PrimitiveTypeCode.UIntPtr: + case PrimitiveTypeCode.IntPtr: + return this.platformType.SystemIntPtr; + + case PrimitiveTypeCode.Float32: + return this.platformType.SystemFloat32; + + case PrimitiveTypeCode.Float64: + return this.platformType.SystemFloat64; + + default: + return rightOperand.Type; //code generators will tend to make both operands be of the same type. Assume this happened because the right operand is an enum. + } + + case PrimitiveTypeCode.UIntPtr: + switch (rightTypeCode) { + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt8: + case PrimitiveTypeCode.UInt64: + case PrimitiveTypeCode.UIntPtr: + return this.platformType.SystemUIntPtr; + + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + case PrimitiveTypeCode.Int64: + case PrimitiveTypeCode.IntPtr: + return this.platformType.SystemUIntPtr; + + case PrimitiveTypeCode.Float32: + return this.platformType.SystemFloat32; + + case PrimitiveTypeCode.Float64: + return this.platformType.SystemFloat64; + + case PrimitiveTypeCode.Pointer: + case PrimitiveTypeCode.Reference: + return rightOperand.Type; + + default: + Contract.Assume(false); + return Dummy.TypeReference; + } + + case PrimitiveTypeCode.IntPtr: + switch (rightTypeCode) { + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt8: + case PrimitiveTypeCode.UInt64: + case PrimitiveTypeCode.UIntPtr: + return this.platformType.SystemUIntPtr; + + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + case PrimitiveTypeCode.Int64: + case PrimitiveTypeCode.IntPtr: + return this.platformType.SystemIntPtr; + + case PrimitiveTypeCode.Float32: + return this.platformType.SystemFloat32; + + case PrimitiveTypeCode.Float64: + return this.platformType.SystemFloat64; + + case PrimitiveTypeCode.Pointer: + case PrimitiveTypeCode.Reference: + return rightOperand.Type; + + default: + Contract.Assume(false); + return Dummy.TypeReference; + } + + case PrimitiveTypeCode.Float32: + case PrimitiveTypeCode.Float64: + return rightOperand.Type; + + case PrimitiveTypeCode.Pointer: + case PrimitiveTypeCode.Reference: + switch (rightTypeCode) { + case PrimitiveTypeCode.Pointer: + case PrimitiveTypeCode.Reference: + return this.platformType.SystemUIntPtr; + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + case PrimitiveTypeCode.Int64: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt8: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt64: + case PrimitiveTypeCode.IntPtr: + case PrimitiveTypeCode.UIntPtr: + return leftOperand.Type; + case PrimitiveTypeCode.NotPrimitive: + return leftOperand.Type; //assume rh type is an enum. + default: + Contract.Assume(false); + return Dummy.TypeReference; + } + + default: + switch (rightTypeCode) { + case PrimitiveTypeCode.Int8: + case PrimitiveTypeCode.Int16: + case PrimitiveTypeCode.Int32: + case PrimitiveTypeCode.Int64: + case PrimitiveTypeCode.Boolean: + case PrimitiveTypeCode.Char: + case PrimitiveTypeCode.UInt8: + case PrimitiveTypeCode.UInt16: + case PrimitiveTypeCode.UInt32: + case PrimitiveTypeCode.UInt64: + //assume that the left operand has an enum type. + return leftOperand.Type; + } + return leftOperand.Type; //assume they are both enums + } + } + + } + +} \ No newline at end of file diff --git a/Metadata/Sources/ILGenerator/ILGenerator.cs b/Metadata/Sources/ILGenerator/ILGenerator.cs new file mode 100644 index 0000000..75322a1 --- /dev/null +++ b/Metadata/Sources/ILGenerator/ILGenerator.cs @@ -0,0 +1,1548 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics.Contracts; + +namespace Microsoft.Cci { + using Microsoft.Cci.ILGeneratorImplementation; + using System.Diagnostics.Contracts; + + /// + /// Generates Microsoft intermediate language (MSIL) instructions. + /// + /// + /// A typical use of the ILGenerator class is to produce the values of the properties of an IMethodBody object. + /// This could be a SourceMethodBody from the mutable Code Model or an instance of ILGeneratorMethodBody (or perhaps a derived class). + /// In the former case, the ILGenerator is a part of the private state of an instance of CodeModelToILConverter (or a derived class) that + /// is usually part of a code model mutator. In the latter case, the ILGenerator may have been used to generate a method body for which no + /// Code Model has been built. + /// + public sealed class ILGenerator { + + List handlers = new List(); + IMetadataHost host; + IMethodDefinition method; + ILocation location = Dummy.Location; + ILocation expressionLocation; + uint offset; + List operations = new List(); + List/*?*/ iteratorScopes; + List scopes = new List(); + Stack scopeStack = new Stack(); + Stack tryBodyStack = new Stack(); + List/*?*/ synchronizationPoints; + IMethodDefinition/*?*/ asyncMethodDefinition; + + /// + /// Allocates an object that helps with the generation of Microsoft intermediate language (MSIL) instructions corresponding to a method body. + /// + /// Provides a standard abstraction over the applications that host components that provide or consume objects from the metadata model. + /// The method to generate MSIL for. + /// The async method for which this generator will generate the "MoveNext" method of its state class. + public ILGenerator(IMetadataHost host, IMethodDefinition methodDefinition, IMethodDefinition/*?*/ asyncMethodDefinition = null) { + Contract.Requires(host != null); + Contract.Requires(methodDefinition != null); + + this.host = host; + this.method = methodDefinition; + this.asyncMethodDefinition = asyncMethodDefinition; + } + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.host != null); + Contract.Invariant(this.method != null); + Contract.Invariant(this.location != null); + Contract.Invariant(this.handlers != null); + Contract.Invariant(this.operations != null); + Contract.Invariant(this.scopes != null); + Contract.Invariant(this.scopeStack != null); + Contract.Invariant(this.tryBodyStack != null); + } + + /// + /// Adds the given local constant to the current lexical scope. + /// + /// The local constant to add to the current scope. + public void AddConstantToCurrentScope(ILocalDefinition local) { + Contract.Requires(local != null); + //Contract.Assume(local.MethodDefinition == this.Method); + + if (this.scopeStack.Count == 0) this.BeginScope(); + var topScope = this.scopeStack.Peek(); + Contract.Assume(topScope != null); + Contract.Assume(topScope.constants != null); + topScope.constants.Add(local); + } + + /// + /// Adds the given local variable to the current lexical scope. + /// + /// The local variable to add to the current scope. + public void AddVariableToCurrentScope(ILocalDefinition local) { + Contract.Requires(local != null); + //Contract.Assume(local.MethodDefinition == this.Method); + + if (this.scopeStack.Count == 0) this.BeginScope(); + var topScope = this.scopeStack.Peek(); + Contract.Assume(topScope != null); + Contract.Assume(topScope.locals != null); + topScope.locals.Add(local); + } + + /// + /// Adds an exception handler entry to the generated body. This intended for IL rewriting scenarios and should not be used in conjunction with methods such as BeginCatchBlock, + /// which are intended for CodeModel to IL generation scenarios. The calls to AddExceptionHandler should result in a list of handlers that + /// satisfies CLI rules with respect to ordering and nesting. + /// + /// The kind of handler being added. + /// The type of exception this handler will handle (may be Dummy.TypeReference). + /// A label identifying the first instruction in the try body. + /// A label identifying the first instruction following the try body. + /// A label identifying the first instruction in the handler body. + /// A label identifying the first instruction following the handler body. + /// A label identifying the first instruction of the filter decision block. May be null. + public void AddExceptionHandlerInformation(HandlerKind kind, ITypeReference exceptionType, ILGeneratorLabel tryStart, ILGeneratorLabel tryEnd, + ILGeneratorLabel handlerStart, ILGeneratorLabel handlerEnd, ILGeneratorLabel/*?*/ filterStart) { + Contract.Requires(exceptionType != null); + Contract.Requires(tryStart != null); + Contract.Requires(tryEnd != null); + Contract.Requires(handlerStart != null); + Contract.Requires(handlerEnd != null); + + var handler = new ExceptionHandler() { + Kind = kind, ExceptionType = exceptionType, TryStart = tryStart, TryEnd = tryEnd, + HandlerStart = handlerStart, HandlerEnd = handlerEnd, FilterDecisionStart = filterStart + }; + this.handlers.Add(handler); + } + + /// + /// Performs one or more extra passes over the list of operations, changing long branches to short if possible and short branches to + /// long branches if necessary. + /// + /// If any long branches in this.operations could have been short, they are adjusted to be short. + /// This can result in an updated version of this.operations where some branches that had to be long in the previous + /// version can now be short as well. Consequently, the adjustment process iterates until no further changes are possible. + /// Note that all decisions are made based on the offsets at the start of an iteration. + public void AdjustBranchSizesToBestFit(bool eliminateBranchesToNext = false) { + int adjustment; + uint numberOfAdjustments; + do { + adjustment = 0; + numberOfAdjustments = 0; + for (int i = 0, n = this.operations.Count; i < n; i++) { + Operation operation = this.operations[i]; + Contract.Assume(operation != null); + uint oldOffset = operation.offset; + uint newOffset = (uint)(((int)oldOffset) + adjustment); + operation.offset = newOffset; + ILGeneratorLabel/*?*/ label = operation.value as ILGeneratorLabel; + if (label != null) { + if (operation.OperationCode == OperationCode.Invalid) { + //Dummy operation that serves as label definition. + label.Offset = operation.offset; + continue; + } + //For backward branches, this test will compare the new offset of the label with the old offset of the current + //instruction. This is OK, because the new offset of the label will be less than or equal to its old offset. + bool isForwardBranch = label.Offset >= oldOffset; + // Short offsets are calculated from the start of the instruction *after* the current instruction, which takes up 2 bytes + // (1 for the opcode and 1 for the signed byte). + bool shortOffsetOk = isForwardBranch ? label.Offset-oldOffset <= 129 : newOffset-label.Offset <= 126; + OperationCode oldOpCode = operation.OperationCode; + if (shortOffsetOk) { + operation.operationCode = ShortVersionOf(operation.OperationCode); + if (operation.operationCode != oldOpCode) { numberOfAdjustments++; adjustment -= 3; } + } else { + if (operation.operationCode != LongVersionOf(operation.operationCode)) + throw new InvalidOperationException(); //A short branch was specified for an offset that is long. + //The test for isForwardBranch depends on label offsets only decreasing, so it is not an option to replace the short branch with a long one. + } + if (eliminateBranchesToNext && operation.OperationCode == OperationCode.Br_S && operation.offset+2 == label.Offset) { + //eliminate branch to the next instruction + operation.operationCode = OperationCode.Invalid; + numberOfAdjustments++; adjustment -= 2; + } + } + } + } while (numberOfAdjustments > 0); + } + + /// + /// Begins a catch block. + /// + /// The Type object that represents the exception. + public void BeginCatchBlock(ITypeReference exceptionType) { + Contract.Requires(exceptionType != null); + Contract.Requires(this.InTryBody); + + ExceptionHandler handler = this.BeginHandler(HandlerKind.Catch); + handler.ExceptionType = exceptionType; + } + + /// + /// Begins an exception block for a filtered exception. See also BeginFilterBody. + /// + public void BeginFilterBlock() { + Contract.Requires(this.InTryBody); + + ExceptionHandler handler = this.BeginHandler(HandlerKind.Filter); + handler.FilterDecisionStart = handler.HandlerStart; + } + + /// + /// Begins the part of a filter handler that is invoked on the second pass if the filter condition returns true on the first pass. + /// + public void BeginFilterBody() { + Contract.Requires(this.InTryBody); + + this.Emit(OperationCode.Endfilter); + ILGeneratorLabel handlerStart = new ILGeneratorLabel(false); + this.MarkLabel(handlerStart); + Contract.Assume(this.handlers.Count > 0); + var handler = this.handlers[this.handlers.Count-1]; + Contract.Assume(handler != null); + handler.HandlerStart = handlerStart; + } + + private ExceptionHandler BeginHandler(HandlerKind kind) { + Contract.Requires(this.InTryBody); + Contract.Ensures(Contract.Result() != null); + + ILGeneratorLabel handlerStart = new ILGeneratorLabel(false); + this.MarkLabel(handlerStart); + TryBody currentTryBody = this.tryBodyStack.Peek(); + Contract.Assume(currentTryBody != null); + ExceptionHandler handler = new ExceptionHandler(kind, currentTryBody, handlerStart); + if (currentTryBody.end == null) + currentTryBody.end = handlerStart; + else if (this.handlers.Count > 0) { + for (int i = this.handlers.Count-1; i >= 0; i--) { + var handleri = this.handlers[i]; + Contract.Assume(handleri != null); + if (handleri.HandlerEnd == null) { + handleri.HandlerEnd = handlerStart; + break; + } + } + } + this.handlers.Add(handler); + return handler; + } + + /// + /// Begins the body of a try statement. + /// + public void BeginTryBody() { + ILGeneratorLabel tryBodyStart = new ILGeneratorLabel(false); + this.MarkLabel(tryBodyStart); + this.tryBodyStack.Push(new TryBody(tryBodyStart)); + } + + /// + /// Begins an exception fault block in the Microsoft intermediate language (MSIL) stream. + /// + public void BeginFaultBlock() { + Contract.Requires(this.InTryBody); + + this.BeginHandler(HandlerKind.Fault); + } + + /// + /// Begins a finally block in the Microsoft intermediate language (MSIL) instruction stream. + /// + public void BeginFinallyBlock() { + Contract.Requires(this.InTryBody); + + this.BeginHandler(HandlerKind.Finally); + } + + /// + /// Begins a lexical scope. + /// + public void BeginScope() { + var startLabel = new ILGeneratorLabel(); + this.MarkLabel(startLabel); + ILGeneratorScope scope = new ILGeneratorScope(startLabel, this.host.NameTable, this.method); + this.scopeStack.Push(scope); + this.scopes.Add(scope); + } + + /// + /// Begins a lexical scope. + /// + public void BeginScope(uint numberOfIteratorLocalsInScope) { + var startLabel = new ILGeneratorLabel(); + this.MarkLabel(startLabel); + ILGeneratorScope scope = new ILGeneratorScope(startLabel, this.host.NameTable, this.method); + this.scopeStack.Push(scope); + this.scopes.Add(scope); + if (numberOfIteratorLocalsInScope == 0) return; + if (this.iteratorScopes == null) this.iteratorScopes = new List(); + while (numberOfIteratorLocalsInScope-- > 0) { + this.iteratorScopes.Add(scope); + } + } + + /// + /// The offset in the IL stream where the next instruction will be emitted. + /// + public uint CurrentOffset { + get { return this.offset; } + } + + /// + /// Puts the specified instruction onto the stream of instructions. + /// + /// The Intermediate Language (IL) instruction to be put onto the stream. + public void Emit(OperationCode opcode) { + var loc = this.GetCurrentSequencePoint(); + if (opcode == OperationCode.Ret) { + int i = this.operations.Count; + while (--i >= 0) { + Operation previousOp = this.operations[i]; + Contract.Assume(previousOp != null); + if (previousOp.OperationCode != OperationCode.Invalid) break; + Contract.Assume(previousOp.value is ILGeneratorLabel); + ILGeneratorLabel labelOfBranch = (ILGeneratorLabel)previousOp.value; + labelOfBranch.locationOfReturnInstruction = loc; + } + } + this.operations.Add(new Operation(opcode, this.offset, loc, null)); + this.offset += SizeOfOperationCode(opcode); + } + + /// + /// Calls a type specific overload of Emit, based on the runtime type of the given object. Use this when copying IL operations. + /// + /// The Intermediate Language (IL) instruction to be put onto the stream. + /// An argument that parameterizes the IL instruction at compile time (not a runtime operand for the instruction). + [ContractVerification(false)] + public void Emit(OperationCode opcode, object value) { + // For the following one-byte opcodes, the reader supplies a value to make it easier for clients. + // But that messes up the dispatch in the succeeding switch statement. + switch (opcode) { + case OperationCode.Ldc_I4_0: + case OperationCode.Ldc_I4_1: + case OperationCode.Ldc_I4_2: + case OperationCode.Ldc_I4_3: + case OperationCode.Ldc_I4_4: + case OperationCode.Ldc_I4_5: + case OperationCode.Ldc_I4_6: + case OperationCode.Ldc_I4_7: + case OperationCode.Ldc_I4_8: + case OperationCode.Ldc_I4_M1: + this.Emit(opcode); + return; + } + switch (System.Convert.GetTypeCode(value)) { + case TypeCode.Byte: this.Emit(opcode, (byte)value); break; + case TypeCode.Double: this.Emit(opcode, (double)value); break; + case TypeCode.Single: this.Emit(opcode, (float)value); break; + case TypeCode.Int32: this.Emit(opcode, (int)value); break; + case TypeCode.Int64: this.Emit(opcode, (long)value); break; + case TypeCode.SByte: this.Emit(opcode, (sbyte)value); break; + case TypeCode.Int16: this.Emit(opcode, (short)value); break; + case TypeCode.String: this.Emit(opcode, (string)value); break; + case TypeCode.Empty: this.Emit(opcode); break; + + default: + var fieldReference = value as IFieldReference; + if (fieldReference != null) { + this.Emit(opcode, fieldReference); + break; + } + var label = value as ILGeneratorLabel; + if (label != null) { + this.Emit(opcode, label); + break; + } + var labels = value as ILGeneratorLabel[]; + if (labels != null) { + this.Emit(opcode, labels); + break; + } + var local = value as ILocalDefinition; + if (local != null) { + this.Emit(opcode, local); + break; + } + var meth = value as IMethodReference; + if (meth != null) { + this.Emit(opcode, meth); + break; + } + var param = value as IParameterDefinition; + if (param != null) { + this.Emit(opcode, param); + break; + } + var sig = value as ISignature; + if (sig != null) { + this.Emit(opcode, sig); + break; + } + var type = value as ITypeReference; + if (type != null) { + this.Emit(opcode, type); + break; + } + throw new InvalidOperationException(); + } + } + + /// + /// Puts the specified instruction and unsigned 8 bit integer argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The unsigned 8 bit integer argument pushed onto the stream immediately after the instruction. + public void Emit(OperationCode opcode, byte arg) { + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), arg)); + this.offset += SizeOfOperationCode(opcode)+1; + } + + /// + /// Puts the specified instruction and 64 bit floating point argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The 64 bit floating point argument pushed onto the stream immediately after the instruction. + public void Emit(OperationCode opcode, double arg) { + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), arg)); + this.offset += SizeOfOperationCode(opcode)+8; + } + + /// + /// Puts the specified instruction and a field reference onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// A reference to a field. + public void Emit(OperationCode opcode, IFieldReference field) { + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), field)); + this.offset += SizeOfOperationCode(opcode)+4; + } + + /// + /// Puts the specified instruction and 32 bit floating point argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The 32 bit floating point argument pushed onto the stream immediately after the instruction. + public void Emit(OperationCode opcode, float arg) { + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), arg)); + this.offset += SizeOfOperationCode(opcode)+4; + } + + /// + /// Puts the specified instruction and 32 bit integer argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The 32 bit integer argument pushed onto the stream immediately after the instruction. + public void Emit(OperationCode opcode, int arg) { + if (opcode == OperationCode.Ldc_I4_S) { + sbyte b = (sbyte)arg; + if (b == arg) { + this.Emit(opcode, b); + return; + } + opcode = OperationCode.Ldc_I4; + } + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), arg)); + this.offset += SizeOfOperationCode(opcode)+4; + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream and leaves space to include a label when fixes are done. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The label to which to branch from this location. + public void Emit(OperationCode opcode, ILGeneratorLabel label) { + Contract.Requires(label != null); + + if (opcode == OperationCode.Br && this.operations.Count > 0) { + Operation previousOp = this.operations[this.operations.Count-1]; + Contract.Assume(previousOp != null); + if (previousOp.OperationCode == OperationCode.Invalid) { + Contract.Assume(previousOp.value is ILGeneratorLabel); + ILGeneratorLabel labelOfBranch = (ILGeneratorLabel)previousOp.value; + if (labelOfBranch.mayAlias) labelOfBranch.alias = label; + } + } + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), label)); + this.offset += SizeOfOperationCode(opcode)+SizeOfOffset(opcode); + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream and leaves space to include an array of labels when fixes are done. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// An array of labels to which to branch from this location. + public void Emit(OperationCode opcode, params ILGeneratorLabel[] labels) { + Contract.Requires(labels != null); + + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), labels)); + this.offset += SizeOfOperationCode(opcode)+4*((uint)labels.Length+1); + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the index of the given local variable. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// A local variable. + public void Emit(OperationCode opcode, ILocalDefinition local) { + Contract.Requires(local != null); + + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), local)); + this.offset += SizeOfOperationCode(opcode); + if (opcode == OperationCode.Ldloc_S || opcode == OperationCode.Ldloca_S || opcode == OperationCode.Stloc_S) + this.offset += 1; + else if (opcode == OperationCode.Ldloc || opcode == OperationCode.Ldloca || opcode == OperationCode.Stloc) + this.offset += 2; + } + + /// + /// Puts the specified instruction and 64 bit integer argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The 64 bit integer argument pushed onto the stream immediately after the instruction. + public void Emit(OperationCode opcode, long arg) { + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), arg)); + this.offset += SizeOfOperationCode(opcode)+8; + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by a token for the given method reference. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// A reference to a method. Generic methods can only be referenced via instances. + public void Emit(OperationCode opcode, IMethodReference meth) { + Contract.Requires(meth != null); + Contract.Requires(meth.GenericParameterCount == 0 || meth is IGenericMethodInstanceReference); + + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), meth)); + this.offset += SizeOfOperationCode(opcode)+4; + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the index of the given local variable. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// A parameter definition. May be null. + public void Emit(OperationCode opcode, IParameterDefinition/*?*/ parameter) { + //Contract.Requires(parameter == null || parameter.ContainingSignature == this.Method); + + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), parameter)); + this.offset += SizeOfOperationCode(opcode); + if (opcode == OperationCode.Ldarg_S || opcode == OperationCode.Ldarga_S || opcode == OperationCode.Starg_S) + this.offset += 1; + else if (opcode == OperationCode.Ldarg || opcode == OperationCode.Ldarga || opcode == OperationCode.Starg) + this.offset += 2; + } + + /// + /// Puts the specified instruction and signed 8 bit integer argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The signed 8 bit integer argument pushed onto the stream immediately after the instruction. + public void Emit(OperationCode opcode, sbyte arg) { + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), (int)arg)); + this.offset += SizeOfOperationCode(opcode)+1; + } + + /// + /// Puts the specified instruction and signed 16 bit integer argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The signed 8 bit integer argument pushed onto the stream immediately after the instruction. + public void Emit(OperationCode opcode, short arg) { + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), arg)); + this.offset += SizeOfOperationCode(opcode)+2; + } + + /// + /// Puts the specified instruction and a token for the given signature onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The signature of the method or function pointer to call. Can include information about extra arguments. + public void Emit(OperationCode opcode, ISignature signature) { + Contract.Requires(signature != null); + + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), signature)); + this.offset += SizeOfOperationCode(opcode)+4; + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the a token for the given string. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The String to be emitted. + public void Emit(OperationCode opcode, string str) { + Contract.Requires(str != null); + + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), str)); + this.offset += SizeOfOperationCode(opcode)+4; + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the a token for the referenced type. + /// + /// The Microsoft intermediate language (MSIL) instruction to be put onto the stream. + /// The referenced type. + public void Emit(OperationCode opcode, ITypeReference cls) { + Contract.Requires(cls != null); + + this.operations.Add(new Operation(opcode, this.offset, this.GetCurrentSequencePoint(), cls)); + this.offset += SizeOfOperationCode(opcode)+SizeOfOffset(opcode); + } + + /// + /// Ends a try body. + /// + public void EndTryBody() { + Contract.Requires(this.InTryBody); + + this.tryBodyStack.Pop(); + if (this.handlers.Count > 0) { + ILGeneratorLabel handlerEnd = new ILGeneratorLabel(false); + this.MarkLabel(handlerEnd); + for (int i = this.handlers.Count-1; i >= 0; i--) { + var handler = this.handlers[i]; + Contract.Assume(handler != null); + if (handler.HandlerEnd == null) { + handler.HandlerEnd = handlerEnd; + if (i < this.handlers.Count-1) { + this.handlers.RemoveAt(i); + this.handlers.Add(handler); + } + break; + } + } + } + } + + /// + /// Ends a lexical scope. + /// + public void EndScope() { + if (this.scopeStack.Count > 0) { + var endLabel = new ILGeneratorLabel(); + this.MarkLabel(endLabel); + var topScope = this.scopeStack.Pop(); + Contract.Assume(topScope != null); + topScope.CloseScope(endLabel); + } + } + + private ILocation GetCurrentSequencePoint() { + Contract.Ensures(Contract.Result() != null); + + ILocation result; + if (this.expressionLocation != null) { + result = this.expressionLocation; + this.expressionLocation = null; + } else { + result = this.location; + this.location = Dummy.Location; + } + return result; + } + + /// + /// True if the ILGenerator is currently inside the body of a try statement. + /// + public bool InTryBody { + get { return this.tryBodyStack.Count > 0; } + } + + /// + /// Marks the next IL operation as the final instruction of an expression, whose location is known and is provided as the argument. + /// + /// The location of the expression whose value will be computed by the next IL instruction. + public void MarkExpressionLocation(ILocation expressionLocation) { + Contract.Requires(expressionLocation != null); + + this.expressionLocation = expressionLocation; + } + + /// + /// Marks the Microsoft intermediate language (MSIL) stream's current position with the given label. + /// + public void MarkLabel(ILGeneratorLabel label) { + Contract.Requires(label != null); + + Contract.Assume(label.Offset == 0); + label.Offset = this.offset; + this.operations.Add(new Operation(OperationCode.Invalid, this.offset, Dummy.Location, label)); + } + + /// + /// Marks a sequence point in the Microsoft intermediate language (MSIL) stream. + /// + /// The location of the sequence point. + public void MarkSequencePoint(ILocation location) { + Contract.Requires(location != null); + + this.location = location; + } + + /// + /// Marks the offset of the next IL operation as the first of a sequence of instructions that will cause the thread executing the "MoveNext" method + /// of the state class of an async method to await the completion of a call to another async method. + /// + /// The helper method that will execute once the execution of the current async method resumes. + /// Usually this will be the same method as the one for which this generator is generating a body, but this is not required. + /// The label inside the continuation method where execution will resume. It should be marked by the + /// time this generator is being used to construct a method body. + public void MarkSynchronizationPoint(IMethodDefinition continuationMethod, ILGeneratorLabel continuationLabel) { + Contract.Requires(continuationMethod != null); + Contract.Requires(continuationLabel != null); + + var syncPoints = this.synchronizationPoints; + if (syncPoints == null) this.synchronizationPoints = syncPoints = new List(); + var labelForCurrentOffset = new ILGeneratorLabel(); + this.MarkLabel(labelForCurrentOffset); + syncPoints.Add( + new SynchronizationPoint() { + startOfSynchronize = labelForCurrentOffset, + continuationMethod = continuationMethod != this.method ? continuationMethod : null, + startOfContinuation = continuationLabel + }); + } + + /// + /// The method for which this generator helps to produce a method body. + /// + public IMethodDefinition Method { + get { + Contract.Ensures(Contract.Result() != null); + return this.method; + } + } + + /// + /// If the given operation code is a short branch, return the corresponding long branch. Otherwise return the given operation code. + /// + /// An operation code. + public static OperationCode LongVersionOf(OperationCode operationCode) { + switch (operationCode) { + case OperationCode.Beq_S: return OperationCode.Beq; + case OperationCode.Bge_S: return OperationCode.Bge; + case OperationCode.Bge_Un_S: return OperationCode.Bge_Un; + case OperationCode.Bgt_S: return OperationCode.Bgt; + case OperationCode.Bgt_Un_S: return OperationCode.Bgt_Un; + case OperationCode.Ble_S: return OperationCode.Ble; + case OperationCode.Ble_Un_S: return OperationCode.Ble_Un; + case OperationCode.Blt_S: return OperationCode.Blt; + case OperationCode.Blt_Un_S: return OperationCode.Blt_Un; + case OperationCode.Bne_Un_S: return OperationCode.Bne_Un; + case OperationCode.Br_S: return OperationCode.Br; + case OperationCode.Brfalse_S: return OperationCode.Brfalse; + case OperationCode.Brtrue_S: return OperationCode.Brtrue; + case OperationCode.Leave_S: return OperationCode.Leave; + default: return operationCode; + } + } + + /// + /// If the given operation code is a long branch, return the corresponding short branch. Otherwise return the given operation code. + /// + /// An operation code. + public static OperationCode ShortVersionOf(OperationCode operationCode) { + switch (operationCode) { + case OperationCode.Beq: return OperationCode.Beq_S; + case OperationCode.Bge: return OperationCode.Bge_S; + case OperationCode.Bge_Un: return OperationCode.Bge_Un_S; + case OperationCode.Bgt: return OperationCode.Bgt_S; + case OperationCode.Bgt_Un: return OperationCode.Bgt_Un_S; + case OperationCode.Ble: return OperationCode.Ble_S; + case OperationCode.Ble_Un: return OperationCode.Ble_Un_S; + case OperationCode.Blt: return OperationCode.Blt_S; + case OperationCode.Blt_Un: return OperationCode.Blt_Un_S; + case OperationCode.Bne_Un: return OperationCode.Bne_Un_S; + case OperationCode.Br: return OperationCode.Br_S; + case OperationCode.Brfalse: return OperationCode.Brfalse_S; + case OperationCode.Brtrue: return OperationCode.Brtrue_S; + case OperationCode.Leave: return OperationCode.Leave_S; + default: return operationCode; + } + } + + private static uint SizeOfOffset(OperationCode opcode) { + switch (opcode) { + case OperationCode.Beq_S: + case OperationCode.Bge_S: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble_S: + case OperationCode.Ble_Un_S: + case OperationCode.Blt_S: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un_S: + case OperationCode.Br_S: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue_S: + case OperationCode.Leave_S: + return 1; + default: + return 4; + } + } + + private static uint SizeOfOperationCode(OperationCode opcode) { + if (((int)opcode) > 0xff && (opcode < OperationCode.Array_Create)) return 2; + return 1; + } + + /// + /// Specifies a namespace to be search when evaluating expressions while stopped in the debugger at a sequence point in the current lexical scope. + /// + public void UseNamespace(string namespaceToUse) { + if (this.scopeStack.Count == 0) this.BeginScope(); + var topScope = this.scopeStack.Peek(); + Contract.Assume(topScope != null); + Contract.Assume(topScope.usedNamespaces != null); + topScope.usedNamespaces.Add(namespaceToUse); + } + + /// + /// Returns a block scope associated with each local variable in the iterator for which this is the generator for its MoveNext method. + /// May return null. + /// + /// The PDB file model seems to be that scopes are duplicated if necessary so that there is a separate scope for each + /// local variable in the original iterator and the mapping from local to scope is done by position. + public IEnumerable/*?*/ GetIteratorScopes() { + if (this.iteratorScopes == null) return null; + return this.iteratorScopes.AsReadOnly(); + } + + /// + /// Returns a sequence of all of the block scopes that have been defined for this method body. Includes nested block scopes. + /// + public IEnumerable GetLocalScopes() { + Contract.Ensures(Contract.Result>() != null); + return this.scopes.AsReadOnly(); + } + + /// + /// Returns zero or more namespace scopes into which the namespace type containing the given method body has been nested. + /// These scopes determine how simple names are looked up inside the method body. There is a separate scope for each dotted + /// component in the namespace type name. For istance namespace type x.y.z will have two namespace scopes, the first is for the x and the second + /// is for the y. + /// + public IEnumerable GetNamespaceScopes() { + Contract.Ensures(Contract.Result>() != null); + foreach (var generatorScope in this.scopes) { + if (generatorScope.usedNamespaces.Count > 0) + yield return generatorScope; + } + } + + /// + /// Returns a sequence of all of the IL operations that make up this method body. + /// + public IEnumerable GetOperations() { + Contract.Ensures(Contract.Result>() != null); + foreach (Operation operation in this.operations) { + if (operation.OperationCode == OperationCode.Invalid) continue; //dummy operation for label + yield return operation; + } + } + + /// + /// Returns a sequence of descriptors that define where try blocks and their associated handlers can be found in the instruction sequence. + /// + [ContractVerification(false)] + public IEnumerable GetOperationExceptionInformation() { + Contract.Ensures(Contract.Result>() != null); + return IteratorHelper.GetConversionEnumerable(this.handlers); + } + + /// + /// Returns an object that describes where synchronization points occur in the IL operations of the "MoveNext" method of the state class of + /// an asynchronous method. This returns null unless the generator has been supplied with an non null value for asyncMethodDefinition parameter + /// during construction. + /// + public ISynchronizationInformation/*?*/ GetSynchronizationInformation() { + if (this.asyncMethodDefinition == null) return null; + if (this.synchronizationPoints != null) this.synchronizationPoints.TrimExcess(); + uint generatedCatchHandlerOffset = uint.MaxValue; + if (this.asyncMethodDefinition.Type.TypeCode == PrimitiveTypeCode.Void && this.handlers.Count > 0) { + Contract.Assume(this.handlers[0] != null && this.handlers[0].HandlerStart != null); + generatedCatchHandlerOffset = this.handlers[0].HandlerStart.Offset; + } + return new SynchronizationInformation() { + asyncMethod = this.asyncMethodDefinition, + moveNextMethod = this.method, + generatedCatchHandlerOffset = generatedCatchHandlerOffset, + synchronizationPoints = this.synchronizationPoints == null ? Enumerable.Empty : this.synchronizationPoints.AsReadOnly() + }; + } + + /// + /// An object that can provide information about the local scopes of a method. + /// + public class LocalScopeProvider : ILocalScopeProvider { + + /// + /// An object that can provide information about the local scopes of a method. + /// + /// The local scope provider to use for methods that have not been decompiled. + public LocalScopeProvider(ILocalScopeProvider originalLocalScopeProvider) { + Contract.Requires(originalLocalScopeProvider != null); + + this.originalLocalScopeProvider = originalLocalScopeProvider; + } + + ILocalScopeProvider originalLocalScopeProvider; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.originalLocalScopeProvider != null); + } + + #region ILocalScopeProvider Members + + /// + /// Returns zero or more local (block) scopes, each defining an IL range in which an iterator local is defined. + /// The scopes are returned by the MoveNext method of the object returned by the iterator method. + /// The index of the scope corresponds to the index of the local. Specifically local scope i corresponds + /// to the local stored in field <localName>x_i of the class used to store the local values in between + /// calls to MoveNext. + /// + public IEnumerable GetIteratorScopes(IMethodBody methodBody) { + var sourceMethodBody = methodBody as ILGeneratorMethodBody; + if (sourceMethodBody == null) return this.originalLocalScopeProvider.GetIteratorScopes(methodBody); + return sourceMethodBody.GetIteratorScopes()??Enumerable.Empty; + } + + /// + /// Returns zero or more local (block) scopes into which the CLR IL operations in the given method body is organized. + /// + /// + /// + public IEnumerable GetLocalScopes(IMethodBody methodBody) { + var sourceMethodBody = methodBody as ILGeneratorMethodBody; + if (sourceMethodBody == null) return this.originalLocalScopeProvider.GetLocalScopes(methodBody); + return sourceMethodBody.GetLocalScopes(); + } + + /// + /// Returns zero or more namespace scopes into which the namespace type containing the given method body has been nested. + /// These scopes determine how simple names are looked up inside the method body. There is a separate scope for each dotted + /// component in the namespace type name. For istance namespace type x.y.z will have two namespace scopes, the first is for the x and the second + /// is for the y. + /// + [ContractVerification(false)] + public IEnumerable GetNamespaceScopes(IMethodBody methodBody) { + var sourceMethodBody = methodBody as ILGeneratorMethodBody; + if (sourceMethodBody == null) return this.originalLocalScopeProvider.GetNamespaceScopes(methodBody); + return sourceMethodBody.GetNamespaceScopes(); + } + + /// + /// Returns zero or more local constant definitions that are local to the given scope. + /// + public IEnumerable GetConstantsInScope(ILocalScope scope) { + var generatorScope = scope as ILGeneratorScope; + if (generatorScope == null) return this.originalLocalScopeProvider.GetConstantsInScope(scope); + return generatorScope.Constants; + } + + /// + /// Returns zero or more local variable definitions that are local to the given scope. + /// + /// + /// + public IEnumerable GetVariablesInScope(ILocalScope scope) { + var generatorScope = scope as ILGeneratorScope; + if (generatorScope == null) return this.originalLocalScopeProvider.GetVariablesInScope(scope); + return generatorScope.Locals; + } + + /// + /// Returns true if the method body is an iterator. + /// + public bool IsIterator(IMethodBody methodBody) { + var generatorMethodBody = methodBody as ILGeneratorMethodBody; + if (generatorMethodBody == null) return this.originalLocalScopeProvider.IsIterator(methodBody); + return generatorMethodBody.GetIteratorScopes() != null; + } + + /// + /// If the given method body is the "MoveNext" method of the state class of an asynchronous method, the returned + /// object describes where synchronization points occur in the IL operations of the "MoveNext" method. Otherwise + /// the result is null. + /// + public ISynchronizationInformation/*?*/ GetSynchronizationInformation(IMethodBody methodBody) { + var generatorMethodBody = methodBody as ILGeneratorMethodBody; + if (generatorMethodBody == null) return this.originalLocalScopeProvider.GetSynchronizationInformation(methodBody); + return generatorMethodBody.GetSynchronizationInformation(); + } + #endregion + } + + } + + /// + /// An object that is used to mark a location in an IL stream and that is used to indicate where branches go to. + /// + public sealed class ILGeneratorLabel { + + /// + /// Initializes an object that is used to mark a location in an IL stream and that is used to indicate where branches go to. + /// + public ILGeneratorLabel() { + } + + internal ILGeneratorLabel(bool mayAlias) { + this.mayAlias = mayAlias; + } + + /// + /// + /// + public uint Offset { + get { + if (this.alias != null) return this.alias.Offset; + return this.offset; + } + set { this.offset = value; } + } + private uint offset; + + internal ILGeneratorLabel/*?*/ alias; + internal bool mayAlias; + /// + /// Non-null only when labelsReturnInstruction is true. + /// + internal ILocation/*?*/ locationOfReturnInstruction; + } + + /// + /// A mutable object that represents a local variable or constant. + /// + public class GeneratorLocal : ILocalDefinition { + + /// + /// Allocates a mutable object that represents a local variable or constant. + /// + public GeneratorLocal() { + this.compileTimeValue = Dummy.Constant; + this.customModifiers = new List(); + this.isModified = false; + this.isPinned = false; + this.isReference = false; + this.locations = new List(); + this.name = Dummy.Name; + this.methodDefinition = Dummy.MethodDefinition; + this.type = Dummy.TypeReference; + } + + [ContractInvariantMethod] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")] + private void ObjectInvariant() { + Contract.Invariant(this.compileTimeValue != null); + Contract.Invariant(this.customModifiers != null); + Contract.Invariant(this.locations != null); + Contract.Invariant(this.name != null); + Contract.Invariant(this.methodDefinition != null); + Contract.Invariant(this.type != null); + } + + + /// + /// The compile time value of the definition, if it is a local constant. + /// + /// + public IMetadataConstant CompileTimeValue { + get { return this.compileTimeValue; } + set { + Contract.Requires(value != null); + this.compileTimeValue = value; + } + } + IMetadataConstant compileTimeValue; + + /// + /// Custom modifiers associated with local variable definition. + /// + /// + public List CustomModifiers { + get { return this.customModifiers; } + set { + Contract.Requires(value != null); + this.customModifiers = value; + } + } + List customModifiers; + + /// + /// True if the local is a temporary variable generated by the compiler and thus does not have a corresponding source declaration. + /// + /// The Visual Studio debugger will hide the existance of compiler generated locals. + public bool IsCompilerGenerated { + get { return this.isCompilerGenerated; } + set { this.isCompilerGenerated = value; } + } + bool isCompilerGenerated; + + /// + /// True if this local definition is readonly and initialized with a compile time constant value. + /// + /// + public bool IsConstant { + get { return !(this.compileTimeValue is Dummy); } + } + + /// + /// The local variable has custom modifiers. + /// + /// + public bool IsModified { + get { return this.isModified; } + set { this.isModified = value; } + } + bool isModified; + + /// + /// True if the value referenced by the local must not be moved by the actions of the garbage collector. + /// + /// + public bool IsPinned { + get { return this.isPinned; } + set { this.isPinned = value; } + } + bool isPinned; + + /// + /// True if the local contains a managed pointer (for example a reference to a local variable or a reference to a field of an object). + /// + /// + public bool IsReference { + get { return this.isReference; } + set { this.isReference = value; } + } + bool isReference; + + /// + /// The name of the entity. + /// + /// + public IName Name { + get { return this.name; } + set { + Contract.Requires(value != null); + this.name = value; + } + } + IName name; + + /// + /// A potentially empty collection of locations that correspond to this instance. + /// + /// + public List Locations { + get { return this.locations; } + set { + Contract.Requires(value != null); + this.locations = value; + } + } + List locations; + + /// + /// The definition of the method in which this local is defined. + /// + public IMethodDefinition MethodDefinition { + get { return this.methodDefinition; } + set { + Contract.Requires(value != null); + this.methodDefinition = value; + } + } + IMethodDefinition methodDefinition; + + /// + /// The type of the local. + /// + /// + public ITypeReference Type { + get { return this.type; } + set { + Contract.Requires(value != null); + this.type = value; + } + } + ITypeReference type; + + #region ILocalDefinition Members + + IEnumerable ILocalDefinition.CustomModifiers { + get { return this.customModifiers.AsReadOnly(); } + } + + IEnumerable IObjectWithLocations.Locations { + [ContractVerification(false)] + get { return this.locations.AsReadOnly(); } + } + + #endregion + } + + /// + /// An object that keeps track of a set of local definitions (variables) and used (imported) namespaces that appear in the + /// source code corresponding to the IL operations from Offset to Offset+Length. + /// + public class ILGeneratorScope : ILocalScope, INamespaceScope { + + internal ILGeneratorScope(ILGeneratorLabel startLabel, INameTable nameTable, IMethodDefinition containingMethod) { + Contract.Requires(startLabel != null); + Contract.Requires(nameTable != null); + Contract.Requires(containingMethod != null); + + this.startLabel = startLabel; + this.nameTable = nameTable; + this.methodDefinition = containingMethod; + } + + ILGeneratorLabel startLabel; + ILGeneratorLabel endLabel; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.startLabel != null); + Contract.Invariant(this.constants != null); + Contract.Invariant(this.locals != null); + Contract.Invariant(this.usedNamespaces != null); + } + + internal void CloseScope(ILGeneratorLabel endLabel) { + this.endLabel = endLabel; + } + + /// + /// The local definitions (constants) defined in the source code corresponding to this scope.(A debugger can use this when evaluating expressions in a program + /// point that falls inside this scope.) + /// + public IEnumerable Constants { + get { return this.constants.AsReadOnly(); } + } + internal readonly List constants = new List(); + + /// + /// The length of the scope. Offset+Length equals the offset of the first operation outside the scope, or equals the method body length. + /// + /// + public uint Length { + get { + Contract.Assume(this.endLabel != null); + return this.endLabel.Offset - this.startLabel.Offset; + } + } + + /// + /// The local definitions (variables) defined in the source code corresponding to this scope.(A debugger can use this when evaluating expressions in a program + /// point that falls inside this scope.) + /// + public IEnumerable Locals { + get { return this.locals.AsReadOnly(); } + } + internal readonly List locals = new List(); + + readonly INameTable nameTable; + + /// + /// The method definition. + /// + public IMethodDefinition MethodDefinition { + get { return this.methodDefinition; } + } + readonly IMethodDefinition methodDefinition; + + /// + /// The offset of the first operation in the scope. + /// + public uint Offset { + get { return this.startLabel.Offset; } + } + + /// + /// The namespaces that are used (imported) into this scope. (A debugger can use this when evaluating expressions in a program + /// point that falls inside this scope.) + /// + /// The used namespace names. + public IEnumerable UsedNamespaceNames { + get { return this.usedNamespaces.AsReadOnly(); } + } + internal readonly List usedNamespaces = new List(0); + + /// + /// Zero or more used namespaces. These correspond to using clauses in C#. + /// + public IEnumerable UsedNamespaces { + get { + foreach (var usedNamespaceName in this.usedNamespaces) + yield return new UsedNamespace(this.nameTable.GetNameFor(usedNamespaceName)); + } + } + } + +} + +namespace Microsoft.Cci.ILGeneratorImplementation { + internal class TryBody { + + internal TryBody(ILGeneratorLabel start) { + this.start = start; + } + + internal readonly ILGeneratorLabel start; + internal ILGeneratorLabel/*?*/ end; + } + + internal class ExceptionHandler : IOperationExceptionInformation { + + internal ExceptionHandler() { + } + + internal ExceptionHandler(HandlerKind kind, TryBody tryBlock, ILGeneratorLabel handlerStart) { + this.ExceptionType = Dummy.TypeReference; + this.Kind = kind; + this.HandlerStart = handlerStart; + this.tryBlock = tryBlock; + } + + internal HandlerKind Kind; + internal ITypeReference ExceptionType; + internal ILGeneratorLabel/*?*/ TryStart; + internal ILGeneratorLabel/*?*/ TryEnd; + internal ILGeneratorLabel HandlerEnd; + internal ILGeneratorLabel HandlerStart; + internal ILGeneratorLabel/*?*/ FilterDecisionStart; + TryBody/*?*/ tryBlock; + + #region IOperationExceptionInformation Members + + HandlerKind IOperationExceptionInformation.HandlerKind { + get { return this.Kind; } + } + + ITypeReference IOperationExceptionInformation.ExceptionType { + get { return this.ExceptionType; } + } + + uint IOperationExceptionInformation.TryStartOffset { + [ContractVerification(false)] + get { + if (this.TryStart != null) return this.TryStart.Offset; + return this.tryBlock.start.Offset; + } + } + + uint IOperationExceptionInformation.TryEndOffset { + [ContractVerification(false)] + get { + if (this.TryEnd != null) return this.TryEnd.Offset; + return this.tryBlock.end.Offset; + } + } + + uint IOperationExceptionInformation.FilterDecisionStartOffset { + get { + if (this.FilterDecisionStart == null) return 0; + return this.FilterDecisionStart.Offset; + } + } + + uint IOperationExceptionInformation.HandlerStartOffset { + get { return this.HandlerStart.Offset; } + } + + uint IOperationExceptionInformation.HandlerEndOffset { + get { return this.HandlerEnd.Offset; } + } + + #endregion + } + + internal class Operation : IOperation { + + internal Operation(OperationCode operationCode, uint offset, ILocation location, object/*?*/ value) { + Contract.Requires(location != null); + + this.operationCode = operationCode; + this.offset = offset; + this.location = location; + this.value = value; + } + + public OperationCode OperationCode { + get { return this.operationCode; } + } + internal OperationCode operationCode; + + public uint Offset { + get { return this.offset; } + } + internal uint offset; + + public ILocation Location { + get { return this.location; } + } + readonly ILocation location; + + public object/*?*/ Value { + get { + ILGeneratorLabel/*?*/ label = this.value as ILGeneratorLabel; + if (label != null) return label.Offset; + ILGeneratorLabel[]/*?*/ labels = this.value as ILGeneratorLabel[]; + if (labels != null) { + uint[] labelOffsets = new uint[labels.Length]; + for (int i = 0; i < labels.Length; i++) { + var labeli = labels[i]; + Contract.Assume(labeli != null); + labelOffsets[i] = labeli.Offset; + } + this.value = labelOffsets; + return labelOffsets; + } + return this.value; + } + } + internal object/*?*/ value; + + } + + internal class SynchronizationInformation : ISynchronizationInformation { + + internal IMethodDefinition asyncMethod; + internal IMethodDefinition moveNextMethod; + internal uint generatedCatchHandlerOffset; + internal IEnumerable synchronizationPoints; + + #region ISynchronizationInformation Members + + public IMethodDefinition AsyncMethod { + get { return this.asyncMethod; } + } + + public IMethodDefinition MoveNextMethod { + get { return this.moveNextMethod; } + } + + public uint GeneratedCatchHandlerOffset { + get { return this.generatedCatchHandlerOffset; } + } + + public IEnumerable SynchronizationPoints { + get { return this.synchronizationPoints; } + } + + #endregion + } + + internal class SynchronizationPoint : ISynchronizationPoint { + + internal ILGeneratorLabel startOfSynchronize; + internal ILGeneratorLabel startOfContinuation; + internal IMethodDefinition/*?*/ continuationMethod; + + #region ISynchronizationPoint Members + + public uint SynchronizeOffset { + get { return this.startOfSynchronize.Offset; } + } + + public IMethodDefinition/*?*/ ContinuationMethod { + get { return this.continuationMethod; } + } + + public uint ContinuationOffset { + get { return this.startOfContinuation.Offset; } + } + + #endregion + } + + /// + /// A namespace that is used (imported) inside a namespace scope. + /// + internal class UsedNamespace : IUsedNamespace { + + /// + /// Allocates a namespace that is used (imported) inside a namespace scope. + /// + /// The name of a namepace that has been aliased. For example the "y.z" of "using x = y.z;" or "using y.z" in C#. + internal UsedNamespace(IName namespaceName) { + Contract.Requires(namespaceName != null); + + this.namespaceName = namespaceName; + } + + /// + /// An alias for a namespace. For example the "x" of "using x = y.z;" in C#. Empty if no alias is present. + /// + /// + public IName Alias { + get { return Dummy.Name; } + } + + /// + /// The name of a namepace that has been aliased. For example the "y.z" of "using x = y.z;" or "using y.z" in C#. + /// + /// + public IName NamespaceName { + get { return this.namespaceName; } + } + readonly IName namespaceName; + + } + + internal class Stack { + + T[] elements = new T[16]; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.elements != null); + Contract.Invariant(this.elements.Length > 0); + } + + internal int Count { get; set; } + + internal T Peek() { + Contract.Assume(this.Count > 0); + Contract.Assume(this.Count <= this.elements.Length); + return this.elements[this.Count-1]; + } + + internal T Pop() { + Contract.Assume(this.Count > 0); + Contract.Assume(this.Count <= this.elements.Length); + var i = this.Count-1; + this.Count = i; + return this.elements[i]; + } + + internal void Push(T element) { + Contract.Assume(this.Count >= 0); + if (this.Count == this.elements.Length) + Array.Resize(ref this.elements, this.elements.Length*2); + Contract.Assume(this.Count < this.elements.Length); + var i = this.Count; + this.Count = i+1; + this.elements[i] = element; + } + } + +} diff --git a/Metadata/Sources/ILGenerator/ILGenerator.csproj b/Metadata/Sources/ILGenerator/ILGenerator.csproj new file mode 100644 index 0000000..13b2c88 --- /dev/null +++ b/Metadata/Sources/ILGenerator/ILGenerator.csproj @@ -0,0 +1,166 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {08156C78-403A-4112-AD81-8646AC51CD2F} + Library + Properties + Microsoft.Cci + Microsoft.Cci.ILGenerator + + + + + + + + + true + + + 2.0 + + + false + v4.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + 0 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + ..\Common\InterimKey.snk + bin\Debug\Microsoft.Cci.ILGenerator.XML + false + AllRules.ruleset + False + False + True + False + False + False + False + False + False + False + False + False + True + False + False + True + + + + + + + False + Full + Build + 0 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + ..\Common\InterimKey.snk + bin\Release\Microsoft.Cci.ILGenerator.XML + AllRules.ruleset + + + true + bin\CompilerOnly\ + DEBUG;TRACE + bin\Debug\Microsoft.Cci.ILGenerator.XML + full + AnyCPU + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + + + + Build\version.cs + + + + + + + + + {4A34A3C5-6176-49D7-A4C5-B2B671247F8F} + MetadataHelper + + + {33CAB640-0D03-43DF-81BD-22CDC6C0A597} + MetadataModel + + + {4B0054FD-124A-4037-9965-BDB55E6BF389} + SourceModel + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + true + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + False + .NET Framework 3.5 SP1 + false + + + + + \ No newline at end of file diff --git a/Metadata/Sources/ILGenerator/ILRewriter.cs b/Metadata/Sources/ILGenerator/ILRewriter.cs new file mode 100644 index 0000000..83f2c88 --- /dev/null +++ b/Metadata/Sources/ILGenerator/ILRewriter.cs @@ -0,0 +1,410 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Cci.ILGeneratorImplementation; +using System.Diagnostics.Contracts; +using Microsoft.Cci.UtilityDataStructures; + +namespace Microsoft.Cci { + + /// + /// Rewrites the IL of method bodies. + /// + public class ILRewriter { + + /// + /// + /// + /// + /// + /// + public ILRewriter(IMetadataHost host, ILocalScopeProvider/*?*/ localScopeProvider, ISourceLocationProvider/*?*/ sourceLocationProvider) { + Contract.Requires(host != null); + + this.host = host; + this.generator = new ILGenerator(host, Dummy.MethodDefinition); + this.localScopeProvider = localScopeProvider; + this.sourceLocationProvider = sourceLocationProvider; + } + + /// + /// + /// + protected readonly IMetadataHost host; + /// + /// + /// + protected readonly ILocalScopeProvider/*?*/ localScopeProvider; + /// + /// + /// + protected readonly ISourceLocationProvider/*?*/ sourceLocationProvider; + + ILGenerator generator; + Hashtable labelFor = new Hashtable(); + readonly HashtableForUintValues localIndex = new HashtableForUintValues(); + readonly List localVariables = new List(); + readonly Microsoft.Cci.ILGeneratorImplementation.Stack scopeStack = new Microsoft.Cci.ILGeneratorImplementation.Stack(); + IEnumerator/*?*/ scopeEnumerator; + bool scopeEnumeratorIsValid; + IEnumerator/*?*/ iteratorScopeEnumerator; + bool iteratorScopeEnumeratorIsValid; + ISynchronizationInformation/*?*/ synchronizationInfo; + IEnumerator/*?*/ syncPointEnumerator; + bool syncPointEnumeratorIsValid; + + /// + /// + /// + protected ushort maxStack; + + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.host != null); + Contract.Invariant(this.generator != null); + Contract.Invariant(this.labelFor != null); + Contract.Invariant(this.localIndex != null); + Contract.Invariant(this.localVariables != null); + Contract.Invariant(this.scopeStack != null); + } + + /// + /// + /// + protected ILGenerator Generator { + get { + Contract.Ensures(Contract.Result() != null); + return this.generator; + } + } + + /// + /// + /// + protected IMetadataHost Host { + get { + Contract.Ensures(Contract.Result() != null); + return this.host; + } + } + + /// + /// + /// + /// + /// + public virtual IMethodBody Rewrite(IMethodBody methodBody) { + Contract.Requires(methodBody != null); + + IMethodDefinition asyncMethodDefinition = null; + if (this.localScopeProvider != null) { + this.synchronizationInfo = this.localScopeProvider.GetSynchronizationInformation(methodBody); + if (this.synchronizationInfo != null) asyncMethodDefinition = this.synchronizationInfo.AsyncMethod; + } + this.generator = new ILGenerator(this.host, methodBody.MethodDefinition, asyncMethodDefinition); + this.maxStack = methodBody.MaxStack; + this.labelFor.Clear(); + this.localIndex.Clear(); + this.localVariables.Clear(); + this.scopeStack.Count = 0; + + this.EmitMethodBody(methodBody); + return new ILGeneratorMethodBody(this.Generator, methodBody.LocalsAreZeroed, this.maxStack, methodBody.MethodDefinition, + this.localVariables.ToArray(), Enumerable.Empty); + } + + /// + /// + /// + /// + [ContractVerification(false)] + protected virtual void EmitMethodBody(IMethodBody methodBody) { + Contract.Requires(methodBody != null); + + var savedLabelFor = this.labelFor; + this.labelFor = new Hashtable(); + var initialScopeStackCount = this.scopeStack.Count; + + foreach (var exceptionInfo in methodBody.OperationExceptionInformation) { + Contract.Assume(exceptionInfo != null); + this.Generator.AddExceptionHandlerInformation(exceptionInfo.HandlerKind, exceptionInfo.ExceptionType, + this.GetLabelFor(exceptionInfo.TryStartOffset), this.GetLabelFor(exceptionInfo.TryEndOffset), + this.GetLabelFor(exceptionInfo.HandlerStartOffset), this.GetLabelFor(exceptionInfo.HandlerEndOffset), + exceptionInfo.HandlerKind == HandlerKind.Filter ? this.GetLabelFor(exceptionInfo.FilterDecisionStartOffset) : null); + } + + if (this.localScopeProvider == null) { + foreach (var localDef in methodBody.LocalVariables) { + Contract.Assume(localDef != null); + this.Generator.AddVariableToCurrentScope(localDef); + } + } else { + foreach (var ns in this.localScopeProvider.GetNamespaceScopes(methodBody)) { + Contract.Assume(ns != null); + foreach (var uns in ns.UsedNamespaces) { + Contract.Assume(uns != null); + this.Generator.UseNamespace(uns.NamespaceName.Value); + } + } + this.scopeEnumerator = this.localScopeProvider.GetLocalScopes(methodBody).GetEnumerator(); + this.scopeEnumeratorIsValid = this.scopeEnumerator.MoveNext(); + this.iteratorScopeEnumerator = this.localScopeProvider.GetIteratorScopes(methodBody).GetEnumerator(); + this.iteratorScopeEnumeratorIsValid = this.iteratorScopeEnumerator.MoveNext(); + if (this.synchronizationInfo != null) { + this.syncPointEnumerator = this.synchronizationInfo.SynchronizationPoints.GetEnumerator(); + this.syncPointEnumeratorIsValid = this.syncPointEnumerator.MoveNext(); + } + } + + foreach (var operation in methodBody.Operations) { + switch (operation.OperationCode) { + case OperationCode.Beq: + case OperationCode.Bge: + case OperationCode.Bge_Un: + case OperationCode.Bgt: + case OperationCode.Bgt_Un: + case OperationCode.Ble: + case OperationCode.Ble_Un: + case OperationCode.Blt: + case OperationCode.Blt_Un: + case OperationCode.Bne_Un: + case OperationCode.Br: + case OperationCode.Br_S: + case OperationCode.Brfalse: + case OperationCode.Brtrue: + case OperationCode.Leave: + case OperationCode.Beq_S: + case OperationCode.Bge_S: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble_S: + case OperationCode.Ble_Un_S: + case OperationCode.Blt_S: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un_S: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue_S: + case OperationCode.Leave_S: + Contract.Assume(operation.Value is uint); + this.GetLabelFor((uint)operation.Value); + break; + case OperationCode.Switch: + uint[] offsets = operation.Value as uint[]; + Contract.Assume(offsets != null); + foreach (var offset in offsets) { + this.GetLabelFor(offset); + } + break; + } + } + + foreach (var operation in methodBody.Operations) { + Contract.Assume(operation != null); + Contract.Assume(this.labelFor != null); + var label = this.labelFor.Find(operation.Offset); + if (label != null) this.Generator.MarkLabel(label); + this.EmitDebugInformationFor(operation); + this.EmitOperation(operation); + this.TrackLocal(operation.Value); + } + + while (this.scopeStack.Count > initialScopeStackCount) { + this.Generator.EndScope(); + this.scopeStack.Pop(); + } + + this.labelFor = savedLabelFor; + Contract.Assume(this.generator != null); + } + + /// + /// + /// + /// + protected virtual void EmitDebugInformationFor(IOperation operation) { + Contract.Requires(operation != null); + + this.Generator.MarkSequencePoint(operation.Location); + if (this.scopeEnumerator == null) return; + ILocalScope/*?*/ currentScope = null; + while (this.scopeStack.Count > 0) { + currentScope = this.scopeStack.Peek(); + Contract.Assume(currentScope != null); + if (operation.Offset < currentScope.Offset+currentScope.Length) break; + this.scopeStack.Pop(); + this.Generator.EndScope(); + currentScope = null; + } + while (this.scopeEnumeratorIsValid) { + currentScope = this.scopeEnumerator.Current; + Contract.Assume(currentScope != null); + if (currentScope.Offset <= operation.Offset && operation.Offset < currentScope.Offset+currentScope.Length) { + this.scopeStack.Push(currentScope); + uint iteratorLocalsInScope = 0; + if (this.iteratorScopeEnumerator != null) { + while (this.iteratorScopeEnumeratorIsValid) { + var iteratorScope = this.iteratorScopeEnumerator.Current; + Contract.Assume(iteratorScope != null); + if (iteratorScope.Offset >= currentScope.Offset && iteratorScope.Offset+iteratorScope.Length <= currentScope.Offset+currentScope.Length) { + iteratorLocalsInScope++; + this.iteratorScopeEnumeratorIsValid = this.iteratorScopeEnumerator.MoveNext(); + } else { + break; + } + } + } + this.Generator.BeginScope(iteratorLocalsInScope); + Contract.Assume(this.localScopeProvider != null); + foreach (var local in this.localScopeProvider.GetVariablesInScope(currentScope)) { + Contract.Assume(local != null); + Contract.Assume(local.MethodDefinition == this.generator.Method); + if (!this.localIndex.ContainsKey(local)) { + this.localIndex.Add(local, (uint)this.localVariables.Count); + this.localVariables.Add(local); + } + this.Generator.AddVariableToCurrentScope(local); + } + foreach (var constant in this.localScopeProvider.GetConstantsInScope(currentScope)) { + Contract.Assume(constant != null); + Contract.Assume(constant.MethodDefinition == this.generator.Method); + this.Generator.AddConstantToCurrentScope(constant); + } + this.scopeEnumeratorIsValid = this.scopeEnumerator.MoveNext(); + } else + break; + } + if (this.syncPointEnumeratorIsValid) { + Contract.Assume(this.syncPointEnumerator != null); + var syncPoint = this.syncPointEnumerator.Current; + Contract.Assume(syncPoint != null); + if (syncPoint.SynchronizeOffset == operation.Offset) { + if (syncPoint.ContinuationMethod == null) + this.generator.MarkSynchronizationPoint(this.generator.Method, this.GetLabelFor(syncPoint.ContinuationOffset)); + else + this.generator.MarkSynchronizationPoint(syncPoint.ContinuationMethod, new ILGeneratorLabel() { Offset = syncPoint.ContinuationOffset }); + Contract.Assume(this.syncPointEnumerator != null); + this.syncPointEnumeratorIsValid = this.syncPointEnumerator.MoveNext(); + } + } + } + + /// + /// Emits the given operation at the current position of the new IL stream. Also tracks any referenced local definitions, + /// so that this.localVariables will contain the exact list of locals used in the new method body. + /// + /// + protected virtual void EmitOperation(IOperation operation) { + Contract.Requires(operation != null); + + var operationCode = operation.OperationCode; + var value = operation.Value; + switch (operationCode) { + case OperationCode.Beq: + case OperationCode.Bge: + case OperationCode.Bge_Un: + case OperationCode.Bgt: + case OperationCode.Bgt_Un: + case OperationCode.Ble: + case OperationCode.Ble_Un: + case OperationCode.Blt: + case OperationCode.Blt_Un: + case OperationCode.Bne_Un: + case OperationCode.Br: + case OperationCode.Br_S: + case OperationCode.Brfalse: + case OperationCode.Brtrue: + case OperationCode.Leave: + case OperationCode.Beq_S: + case OperationCode.Bge_S: + case OperationCode.Bge_Un_S: + case OperationCode.Bgt_S: + case OperationCode.Bgt_Un_S: + case OperationCode.Ble_S: + case OperationCode.Ble_Un_S: + case OperationCode.Blt_S: + case OperationCode.Blt_Un_S: + case OperationCode.Bne_Un_S: + case OperationCode.Brfalse_S: + case OperationCode.Brtrue_S: + case OperationCode.Leave_S: + operationCode = ILGenerator.LongVersionOf(operationCode); + Contract.Assume(operation.Value is uint); + value = this.GetLabelFor(+(uint)operation.Value); + break; + case OperationCode.Switch: + uint[] offsets = operation.Value as uint[]; + Contract.Assume(offsets != null); + var n = offsets.Length; + ILGeneratorLabel[] labels = new ILGeneratorLabel[n]; + for (int i = 0; i < n; i++) { + var offset = offsets[i]; + labels[i] = this.GetLabelFor(offset); + } + value = labels; + break; + + //Avoid the short forms because the locals can get reordered. + case OperationCode.Ldloc_0: + case OperationCode.Ldloc_1: + case OperationCode.Ldloc_2: + case OperationCode.Ldloc_3: + case OperationCode.Ldloc_S: + operationCode = OperationCode.Ldloc; + break; + + case OperationCode.Ldloca_S: + operationCode = OperationCode.Ldloca; + break; + + case OperationCode.Stloc_0: + case OperationCode.Stloc_1: + case OperationCode.Stloc_2: + case OperationCode.Stloc_3: + case OperationCode.Stloc_S: + operationCode = OperationCode.Stloc; + break; + } + this.generator.Emit(operationCode, value); + } + + /// + /// + /// + /// + protected void TrackLocal(object operationValue) { + var local = operationValue as ILocalDefinition; + if (local != null) { + if (!this.localIndex.ContainsKey(local)) { + this.localIndex.Add(local, (uint)this.localVariables.Count); + this.localVariables.Add(local); + } + } + } + + /// + /// Returns a label that represents the given offset in the original IL. This label must be marked + /// at the corresponding location in the rewritten IL. + /// + protected virtual ILGeneratorLabel GetLabelFor(uint offset) { + Contract.Ensures(Contract.Result() != null); + + var result = this.labelFor[offset]; + if (result == null) + this.labelFor[offset] = result = new ILGeneratorLabel(); + return result; + } + + } + +} diff --git a/Metadata/Sources/ILGenerator/MethodBody.cs b/Metadata/Sources/ILGenerator/MethodBody.cs new file mode 100644 index 0000000..0ab5068 --- /dev/null +++ b/Metadata/Sources/ILGenerator/MethodBody.cs @@ -0,0 +1,290 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Microsoft.Cci { + using Microsoft.Cci.ILGeneratorImplementation; + /// + /// A metadata (IL) level represetation of the body of a method or of a property/event accessor. + /// + public class ILGeneratorMethodBody : IMethodBody { + + /// + /// Allocates an object that is the metadata (IL) level represetation of the body of a method or of a property/event accessor. + /// + /// An object that provides a way to construct the information needed by a method body. Construction should + /// be completed by the time the generator is passed to this constructor. The generator is not referenced by the resulting method body. + /// True if the locals are initialized by zeroeing the stack upon method entry. + /// The maximum number of elements on the evaluation stack during the execution of the method. + /// The definition of the method whose body this is. + /// If this is the body of an event or property accessor, this will hold the corresponding adder/remover/setter or getter method. + /// + /// Any types that are implicitly defined in order to implement the body semantics. + /// In case of AST to instructions conversion this lists the types produced. + /// In case of instructions to AST decompilation this should ideally be list of all types + /// which are local to method. + public ILGeneratorMethodBody(ILGenerator generator, bool localsAreZeroed, ushort maxStack, IMethodDefinition methodDefinition, + IEnumerable localVariables, IEnumerable privateHelperTypes) { + Contract.Requires(generator != null); + Contract.Requires(methodDefinition != null); + Contract.Requires(localVariables != null); + Contract.Requires(privateHelperTypes != null); + + this.localsAreZeroed = localsAreZeroed; + this.operationExceptionInformation = generator.GetOperationExceptionInformation(); + this.operations = generator.GetOperations(); + this.privateHelperTypes = privateHelperTypes; + this.generatorIteratorScopes = generator.GetIteratorScopes(); + this.generatorLocalScopes = generator.GetLocalScopes(); + this.localVariables = localVariables; + this.maxStack = maxStack; + this.methodDefinition = methodDefinition; + this.size = generator.CurrentOffset; + this.synchronizationInformation = generator.GetSynchronizationInformation(); + } + + /// + /// Calls visitor.Visit(IMethodBody). + /// + public void Dispatch(IMetadataVisitor visitor) { + visitor.Visit(this); + } + + readonly IEnumerable/*?*/ generatorIteratorScopes; + readonly IEnumerable generatorLocalScopes; + readonly ISynchronizationInformation/*?*/ synchronizationInformation; + + /// + /// Returns a block scope associated with each local variable in the iterator for which this is the generator for its MoveNext method. + /// May return null. + /// + /// The PDB file model seems to be that scopes are duplicated if necessary so that there is a separate scope for each + /// local variable in the original iterator and the mapping from local to scope is done by position. + public IEnumerable/*?*/ GetIteratorScopes() { + return this.generatorIteratorScopes; + } + + /// + /// Returns zero or more local (block) scopes into which the CLR IL operations of this method body is organized. + /// + public IEnumerable GetLocalScopes() { + foreach (var generatorScope in this.generatorLocalScopes) { + if (generatorScope.locals.Count > 0) + yield return generatorScope; + } + } + + /// + /// Returns zero or more namespace scopes into which the namespace type containing the given method body has been nested. + /// These scopes determine how simple names are looked up inside the method body. There is a separate scope for each dotted + /// component in the namespace type name. For istance namespace type x.y.z will have two namespace scopes, the first is for the x and the second + /// is for the y. + /// + public IEnumerable GetNamespaceScopes() { + foreach (var generatorScope in this.generatorLocalScopes) { + if (generatorScope.usedNamespaces.Count > 0) + yield return generatorScope; + } + } + + /// + /// Returns an object that describes where synchronization points occur in the IL operations of the "MoveNext" method of + /// the state class of an async method. Returns null otherwise. + /// + public ISynchronizationInformation/*?*/ GetSynchronizationInformation() { + return this.synchronizationInformation; + } + + /// + /// A list exception data within the method body IL. + /// + public IEnumerable OperationExceptionInformation { + [ContractVerification(false)] + get { return this.operationExceptionInformation; } + } + readonly IEnumerable operationExceptionInformation; + + /// + /// True if the locals are initialized by zeroeing the stack upon method entry. + /// + public bool LocalsAreZeroed { + get { return this.localsAreZeroed; } + } + readonly bool localsAreZeroed; + + /// + /// The local variables of the method. + /// + public IEnumerable LocalVariables { + [ContractVerification(false)] + get { return this.localVariables; } + } + readonly IEnumerable localVariables; + + /// + /// The definition of the method whose body this is. + /// If this is the body of an event or property accessor, this will hold the corresponding adder/remover/setter or getter method. + /// + public IMethodDefinition MethodDefinition { + get { return this.methodDefinition; } + } + readonly IMethodDefinition methodDefinition; + + /// + /// A list CLR IL operations that implement this method body. + /// + public IEnumerable Operations { + [ContractVerification(false)] + get { return this.operations; } + } + readonly IEnumerable operations; + + /// + /// The maximum number of elements on the evaluation stack during the execution of the method. + /// + public ushort MaxStack { + get { return this.maxStack; } + } + readonly ushort maxStack; + + /// + /// Any types that are implicitly defined in order to implement the body semantics. + /// In case of AST to instructions conversion this lists the types produced. + /// In case of instructions to AST decompilation this should ideally be list of all types + /// which are local to method. + /// + public IEnumerable PrivateHelperTypes { + [ContractVerification(false)] + get { return this.privateHelperTypes; } + } + readonly IEnumerable privateHelperTypes; + + /// + /// The size in bytes of the method body when serialized. + /// + public uint Size { + get { return this.size; } + } + readonly uint size; + } + + /// + /// An object that can provide information about the local scopes of a method and that can map ILocation objects + /// to IPrimarySourceLocation objects. + /// + public class ILGeneratorSourceInformationProvider : ILocalScopeProvider, ISourceLocationProvider { + + /// + /// Returns zero or more local (block) scopes, each defining an IL range in which an iterator local is defined. + /// The scopes are returned by the MoveNext method of the object returned by the iterator method. + /// The index of the scope corresponds to the index of the local. Specifically local scope i corresponds + /// to the local stored in field <localName>x_i of the class used to store the local values in between + /// calls to MoveNext. + /// + public virtual IEnumerable GetIteratorScopes(IMethodBody methodBody) { + return Enumerable.Empty; + } + + /// + /// Returns zero or more local (block) scopes into which the CLR IL operations in the given method body is organized. + /// + public virtual IEnumerable GetLocalScopes(IMethodBody methodBody) { + var ilGeneratorMethodBody = methodBody as ILGeneratorMethodBody; + if (ilGeneratorMethodBody == null) return Enumerable.Empty; + return ilGeneratorMethodBody.GetLocalScopes(); + } + + /// + /// Returns zero or more namespace scopes into which the namespace type containing the given method body has been nested. + /// These scopes determine how simple names are looked up inside the method body. There is a separate scope for each dotted + /// component in the namespace type name. For istance namespace type x.y.z will have two namespace scopes, the first is for the x and the second + /// is for the y. + /// + public virtual IEnumerable GetNamespaceScopes(IMethodBody methodBody) { + var ilGeneratorMethodBody = methodBody as ILGeneratorMethodBody; + if (ilGeneratorMethodBody == null) return Enumerable.Empty; + return ilGeneratorMethodBody.GetNamespaceScopes(); + } + + /// + /// Returns zero or more local constant definitions that are local to the given scope. + /// + public virtual IEnumerable GetConstantsInScope(ILocalScope scope) { + var ilGeneratorScope = scope as ILGeneratorScope; + if (ilGeneratorScope == null) return Enumerable.Empty; + return ilGeneratorScope.Constants; + } + + /// + /// Returns zero or more local variable definitions that are local to the given scope. + /// + public virtual IEnumerable GetVariablesInScope(ILocalScope scope) { + var ilGeneratorScope = scope as ILGeneratorScope; + if (ilGeneratorScope == null) return Enumerable.Empty; + return ilGeneratorScope.Locals; + } + + /// + /// Returns true if the method body is an iterator. + /// + public virtual bool IsIterator(IMethodBody methodBody) { + return false; + } + + /// + /// If the given method body is the "MoveNext" method of the state class of an asynchronous method, the returned + /// object describes where synchronization points occur in the IL operations of the "MoveNext" method. Otherwise + /// the result is null. + /// + public ISynchronizationInformation/*?*/ GetSynchronizationInformation(IMethodBody methodBody) { + return null; + } + + /// + /// Return zero or more locations in primary source documents that correspond to one or more of the given derived (non primary) document locations. + /// + public virtual IEnumerable GetPrimarySourceLocationsFor(IEnumerable locations) { + foreach (var location in locations) { + IPrimarySourceLocation psloc = location as IPrimarySourceLocation; + if (psloc != null) yield return psloc; + } + } + + /// + /// Return zero or more locations in primary source documents that correspond to the given derived (non primary) document location. + /// + public virtual IEnumerable GetPrimarySourceLocationsFor(ILocation location) { + IPrimarySourceLocation psloc = location as IPrimarySourceLocation; + if (psloc != null) yield return psloc; + } + + /// + /// Return zero or more locations in primary source documents that correspond to the definition of the given local. + /// + public virtual IEnumerable GetPrimarySourceLocationsForDefinitionOf(ILocalDefinition localDefinition) { + return Enumerable.Empty; + } + + /// + /// Returns the source name of the given local definition, if this is available. + /// Otherwise returns the value of the Name property and sets isCompilerGenerated to true. + /// + public virtual string GetSourceNameFor(ILocalDefinition localDefinition, out bool isCompilerGenerated) { + isCompilerGenerated = false; + var generatorLocal = localDefinition as GeneratorLocal; + if (generatorLocal != null) isCompilerGenerated = generatorLocal.IsCompilerGenerated; + return localDefinition.Name.Value; + } + + } +} \ No newline at end of file diff --git a/Metadata/Sources/ILGenerator/Properties/AssemblyInfo.cs b/Metadata/Sources/ILGenerator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fd61c07 --- /dev/null +++ b/Metadata/Sources/ILGenerator/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsof.Cci.ILGenerator")] +[assembly: AssemblyDescription("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("64b687bd-39ec-4270-9ecf-6bfa09db035d")] + diff --git a/Metadata/Sources/MetadataHelper/AttributeHelper.cs b/Metadata/Sources/MetadataHelper/AttributeHelper.cs new file mode 100644 index 0000000..1eb5605 --- /dev/null +++ b/Metadata/Sources/MetadataHelper/AttributeHelper.cs @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics.Contracts; + +//^ using Microsoft.Contracts; + +namespace Microsoft.Cci { + /// + /// Class containing helper routines for Attributes + /// + public static class AttributeHelper { + /// + /// Returns true if the type definition is an attribute. Typedefinition is said to be attribute when it inherits from [mscorlib]System.Attribute + /// + public static bool IsAttributeType(ITypeDefinition typeDefinition) { + Contract.Requires(typeDefinition != null); + + return TypeHelper.Type1DerivesFromType2(typeDefinition, typeDefinition.PlatformType.SystemAttribute); + } + + /// + /// Returns true if the given collection of attributes contains an attribute of the given type. + /// + public static bool Contains(IEnumerable attributes, ITypeReference attributeType) { + Contract.Requires(attributes != null); + Contract.Requires(attributeType != null); + + foreach (ICustomAttribute attribute in attributes) { + if (attribute == null) continue; + if (TypeHelper.TypesAreEquivalent(attribute.Type, attributeType)) return true; + } + return false; + } + + /// + /// Specifies whether more than one instance of this type of attribute is allowed on same element. + /// This information is obtained from an attribute on the attribute type definition. + /// + public static bool AllowMultiple(ITypeDefinition attributeType, INameTable nameTable) { + Contract.Requires(attributeType != null); + Contract.Requires(nameTable != null); + + foreach (ICustomAttribute ca in attributeType.Attributes) { + if (!TypeHelper.TypesAreEquivalent(ca.Type, attributeType.PlatformType.SystemAttributeUsageAttribute)) + continue; + foreach (IMetadataNamedArgument namedArgument in ca.NamedArguments) { + if (namedArgument.ArgumentName.UniqueKey == nameTable.AllowMultiple.UniqueKey) { + IMetadataConstant/*?*/ compileTimeConst = namedArgument.ArgumentValue as IMetadataConstant; + if (compileTimeConst == null || compileTimeConst.Value == null || !(compileTimeConst.Value is bool)) + continue; + //^ assume false; //Unboxing cast might fail + return (bool)compileTimeConst.Value; + } + } + } + return false; + } + + /// + /// Specifies whether this attribute applies to derived types and/or overridden methods. + /// This information is obtained from an attribute on the attribute type definition. + /// + public static bool Inherited(ITypeDefinition attributeType, INameTable nameTable) { + Contract.Requires(attributeType != null); + Contract.Requires(nameTable != null); + + foreach (ICustomAttribute ca in attributeType.Attributes) { + if (!TypeHelper.TypesAreEquivalent(ca.Type, attributeType.PlatformType.SystemAttributeUsageAttribute)) + continue; + foreach (IMetadataNamedArgument namedArgument in ca.NamedArguments) { + if (namedArgument.ArgumentName.UniqueKey == nameTable.AllowMultiple.UniqueKey) { + IMetadataConstant/*?*/ compileTimeConst = namedArgument.ArgumentValue as IMetadataConstant; + if (compileTimeConst == null || compileTimeConst.Value == null || !(compileTimeConst.Value is bool)) + continue; + //^ assume false; //Unboxing cast might fail + return (bool)compileTimeConst.Value; + } + } + } + return false; + } + + /// + /// Specifies the symbol table elements on which it is valid to apply this attribute. + /// This information is obtained from an attribute on the attribute type definition. + /// + public static AttributeTargets ValidOn(ITypeDefinition attributeType) { + Contract.Requires(attributeType != null); + + foreach (ICustomAttribute ca in attributeType.Attributes) { + if (!TypeHelper.TypesAreEquivalent(ca.Type, attributeType.PlatformType.SystemAttributeUsageAttribute)) + continue; + foreach (IMetadataExpression expr in ca.Arguments) { + IMetadataConstant/*?*/ ctorParam = expr as IMetadataConstant; + if (ctorParam == null || ctorParam.Value == null || !(ctorParam.Value is int)) + break; + //^ assume false; //Unboxing cast might fail + int val = (int)ctorParam.Value; + return (AttributeTargets)val; + } + } + return AttributeTargets.All; + } + } +} diff --git a/Metadata/Sources/MetadataHelper/CommandLineOptions.cs b/Metadata/Sources/MetadataHelper/CommandLineOptions.cs new file mode 100644 index 0000000..91e7c20 --- /dev/null +++ b/Metadata/Sources/MetadataHelper/CommandLineOptions.cs @@ -0,0 +1,950 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the Microsoft Public License. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; +using System.IO; + +namespace Microsoft.Cci +{ + /// + /// Subclass this class and define public fields for options + /// + [ContractVerification(false)] + public abstract class OptionParsing //TODO: move this to another assembly. + { + /// + /// Base constructor for building parseable options + /// + public OptionParsing() + { + this.requiredOptions = GatherRequiredOptions(); + } + + [ContractInvariantMethod] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")] + private void ObjectInvariant() + { + Contract.Invariant(this.generalArguments != null); + Contract.Invariant(this.errorMessages != null); + } + + /// + /// The number of errors discovered during command-line option parsing. + /// + protected int errors = 0; + private bool helpRequested; + + /// + /// True if and only if a question mark was given as a command-line option. + /// + public bool HelpRequested { get { return helpRequested; } } + + /// + /// True if and only if some command-line option caused a parsing error, or specifies an option + /// that does not exist. + /// + public bool HasErrors { get { return errors > 0; } } + + List errorMessages = new List(); + + /// + /// Allows a client to signal that there is an error in the command-line options. + /// + public void AddError() { this.errors++; } + + /// + /// Allows a client to signal that there is an error in the command-line options. + /// + public void AddError(string format, params object[] args) + { + this.AddMessage(format, args); + this.errors++; + } + + /// + /// Allows a client add a message to the output. + /// + public void AddMessage(string format, params object[] args) + { + this.errorMessages.Add(String.Format(format, args)); + } + + /// + /// The list of errors or other messages produced during parsing + /// + protected IEnumerable Messages + { + get + { + Contract.Ensures(Contract.Result>() != null); + return this.errorMessages; + } + } + + /// + /// Put this on fields if you want a more verbose help description + /// + protected class OptionDescription : Attribute + { + /// + /// The text that is shown when the usage is displayed. + /// + readonly public string Description; + /// + /// Constructor for creating the information about an option. + /// + public OptionDescription(string s) { this.Description = s; } + /// + /// Indicates whether the associated option is required or not. + /// + public bool Required { get; set; } + /// + /// Indicates a short form for the option. Very useful for options + /// whose names are reserved keywords. + /// + public string ShortForm { get; set; } + } + + /// + /// Put this on fields if you want the field to be relevant when hashing an Option object + /// + protected class OptionWitness : Attribute { } + + /// + /// If a field has this attribute, then its value is inherited by all the family of analyses + /// + public class OptionValueOverridesFamily : Attribute { } + + /// + /// A way to have a single option be a macro for several options. + /// + protected class OptionFor : Attribute + { + /// + /// The field that this option is a macro for. + /// + readonly public string options; + /// + /// Constructor for specifying which field this is a macro option for. + /// + public OptionFor(string options) + { + this.options = options; + } + } + + /// + /// Override and return false if options do not start with '-' or '/' + /// + protected virtual bool UseDashOptionPrefix { get { return true; } } + + /// + /// This field will hold non-option arguments + /// + readonly List generalArguments = new List(); + + private IList requiredOptions; + + /// + /// The non-option arguments provided on the command line. + /// + public List GeneralArguments + { + get + { + Contract.Ensures(Contract.Result>() != null); + + return this.generalArguments; + } + } + + #region Parsing and Reflection + + /// + /// Called when reflection based resolution does not find option + /// + /// option name (no - or /) + /// all args being parsed + /// current index of arg + /// null, or the optionArgument when option was option=optionArgument + /// true if option is recognized, false otherwise + protected virtual bool ParseUnknown(string option, string[] args, ref int index, string optionEqualsArgument) + { + return false; + } + + /// + /// Main method called by a client to process the command-line options. + /// + public void Parse(string[] args) + { + int index = 0; + while (index < args.Length) + { + string arg = args[index]; + + if (arg == "") { index++; continue; } + + if (arg[0] == '@') + { + var responseFile = arg.Substring(1); + ParseResponseFile(responseFile); + } + else if (!UseDashOptionPrefix || arg[0] == '/' || arg[0] == '-') + { + if (UseDashOptionPrefix) + { + arg = arg.Remove(0, 1); + } + if (arg == "?") + { + this.helpRequested = true; + index++; + continue; + } + + string equalArgument = null; + int equalIndex = arg.IndexOf(':'); + if (equalIndex <= 0) + { + equalIndex = arg.IndexOf('='); + if (equalIndex < 0) // Also add '!' as synonim for '=', as cmd.exe performs fuzzy things with '=' + equalIndex = arg.IndexOf('!'); + } + if (equalIndex > 0) + { + equalArgument = arg.Substring(equalIndex + 1); + arg = arg.Substring(0, equalIndex); + } + + bool optionOK = this.FindOptionByReflection(arg, args, ref index, equalArgument); + if (!optionOK) + { + optionOK = this.ParseUnknown(arg, args, ref index, equalArgument); + if (!optionOK) + { + AddError("Unknown option '{0}'", arg); + } + } + } + else + { + this.generalArguments.Add(arg); + } + + index++; + } + if (!helpRequested) CheckRequiredOptions(); + } + + private void ParseResponseFile(string responseFile) + { + if (!File.Exists(responseFile)) + { + AddError("Response file '{0}' does not exist.", responseFile); + return; + } + try + { + var lines = File.ReadAllLines(responseFile); + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + if (line.Length == 0) continue; + if (line[0] == '#') { + // comment skip + continue; + } + if (ContainsQuote(line)) + { + Parse(SplitLineWithQuotes(responseFile, i, line)); + } + else + { + // simple splitting + Parse(line.Split(' ')); + } + } + } + catch + { + AddError("Failure reading from response file '{0}'.", responseFile); + } + } + + private string[] SplitLineWithQuotes(string responseFileName, int lineNo, string line) + { + var start = 0; + var args = new List(); + bool inDoubleQuotes = false; + int escaping = 0; // number of escape characters in sequence + var currentArg = new StringBuilder(); + + for (var index = 0; index < line.Length; index++) + { + var current = line[index]; + + if (current == '\\') + { + // escape character + escaping++; + // swallow the escape character for now + // grab everything prior to prior escape character + if (index > start) + { + currentArg.Append(line.Substring(start, index - start)); + } + start = index + 1; + continue; + } + if (escaping > 0) + { + if (current == '"') + { + var backslashes = escaping / 2; + for (int i = 0; i < backslashes; i++) { currentArg.Append('\\'); } + if (escaping % 2 == 1) + { + // escapes the " + currentArg.Append('"'); + escaping = 0; + start = index + 1; + continue; + } + } + else + { + var backslashes = escaping; + for (int i = 0; i < backslashes; i++) { currentArg.Append('\\'); } + } + escaping = 0; + } + if (inDoubleQuotes) + { + if (current == '"') + { + // ending argument + FinishArgument(line, start, args, currentArg, index, true); + start = index + 1; + inDoubleQuotes = false; + continue; + } + } + else // not in quotes + { + if (Char.IsWhiteSpace(current)) + { + // end previous, start new + FinishArgument(line, start, args, currentArg, index, false); + start = index + 1; + continue; + } + if (current == '"') + { + // starting double quote + if (index != start) + { + AddError("Response file '{0}' line {1}, char {2} contains '\"' not starting or ending an argument", responseFileName, lineNo, index); + } + start = index + 1; + inDoubleQuotes = true; + continue; + } + } + } + // add outstanding escape characters + while (escaping > 0) { currentArg.Append('\\'); escaping--; } + FinishArgument(line, start, args, currentArg, line.Length, inDoubleQuotes); + return args.ToArray(); + } + + private static void FinishArgument(string line, int start, List args, StringBuilder currentArg, int index, bool includeEmpty) + { + currentArg.Append(line.Substring(start, index - start)); + if (includeEmpty || currentArg.Length > 0) + { + args.Add(currentArg.ToString()); + currentArg.Length = 0; + } + } + + private bool ContainsQuote(string line) + { + var index = line.IndexOf('"'); + if (index >= 0) return true; + index = line.IndexOf('\''); + if (index >= 0) return true; + return false; + } + + + private void CheckRequiredOptions() + { + foreach (var missed in this.requiredOptions) + { + AddError("Required option '-{0}' was not given.", missed); + } + } + + private IList GatherRequiredOptions() + { + List result = new List(); + + foreach (var field in this.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)) + { + var options = field.GetCustomAttributes(typeof(OptionDescription), false); + foreach (OptionDescription attr in options) + { + if (attr.Required) + { + result.Add(field.Name.ToLowerInvariant()); + } + } + } + return result; + } + + private bool ParseValue(Converter parser, string equalArgument, string[] args, ref int index, ref object result) + { + if (equalArgument == null) + { + if (index + 1 < args.Length) + { + equalArgument = args[++index]; + } + } + bool success = false; + if (equalArgument != null) + { + try + { + result = parser(equalArgument); + success = true; + } + catch + { + } + } + return success; + } + + private bool ParseValue(Converter parser, string argument, ref object result) + { + bool success = false; + if (argument != null) + { + try + { + result = parser(argument); + success = true; + } + catch + { + } + } + return success; + } + + /// + /// A delegate that represents the various TryParse methods from int, bool, etc. + /// + public delegate bool TryParseConverter(TInput input, out TOutput output); + + private bool TryParseValue(TryParseConverter tryParser, string argument, ref T result) { + return (argument != null) ? tryParser(argument, out result) : false; + } + + private object ParseValue(Type type, string argument, string option) + { + object result = null; + if (type == typeof(bool)) + { + bool boolResult = false; + if (argument != null) + { + if (!TryParseValue(Boolean.TryParse, argument, ref boolResult)) + { + // Allow "+/-" to turn on/off boolean options + if (argument.Equals("-")) + { + result = false; + } + else if (argument.Equals("+")) + { + result = true; + } + else + { + AddError("option -{0} requires a bool argument", option); + } + } + else + { + result = boolResult; + } + } + else + { + result = true; + } + } + else if (type == typeof(string)) + { + if (!ParseValue(s => s, argument, ref result)) + { + AddError("option -{0} requires a string argument", option); + } + } + else if (type == typeof(int)) + { + int intResult = 0; + if (!TryParseValue(Int32.TryParse, argument, ref intResult)) + { + AddError("option -{0} requires an int argument", option); + } + else + { + result = intResult; + } + } + else if (type.IsEnum) + { + if (!ParseValue(ParseEnum(type), argument, ref result)) + { + AddError("option -{0} expects one of", option); + + foreach (System.Reflection.FieldInfo enumConstant in type.GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)) + { + if (enumConstant.IsLiteral) + { + AddMessage(" {0}", enumConstant.Name); + } + } + } + } + return result; + } + + string AdvanceArgumentIfNoExplicitArg(Type type, string explicitArg, string[] args, ref int index) + { + if (explicitArg != null) return explicitArg; + if (type == typeof(bool)) + { + // bool args don't grab the next arg + return null; + } + if (index + 1 < args.Length) + { + return args[++index]; + } + return null; + } + + private bool FindOptionByReflection(string arg, string[] args, ref int index, string explicitArgument) + { + System.Reflection.FieldInfo fi = this.GetType().GetField(arg, System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); + if (fi != null) + { + this.requiredOptions.Remove(arg.ToLowerInvariant()); + return ProcessOptionWithMatchingField(arg, args, ref index, explicitArgument, ref fi); + } + else + { + // derived options + fi = this.GetType().GetField(arg, System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + if (fi != null) + { + object derived = fi.GetValue(this); + + if (derived is string) + { + this.Parse(((string)derived).Split(' ')); + this.requiredOptions.Remove(arg.ToLowerInvariant()); + return true; + } + } + + // Try to see if the arg matches any ShortForm of an option + var allFields = this.GetType().GetFields(); + System.Reflection.FieldInfo matchingField = null; + foreach (var f in allFields) + { + matchingField = f; + var options = matchingField.GetCustomAttributes(typeof(OptionDescription), false); + foreach (OptionDescription attr in options) + { + if (attr.ShortForm != null) + { + var lower1 = attr.ShortForm.ToLowerInvariant(); + var lower2 = arg.ToLowerInvariant(); + if (lower1.Equals(lower2)) + { + this.requiredOptions.Remove(matchingField.Name.ToLowerInvariant()); + return ProcessOptionWithMatchingField(arg, args, ref index, explicitArgument, ref matchingField); + } + } + } + } + } + return false; + } + + private bool ProcessOptionWithMatchingField(string arg, string[] args, ref int index, string explicitArgument, ref System.Reflection.FieldInfo fi) + { + Type type = fi.FieldType; + bool isList = false; + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) + { + isList = true; + type = type.GetGenericArguments()[0]; + } + if (isList && explicitArgument == "!!") + { + // way to set list to empty + System.Collections.IList listhandle = (System.Collections.IList)fi.GetValue(this); + listhandle.Clear(); + return true; + } + string argument = AdvanceArgumentIfNoExplicitArg(type, explicitArgument, args, ref index); + if (isList) + { + if (argument == null) { + AddError("option -{0} requires an argument", arg); + return true; + } + string[] listargs = argument.Split(';'); + for (int i = 0; i < listargs.Length; i++) + { + if (listargs[i].Length == 0) continue; // skip empty values + bool remove = listargs[i][0] == '!'; + string listarg = remove ? listargs[i].Substring(1) : listargs[i]; + object value = ParseValue(type, listarg, arg); + if (value != null) + { + if (remove) + { + this.GetListField(fi).Remove(value); + } + else + { + this.GetListField(fi).Add(value); + } + } + } + } + else + { + object value = ParseValue(type, argument, arg); + if (value != null) + { + fi.SetValue(this, value); + + string argname; + if (value is Int32 && HasOptionForAttribute(fi, out argname)) + { + this.Parse(DerivedOptionFor(argname, (Int32)value).Split(' ')); + } + + } + } + return true; + } + + private bool HasOptionForAttribute(System.Reflection.FieldInfo fi, out string argname) + { + var options = fi.GetCustomAttributes(typeof(OptionFor), true); + if (options != null && options.Length == 1) + { + argname = ((OptionFor)options[0]).options; + return true; + } + + argname = null; + return false; + } + + /// + /// For the given field, returns the derived option that is indexed by + /// option in the list of derived options. + /// + protected string DerivedOptionFor(string fieldWithOptions, int option) + { + string[] options; + if (TryGetOptions(fieldWithOptions, out options)) + { + if (option < 0 || option >= options.Length) + { + return ""; + } + + return options[option]; + } + + return ""; + } + + /// + /// Returns the options associated with the field, specified as a string. + /// If there are none, options is set to null and false is returned. + /// + protected bool TryGetOptions(string fieldWithOptions, out string[] options) + { + var fi = this.GetType().GetField(fieldWithOptions, + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.FlattenHierarchy | System.Reflection.BindingFlags.Public); + + if (fi != null) + { + var obj = fi.GetValue(this); + + if (obj is string[]) + { + options = (string[])obj; + return true; + } + } + + options = null; + return false; + } + + /// + /// Use this + /// + public long GetCheckCode() + { + var res = 0L; + foreach (var f in this.GetType().GetFields()) + { + foreach (var a in f.GetCustomAttributes(true)) + { + if (a is OptionWitness) + { + res += (f.GetValue(this).GetHashCode()) * f.GetHashCode(); + + break; + } + } + } + + return res; + } + + Converter ParseEnum(Type enumType) + { + return delegate(string s) { return Enum.Parse(enumType, s, true); }; + } + + System.Collections.IList GetListField(System.Reflection.FieldInfo fi) + { + object obj = fi.GetValue(this); + if (obj != null) { return (System.Collections.IList)obj; } + System.Collections.IList result = (System.Collections.IList)fi.FieldType.GetConstructor(new Type[] { }).Invoke(new object[] { }); + fi.SetValue(this, result); + return result; + } + + /// + /// Writes all of the options out to the console. + /// + public void PrintOptions(string indent) + { + foreach (System.Reflection.FieldInfo f in this.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) + { + System.Type opttype = f.FieldType; + bool isList; + if (opttype.IsGenericType && opttype.GetGenericTypeDefinition() == typeof(List<>)) + { + opttype = opttype.GetGenericArguments()[0]; + isList = true; + } + else + { + isList = false; + } + string description = GetOptionAttribute(f); + string option = null; + if (opttype == typeof(bool)) + { + if (!isList && f.GetValue(this).Equals(true)) + { + option = String.Format("{0} (default=true)", f.Name); + } + else + { + option = f.Name; + } + } + else if (opttype == typeof(string)) + { + if (!f.IsLiteral) + { + object defaultValue = f.GetValue(this); + if (!isList && defaultValue != null) + { + option = String.Format("{0} (default={1})", f.Name, defaultValue); + } + else + { + option = String.Format("{0} ", f.Name); + } + } + } + else if (opttype == typeof(int)) + { + if (!isList) + { + option = String.Format("{0} (default={1})", f.Name, f.GetValue(this)); + } + else + { + option = String.Format("{0} ", f.Name); + } + } + else if (opttype.IsEnum) + { + StringBuilder sb = new StringBuilder(); + sb.Append(f.Name).Append(" ("); + bool first = true; + foreach (System.Reflection.FieldInfo enumConstant in opttype.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)) + { + if (enumConstant.IsLiteral) + { + if (!first) + { + if (isList) + { + sb.Append(" + "); + } + else + { + sb.Append(" | "); + } + } + else + { + first = false; + } + sb.Append(enumConstant.Name); + } + } + sb.Append(") "); + if (!isList) + { + sb.AppendFormat("(default={0})", f.GetValue(this)); + } + else + { + sb.Append("(default="); + bool first2 = true; + foreach (object eval in (System.Collections.IEnumerable)f.GetValue(this)) + { + if (!first2) + { + sb.Append(','); + } + else + { + first2 = false; + } + sb.Append(eval.ToString()); + } + sb.Append(')'); + } + option = sb.ToString(); + } + if (option != null) + { + Console.Write("{1} -{0,-30}", option, indent); + + if (description != null) + { + Console.WriteLine(" : {0}", description); + } + else + { + Console.WriteLine(); + } + } + } + Console.WriteLine(Environment.NewLine + "To clear a list, use -