Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
76 changes: 76 additions & 0 deletions constants/tunables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"allowExpressionResultCoercion": "boolean",
"allowExternalPathExpressions": "boolean",
"allowSignedIntegerLength1Bit": "boolean",

"blobChunkSizeInBytes": "number",

"defaultEmptyElementParsePolicy": "string",

"escalateWarningsToErrors": "boolean",

"generatedNamespacePrefixStem": "string",

"initialElementOccurrencesHint": "number",
"initialRegexMatchLimitInCharacters": "number",

"infosetWalkerSkipMin": "number",
"infosetWalkerSkipMax": "number",

"invalidRestrictionPolicy": {
"type": "enum",
"values": ["error", "ignore", "validate"]
},

"maxBinaryDecimalVirtualPoint": "number",
"maxByteArrayOutputStreamBufferSizeInBytes": "number",
"maxDataDumpSizeInBytes": "number",
"maxHexBinaryLengthInBytes": "number",

"maxLengthForVariableLengthDelimiterDisplay": "number",
"maxLookaheadFunctionBits": "number",

"maxOccursBounds": "number",

"maxSkipLengthInBytes": "number",

"maxValidYear": "number",

"maximumRegexMatchLengthInCharacters": "number",
"maximumSimpleElementSizeInCharacters": "number",

"minBinaryDecimalVirtualPoint": "number",
"minValidYear": "number",

"outputStreamChunkSizeInBytes": "number",

"parseUnparsePolicy": "string",

"releaseUnneededInfoset": "boolean",

"requireBitOrderProperty": "boolean",
"requireEmptyElementParsePolicyProperty": "boolean",

"requireEncodingErrorPolicyProperty": "boolean",
"requireFloatingProperty": "boolean",
"requireTextBidiProperty": "boolean",
"requireTextStandardBaseProperty": "boolean",

"saxUnparseEventBatchSize": "number",

"suppressSchemaDefinitionWarnings": "string",

"tempFilePath": "string",

"unqualifiedPathStepPolicy": {
"type": "enum",
"values": [
"noNamespace",
"defaultNamespace",
"preferDefaultNamespace"
]
},

"unparseSuspensionWaitOld": "number",
"unparseSuspensionWaitYoung": "number"
}
11 changes: 11 additions & 0 deletions doc/Wiki.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,12 +372,23 @@ Tunables are configured using **Key** and **Value** fields in the launch configu

Use the **“+ Add Tunable”** button to add additional entries. Each row can be removed using the **“X”** button.

Tunables are case sensitive, and only allowed tunables will be allowed to be saved in the wizard.

Tunables control runtime behavior and performance characteristics. Most tunables have default values, so you only need to specify a value if you want to override them.

<img width="790" height="176" alt="tunables_gui" src="https://github.com/user-attachments/assets/f78d5471-bfff-4cfb-b6cf-62c787a8e31b" />

<img width="360" height="177" alt="tunables_launchjson" src="https://github.com/user-attachments/assets/0d70044d-d04c-4f47-958c-175c6e67f3f9" />

### List of allowed Tunables

In order to prevent unexpected tunables from being ran, there is a list of allowed tunables at `constants/tunables.json` which contains all valid tunables. Tunables are case sensitive and the values type must match what is expected. For example boolean values will only allow true or false. If a invalid tunable is saved in the `launch.json` , when you attempt to run daffodil you will encounter a error saying `Cancel` or `Ignore invalid tunable`. Cancel will send you back where you fix the error, and Ignore invalid tunable runs daffodil but does not register the invalid tunable. The tunable will stay in your launch.json however. The tunables.json file will be updated as new tunables are added and removed.

The validation for tunable values is reasonably loose to allow for possibly unknown tunables to be applied. For example suppressSchemaDefinitionWarnings documentation didn't explicitly list all the possible values, but unqualifiedPathStepPolicy explicitly stated the allowed values so only those values are allowing.

<img width="914" height="718" alt="image" src="https://github.com/user-attachments/assets/22b24b29-de7e-4ec9-b012-85fe8f1bb54c" />


### Reference
- https://daffodil.apache.org/tunables/

