Skip to content

Commit

Permalink
win, compiler: fix and validate empty comment code
Browse files Browse the repository at this point in the history
This commit fixes empty code scripts that create revert code with only
comment lines.

It also improves compiler validation so if a generated code consists
solely of comments, it displays error message.

TODO: Missing unit tests for compiler
  • Loading branch information
undergroundwires committed Dec 18, 2024
1 parent 92ec138 commit 761e0b2
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class FunctionCallScriptCompiler implements ScriptCompiler {
}
const calls = parseFunctionCalls(script.call);
const compiledCode = this.utilities.callCompiler.compileFunctionCalls(calls, this.functions);
validateCompiledCode(
validateFinalCompiledCode(
compiledCode,
this.language,
this.utilities.codeValidator,
Expand All @@ -95,7 +95,7 @@ class FunctionCallScriptCompiler implements ScriptCompiler {
}
}

function validateCompiledCode(
function validateFinalCompiledCode(
compiledCode: CompiledCode,
language: ScriptingLanguage,
validate: CodeValidator,
Expand All @@ -109,6 +109,7 @@ function validateCompiledCode(
CodeValidationRule.NoEmptyLines,
CodeValidationRule.NoTooLongLines,
// Allow duplicated lines to enable calling same function multiple times
CodeValidationRule.NoCommentOnlyLines,
],
),
);
Expand Down
1 change: 1 addition & 0 deletions src/application/Parser/Executable/Script/ScriptParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ function validateHardcodedCodeWithoutCalls(
CodeValidationRule.NoEmptyLines,
CodeValidationRule.NoDuplicatedLines,
CodeValidationRule.NoTooLongLines,
CodeValidationRule.NoCommentOnlyLines,
],
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { isCommentLine, type CommentLineChecker } from './Common/CommentLineChecker';
import { createSyntax, type SyntaxFactory } from './Syntax/SyntaxFactory';
import type { CodeLine, CodeValidationAnalyzer, InvalidCodeLine } from './CodeValidationAnalyzer';

export type CommentOnlyCodeAnalyzer = CodeValidationAnalyzer & {
(
...args: [
...Parameters<CodeValidationAnalyzer>,
syntaxFactory?: SyntaxFactory,
commentLineChecker?: CommentLineChecker,
]
): ReturnType<CodeValidationAnalyzer>;
};

export const analyzeCommentOnlyCode: CommentOnlyCodeAnalyzer = (
lines: readonly CodeLine[],
language: ScriptingLanguage,
syntaxFactory: SyntaxFactory = createSyntax,
commentLineChecker: CommentLineChecker = isCommentLine,
) => {
const syntax = syntaxFactory(language);
if (!lines.every((line) => commentLineChecker(line.text, syntax))) {
return [];
}
return lines
.map((line): InvalidCodeLine => ({
lineNumber: line.lineNumber,
error: (() => {
return 'Script is consists of comments only';
})(),
}));
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { LanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/LanguageSyntax';
import type { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { createSyntax, type SyntaxFactory } from './Syntax/SyntaxFactory';
import { isCommentLine, type CommentLineChecker } from './Common/CommentLineChecker';
import type { CodeLine, CodeValidationAnalyzer, InvalidCodeLine } from './CodeValidationAnalyzer';

export type DuplicateLinesAnalyzer = CodeValidationAnalyzer & {
(
...args: [
...Parameters<CodeValidationAnalyzer>,
syntaxFactory?: SyntaxFactory,
commentLineChecker?: CommentLineChecker,
]
): ReturnType<CodeValidationAnalyzer>;
};
Expand All @@ -16,12 +18,13 @@ export const analyzeDuplicateLines: DuplicateLinesAnalyzer = (
lines: readonly CodeLine[],
language: ScriptingLanguage,
syntaxFactory: SyntaxFactory = createSyntax,
commentLineChecker: CommentLineChecker = isCommentLine,
) => {
const syntax = syntaxFactory(language);
return lines
.map((line): CodeLineWithDuplicateOccurrences => ({
lineNumber: line.lineNumber,
shouldBeIgnoredInAnalysis: shouldIgnoreLine(line.text, syntax),
shouldBeIgnoredInAnalysis: shouldIgnoreLine(line.text, syntax, commentLineChecker),
duplicateLineNumbers: lines
.filter((other) => other.text === line.text)
.map((duplicatedLine) => duplicatedLine.lineNumber),
Expand All @@ -43,17 +46,15 @@ function isNonIgnorableDuplicateLine(line: CodeLineWithDuplicateOccurrences): bo
return !line.shouldBeIgnoredInAnalysis && line.duplicateLineNumbers.length > 1;
}

function shouldIgnoreLine(codeLine: string, syntax: LanguageSyntax): boolean {
return isCommentLine(codeLine, syntax)
function shouldIgnoreLine(
codeLine: string,
syntax: LanguageSyntax,
commentLineChecker: CommentLineChecker,
): boolean {
return commentLineChecker(codeLine, syntax)
|| isLineComposedEntirelyOfCommonCodeParts(codeLine, syntax);
}

function isCommentLine(codeLine: string, syntax: LanguageSyntax): boolean {
return syntax.commentDelimiters.some(
(delimiter) => codeLine.startsWith(delimiter),
);
}

function isLineComposedEntirelyOfCommonCodeParts(
codeLine: string,
syntax: LanguageSyntax,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { LanguageSyntax } from '../Syntax/LanguageSyntax';

export interface CommentLineChecker {
(
codeLine: string,
syntax: LanguageSyntax,
): boolean;
}

export const isCommentLine: CommentLineChecker = (codeLine, syntax) => {
return syntax.commentDelimiters.some(
(delimiter) => codeLine.startsWith(delimiter),
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum CodeValidationRule {
NoEmptyLines,
NoDuplicatedLines,
NoTooLongLines,
NoCommentOnlyLines,
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CodeValidationRule } from './CodeValidationRule';
import { analyzeDuplicateLines } from './Analyzers/AnalyzeDuplicateLines';
import { analyzeEmptyLines } from './Analyzers/AnalyzeEmptyLines';
import { analyzeTooLongLines } from './Analyzers/AnalyzeTooLongLines';
import { analyzeCommentOnlyCode } from './Analyzers/AnalyzeCommentOnlyCode';
import type { CodeValidationAnalyzer } from './Analyzers/CodeValidationAnalyzer';

export interface ValidationRuleAnalyzerFactory {
Expand All @@ -26,6 +27,8 @@ function createValidationRule(rule: CodeValidationRule): CodeValidationAnalyzer
return analyzeDuplicateLines;
case CodeValidationRule.NoTooLongLines:
return analyzeTooLongLines;
case CodeValidationRule.NoCommentOnlyLines:
return analyzeCommentOnlyCode;
default:
throw new Error(`Unknown rule: ${rule}`);
}
Expand Down
112 changes: 95 additions & 17 deletions src/application/collections/windows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40136,7 +40136,9 @@ functions:
Remove the registry key "{{ $keyPath }}"
{{ with $codeComment }}({{ . }}){{ end }}
revertCodeComment: >-
Recreate the registry key "{{ $keyPath }}"
{{ with $recreateOnRevert }}
Recreate the registry key "{{ $keyPath }}"
{{ end }}
-
# Marked: refactor-with-variables
# - Replacing SID is same as `CreateRegistryKey`
Expand Down Expand Up @@ -40710,32 +40712,108 @@ functions:
setupCodeUnelevated: '{{ with $setupCode }}{{ . }}{{ end }}'
minimumWindowsVersion: '{{ with $minimumWindowsVersion }}{{ . }}{{ end }}'
elevateToTrustedInstaller: '{{ with $elevateToTrustedInstaller }}true{{ end }}'
# Marked: refactor-with-variables
# - Setting registry value is same in `code` and `revert code`
code: |-
$registryPath = '{{ $keyPath }}'
$name = '{{ $valueName }}'
$rawPath = '{{ $keyPath }}'
$rawType = '{{ $dataType }}'
$data = '{{ $data }}'
{{ with $evaluateDataAsPowerShell }}
$data = $({{ $data }})
{{ end }}
reg add '{{ $keyPath }}' `
/v '{{ $valueName }}' `
/t '{{ $dataType }}' `
/d "$data" `
/f
Write-Host "Setting '$name' registry value in '$rawPath'."
$hive = $rawPath.Split('\')[0]
$path = $hive + ':' + $rawPath.Substring($hive.Length)
if (!(Test-Path $path)) {
New-Item -Path $path -Force -ErrorAction Stop | Out-Null
Write-Host 'Successfully created the parent registry key.'
}
$type = switch ($rawType) {
'REG_SZ' { 'String' }
'REG_DWORD' { 'DWord' }
'REG_QWORD' { 'QWord' }
'REG_EXPAND_SZ' { 'ExpandString' }
'REG_MULTI_SZ' { 'MultiString' }
default {
throw "Internal privacy$([char]0x002E)sexy error: Failed to find data type for: '$rawType'."
}
}
$key = Get-Item -Path $path -ErrorAction Stop
$currentData = $key.GetValue($name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($currentData -ne $null) {
$currentType = $key.GetValueKind($name)
if (($type -eq $currentType) -and ($currentData -eq $data)) {
Write-Output 'Skipping, no action needed.'
Exit 0
}
}
Set-ItemProperty `
-Path $path `
-Name $name `
-Value $data `
-Type $type `
-Force `
-ErrorAction Stop
Write-Host 'Successfully configured.'
revertCode: |-
{{ with $deleteOnRevert }}
reg delete '{{ $keyPath }}' `
/v '{{ $valueName }}' `
/f 2>$null
$name = '{{ $valueName }}'
$rawPath = '{{ $keyPath }}'
Write-Host "Restoring '$name' registry value in '$rawPath' by deleting."
$hive = $rawPath.Split('\')[0]
$path = $hive + ':' + $rawPath.Substring($hive.Length)
if (!(Test-Path $path)) {
Write-Host 'Skipping: Parent key does not exist; no action needed.'
Exit 0
}
Remove-ItemProperty `
-Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer' `
-Name 'DisallowRun' `
-Force `
-ErrorAction Stop
{{ end }}{{ with $dataOnRevert }}
$revertData = '{{ . }}'
$name = '{{ $valueName }}'
$rawPath = '{{ $keyPath }}'
$data = '{{ $data }}'
$rawType = '{{ $dataType }}'
{{ with $evaluateDataAsPowerShell }}
$revertData = $({{ . }})
$data = $({{ $data }})
{{ end }}
reg add '{{ $keyPath }}' `
/v '{{ $valueName }}' `
/t '{{ $dataType }}' `
/d "$revertData" `
/f
Write-Host "Restoring '$name' registry value in '$rawPath' by updating."
$hive = $rawPath.Split('\')[0]
$path = $hive + ':' + $rawPath.Substring($hive.Length)
if (!(Test-Path $path)) {
New-Item -Path $path -Force -ErrorAction Stop | Out-Null
Write-Host 'Successfully created the parent registry key.'
}
$type = switch ($rawType) {
'REG_SZ' { 'String' }
'REG_DWORD' { 'DWord' }
'REG_QWORD' { 'QWord' }
'REG_EXPAND_SZ' { 'ExpandString' }
'REG_MULTI_SZ' { 'MultiString' }
default {
throw "Internal privacy$([char]0x002E)sexy error: Failed to find data type for: '$rawType'."
}
}
$key = Get-Item -Path $path -ErrorAction Stop
$currentData = $key.GetValue($name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($currentData -ne $null) {
$currentType = $key.GetValueKind($name)
if (($type -eq $currentType) -and ($currentData -eq $data)) {
Write-Output 'Skipping, no action needed.'
Exit 0
}
}
Set-ItemProperty `
-Path $path `
-Name $name `
-Value $data `
-Type $type `
-Force `
-ErrorAction Stop
Write-Host 'Successfully restored.'
{{ end }}
-
name: EnableTLSProtocol
Expand Down

0 comments on commit 761e0b2

Please sign in to comment.