diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..32eadd0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +.pytest_cache/ + +# .NET build output (golden_arraypool demo) +bin/ +obj/ diff --git a/README.md b/README.md index 49d5e53b..78ae391d 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ C# (шаблоны emit_* → реальный .NET; try/finally на straight- ### Запуск ```bash -cd ownlang +# запускать из корня репозитория (там, где лежит пакет ownlang/ и examples/) python -m ownlang check examples/ok_extern_calls.own # проверка python -m ownlang emit examples/golden_arraypool/buffer.own # проверка + печать C# python -m ownlang cfg examples/bad_maybe_release.own # дамп CFG @@ -60,7 +60,10 @@ python tests/run_tests.py # 42 кейса + c ```bash cd examples/golden_arraypool -dotnet run # требует .NET SDK; в песочнице PoC его нет +# Здесь лежат buffer.own (источник) и Program.cs (сгенерённый process + host). +# Своего .csproj PoC не возит; чтобы запустить — заверни Program.cs в console-проект: +dotnet new console -o demo && cp Program.cs demo/ && cd demo && dotnet run +# (требует .NET SDK; в песочнице PoC его нет — проверено по построению, не запуском) ``` `buffer.own` объявляет ресурс `Buffer` с шаблонами `emit_*`, отображающими его на @@ -208,7 +211,7 @@ fn process(size: int) { | **OWN004** | borrow убегает из своей области (например, `return` borrow'а) | | **OWN005** | use/… после move (**definite**) | | **OWN006** | `borrow_mut` при живом shared borrow | -| **OWN007** | move/consume владельца под живым borrow'ом | +| **OWN007** | move/consume/return владельца под живым borrow'ом | | **OWN008** | release владельца под живым borrow'ом | | **OWN009** | операция над ресурсом, который **мог** быть освобождён на каком-то пути (**maybe**) | | **OWN010** | операция над ресурсом, который **мог** быть перемещён на каком-то пути (**maybe**) | @@ -389,6 +392,6 @@ ownlang/ examples/ ok_*.own # проходят bad_*.own # падают с конкретным кодом - golden_arraypool/ # buffer.own + Program.cs + demo.csproj (dotnet run) + golden_arraypool/ # buffer.own + Program.cs (host-код; .csproj не входит) tests/run_tests.py # 42 кейса анализа + codegen smoke + golden smoke ``` diff --git a/bad_leak_branch.own.txt b/examples/bad_leak_branch.own similarity index 100% rename from bad_leak_branch.own.txt rename to examples/bad_leak_branch.own diff --git a/examples/bad_maybe_release.own b/examples/bad_maybe_release.own new file mode 100644 index 00000000..f14ce2ed --- /dev/null +++ b/examples/bad_maybe_release.own @@ -0,0 +1,18 @@ +module MaybeReleaseDemo + +resource Buffer { + acquire rent + release give +} + +// 'b' is released on the 'then' path only. At the join its state is +// inconsistent, so the following `use b` touches a maybe-released value +// (OWN009). The 'else' path never releases, so it also leaks (OWN001). +// Good for `python -m ownlang cfg examples/bad_maybe_release.own`. +fn use_after_maybe(flag: int) { + let b = acquire Buffer(flag); + if (flag) { + release b; + } + use b; +} diff --git a/Program.cs.txt b/examples/golden_arraypool/Program.cs similarity index 100% rename from Program.cs.txt rename to examples/golden_arraypool/Program.cs diff --git a/buffer.own.txt b/examples/golden_arraypool/buffer.own similarity index 100% rename from buffer.own.txt rename to examples/golden_arraypool/buffer.own diff --git a/examples/ok_extern_calls.own b/examples/ok_extern_calls.own new file mode 100644 index 00000000..671fba46 --- /dev/null +++ b/examples/ok_extern_calls.own @@ -0,0 +1,30 @@ +module ExternCalls + +resource Buffer { + acquire rent + release give +} + +// Host (C#) calls must declare what they do with ownership. The checker +// trusts these effect annotations and nothing else: an undeclared call that +// is handed an owned value is a hard OWN040. Borrow params are noescape, so +// the value cannot leak out through them. +extern fn Fill(borrow_mut Buffer); // writes through the buffer, hands it back +extern fn Hash(borrow Buffer); // reads only, hands it back +extern fn Store(consume Buffer); // takes ownership; caller must not reuse + +// Happy path: lend the buffer to the host mutably, then immutably, then hand +// it back ourselves. Both extern calls borrow, so 'b' is still owned after. +fn read_write(size: int) { + let b = acquire Buffer(size); + Fill(b); + Hash(b); + release b; +} + +// Happy path: give the buffer to the host for good. Store consumes it, so we +// neither release nor use 'b' afterwards — either of those would be OWN002. +fn hand_off(size: int) { + let b = acquire Buffer(size); + Store(b); +} diff --git a/ok_pool.own.txt b/examples/ok_pool.own similarity index 100% rename from ok_pool.own.txt rename to examples/ok_pool.own diff --git a/ownlang/__init__.py b/ownlang/__init__.py new file mode 100644 index 00000000..42bdc001 --- /dev/null +++ b/ownlang/__init__.py @@ -0,0 +1 @@ +"""OwnLang — a micro ownership/borrow checker PoC for .NET codegen.""" diff --git a/__main__.py b/ownlang/__main__.py similarity index 100% rename from __main__.py rename to ownlang/__main__.py diff --git a/analysis.py b/ownlang/analysis.py similarity index 97% rename from analysis.py rename to ownlang/analysis.py index 2ab46e47..89c2645a 100644 --- a/analysis.py +++ b/ownlang/analysis.py @@ -337,6 +337,14 @@ def step(self, ins, st: State) -> None: self.err("OWN002", f"'{ins.sym.name}' returned after it was released", ins.line) + else: + # returning an owner is an escape (consume): it needs Own + # permission, so a live loan on it is OWN007 just like move. + shared, mut = self.loans_on(st, ins.sym) + if shared or mut: + self.err("OWN007", + f"cannot return '{ins.sym.name}' while it is " + f"borrowed", ins.line) st.var[id(ins.sym)] = {VarState.ESCAPED} return diff --git a/ast_nodes.py b/ownlang/ast_nodes.py similarity index 100% rename from ast_nodes.py rename to ownlang/ast_nodes.py diff --git a/cfg.py b/ownlang/cfg.py similarity index 100% rename from cfg.py rename to ownlang/cfg.py diff --git a/codegen.py b/ownlang/codegen.py similarity index 100% rename from codegen.py rename to ownlang/codegen.py diff --git a/diagnostics.py b/ownlang/diagnostics.py similarity index 100% rename from diagnostics.py rename to ownlang/diagnostics.py diff --git a/lexer.py b/ownlang/lexer.py similarity index 100% rename from lexer.py rename to ownlang/lexer.py diff --git a/parser.py b/ownlang/parser.py similarity index 100% rename from parser.py rename to ownlang/parser.py diff --git a/run_tests.py b/tests/run_tests.py similarity index 98% rename from run_tests.py rename to tests/run_tests.py index 58ae2523..54af40bd 100644 --- a/run_tests.py +++ b/tests/run_tests.py @@ -98,6 +98,9 @@ def codes(src: str) -> list[str]: ("move_while_borrowed", "fn f(){ let b = acquire Buffer(1); borrow b as s { let c = move b; " "release c; } }", ["OWN007"]), + ("return_while_borrowed", + "fn f() -> Buffer { let b = acquire Buffer(1); borrow b as s " + "{ return b; } }", ["OWN007"]), ("release_while_borrowed", "fn f(){ let b = acquire Buffer(1); borrow b as s { release b; } }", ["OWN008"]),