diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 32f063c44..ae0256219 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -97,7 +97,11 @@ jobs: # broken link still produces a site worth inspecting -- so the # step fails on the exit code: 1 when the check found a problem, # 2 when the build could not run. - run: node builder/tbdocs.mjs --src docs --no-fetch-assets --check-audit-index + # + # --no-help-archive: a local build ends by zipping the offline tree + # for the IDE help add-in. CI has no use for that archive and must + # not spend time on it. + run: node builder/tbdocs.mjs --src docs --no-fetch-assets --no-help-archive --check-audit-index # The gates both workflows run, in one list: see # .github/actions/run-gates/action.yml, which check_ci_workflows.mjs # checks against test.bat and check.bat. Each gate is its own group in diff --git a/.github/workflows/tbdocs-gh-pages.yml b/.github/workflows/tbdocs-gh-pages.yml index da359aed7..9de489c0a 100644 --- a/.github/workflows/tbdocs-gh-pages.yml +++ b/.github/workflows/tbdocs-gh-pages.yml @@ -92,7 +92,11 @@ jobs: # baseurl off the config it just built with. Only the # online tree gets a base path -- the offline tree's links are all # relative after the rewrite. - run: node builder/tbdocs.mjs --src docs --url '${{ steps.pages.outputs.origin }}' --baseurl '${{ steps.pages.outputs.base_path }}' --no-fetch-assets --check-audit-index + # + # --no-help-archive: a local build ends by zipping the offline tree + # for the IDE help add-in. CI has no use for that archive and must + # not spend time on it. + run: node builder/tbdocs.mjs --src docs --url '${{ steps.pages.outputs.origin }}' --baseurl '${{ steps.pages.outputs.base_path }}' --no-fetch-assets --no-help-archive --check-audit-index # The gates both workflows run, in one list: see # .github/actions/run-gates/action.yml, which check_ci_workflows.mjs # checks against test.bat and check.bat. checks.yml runs only on pull diff --git a/.gitignore b/.gitignore index 0ce436d08..d38d8daea 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,9 @@ indexer/.packages/ # docs/assets/fonts/ -- only the archives are throwaway. /.font-cache/ -# The help add-in's documentation archive is built by scripts/build_help_archive.mjs -# from docs/_site-offline, and is too large to commit. +# The help add-in's documentation archive is written by a local build of the +# documentation (builder/help-archive-step.mjs) from docs/_site-offline, and is +# too large to commit. /add-in/Resources/HELP/ # A bug reproducer's zip is only for attaching to its GitHub issue (see diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index f30e3634f..cc9917c14 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -464,41 +464,6 @@ What does not reproduce it: `import`'s project path and the printing commands ta --- -## `import` of a folder with no `Settings` file fails without saying why - -*DEFERRED until after v1* - -**Describe the bug** -Given a folder with no `Settings` file at its top, `import` lists the files it read, ends `... FAILED` with no `ERROR:` line, and writes nothing. Every other failure measured names its cause. The refusal is right, and the silence is not. - -**To Reproduce** -Steps to reproduce the behavior: -1. Unzip `import-no-settings-silent.zip` (it holds `import-no-settings-silent.twinproj`, an ordinary project) and export it: `twinBASIC_win32.exe export C:\p\import-no-settings-silent.twinproj C:\p\src\`. -2. Import only the folder `Sources`, which has no `Settings` file at its top: `twinBASIC_win32.exe import C:\p\x.twinproj C:\p\src\Sources\`. -3. See: - ``` - importing into "C:\p\x.twinproj" from "C:\p\src\Sources\"... - IMPORTED: C:\p\src\Sources\\Startup.twin - ... FAILED - ``` -4. See that there is no `ERROR:` line, the exit code is 0, and `C:\p\x.twinproj` was not written. - -**Expected behavior** -An `ERROR:` line that names the cause, such as `ERROR: no Settings file in the input folder`, before `... FAILED`, as the other failures print. A failure should also not exit with 0. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 995 - -**Additional context** -Severity: minor, because the refusal is right. - -Also on BETA 983, where a copy of the HelloWorld sample's tree with `Settings` deleted behaves the same: it lists `Resources\ICON\twinBASIC.ico` and `Sources\HelloWorld.twin`, ends `... FAILED` and writes no project. - - - ---- - ## Export Project stops at a read-only file after deleting everything before it, and the IDE reports nothing *DEFERRED until after v1* @@ -590,55 +555,6 @@ What does not reproduce it: choosing **Ignore** without pressing a step key firs --- -## A `Static` in a Module procedure cannot be initialised with an argument constructor of a class declared later in the project - -*DEFERRED until after v1* - -**Describe the bug** -A `Static` variable in a Module procedure, initialised with `New` and a constructor that takes arguments, fails with TB5074 when the class is declared later in the project than the Module, in another file or further down the same file. With the class declared first, the same line compiles. `Dim` with the same initialiser compiles in both orders, so the declaration's position in the project changes whether a valid `Static` compiles. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `static-ctor-args.twinproj` (attached as `static-ctor-args.zip`). The project lists `Probe.twin`, which holds the Module, before `ProbeDog.twin`, which holds the class: - ``` - ' Probe.twin - Module Probe - Public Sub T() - Static s As Dog = New Dog("Rex") - Debug.Print s.GetName() - End Sub - End Module - - ' ProbeDog.twin - Private Class Dog - Private m_Name As String - Public Sub New(ByVal Name As String) - m_Name = Name - End Sub - Public Function GetName() As String - Return m_Name - End Function - End Class - ``` -2. See the project fail to compile with `TB5074 Could not bind to parameterized constructor of class 'Dog'. No compatible Sub New() method found`, at the `New`. -3. Move the `Dog` class into `Probe.twin`, above the Module: the project compiles with no error. - -What does not reproduce it, each measured with the class declared later than the Module unless it says otherwise: `Dim s As Dog = New Dog("Rex")` compiles, and so does `Static s As Dog` with `Set s = New Dog("Rex")`; a `Static` with a constructor that takes no arguments compiles; the same `Static` in a method of a Class compiles; and the same `Static` with the class declared first compiles, in a Module procedure, a Function, a Class method and a Property Get, in one file or two, with the class `Private` or `[COMCreatable(False)]`. The failure holds for a `Private` class and a public `[COMCreatable(False)]` one, and with a second constructor beside the one that takes arguments. - -**Expected behavior** -The declaration compiles whichever of the Module and the class comes first, as `Dim` does and as `Static` does when the class is first. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 995 - -**Additional context** -Also on BETA 983. Severity: a valid declaration does not compile, and which projects it affects depends on file order, so it appears and disappears as files are added. The workaround is a `Static` without an initialiser and a `Set` on first use, or declaring the class first. The order is the order of the files inside the `.twinproj`; the reproducer is packed with `Probe.twin` before `ProbeDog.twin`. - - - ---- - ## *Import from file...* leaves the imported package unticked *DEFERRED until after v1* @@ -786,42 +702,6 @@ Severity: low; the IDE recovers, but the project is compiled again, and LLVM-com --- -## Builds running at the same time in one TEMP folder sometimes fail to write the type library - -*DEFERRED until after v1* - -**Describe the bug** -When several twinBASIC IDEs build at the same time, and their `TEMP` is the same folder, which is the default for one user, a build now and then fails while writing the type library, with nothing wrong in the project. The same build passes when repeated, or when it runs alone. With a different `TEMP` folder for each IDE, the failure did not occur. - -**To Reproduce** -Steps to reproduce the behavior: -1. Unzip `concurrent-builds-shared-temp.zip` (it holds `concurrent-builds-shared-temp.twinproj`, an ordinary console project with an empty `Sub Main`) and copy the project into eight folders, one copy each. -2. Open each copy in its own instance of the IDE, all eight running at once, with the default environment, so that every instance has the same `TEMP`. -3. Press **Build** in all eight at about the same time, and repeat. About one build in 24 fails; the DEBUG CONSOLE of the one that failed shows: - ``` - [BUILD] Starting... - [TYPELIB] failed to finalize typelibrary. Disk error? - [LINKER] FAILED to create type library - [BUILD] failed - ``` -4. Control: start each instance with `TEMP` and `TMP` set to a folder of its own (for example from a Command Prompt, `set TEMP=C:\t\1` and `set TMP=C:\t\1`, then start the IDE from it). The failure does not occur. - -Measured with the documentation's build harness, which starts each IDE on its own port and presses Build through it, eight IDEs at once, over 192 builds each way: 8 failed with one `TEMP` for all eight, and 0 failed with a `TEMP` folder for each. With one or two IDEs at once, 0 of 120 failed. Distinct project names and project ids in each copy did not prevent it. The failing build is a different one each time. - -**Expected behavior** -Each build writes its type library and succeeds, however many IDEs share the `TEMP` folder. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 995 - -**Additional context** -Severity: low; the build passes when repeated, and an IDE run by a person rarely builds at the same moment as another. It affects tools that build several projects at once. The compiler imports `GetTempFileNameW`, and its messages show that it writes the type library to a file and reads it back (`[TYPELIB] failed to read in generated type library file` is the message beside this one). A temporary file name that two processes both use would explain the counts; that is an inference, not observed. The temp folder is empty after the builds, so whatever is written there is deleted. - - - ---- - ## An `Interface` declared with the identifier of `IUnknown` compiles, and calling its method ends in an access violation **Describe the bug** @@ -835,13 +715,13 @@ Steps to reproduce the behavior: Private Interface IUnk Sub Dummy() End Interface - + Private Class RC Implements IUnk Private Sub IUnk_Dummy() Implements IUnk.Dummy End Sub End Class - + Module Startup Public Sub Main() Dim u As IUnk @@ -875,91 +755,6 @@ What was tried: --- -## A late-bound call that passes arguments and fails is issued a second time, without them - -**Describe the bug** -When a late-bound call that passes arguments fails, twinBASIC calls `Invoke` a second time, as a property read (`wFlags` 3) with no arguments. A `Sub` called with an argument it does not take therefore runs twice before error 13 is raised, a failed property assignment runs the property's `Property Get` afterwards, and the error the caller sees is replaced. Observed in a run of the reproducer project, and with `Invoke` implemented by a class that records its calls. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `latebound-call-retried.twinproj` (attached as `latebound-call-retried.zip`). Its one source file, `Startup.twin`, holds a class and `Sub Main`. `Widget` has `Sub Hello()`, which counts how often it runs, and a `Property Let Prop` that raises error 5 beside a `Property Get Prop` that counts how often it runs. -2. Run it. `Main` calls `o.Hello 1` and `o.Prop = 1` through `Dim o As Object = New Widget`, with `On Error Resume Next`, and prints the error and the counts after each: - ``` - o.Hello 1: error 13, ran 2 time(s), Property Get ran 0 - o.Boom 1: error 5, ran 1 time(s), Property Get ran 0 - x = o.BoomFn(1): error 5, ran 1 time(s), Property Get ran 0 - o.Prop = 1: error -2147352567, ran 1 time(s), Property Get ran 1 - w.Boom 1 (early bound): error 5, ran 1 time(s), Property Get ran 0 - ``` -3. See `Hello` run twice, and `Property Get Prop` run after the `Property Let` that failed. The error from the assignment is `&H80020009` (`DISP_E_EXCEPTION`) where the `Property Let` raised 5. -4. To see the second call, implement `IDispatch` in a `NotDispatchable` class whose `Invoke` prints `wFlags`, `pDispParams.cArgs` and whether `pVarResult` is null, and returns a failure code. With `Fail3` returning `DISP_E_TYPEMISMATCH` and `Fail5` raising error 5: - ``` - o.Fail3 1 Invoke flags=1 cArgs=1 result=null, then Invoke flags=3 cArgs=0 result=set - o.Fail3 = 5 Invoke flags=4 cArgs=1 result=null, then Invoke flags=3 cArgs=0 result=set - o.Fail5 = 5 Invoke flags=4 cArgs=1 result=null, then Invoke flags=3 cArgs=0 result=set - Set o.Fail1 = e Invoke flags=8 cArgs=1 result=null, then Invoke flags=3 cArgs=0 result=set - ``` - -**Expected behavior** -One `Invoke` per late-bound call, as a raw `Invoke` does: calling `Invoke` directly with the same argument runs `Hello` once and returns `DISP_E_TYPEMISMATCH`. In VB6 the same two statements fail without running anything the callee counts: `o.Hello 1` raises error 450, *Wrong number of arguments or invalid property assignment*, with `Hello` run 0 times, and `o.Prop = 1` raises error 5 from the `Property Let` with the `Property Get` run 0 times. The VB6 project is attached as `latebound-call-retried-vb6.zip`; it prints `o.Hello 1 -> error 450 (1C2) [Wrong number of arguments or invalid property assignment]` with `Hits=0`, and `o.Prop = 1 -> error 5 (5) [Invalid procedure call or argument]` with `Hits=1 Gets=0` (the one run is the `Property Let`). At the least a failed call must not run the callee again, and the error of the assignment must be the one the `Property Let` raised. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 995 - -**Additional context** -Severity: a late-bound call with a wrong argument count has side effects twice, and a late-bound property assignment that fails calls the getter, which may be expensive or have effects of its own. - -What does not reproduce it: a statement with no arguments (`o.Fail3`, `o.Hello`, `x = o.Fail3`); a statement that raises from inside the callee (`o.Boom 1` and `x = o.BoomFn(1)` run once and keep error 5); `CallByName` with an argument, for every call type (one `Invoke`); a call that succeeds; early-bound calls. What does reproduce it: `o.Hello 1`, `o.Hello(1)`, `o.Hello 1, 2`, the same through a `Variant` holding the object, and `o.Fn 1` for a `Function` with no parameters (ran twice, error 13). An assignment is repeated whatever the failure was (`DISP_E_TYPEMISMATCH`, `DISP_E_MEMBERNOTFOUND`, `E_FAIL`, an error raised by the setter); a statement call is repeated after `DISP_E_TYPEMISMATCH`, but not after an error raised in the callee. - -The second call resembles VB's rule for `o.Member(args)` on a property that returns an object or a collection: read the property with no arguments, then apply the arguments to the result. It is applied after a failure of any call that has arguments. The single run of `Hello` in the raw `Invoke` case is also at odds with the COM contract, which gives `DISP_E_BADPARAMCOUNT` for too many arguments without running the member; it is left out of this entry. - - - ---- - -## A late-bound call to a member that does not exist raises &H80020006, and `CallByName` raises &H80004005, where VB6 raises 438 - -**Describe the bug** -Calling a member that an object does not have, through an `Object` variable, raises error `&H80020006` (`DISP_E_UNKNOWNNAME`, *Unknown name.*) instead of 438, *Object doesn't support this property or method*. `CallByName` with the same name raises `&H80004005`, *Unspecified error*. An `On Error` handler written for VB6 or VBA, which tests for 438, does not catch either. Observed in a run of the reproducer project. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `latebound-unknown-member-error.twinproj` (attached as `latebound-unknown-member-error.zip`). Its one source file, `Startup.twin`, holds a class `Widget` with a `Sub Hello()` and a `Sub Main` that calls members through `Object` variables, with `On Error Resume Next`. -2. Run it and read the DEBUG CONSOLE: - ``` - o.Nope -> error -2147352570 (80020006) [Unknown name.] - Collection.Nope -> error -2147352570 (80020006) [Unknown name.] - CallByName o, Nope -> error -2147467259 (80004005) [Unspecified error] - o.Hello (control) -> error 0 (0) [] - ``` - -**Expected behavior** -Error 438 for all three, as in VB6 and VBA. The same cases in a VB6 project (a class, a `Variant` holding it, a `Collection`, a `Scripting.Dictionary`, and `CallByName` on each of them), attached as `latebound-unknown-member-error-vb6.zip`: -``` -o.Nope (class) -> error 438 (1B6) [Object doesn't support this property or method] -v.Nope (class in a Variant) -> error 438 (1B6) [Object doesn't support this property or method] -Collection.Nope -> error 438 (1B6) [Object doesn't support this property or method] -Dictionary.Nope -> error 438 (1B6) [Object doesn't support this property or method] -CallByName class Nope -> error 438 (1B6) [Object doesn't support this property or method] -CallByName Dictionary Nope -> error 438 (1B6) [Object doesn't support this property or method] -CallByName Collection Nope -> error 438 (1B6) [Object doesn't support this property or method] -``` -twinBASIC already maps `DISP_E_MEMBERNOTFOUND` from `Invoke` to 438; `DISP_E_UNKNOWNNAME` from `GetIDsOfNames`, which is the answer for a name the object does not have, should be mapped the same way, and `CallByName` should not turn it into `E_FAIL`. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 995 - -**Additional context** -Severity: code ported from VB6 or VBA that probes an object for a member with `On Error` and 438 stops working, and the message names a COM code instead of the problem. - -The twinBASIC class's `GetIDsOfNames` itself is right: it returns `DISP_E_UNKNOWNNAME` for a name it does not know, as the contract says; the conversion into a run-time error is what differs. The same `&H80020006` came from a twinBASIC class, a `Collection`, a `Scripting.Dictionary` and a `Scripting.FileSystemObject`, and a `Variant` holding an object; `CallByName` gave `&H80004005` for a twinBASIC class and a `Dictionary`. Other failures are mapped as VB6 does: a call that `Invoke` rejects with `DISP_E_MEMBERNOTFOUND` raises 438, and a `Nothing` object raises 91. - - - ---- - ## Calling a method of `stdole.IDispatch` is a late-bound call by name and fails with &H80020006 **Describe the bug** @@ -1008,1295 +803,177 @@ What was tried: `GetIDsOfNames` and `Invoke` with zero arguments, and `GetTypeIn --- -## A twinBASIC class's `GetTypeInfo` with an `iTInfo` of 1 returns E_UNEXPECTED, not DISP_E_BADINDEX +## After Break Into Code stops in DoEvents, evaluating anything ends the program with an access violation **Describe the bug** -`IDispatch::GetTypeInfo` on an object of a twinBASIC class returns `E_UNEXPECTED` (`&H8000FFFF`) for any `iTInfo` other than 0. The object's `GetTypeInfoCount` returns 1, so 0 is the only valid index, and the contract gives `DISP_E_BADINDEX` for a bad one. Observed in a run of the reproducer project, calling the method through a project's own declaration of the interface. +**Break Into Code** (Ctrl+Break), pressed while a program is in `DoEvents`, stops inside the VB package rather than in the program: the IDE opens the package's `IdleMessageLoopBreak.twin` at `IdleMessageLoopBreakpoint`, which **Call Stack** shows above the program's procedure. Evaluating anything at that stop ends the program with a native access violation: a line in the Debug Console, even `? 1`, or any watch, which the IDE evaluates at every stop. The IDE then stays in break mode, with the yellow arrow on the package's line and **Stop** and the step commands enabled, although the program has ended. **To Reproduce** Steps to reproduce the behavior: -1. Open `gettypeinfo-badindex.twinproj` (attached as `gettypeinfo-badindex.zip`). Its one source file, `Startup.twin`, declares a copy of `IDispatch` with the same `[InterfaceId]` (the `stdole` declaration cannot be called), a class `Widget` with one `Sub`, and a `Sub Main` that calls `GetTypeInfoCount` and `GetTypeInfo` on a `Widget` through the copy. -2. Run it and read the DEBUG CONSOLE: +1. Open `break-in-doevents-evaluate-crash.twinproj` (attached as `break-in-doevents-evaluate-crash.zip`). `Main` loops on `DoEvents`: ``` - GetTypeInfoCount = 1 - GetTypeInfo(0): HRESULT 0, pointer returned True - GetTypeInfo(1): HRESULT 8000FFFF, error -2147418113, pointer returned False - GetTypeInfo(2): HRESULT 8000FFFF, error -2147418113, pointer returned False - GetTypeInfo(-1): HRESULT 8000FFFF, error -2147418113 + Public Sub Main() + Do + DoEvents + Loop + End Sub ``` - -**Expected behavior** -`DISP_E_BADINDEX` (`&H8002000B`), which the `IDispatch::GetTypeInfo` documentation gives for an index that is not valid, for an index the object does not have. `E_UNEXPECTED` (*Catastrophic failure*) is the code for a call made at a time when the object cannot take it, and it reads as a fault in the object. - -VB6 does not return `E_UNEXPECTED`. The same calls on a VB6 class, made through `DispCallFunc` (the VB6 project is attached as `gettypeinfo-badindex-vb6.zip`), return `TYPE_E_ELEMENTNOTFOUND` (`&H8002802B`) for every index but 0: -``` -GetTypeInfoCount = 1 (HRESULT 0) -GetTypeInfo(0): HRESULT 0, pointer returned True -GetTypeInfo(1): HRESULT 8002802B, pointer returned False -GetTypeInfo(2): HRESULT 8002802B, pointer returned False -GetTypeInfo(-1): HRESULT 8002802B, pointer returned False -``` -Either `DISP_E_BADINDEX` or VB6's `TYPE_E_ELEMENTNOTFOUND` says that the index names nothing; `E_UNEXPECTED` does not. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 995 - -**Additional context** -Severity: minor. A caller that checks for `DISP_E_BADINDEX` to learn that an object has no more type information gets a different code; `GetTypeInfo(0)` and `GetTypeInfoCount` are right. - - - ---- - -## `Err.Raise` without a source or a description leaves `Source` empty, and the defaults are not VBA's - -**Describe the bug** -`Err.Raise` called with only a number leaves `Err.Source` empty, where VBA and VB6 set it to the name of the project. It gives `Err.Description` an empty string for numbers such as 1, 95, 99 and 513, where VBA gives `Application-defined or object-defined error`, and `Automation error` for numbers from 1000 up, where VBA gives the same generic text. And an omitted argument no longer keeps the value an earlier `Err.Raise` left, which VBA-Docs describes for `Raise`. Observed in a run of the reproducer project, in the IDE and in a built exe alike. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `err-raise-defaults.twinproj` (attached as `err-raise-defaults.zip`) and run it (F5). Its `Sub Main` raises with `On Error Resume Next` and prints `[Err.Source] [Err.Description]` after each call, calling `Err.Clear` first except in the last case: +2. Press F5, then Ctrl+Break (**Run > Break**). The IDE opens `IdleMessageLoopBreak.twin` under `Packages/VB/Sources/SUPPORT`, stopped in `IdleMessageLoopBreakpoint`, and **Call Stack** shows `IdleMessageLoopBreakpoint` above `Main`. +3. Type `? 1` in the Debug Console and press Enter. The program ends, and the Debug Console prints: ``` - Err.Raise 5 - Err.Raise 1 - Err.Raise 513 - Err.Raise 1000 - Err.Raise 1000, "A.Src", "B desc" - Err.Raise 5 + (runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION) + 0001