{"id":24312007,"url":"https://github.com/sdilts/restartable-c-exceptions","last_synced_at":"2025-04-12T01:43:32.049Z","repository":{"id":93530213,"uuid":"178489052","full_name":"sdilts/restartable-c-exceptions","owner":"sdilts","description":"An experimental library for implementing restartable exceptions in C.","archived":false,"fork":false,"pushed_at":"2019-10-07T01:50:18.000Z","size":36,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T21:23:04.265Z","etag":null,"topics":["c","error-handling"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sdilts.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-29T23:44:35.000Z","updated_at":"2024-05-29T20:12:49.000Z","dependencies_parsed_at":"2023-06-19T10:32:16.346Z","dependency_job_id":null,"html_url":"https://github.com/sdilts/restartable-c-exceptions","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdilts%2Frestartable-c-exceptions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdilts%2Frestartable-c-exceptions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdilts%2Frestartable-c-exceptions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdilts%2Frestartable-c-exceptions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sdilts","download_url":"https://codeload.github.com/sdilts/restartable-c-exceptions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248505922,"owners_count":21115354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","error-handling"],"created_at":"2025-01-17T07:29:41.370Z","updated_at":"2025-04-12T01:43:32.027Z","avatar_url":"https://github.com/sdilts.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Conditions and Exceptions in C\n\nThis repository contains an implementation of `setjmp`/`longjmp` based\nerror handling for C programs. It contains facilities for throwing\nand catching exceptions, as well as establishing finalizers to cleanup\nresources when a non-local exit occurs. Experimental support for\nrestarts is also available.\n\nThe `try {} catch {} finally {}` pattern is supported, with a twist:\nbefore the stack is unwound and finalizers executed, a `handler`\nfunction is called that decides whether to ignore the error, decline\nto handle the error, or unwind the stack to the location where the\nhandler was established. Because this mechanism is marginally useful\nbeyond just error handling, events are known as `condition`s rather\nthan errors. You can read about the benefits of a mechanism like this\nhere: https://news.ycombinator.com/item?id=8475633\n\n## Limitations\n\nThe major limitation to this implementation is that strings are used\nto represent condition types and restarts. This could cause name\ncollisions between two different modules, and prevents errors from\ninheriting from one another. This could easily be changed, as the\nstrings are used for identification and nothing else.\n\nThe second limitation is the one present when using any sort of\nnon-local exits in c: you may bypass any cleanup code not present in a\nfinalizer. This is most relevant when using callbacks with an external\nlibrary that doesn't use this condition system, as establishing finalizers avoids\nthis problem in code that has access to them. To this end, any\nfunction that is used as a callback to an external library should\nestablish handlers for any error that may be signaled during its execution.\n\nBecause `setjmp`/`longjmp` are being used, variable-modified types\nlike VLAs will cause undefined behavior and memory leaks.\n\n## Usage\nA complete description of each function and macro is available in the\n`include/exceptions.h` file.\n\n### Non local exits\nThe simplest case when an error is received is to unwind the call stack\nto where the handler was established, and run some code to handle the\nerror. In Python, the code could look something like this:\n\n``` python3\ntry:\n    print(\"I'm about to throw an error!\")\n    raise Error\nexcept Error:\n    print(\"An error occurred\")\n    exit(1)\n````\nBecause we can't introduce any new syntax to C, our version is\nsignificantly uglier. In addition, we need to have a handler function\nthat returns `HANDLER_ABORT`, which signals to the condition machinery\nto perform a non-local exit.\n\nThe handler function is extremely straightforward:\n``` c\nenum handler_result error_abort_handler(struct condition *cond, const void *data) {\n\treturn HANDLER_ABORT;\n}\n```\nThe `data` pointer will be explained in the next section.\n\nTo establish the try/catch blocks, we need to establish a\n`condition_handler` struct, then register it with the condition\nmachinery. To make this more succinct, two macros are provided:\n`INIT_STATIC_HANDLER` and `REGISTER_HANDLER`. `INIT_STATIC_HANDLER` is\njust some syntax sugar to initialize a `condition_handler` struct,\nwhile the `REGISTER_HANDLER` macro establishes the\n`except Error: ...` portion of our code. Here's the first portion:\n``` c\n\tstruct condition_handler aborter = INIT_STATIC_HANDLER(\"error\", error_abort_handler, NULL);\n```\nThis initializes a `condition_handler` struct that handles the\ncondition called `\"error\"`, and uses the `error_abort_handler`\nfunction to decide what action is taken.\n\nWe can then write our error handling code. To explain it, the rest of\nthe code will be included:\n``` c\n\t// except block:\n\tREGISTER_HANDLER(\u0026aborter) {\n\t\tprintf(\"An error occurred\");\n\t\tprint_condition(aborter.condition);\n\t\tdestroy_condition(aborter.condition);\n\t\tgoto handler_scope;\n\t}\n\t// try block:\n\t{\n\t\tprintf(\"I'm about to throw an error!\");\n\t\tthrow(\"error\", \"A diagnostic message\");\n\t}\n\t// unregister the handler:\n handler_scope:\n\tunregister_handler(\u0026aborter);\n```\nThe `REGISTER_HANDLER() { ... }` portion tells the machinery about the\naborter condition handler, and provides the code that is run when an\n`\"error\"` condition is signaled. In the handling code, we need to jump\npast the try block, otherwise it will be ran again. You could use some\nsort of flag instead of `goto`, but that just adds even more noise to\nthis already messy code snippet, especially when multiple condition\nhandlers are present in a function. To understand why the code is\nformatted this way, research the function `setjmp` and look at the\nmacro definition of `REGISTER_HANDLER`.\n\n### Handler functions\n\nHandler functions give us the ability to decide if the code\nassociated with it is capable of handling the error, and provides some\nside benefits that aren't directly related to error handling. To do\nany of this, however, it needs information about the condition being\nsignaled and the context surrounding the error. This is provided in\ntwo ways: the signaled condition is provided as an argument, as well\nas a pointer to some data.\n\n#### The void *data pointer\n\nThe data argument provides a crude way of having lambda functions in\nc. By passing pointers to variables in the calling context, we can\nprovide handler functions information about the state of the execution\nenvironment. An example is probably the easiest way to see how this works:\n``` c\nstruct env_data {\n\tvolatile int *const a;\n}\n\nvoid print_env(void *data) {\n\tstruct env_data *env = (struct env_data *)data;\n\tprintf(\"a = %d\\n\", env_data-\u003ea);\n}\n\nvoid modify_env(void *data) {\n\tstruct env_data *env = (struct env_data *)data;\n\tenv_data-\u003ea = 30;\n}\n\nint main(void) {\n\tvolatile int a = 2;\n\tstruct env_data data = {\n\t\t.a = \u0026a\n\t};\n\tprint_env((void *)data);\n\tmodify_env((void *)data);\n\tprintf(\"a = %d\\n\", a);\n}\n```\nThe output of this program should be fairly obvious to most c\nprogrammers, but is worth noting in the context of lambda\nfunctions. The output is:\n```\na = 2\na = 30\n```\nTo avoid accidentally changing anything you shouldn't, it is a good\nidea to declare pointers in your environment struct as\nconstant. Along the same lines, declaring variables passed in\nthis manner as `volatile` prevents the compiler from making any\nassumptions about the value of the variable and causing hard to\ntrack down issues. If you don't modify the variable in the handler\nfunction, the variable doesn't need to be declared `volatile`.\n\nThe `data` member in the `condition_handler` struct stores the pointer\nto this data. If you are using the `INIT_STATIC_HANDLER` macro, this\nis the last argument to the macro.\n\n#### Handling and declining errors\n\nIf the handler function decides that it cannot make a decision to\nunwind the stack, it can return `HANDLER_PASS` to tell the machinery\nto find another error handler. Similarly, if no further action needs to be\ntaken, then it can return `HANDLER_HANDLED` to return control to where\nthe condition was signaled.\n\n## Finalizers\n\nFinalizers are guaranteed to run when the call stack is\nunwound. Finalizer functions operate in a similar way to handlers,\nexcept there is no meaningful information to return. You register them\nwith `register_finalizer` and remove them with\n`unregister_finalizer`. For programmer convenience, finalizer functions\nare automatically ran when they are unregistered.\n\n## Restarts\n\nRestarts provide a way of allowing higher level code to choose how to\nfix a problem. Use `register_restart` and `unregister_restart` to make\nthe machinery aware of them, and `invoke_restart` to actually call\none. Note that this function can return 3 possible values:\n+ `RESTART_SUCCEED`: the restart was available and was able to perform\n  its desired action. Any handler function that receives this result\n  must return `HANDLER_HANDLED`.\n+ `RESTART_FAIL`: the restart was unable to perform its desired\n  actions and was unsuccessful.\n+ `RESTART_NOT_FOUND`: The restart that was specified does not exist.\n\n\n## Other uses for the condition system\n\n`HANDLER_HANDLED` and `HANDLER_PASS` have interesting applications\nbeyond error handling. While other more direct mechanisms exist, they\ncan be used for logging or notifying higher level code when certain\nevents occur. Whether or not this is a good idea is up to the\nindividual designer or programmer. It can abstract away\nsome of the mechanisms associated with these notifications, but at the\ncost of a more complex code path and the extra runtime associated with it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsdilts%2Frestartable-c-exceptions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsdilts%2Frestartable-c-exceptions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsdilts%2Frestartable-c-exceptions/lists"}