Expand Down
1 change: 1 addition & 0 deletions project/Rat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ object Rat {
file("build/package/NOLICENSE"),
file("build/package/NONOTICE"),
file("src/tests/data/test.txt"),
file("constants/tunables.json"),
file("debugger/src/test/data/emptyData.xml"),
file("debugger/src/test/data/emptyInfoset.xml"),
file("debugger/src/test/data/notInfoset.xml"),
Expand Down
62 changes: 59 additions & 3 deletions src/adapter/activateDaffodilDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as rootCompletion from '../rootCompletion'
import { tmpdir } from 'os'
import JSZip from 'jszip'
import { rm } from 'node:fs/promises'
import { getTunables } from './extension'

import {
CancellationToken,
Expand Down Expand Up @@ -766,16 +767,15 @@ class DaffodilConfigurationProvider
constructor(context: vscode.ExtensionContext) {
this.context = context
}

/**
* Massage a debug configuration just before a debug session is being launched,
* e.g. add all missing attributes to the debug configuration.
*/
resolveDebugConfiguration(
async resolveDebugConfiguration(
folder: WorkspaceFolder | undefined,
config: DebugConfiguration,
token?: CancellationToken
): ProviderResult<DebugConfiguration> {
): Promise<DebugConfiguration | undefined> {
// if launch.json is missing or empty
if (!config.type && !config.request && !config.name) {
config = getConfig({ name: 'Launch', request: 'launch', type: 'dfdl' })
Expand All @@ -791,6 +791,62 @@ class DaffodilConfigurationProvider
data: config.data || '${command:AskForDataName}',
}

const validTunables = getTunables(this.context)
const currentTunables = config.tunables ?? {}

const invalidTunables = Object.keys(currentTunables).filter(
(name) => !(name in validTunables)
)

const invalidValues = Object.entries(currentTunables)
.filter(([name, value]) => {
if (!(name in validTunables)) {
return false
}

const type = validTunables[name]

switch (type) {
case 'boolean':
return value !== 'true' && value !== 'false'

case 'number':
return isNaN(Number(value))

case 'string':
return typeof value !== 'string'

default:
return false
}
})
.map(([name]) => name)

const invalid = [...invalidTunables, ...invalidValues]

if (invalid.length > 0) {
const messages = [
...invalidTunables.map((name) => `${name} (invalid tunable)`),
...invalidValues.map((name) => `${name} (invalid value)`),
]

const choice = await vscode.window.showWarningMessage(
`Invalid tunables found:\n\n${messages.join('\n')}`,
{ modal: true },
'Ignore Invalid Tunables'
)

if (choice !== 'Ignore Invalid Tunables') {
return undefined
}

config.tunables = { ...config.tunables }

for (const invalidTunable of invalid) {
delete config.tunables[invalidTunable]
}
}

let dataFolder = config.data

if (
Expand Down
13 changes: 13 additions & 0 deletions src/adapter/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import * as vscode from 'vscode'
import * as position from '../position'
import * as fs from 'fs'
import * as path from 'path'
import { ProviderResult } from 'vscode'
import { DaffodilDebugSession } from './daffodilDebug'
import {
Expand All @@ -45,6 +47,17 @@ export function deactivate() {
position.deactivate()
}

export function getTunables(
context: vscode.ExtensionContext
): Record<string, string> {
const file = path.join(context.extensionPath, 'constants', 'tunables.json')

const data = fs.readFileSync(file, 'utf8')
const json = JSON.parse(data)

return json
}

export class InlineDebugAdapterFactory
implements vscode.DebugAdapterDescriptorFactory
{
Expand Down
20 changes: 20 additions & 0 deletions src/launchWizard/launchWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { DFDLDebugger } from '../classes/dfdlDebugger'
import { VSCodeLaunchConfigArgs } from '../classes/vscode-launch'
import { DataEditorConfig } from '../classes/dataEditor'
import { parse as jsoncParse } from 'jsonc-parser'
import * as path from 'path'

let launchWizard: LaunchWizard | undefined

Expand Down Expand Up @@ -279,6 +280,20 @@ async function createWizard(ctx: vscode.ExtensionContext) {
let panel = launchWiz.getPanel()
panel.webview.html = launchWiz.getWebViewContent()

//Read in list of valid

const tunablesPath = path.join(
ctx.extensionPath,
'constants',
'tunables.json'
)

const tunables = JSON.parse(fs.readFileSync(tunablesPath, 'utf8'))
panel.webview.postMessage({
command: 'loadTunables',
tunables: tunables,
})

panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.command) {
Expand Down Expand Up @@ -782,6 +797,11 @@ class LaunchWizard {
<p class="setting-description">Key/value configuration options</p>

<table style="width: 100%; border-collapse: collapse; margin-top: 5px;">
<style>
td {
vertical-align: top;
}
</style>
<thead>
<tr>
<th style="text-align: left;">Key</th>
Expand Down
Loading
Loading