Skip to content

Fix file recursion overflow problems #795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/PowerShellEditorServices/Utility/Logging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ void WriteException(
[CallerMemberName] string callerName = null,
[CallerFilePath] string callerSourceFile = null,
[CallerLineNumber] int callerLineNumber = 0);

/// <summary>
/// Log an exception that has been handled cleanly or is otherwise not expected to cause problems in the logs.
/// </summary>
/// <param name="errorMessage">The error message of the exception to be logged.</param>
/// <param name="exception">The exception itself that has been thrown.</param>
/// <param name="callerName">The name of the method in which the ILogger is being called.</param>
/// <param name="callerSourceFile">The name of the source file in which the ILogger is being called.</param>
/// <param name="callerLineNumber">The line number in the file where the ILogger is being called.</param>
void WriteHandledException(
string errorMessage,
Exception exception,
[CallerMemberName] string callerName = null,
[CallerFilePath] string callerSourceFile = null,
[CallerLineNumber] int callerLineNumber = 0);
}


Expand Down
72 changes: 60 additions & 12 deletions src/PowerShellEditorServices/Utility/PsesLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ namespace Microsoft.PowerShell.EditorServices.Utility
/// </summary>
public class PsesLogger : ILogger
{
/// <summary>
/// The standard log template for all log entries.
/// </summary>
private static readonly string s_logMessageTemplate =
"[{LogLevelName:l}] tid:{ThreadId} in '{CallerName:l}' {CallerSourceFile:l} (line {CallerLineNumber}):{IndentedLogMsg:l}";

/// <summary>
/// The name of the ERROR log level.
/// </summary>
private static readonly string ErrorLevelName = LogLevel.Error.ToString().ToUpper();

/// <summary>
/// The name of the WARNING log level.
/// </summary>
private static readonly string WarningLevelName = LogLevel.Warning.ToString().ToUpper();

/// <summary>
/// The internal Serilog logger to log to.
/// </summary>
Expand Down Expand Up @@ -57,25 +68,22 @@ public void Write(

int threadId = Thread.CurrentThread.ManagedThreadId;

string messageTemplate =
"[{LogLevelName:l}] tid:{threadId} in '{CallerName:l}' {CallerSourceFile:l}:{CallerLineNumber}:{IndentedLogMsg:l}";

switch (logLevel)
{
case LogLevel.Diagnostic:
_logger.Verbose(messageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
_logger.Verbose(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
return;
case LogLevel.Verbose:
_logger.Debug(messageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
_logger.Debug(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
return;
case LogLevel.Normal:
_logger.Information(messageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
_logger.Information(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
return;
case LogLevel.Warning:
_logger.Warning(messageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
_logger.Warning(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
return;
case LogLevel.Error:
_logger.Error(messageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
_logger.Error(s_logMessageTemplate, logLevelName, threadId, callerName, callerSourceFile, callerLineNumber, indentedLogMsg);
return;
}
}
Expand All @@ -95,10 +103,27 @@ public void WriteException(
[CallerFilePath] string callerSourceFile = null,
[CallerLineNumber] int callerLineNumber = 0)
{
string indentedException = IndentMsg(exception.ToString());
string body = IndentExceptionMessage("Exception", errorMessage, exception);
Write(LogLevel.Error, body, callerName, callerSourceFile, callerLineNumber);
}

_logger.Error("[{ErrorLevelName:l}] {CallerSourceFile:l}: In method '{CallerName:l}', line {CallerLineNumber}: {ErrorMessage:l}{IndentedException:l}",
ErrorLevelName, callerSourceFile, callerName, callerLineNumber, errorMessage, indentedException);
/// <summary>
/// Log an exception that has been handled cleanly or is otherwise not expected to cause problems in the logs.
/// </summary>
/// <param name="errorMessage">The error message of the exception to be logged.</param>
/// <param name="exception">The exception itself that has been thrown.</param>
/// <param name="callerName">The name of the method in which the ILogger is being called.</param>
/// <param name="callerSourceFile">The name of the source file in which the ILogger is being called.</param>
/// <param name="callerLineNumber">The line number in the file where the ILogger is being called.</param>
public void WriteHandledException(
string errorMessage,
Exception exception,
[CallerMemberName] string callerName = null,
[CallerFilePath] string callerSourceFile = null,
[CallerLineNumber] int callerLineNumber = 0)
{
string body = IndentExceptionMessage("Handled exception", errorMessage, exception);
Write(LogLevel.Warning, body, callerName, callerSourceFile, callerLineNumber);
}

/// <summary>
Expand All @@ -108,13 +133,36 @@ public void WriteException(
/// <returns>The indented log message string.</returns>
private static string IndentMsg(string logMessage)
{
return new StringBuilder(logMessage)
return IndentMsg(new StringBuilder(logMessage));
}

/// <summary>
/// Utility function to indent a log message by one level.
/// </summary>
/// <param name="logMessageBuilder">Log message string builder to transform.</param>
/// <returns>The indented log message string.</returns>
private static string IndentMsg(StringBuilder logMessageBuilder)
{
return logMessageBuilder
.Replace(Environment.NewLine, s_indentedPrefix)
.Insert(0, s_indentedPrefix)
.AppendLine()
.ToString();
}

private static string IndentExceptionMessage(
string messagePrelude,
string errorMessage,
Exception exception)
{
var sb = new StringBuilder()
.Append(messagePrelude).Append(": ").Append(errorMessage).Append(Environment.NewLine)
.Append(Environment.NewLine)
.Append(exception.ToString());

return IndentMsg(sb);
}

/// <summary>
/// A newline followed by a single indentation prefix.
/// </summary>
Expand Down
126 changes: 86 additions & 40 deletions src/PowerShellEditorServices/Workspace/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ public class Workspace
{
#region Private Fields

private static readonly string[] s_psFilePatterns = new []
{
"*.ps1",
"*.psm1",
"*.psd1"
};

private ILogger logger;
private Version powerShellVersion;
private Dictionary<string, ScriptFile> workspaceFiles = new Dictionary<string, ScriptFile>();
Expand Down Expand Up @@ -292,67 +299,106 @@ public IEnumerable<string> EnumeratePSFiles()

#region Private Methods

/// <summary>
/// Find PowerShell files recursively down from a given directory path.
/// Currently returns files in depth-first order.
/// Directory.GetFiles(folderPath, pattern, SearchOption.AllDirectories) would provide this,
/// but a cycle in the filesystem will cause that to enter an infinite loop.
/// </summary>
/// <param name="folderPath">The absolute path of the base folder to search.</param>
/// <returns>
/// All PowerShell files in the recursive directory hierarchy under the given base directory, up to 64 directories deep.
/// </returns>
private IEnumerable<string> RecursivelyEnumerateFiles(string folderPath)
{
var foundFiles = Enumerable.Empty<string>();
var patterns = new string[] { @"*.ps1", @"*.psm1", @"*.psd1" };
var foundFiles = new List<string>();
var dirStack = new Stack<string>();

try
// Kick the search off with the base directory
dirStack.Push(folderPath);

const int recursionDepthLimit = 64;
while (dirStack.Any())
{
IEnumerable<string> subDirs = Directory.GetDirectories(folderPath);
foreach (string dir in subDirs)
string currDir = dirStack.Pop();

// Look for any PowerShell files in the current directory
foreach (string pattern in s_psFilePatterns)
{
foundFiles =
foundFiles.Concat(
RecursivelyEnumerateFiles(dir));
string[] psFiles;
try
{
psFiles = Directory.GetFiles(currDir, pattern, SearchOption.TopDirectoryOnly);
}
catch (DirectoryNotFoundException e)
{
this.logger.WriteHandledException(
$"Could not enumerate files in the path '{currDir}' due to it being an invalid path",
e);

continue;
}
catch (PathTooLongException e)
{
this.logger.WriteHandledException(
$"Could not enumerate files in the path '{currDir}' due to the path being too long",
e);

continue;
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
this.logger.WriteHandledException(
$"Could not enumerate files in the path '{currDir}' due to the path not being accessible",
e);

continue;
}

foundFiles.AddRange(psFiles);
}
}
catch (DirectoryNotFoundException e)
{
this.logger.WriteException(
$"Could not enumerate files in the path '{folderPath}' due to it being an invalid path",
e);
}
catch (PathTooLongException e)
{
this.logger.WriteException(
$"Could not enumerate files in the path '{folderPath}' due to the path being too long",
e);
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
this.logger.WriteException(
$"Could not enumerate files in the path '{folderPath}' due to the path not being accessible",
e);
}

foreach (var pattern in patterns)
{
// Prevent unbounded recursion here
// If we get too deep, keep processing but go no deeper
if (dirStack.Count >= recursionDepthLimit)
{
continue;
}

// Add the recursive directories to search next
string[] subDirs;
try
{
foundFiles =
foundFiles.Concat(
Directory.GetFiles(
folderPath,
pattern));
subDirs = Directory.GetDirectories(currDir);
}
catch (DirectoryNotFoundException e)
{
this.logger.WriteException(
$"Could not enumerate files in the path '{folderPath}' due to a path being an invalid path",
this.logger.WriteHandledException(
$"Could not enumerate directories in the path '{currDir}' due to it being an invalid path",
e);

continue;
}
catch (PathTooLongException e)
{
this.logger.WriteException(
$"Could not enumerate files in the path '{folderPath}' due to a path being too long",
this.logger.WriteHandledException(
$"Could not enumerate directories in the path '{currDir}' due to the path being too long",
e);

continue;
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
this.logger.WriteException(
$"Could not enumerate files in the path '{folderPath}' due to a path not being accessible",
this.logger.WriteHandledException(
$"Could not enumerate directories in the path '{currDir}' due to the path not being accessible",
e);

continue;
}

foreach (string subDir in subDirs)
{
dirStack.Push(subDir);
}
}

Expand Down