diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs index a2f210bdfc..e0744b4f5d 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs @@ -59,7 +59,7 @@ public DMASTProcDefinition(Location location, DreamPath path, DMASTDefinitionPar } ObjectPath = (path.Elements.Length > 1) ? path.FromElements(0, -2) : DreamPath.Root; - Name = path.LastElement; + Name = path.LastElement ?? throw new ArgumentException($"Proc path \"{path}\" is missing a name", nameof(path)); Parameters = parameters; Body = body; ReturnTypes = returnType; @@ -103,7 +103,7 @@ public sealed class DMASTObjectVarOverride : DMASTStatement { public DMASTObjectVarOverride(Location location, DreamPath path, DMASTExpression value) : base(location) { ObjectPath = path.FromElements(0, -2); - VarName = path.LastElement; + VarName = path.LastElement ?? throw new ArgumentException($"Var override path \"{path}\" is missing a name", nameof(path)); Value = value; } } diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index 0996f41bf0..543132d395 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using DMCompiler.Compiler.DMPreprocessor; using System.Linq; using DMCompiler.Compiler.DM.AST; @@ -506,6 +507,7 @@ public DMASTFile File() { return null; } + [return: NotNullIfNotNull(nameof(expression))] private DMASTExpression? ParseScopeIdentifier(DMASTExpression? expression) { do { var identifier = Identifier(); @@ -1265,6 +1267,7 @@ private DMASTProcStatement For() { if (Check(TokenType.DM_In)) { Whitespace(); DMASTExpression? listExpr = Expression(); + RequireExpression(ref listExpr); Whitespace(); Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 2"); ExtraColonPeriod(); diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs index 8ed167d0ab..007370f04d 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs @@ -197,8 +197,8 @@ public void DefineMacro(string key, string value) { // NB: Pushes files to a stack, so call in reverse order if you are // including multiple files. - public void IncludeFile(string includeDir, string file, bool isDMStandard, Location? includedFrom = null) { - string filePath = Path.Combine(includeDir, file); + public void IncludeFile(string? includeDir, string file, bool isDMStandard, Location? includedFrom = null) { + string filePath = Path.Combine(includeDir ?? string.Empty, file); filePath = filePath.Replace('\\', Path.DirectorySeparatorChar); filePath = Path.GetFullPath(filePath); // Strips out path operators @@ -243,7 +243,7 @@ public void IncludeFile(string includeDir, string file, bool isDMStandard, Locat } } - public void PreprocessFile(string includeDir, string file, bool isDMStandard) { + public void PreprocessFile(string? includeDir, string file, bool isDMStandard) { file = file.Replace('\\', '/'); _lexerStack.Push(new DMPreprocessorLexer(compiler, includeDir, file, isDMStandard)); @@ -275,7 +275,7 @@ private void HandleIncludeDirective(Token includeToken) { DMPreprocessorLexer currentLexer = _lexerStack.Peek(); string file = Path.Combine(Path.GetDirectoryName(currentLexer.File.Replace('\\', Path.DirectorySeparatorChar)), includedFileToken.ValueAsString()); - string directory = currentLexer.IncludeDirectory; + string? directory = currentLexer.IncludeDirectory; IncludeFile(directory, file, includeToken.Location.InDMStandard, includedFrom: includeToken.Location); } @@ -307,7 +307,7 @@ private void HandleDefineDirective(Token defineToken) { } DMPreprocessorLexer currentLexer = _lexerStack.Peek(); - string dir = Path.Combine(currentLexer.IncludeDirectory, dirTokenValue); + string dir = Path.Combine(currentLexer.IncludeDirectory ?? string.Empty, dirTokenValue); compiler.AddResourceDirectory(dir, dirToken.Location); // In BYOND it goes on to set the FILE_DIR macro's value to the added directory diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs index 3d02133665..1eb8fde8a5 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs @@ -12,7 +12,7 @@ namespace DMCompiler.Compiler.DMPreprocessor; internal sealed class DMPreprocessorLexer { private static readonly StringBuilder TokenTextBuilder = new(); - public readonly string IncludeDirectory; + public readonly string? IncludeDirectory; public readonly string File; private readonly DMCompiler _compiler; @@ -23,7 +23,7 @@ internal sealed class DMPreprocessorLexer { private int _previousLine = 1, _previousColumn; private readonly Queue _pendingTokenQueue = new(); // TODO: Possible to remove this? - public DMPreprocessorLexer(DMCompiler compiler, string includeDirectory, string file, string source) { + public DMPreprocessorLexer(DMCompiler compiler, string? includeDirectory, string file, string source) { _compiler = compiler; IncludeDirectory = includeDirectory; File = file; @@ -32,12 +32,12 @@ public DMPreprocessorLexer(DMCompiler compiler, string includeDirectory, string Advance(); } - public DMPreprocessorLexer(DMCompiler compiler, string includeDirectory, string file, bool isDMStandard) { + public DMPreprocessorLexer(DMCompiler compiler, string? includeDirectory, string file, bool isDMStandard) { _compiler = compiler; IncludeDirectory = includeDirectory; File = file; - _source = new StreamReader(Path.Combine(includeDirectory, file), Encoding.UTF8); + _source = new StreamReader(Path.Combine(includeDirectory ?? string.Empty, file), Encoding.UTF8); _isDMStandard = isDMStandard; Advance(); } diff --git a/DMCompiler/DM/Builders/DMASTFolder.cs b/DMCompiler/DM/Builders/DMASTFolder.cs index 0bbe08b1ea..12291946cc 100644 --- a/DMCompiler/DM/Builders/DMASTFolder.cs +++ b/DMCompiler/DM/Builders/DMASTFolder.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using DMCompiler.Compiler.DM.AST; namespace DMCompiler.DM.Builders; @@ -122,7 +123,8 @@ public void FoldAst(DMASTNode? ast) { } } - private DMASTExpression FoldExpression(DMASTExpression? expression) { + [return: NotNullIfNotNull(nameof(expression))] + private DMASTExpression? FoldExpression(DMASTExpression? expression) { if (expression is DMASTUnary unary) { unary.Value = FoldExpression(unary.Value); } else if (expression is DMASTBinary binary) { diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index e93d90a0d8..d1564ccda7 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -19,7 +19,7 @@ public void ProcessProcDefinition(DMASTProcDefinition procDefinition) { if (parameter.Value != null) { //Parameter has a default value string afterDefaultValueCheck = proc.NewLabelName(); - DMReference parameterRef = proc.GetLocalVariableReference(parameterName); + DMReference parameterRef = proc.GetLocalVariableReference(parameterName, parameter.Location); //Don't set parameter to default if not null proc.PushReferenceValue(parameterRef); @@ -197,7 +197,7 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var } value.EmitPushValue(ExprContext); - proc.Assign(proc.GetLocalVariableReference(varDeclaration.Name)); + proc.Assign(proc.GetLocalVariableReference(varDeclaration.Name, varDeclaration.Location)); proc.Pop(); } @@ -396,10 +396,11 @@ private void ProcessStatementFor(DMASTProcStatementFor statementFor) { } proc.EndScope(); - IEnumerable FindVarDecls(DMASTExpression expr) { - if (expr is DMASTVarDeclExpression p) { + IEnumerable FindVarDecls(DMASTExpression? expr) { + if (expr is null) + yield break; + if (expr is DMASTVarDeclExpression p) yield return p; - } foreach (var leaf in expr.Leaves()) { foreach(var decl in FindVarDecls(leaf)) { @@ -863,7 +864,7 @@ private void ProcessStatementTryCatch(DMASTProcStatementTryCatch tryCatch) { compiler.Emit(WarningCode.DuplicateVariable, param.Location, $"Duplicate var {param.Name}"); } - proc.StartTry(catchLabel, proc.GetLocalVariableReference(param.Name)); + proc.StartTry(catchLabel, proc.GetLocalVariableReference(param.Name, param.Location)); } else { if (tryCatch.CatchParameter != null) compiler.Emit(WarningCode.InvalidVarDefinition, tryCatch.CatchParameter.Location, diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index df36b4afeb..3f9e60b83f 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -532,9 +532,14 @@ public bool TryAddLocalConstVariable(string name, DreamPath? type, Constant valu return null; } - public DMReference GetLocalVariableReference(string name) { + public DMReference GetLocalVariableReference(string name, Location loc) { LocalVariable? local = GetLocalVariable(name); + if (local is null) { + _compiler.Emit(WarningCode.InvalidReference, loc, $"Attempted to reference invalid local var \"{name}\""); + return DMReference.Invalid; + } + return local.IsParameter ? DMReference.CreateArgument(local.Id) : DMReference.CreateLocal(local.Id); } diff --git a/DMCompiler/DMCompiler.cs b/DMCompiler/DMCompiler.cs index f55c5e55bc..f0afab4843 100644 --- a/DMCompiler/DMCompiler.cs +++ b/DMCompiler/DMCompiler.cs @@ -127,11 +127,12 @@ public void AddResourceDirectory(string dir, Location loc) { return null; } - string includeDir = Path.GetDirectoryName(files[i]); + string? includeDir = Path.GetDirectoryName(files[i]); string fileName = Path.GetFileName(files[i]); preproc.IncludeFile(includeDir, fileName, false); - compiler.AddResourceDirectory(includeDir, Location.Internal); + if (includeDir is not null) + compiler.AddResourceDirectory(includeDir, Location.Internal); } // Adds the root of the DM project to FILE_DIR @@ -160,8 +161,8 @@ public void AddResourceDirectory(string dir, Location loc) { result.Append(t.Text); } - string outputDir = Path.GetDirectoryName(Settings.Files[0]); - string outputPath = Path.Combine(outputDir, "preprocessor_dump.dm"); + string? outputDir = Path.GetDirectoryName(Settings.Files[0]); + string outputPath = Path.Combine(outputDir ?? string.Empty, "preprocessor_dump.dm"); File.WriteAllText(outputPath, result.ToString()); Console.WriteLine($"Preprocessor output dumped to {outputPath}");