@wazir-dev/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +111 -0
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +101 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/assets/composition-engine.mmd +34 -0
- package/assets/demo-script.sh +17 -0
- package/assets/logo-dark.svg +14 -0
- package/assets/logo.svg +14 -0
- package/assets/pipeline.mmd +39 -0
- package/assets/record-demo.sh +51 -0
- package/docs/README.md +51 -0
- package/docs/adapters/context-mode.md +60 -0
- package/docs/concepts/architecture.md +87 -0
- package/docs/concepts/artifact-model.md +60 -0
- package/docs/concepts/composition-engine.md +36 -0
- package/docs/concepts/indexing-and-recall.md +160 -0
- package/docs/concepts/observability.md +41 -0
- package/docs/concepts/roles-and-workflows.md +59 -0
- package/docs/concepts/terminology-policy.md +27 -0
- package/docs/getting-started/01-installation.md +78 -0
- package/docs/getting-started/02-first-run.md +102 -0
- package/docs/getting-started/03-adding-to-project.md +15 -0
- package/docs/getting-started/04-host-setup.md +15 -0
- package/docs/guides/ci-integration.md +15 -0
- package/docs/guides/creating-skills.md +15 -0
- package/docs/guides/expertise-module-authoring.md +15 -0
- package/docs/guides/hook-development.md +15 -0
- package/docs/guides/memory-and-learnings.md +34 -0
- package/docs/guides/multi-host-export.md +15 -0
- package/docs/guides/troubleshooting.md +101 -0
- package/docs/guides/writing-custom-roles.md +15 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-design.md +592 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +598 -0
- package/docs/plans/2026-03-15-docs-enforcement-plan.md +238 -0
- package/docs/readmes/INDEX.md +99 -0
- package/docs/readmes/features/expertise/README.md +171 -0
- package/docs/readmes/features/exports/README.md +222 -0
- package/docs/readmes/features/hooks/README.md +103 -0
- package/docs/readmes/features/hooks/loop-cap-guard.md +133 -0
- package/docs/readmes/features/hooks/post-tool-capture.md +121 -0
- package/docs/readmes/features/hooks/post-tool-lint.md +130 -0
- package/docs/readmes/features/hooks/pre-compact-summary.md +122 -0
- package/docs/readmes/features/hooks/pre-tool-capture-route.md +100 -0
- package/docs/readmes/features/hooks/protected-path-write-guard.md +128 -0
- package/docs/readmes/features/hooks/session-start.md +119 -0
- package/docs/readmes/features/hooks/stop-handoff-harvest.md +125 -0
- package/docs/readmes/features/roles/README.md +157 -0
- package/docs/readmes/features/roles/clarifier.md +152 -0
- package/docs/readmes/features/roles/content-author.md +190 -0
- package/docs/readmes/features/roles/designer.md +193 -0
- package/docs/readmes/features/roles/executor.md +184 -0
- package/docs/readmes/features/roles/learner.md +210 -0
- package/docs/readmes/features/roles/planner.md +182 -0
- package/docs/readmes/features/roles/researcher.md +164 -0
- package/docs/readmes/features/roles/reviewer.md +184 -0
- package/docs/readmes/features/roles/specifier.md +162 -0
- package/docs/readmes/features/roles/verifier.md +215 -0
- package/docs/readmes/features/schemas/README.md +178 -0
- package/docs/readmes/features/skills/README.md +63 -0
- package/docs/readmes/features/skills/brainstorming.md +96 -0
- package/docs/readmes/features/skills/debugging.md +148 -0
- package/docs/readmes/features/skills/design.md +120 -0
- package/docs/readmes/features/skills/prepare-next.md +109 -0
- package/docs/readmes/features/skills/run-audit.md +159 -0
- package/docs/readmes/features/skills/scan-project.md +109 -0
- package/docs/readmes/features/skills/self-audit.md +176 -0
- package/docs/readmes/features/skills/tdd.md +137 -0
- package/docs/readmes/features/skills/using-skills.md +92 -0
- package/docs/readmes/features/skills/verification.md +120 -0
- package/docs/readmes/features/skills/writing-plans.md +104 -0
- package/docs/readmes/features/tooling/README.md +320 -0
- package/docs/readmes/features/workflows/README.md +186 -0
- package/docs/readmes/features/workflows/author.md +181 -0
- package/docs/readmes/features/workflows/clarify.md +154 -0
- package/docs/readmes/features/workflows/design-review.md +171 -0
- package/docs/readmes/features/workflows/design.md +169 -0
- package/docs/readmes/features/workflows/discover.md +162 -0
- package/docs/readmes/features/workflows/execute.md +173 -0
- package/docs/readmes/features/workflows/learn.md +167 -0
- package/docs/readmes/features/workflows/plan-review.md +165 -0
- package/docs/readmes/features/workflows/plan.md +170 -0
- package/docs/readmes/features/workflows/prepare-next.md +167 -0
- package/docs/readmes/features/workflows/review.md +169 -0
- package/docs/readmes/features/workflows/run-audit.md +191 -0
- package/docs/readmes/features/workflows/spec-challenge.md +159 -0
- package/docs/readmes/features/workflows/specify.md +160 -0
- package/docs/readmes/features/workflows/verify.md +177 -0
- package/docs/readmes/packages/README.md +50 -0
- package/docs/readmes/packages/ajv.md +117 -0
- package/docs/readmes/packages/context-mode.md +118 -0
- package/docs/readmes/packages/gray-matter.md +116 -0
- package/docs/readmes/packages/node-test.md +137 -0
- package/docs/readmes/packages/yaml.md +112 -0
- package/docs/reference/configuration-reference.md +159 -0
- package/docs/reference/expertise-index.md +52 -0
- package/docs/reference/git-flow.md +43 -0
- package/docs/reference/hooks.md +87 -0
- package/docs/reference/host-exports.md +50 -0
- package/docs/reference/launch-checklist.md +172 -0
- package/docs/reference/marketplace-listings.md +76 -0
- package/docs/reference/release-process.md +34 -0
- package/docs/reference/roles-reference.md +77 -0
- package/docs/reference/skills.md +33 -0
- package/docs/reference/templates.md +29 -0
- package/docs/reference/tooling-cli.md +94 -0
- package/docs/truth-claims.yaml +222 -0
- package/expertise/PROGRESS.md +63 -0
- package/expertise/README.md +18 -0
- package/expertise/antipatterns/PROGRESS.md +56 -0
- package/expertise/antipatterns/backend/api-design-antipatterns.md +1271 -0
- package/expertise/antipatterns/backend/auth-antipatterns.md +1195 -0
- package/expertise/antipatterns/backend/caching-antipatterns.md +622 -0
- package/expertise/antipatterns/backend/database-antipatterns.md +1038 -0
- package/expertise/antipatterns/backend/index.md +24 -0
- package/expertise/antipatterns/backend/microservices-antipatterns.md +850 -0
- package/expertise/antipatterns/code/architecture-antipatterns.md +919 -0
- package/expertise/antipatterns/code/async-antipatterns.md +622 -0
- package/expertise/antipatterns/code/code-smells.md +1186 -0
- package/expertise/antipatterns/code/dependency-antipatterns.md +1209 -0
- package/expertise/antipatterns/code/error-handling-antipatterns.md +1360 -0
- package/expertise/antipatterns/code/index.md +27 -0
- package/expertise/antipatterns/code/naming-and-abstraction.md +1118 -0
- package/expertise/antipatterns/code/state-management-antipatterns.md +1076 -0
- package/expertise/antipatterns/code/testing-antipatterns.md +1053 -0
- package/expertise/antipatterns/design/accessibility-antipatterns.md +1136 -0
- package/expertise/antipatterns/design/dark-patterns.md +1121 -0
- package/expertise/antipatterns/design/index.md +22 -0
- package/expertise/antipatterns/design/ui-antipatterns.md +1202 -0
- package/expertise/antipatterns/design/ux-antipatterns.md +680 -0
- package/expertise/antipatterns/frontend/css-layout-antipatterns.md +691 -0
- package/expertise/antipatterns/frontend/flutter-antipatterns.md +1827 -0
- package/expertise/antipatterns/frontend/index.md +23 -0
- package/expertise/antipatterns/frontend/mobile-antipatterns.md +573 -0
- package/expertise/antipatterns/frontend/react-antipatterns.md +1128 -0
- package/expertise/antipatterns/frontend/spa-antipatterns.md +1235 -0
- package/expertise/antipatterns/index.md +31 -0
- package/expertise/antipatterns/performance/index.md +20 -0
- package/expertise/antipatterns/performance/performance-antipatterns.md +1013 -0
- package/expertise/antipatterns/performance/premature-optimization.md +623 -0
- package/expertise/antipatterns/performance/scaling-antipatterns.md +785 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +853 -0
- package/expertise/antipatterns/process/code-review-antipatterns.md +656 -0
- package/expertise/antipatterns/process/deployment-antipatterns.md +920 -0
- package/expertise/antipatterns/process/index.md +23 -0
- package/expertise/antipatterns/process/technical-debt-antipatterns.md +647 -0
- package/expertise/antipatterns/security/index.md +20 -0
- package/expertise/antipatterns/security/secrets-antipatterns.md +849 -0
- package/expertise/antipatterns/security/security-theater.md +843 -0
- package/expertise/antipatterns/security/vulnerability-patterns.md +801 -0
- package/expertise/architecture/PROGRESS.md +70 -0
- package/expertise/architecture/data/caching-architecture.md +671 -0
- package/expertise/architecture/data/data-consistency.md +574 -0
- package/expertise/architecture/data/data-modeling.md +536 -0
- package/expertise/architecture/data/event-streams-and-queues.md +634 -0
- package/expertise/architecture/data/index.md +25 -0
- package/expertise/architecture/data/search-architecture.md +663 -0
- package/expertise/architecture/data/sql-vs-nosql.md +708 -0
- package/expertise/architecture/decisions/architecture-decision-records.md +640 -0
- package/expertise/architecture/decisions/build-vs-buy.md +616 -0
- package/expertise/architecture/decisions/index.md +23 -0
- package/expertise/architecture/decisions/monolith-to-microservices.md +790 -0
- package/expertise/architecture/decisions/technology-selection.md +616 -0
- package/expertise/architecture/distributed/cap-theorem-and-tradeoffs.md +800 -0
- package/expertise/architecture/distributed/circuit-breaker-bulkhead.md +741 -0
- package/expertise/architecture/distributed/consensus-and-coordination.md +796 -0
- package/expertise/architecture/distributed/distributed-systems-fundamentals.md +564 -0
- package/expertise/architecture/distributed/idempotency-and-retry.md +796 -0
- package/expertise/architecture/distributed/index.md +25 -0
- package/expertise/architecture/distributed/saga-pattern.md +797 -0
- package/expertise/architecture/foundations/architectural-thinking.md +460 -0
- package/expertise/architecture/foundations/coupling-and-cohesion.md +770 -0
- package/expertise/architecture/foundations/design-principles-solid.md +649 -0
- package/expertise/architecture/foundations/domain-driven-design.md +719 -0
- package/expertise/architecture/foundations/index.md +25 -0
- package/expertise/architecture/foundations/separation-of-concerns.md +472 -0
- package/expertise/architecture/foundations/twelve-factor-app.md +797 -0
- package/expertise/architecture/index.md +34 -0
- package/expertise/architecture/integration/api-design-graphql.md +638 -0
- package/expertise/architecture/integration/api-design-grpc.md +804 -0
- package/expertise/architecture/integration/api-design-rest.md +892 -0
- package/expertise/architecture/integration/index.md +25 -0
- package/expertise/architecture/integration/third-party-integration.md +795 -0
- package/expertise/architecture/integration/webhooks-and-callbacks.md +1152 -0
- package/expertise/architecture/integration/websockets-realtime.md +791 -0
- package/expertise/architecture/mobile-architecture/index.md +22 -0
- package/expertise/architecture/mobile-architecture/mobile-app-architecture.md +780 -0
- package/expertise/architecture/mobile-architecture/mobile-backend-for-frontend.md +670 -0
- package/expertise/architecture/mobile-architecture/offline-first.md +719 -0
- package/expertise/architecture/mobile-architecture/push-and-sync.md +782 -0
- package/expertise/architecture/patterns/cqrs-event-sourcing.md +717 -0
- package/expertise/architecture/patterns/event-driven.md +797 -0
- package/expertise/architecture/patterns/hexagonal-clean-architecture.md +870 -0
- package/expertise/architecture/patterns/index.md +27 -0
- package/expertise/architecture/patterns/layered-architecture.md +736 -0
- package/expertise/architecture/patterns/microservices.md +753 -0
- package/expertise/architecture/patterns/modular-monolith.md +692 -0
- package/expertise/architecture/patterns/monolith.md +626 -0
- package/expertise/architecture/patterns/plugin-architecture.md +735 -0
- package/expertise/architecture/patterns/serverless.md +780 -0
- package/expertise/architecture/scaling/database-scaling.md +615 -0
- package/expertise/architecture/scaling/feature-flags-and-rollouts.md +757 -0
- package/expertise/architecture/scaling/horizontal-vs-vertical.md +606 -0
- package/expertise/architecture/scaling/index.md +24 -0
- package/expertise/architecture/scaling/multi-tenancy.md +800 -0
- package/expertise/architecture/scaling/stateless-design.md +787 -0
- package/expertise/backend/embedded-firmware.md +625 -0
- package/expertise/backend/go.md +853 -0
- package/expertise/backend/index.md +24 -0
- package/expertise/backend/java-spring.md +448 -0
- package/expertise/backend/node-typescript.md +625 -0
- package/expertise/backend/python-fastapi.md +724 -0
- package/expertise/backend/rust.md +458 -0
- package/expertise/backend/solidity.md +711 -0
- package/expertise/composition-map.yaml +443 -0
- package/expertise/content/foundations/content-modeling.md +395 -0
- package/expertise/content/foundations/editorial-standards.md +449 -0
- package/expertise/content/foundations/index.md +24 -0
- package/expertise/content/foundations/microcopy.md +455 -0
- package/expertise/content/foundations/terminology-governance.md +509 -0
- package/expertise/content/index.md +34 -0
- package/expertise/content/patterns/accessibility-copy.md +518 -0
- package/expertise/content/patterns/index.md +24 -0
- package/expertise/content/patterns/notification-content.md +433 -0
- package/expertise/content/patterns/sample-content.md +486 -0
- package/expertise/content/patterns/state-copy.md +439 -0
- package/expertise/design/PROGRESS.md +58 -0
- package/expertise/design/disciplines/dark-mode-theming.md +577 -0
- package/expertise/design/disciplines/design-systems.md +595 -0
- package/expertise/design/disciplines/index.md +25 -0
- package/expertise/design/disciplines/information-architecture.md +800 -0
- package/expertise/design/disciplines/interaction-design.md +788 -0
- package/expertise/design/disciplines/responsive-design.md +552 -0
- package/expertise/design/disciplines/usability-testing.md +516 -0
- package/expertise/design/disciplines/user-research.md +792 -0
- package/expertise/design/foundations/accessibility-design.md +796 -0
- package/expertise/design/foundations/color-theory.md +797 -0
- package/expertise/design/foundations/iconography.md +795 -0
- package/expertise/design/foundations/index.md +26 -0
- package/expertise/design/foundations/motion-and-animation.md +653 -0
- package/expertise/design/foundations/rtl-design.md +585 -0
- package/expertise/design/foundations/spacing-and-layout.md +607 -0
- package/expertise/design/foundations/typography.md +800 -0
- package/expertise/design/foundations/visual-hierarchy.md +761 -0
- package/expertise/design/index.md +32 -0
- package/expertise/design/patterns/authentication-flows.md +474 -0
- package/expertise/design/patterns/content-consumption.md +789 -0
- package/expertise/design/patterns/data-display.md +618 -0
- package/expertise/design/patterns/e-commerce.md +1494 -0
- package/expertise/design/patterns/feedback-and-states.md +642 -0
- package/expertise/design/patterns/forms-and-input.md +819 -0
- package/expertise/design/patterns/gamification.md +801 -0
- package/expertise/design/patterns/index.md +31 -0
- package/expertise/design/patterns/microinteractions.md +449 -0
- package/expertise/design/patterns/navigation.md +800 -0
- package/expertise/design/patterns/notifications.md +705 -0
- package/expertise/design/patterns/onboarding.md +700 -0
- package/expertise/design/patterns/search-and-filter.md +601 -0
- package/expertise/design/patterns/settings-and-preferences.md +768 -0
- package/expertise/design/patterns/social-and-community.md +748 -0
- package/expertise/design/platforms/desktop-native.md +612 -0
- package/expertise/design/platforms/index.md +25 -0
- package/expertise/design/platforms/mobile-android.md +825 -0
- package/expertise/design/platforms/mobile-cross-platform.md +983 -0
- package/expertise/design/platforms/mobile-ios.md +699 -0
- package/expertise/design/platforms/tablet.md +794 -0
- package/expertise/design/platforms/web-dashboard.md +790 -0
- package/expertise/design/platforms/web-responsive.md +550 -0
- package/expertise/design/psychology/behavioral-nudges.md +449 -0
- package/expertise/design/psychology/cognitive-load.md +1191 -0
- package/expertise/design/psychology/error-psychology.md +778 -0
- package/expertise/design/psychology/index.md +22 -0
- package/expertise/design/psychology/persuasive-design.md +736 -0
- package/expertise/design/psychology/user-mental-models.md +623 -0
- package/expertise/design/tooling/open-pencil.md +266 -0
- package/expertise/frontend/angular.md +1073 -0
- package/expertise/frontend/desktop-electron.md +546 -0
- package/expertise/frontend/flutter.md +782 -0
- package/expertise/frontend/index.md +27 -0
- package/expertise/frontend/native-android.md +409 -0
- package/expertise/frontend/native-ios.md +490 -0
- package/expertise/frontend/react-native.md +1160 -0
- package/expertise/frontend/react.md +808 -0
- package/expertise/frontend/vue.md +1089 -0
- package/expertise/humanize/domain-rules-code.md +79 -0
- package/expertise/humanize/domain-rules-content.md +67 -0
- package/expertise/humanize/domain-rules-technical-docs.md +56 -0
- package/expertise/humanize/index.md +35 -0
- package/expertise/humanize/self-audit-checklist.md +87 -0
- package/expertise/humanize/sentence-patterns.md +218 -0
- package/expertise/humanize/vocabulary-blacklist.md +105 -0
- package/expertise/i18n/PROGRESS.md +65 -0
- package/expertise/i18n/advanced/accessibility-and-i18n.md +28 -0
- package/expertise/i18n/advanced/bidirectional-text-algorithm.md +38 -0
- package/expertise/i18n/advanced/complex-scripts.md +30 -0
- package/expertise/i18n/advanced/performance-and-i18n.md +27 -0
- package/expertise/i18n/advanced/testing-i18n.md +28 -0
- package/expertise/i18n/content/content-adaptation.md +23 -0
- package/expertise/i18n/content/locale-specific-formatting.md +23 -0
- package/expertise/i18n/content/machine-translation-integration.md +28 -0
- package/expertise/i18n/content/translation-management.md +29 -0
- package/expertise/i18n/foundations/date-time-calendars.md +67 -0
- package/expertise/i18n/foundations/i18n-architecture.md +272 -0
- package/expertise/i18n/foundations/locale-and-language-tags.md +79 -0
- package/expertise/i18n/foundations/numbers-currency-units.md +61 -0
- package/expertise/i18n/foundations/pluralization-and-gender.md +109 -0
- package/expertise/i18n/foundations/string-externalization.md +236 -0
- package/expertise/i18n/foundations/text-direction-bidi.md +241 -0
- package/expertise/i18n/foundations/unicode-and-encoding.md +86 -0
- package/expertise/i18n/index.md +38 -0
- package/expertise/i18n/platform/backend-i18n.md +31 -0
- package/expertise/i18n/platform/flutter-i18n.md +148 -0
- package/expertise/i18n/platform/native-android-i18n.md +36 -0
- package/expertise/i18n/platform/native-ios-i18n.md +36 -0
- package/expertise/i18n/platform/react-i18n.md +103 -0
- package/expertise/i18n/platform/web-css-i18n.md +81 -0
- package/expertise/i18n/rtl/arabic-specific.md +175 -0
- package/expertise/i18n/rtl/hebrew-specific.md +149 -0
- package/expertise/i18n/rtl/rtl-animations-and-transitions.md +111 -0
- package/expertise/i18n/rtl/rtl-forms-and-input.md +161 -0
- package/expertise/i18n/rtl/rtl-fundamentals.md +211 -0
- package/expertise/i18n/rtl/rtl-icons-and-images.md +181 -0
- package/expertise/i18n/rtl/rtl-layout-mirroring.md +252 -0
- package/expertise/i18n/rtl/rtl-navigation-and-gestures.md +107 -0
- package/expertise/i18n/rtl/rtl-testing-and-qa.md +147 -0
- package/expertise/i18n/rtl/rtl-typography.md +160 -0
- package/expertise/index.md +113 -0
- package/expertise/index.yaml +216 -0
- package/expertise/infrastructure/cloud-aws.md +597 -0
- package/expertise/infrastructure/cloud-gcp.md +599 -0
- package/expertise/infrastructure/cybersecurity.md +816 -0
- package/expertise/infrastructure/database-mongodb.md +447 -0
- package/expertise/infrastructure/database-postgres.md +400 -0
- package/expertise/infrastructure/devops-cicd.md +787 -0
- package/expertise/infrastructure/index.md +27 -0
- package/expertise/performance/PROGRESS.md +50 -0
- package/expertise/performance/backend/api-latency.md +1204 -0
- package/expertise/performance/backend/background-jobs.md +506 -0
- package/expertise/performance/backend/connection-pooling.md +1209 -0
- package/expertise/performance/backend/database-query-optimization.md +515 -0
- package/expertise/performance/backend/index.md +23 -0
- package/expertise/performance/backend/rate-limiting-and-throttling.md +971 -0
- package/expertise/performance/foundations/algorithmic-complexity.md +954 -0
- package/expertise/performance/foundations/caching-strategies.md +489 -0
- package/expertise/performance/foundations/concurrency-and-parallelism.md +847 -0
- package/expertise/performance/foundations/index.md +24 -0
- package/expertise/performance/foundations/measuring-and-profiling.md +440 -0
- package/expertise/performance/foundations/memory-management.md +964 -0
- package/expertise/performance/foundations/performance-budgets.md +1314 -0
- package/expertise/performance/index.md +31 -0
- package/expertise/performance/infrastructure/auto-scaling.md +1059 -0
- package/expertise/performance/infrastructure/cdn-and-edge.md +1081 -0
- package/expertise/performance/infrastructure/index.md +22 -0
- package/expertise/performance/infrastructure/load-balancing.md +1081 -0
- package/expertise/performance/infrastructure/observability.md +1079 -0
- package/expertise/performance/mobile/index.md +23 -0
- package/expertise/performance/mobile/mobile-animations.md +544 -0
- package/expertise/performance/mobile/mobile-memory-battery.md +416 -0
- package/expertise/performance/mobile/mobile-network.md +452 -0
- package/expertise/performance/mobile/mobile-rendering.md +599 -0
- package/expertise/performance/mobile/mobile-startup-time.md +505 -0
- package/expertise/performance/platform-specific/flutter-performance.md +647 -0
- package/expertise/performance/platform-specific/index.md +22 -0
- package/expertise/performance/platform-specific/node-performance.md +1307 -0
- package/expertise/performance/platform-specific/postgres-performance.md +1366 -0
- package/expertise/performance/platform-specific/react-performance.md +1403 -0
- package/expertise/performance/web/bundle-optimization.md +1239 -0
- package/expertise/performance/web/image-and-media.md +636 -0
- package/expertise/performance/web/index.md +24 -0
- package/expertise/performance/web/network-optimization.md +1133 -0
- package/expertise/performance/web/rendering-performance.md +1098 -0
- package/expertise/performance/web/ssr-and-hydration.md +918 -0
- package/expertise/performance/web/web-vitals.md +1374 -0
- package/expertise/quality/accessibility.md +985 -0
- package/expertise/quality/evidence-based-verification.md +499 -0
- package/expertise/quality/index.md +24 -0
- package/expertise/quality/ml-model-audit.md +614 -0
- package/expertise/quality/performance.md +600 -0
- package/expertise/quality/testing-api.md +891 -0
- package/expertise/quality/testing-mobile.md +496 -0
- package/expertise/quality/testing-web.md +849 -0
- package/expertise/security/PROGRESS.md +54 -0
- package/expertise/security/agentic-identity.md +540 -0
- package/expertise/security/compliance-frameworks.md +601 -0
- package/expertise/security/data/data-encryption.md +364 -0
- package/expertise/security/data/data-privacy-gdpr.md +692 -0
- package/expertise/security/data/database-security.md +1171 -0
- package/expertise/security/data/index.md +22 -0
- package/expertise/security/data/pii-handling.md +531 -0
- package/expertise/security/foundations/authentication.md +1041 -0
- package/expertise/security/foundations/authorization.md +603 -0
- package/expertise/security/foundations/cryptography.md +1001 -0
- package/expertise/security/foundations/index.md +25 -0
- package/expertise/security/foundations/owasp-top-10.md +1354 -0
- package/expertise/security/foundations/secrets-management.md +1217 -0
- package/expertise/security/foundations/secure-sdlc.md +700 -0
- package/expertise/security/foundations/supply-chain-security.md +698 -0
- package/expertise/security/index.md +31 -0
- package/expertise/security/infrastructure/cloud-security-aws.md +1296 -0
- package/expertise/security/infrastructure/cloud-security-gcp.md +1376 -0
- package/expertise/security/infrastructure/container-security.md +721 -0
- package/expertise/security/infrastructure/incident-response.md +1295 -0
- package/expertise/security/infrastructure/index.md +24 -0
- package/expertise/security/infrastructure/logging-and-monitoring.md +1618 -0
- package/expertise/security/infrastructure/network-security.md +1337 -0
- package/expertise/security/mobile/index.md +23 -0
- package/expertise/security/mobile/mobile-android-security.md +1218 -0
- package/expertise/security/mobile/mobile-binary-protection.md +1229 -0
- package/expertise/security/mobile/mobile-data-storage.md +1265 -0
- package/expertise/security/mobile/mobile-ios-security.md +1401 -0
- package/expertise/security/mobile/mobile-network-security.md +1520 -0
- package/expertise/security/smart-contract-security.md +594 -0
- package/expertise/security/testing/index.md +22 -0
- package/expertise/security/testing/penetration-testing.md +1258 -0
- package/expertise/security/testing/security-code-review.md +1765 -0
- package/expertise/security/testing/threat-modeling.md +1074 -0
- package/expertise/security/testing/vulnerability-scanning.md +1062 -0
- package/expertise/security/web/api-security.md +586 -0
- package/expertise/security/web/cors-and-headers.md +433 -0
- package/expertise/security/web/csrf.md +562 -0
- package/expertise/security/web/file-upload.md +1477 -0
- package/expertise/security/web/index.md +25 -0
- package/expertise/security/web/injection.md +1375 -0
- package/expertise/security/web/session-management.md +1101 -0
- package/expertise/security/web/xss.md +1158 -0
- package/exports/README.md +17 -0
- package/exports/hosts/claude/.claude/agents/clarifier.md +42 -0
- package/exports/hosts/claude/.claude/agents/content-author.md +63 -0
- package/exports/hosts/claude/.claude/agents/designer.md +55 -0
- package/exports/hosts/claude/.claude/agents/executor.md +55 -0
- package/exports/hosts/claude/.claude/agents/learner.md +51 -0
- package/exports/hosts/claude/.claude/agents/planner.md +53 -0
- package/exports/hosts/claude/.claude/agents/researcher.md +43 -0
- package/exports/hosts/claude/.claude/agents/reviewer.md +54 -0
- package/exports/hosts/claude/.claude/agents/specifier.md +47 -0
- package/exports/hosts/claude/.claude/agents/verifier.md +71 -0
- package/exports/hosts/claude/.claude/commands/author.md +42 -0
- package/exports/hosts/claude/.claude/commands/clarify.md +38 -0
- package/exports/hosts/claude/.claude/commands/design-review.md +46 -0
- package/exports/hosts/claude/.claude/commands/design.md +44 -0
- package/exports/hosts/claude/.claude/commands/discover.md +37 -0
- package/exports/hosts/claude/.claude/commands/execute.md +48 -0
- package/exports/hosts/claude/.claude/commands/learn.md +38 -0
- package/exports/hosts/claude/.claude/commands/plan-review.md +42 -0
- package/exports/hosts/claude/.claude/commands/plan.md +39 -0
- package/exports/hosts/claude/.claude/commands/prepare-next.md +37 -0
- package/exports/hosts/claude/.claude/commands/review.md +40 -0
- package/exports/hosts/claude/.claude/commands/run-audit.md +41 -0
- package/exports/hosts/claude/.claude/commands/spec-challenge.md +41 -0
- package/exports/hosts/claude/.claude/commands/specify.md +38 -0
- package/exports/hosts/claude/.claude/commands/verify.md +37 -0
- package/exports/hosts/claude/.claude/settings.json +34 -0
- package/exports/hosts/claude/CLAUDE.md +19 -0
- package/exports/hosts/claude/export.manifest.json +38 -0
- package/exports/hosts/claude/host-package.json +67 -0
- package/exports/hosts/codex/AGENTS.md +19 -0
- package/exports/hosts/codex/export.manifest.json +38 -0
- package/exports/hosts/codex/host-package.json +41 -0
- package/exports/hosts/cursor/.cursor/hooks.json +16 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +19 -0
- package/exports/hosts/cursor/export.manifest.json +38 -0
- package/exports/hosts/cursor/host-package.json +42 -0
- package/exports/hosts/gemini/GEMINI.md +19 -0
- package/exports/hosts/gemini/export.manifest.json +38 -0
- package/exports/hosts/gemini/host-package.json +41 -0
- package/hooks/README.md +18 -0
- package/hooks/definitions/loop_cap_guard.yaml +21 -0
- package/hooks/definitions/post_tool_capture.yaml +24 -0
- package/hooks/definitions/pre_compact_summary.yaml +19 -0
- package/hooks/definitions/pre_tool_capture_route.yaml +19 -0
- package/hooks/definitions/protected_path_write_guard.yaml +19 -0
- package/hooks/definitions/session_start.yaml +19 -0
- package/hooks/definitions/stop_handoff_harvest.yaml +20 -0
- package/hooks/loop-cap-guard +17 -0
- package/hooks/post-tool-lint +36 -0
- package/hooks/protected-path-write-guard +17 -0
- package/hooks/session-start +41 -0
- package/llms-full.txt +2355 -0
- package/llms.txt +43 -0
- package/package.json +79 -0
- package/roles/README.md +20 -0
- package/roles/clarifier.md +42 -0
- package/roles/content-author.md +63 -0
- package/roles/designer.md +55 -0
- package/roles/executor.md +55 -0
- package/roles/learner.md +51 -0
- package/roles/planner.md +53 -0
- package/roles/researcher.md +43 -0
- package/roles/reviewer.md +54 -0
- package/roles/specifier.md +47 -0
- package/roles/verifier.md +71 -0
- package/schemas/README.md +24 -0
- package/schemas/accepted-learning.schema.json +20 -0
- package/schemas/author-artifact.schema.json +156 -0
- package/schemas/clarification.schema.json +19 -0
- package/schemas/design-artifact.schema.json +80 -0
- package/schemas/docs-claim.schema.json +18 -0
- package/schemas/export-manifest.schema.json +20 -0
- package/schemas/hook.schema.json +67 -0
- package/schemas/host-export-package.schema.json +18 -0
- package/schemas/implementation-plan.schema.json +19 -0
- package/schemas/proposed-learning.schema.json +19 -0
- package/schemas/research.schema.json +18 -0
- package/schemas/review.schema.json +29 -0
- package/schemas/run-manifest.schema.json +18 -0
- package/schemas/spec-challenge.schema.json +18 -0
- package/schemas/spec.schema.json +20 -0
- package/schemas/usage.schema.json +102 -0
- package/schemas/verification-proof.schema.json +29 -0
- package/schemas/wazir-manifest.schema.json +173 -0
- package/skills/README.md +40 -0
- package/skills/brainstorming/SKILL.md +77 -0
- package/skills/debugging/SKILL.md +50 -0
- package/skills/design/SKILL.md +61 -0
- package/skills/dispatching-parallel-agents/SKILL.md +128 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +169 -0
- package/skills/humanize/SKILL.md +123 -0
- package/skills/init-pipeline/SKILL.md +124 -0
- package/skills/prepare-next/SKILL.md +20 -0
- package/skills/receiving-code-review/SKILL.md +123 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +108 -0
- package/skills/run-audit/SKILL.md +197 -0
- package/skills/scan-project/SKILL.md +41 -0
- package/skills/self-audit/SKILL.md +153 -0
- package/skills/subagent-driven-development/SKILL.md +154 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +102 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/tdd/SKILL.md +23 -0
- package/skills/using-git-worktrees/SKILL.md +163 -0
- package/skills/using-skills/SKILL.md +95 -0
- package/skills/verification/SKILL.md +22 -0
- package/skills/wazir/SKILL.md +463 -0
- package/skills/writing-plans/SKILL.md +30 -0
- package/skills/writing-skills/SKILL.md +157 -0
- package/skills/writing-skills/anthropic-best-practices.md +122 -0
- package/skills/writing-skills/persuasion-principles.md +50 -0
- package/templates/README.md +20 -0
- package/templates/artifacts/README.md +10 -0
- package/templates/artifacts/accepted-learning.md +19 -0
- package/templates/artifacts/accepted-learning.template.json +12 -0
- package/templates/artifacts/author.md +74 -0
- package/templates/artifacts/author.template.json +19 -0
- package/templates/artifacts/clarification.md +21 -0
- package/templates/artifacts/clarification.template.json +12 -0
- package/templates/artifacts/execute-notes.md +19 -0
- package/templates/artifacts/implementation-plan.md +21 -0
- package/templates/artifacts/implementation-plan.template.json +11 -0
- package/templates/artifacts/learning-proposal.md +19 -0
- package/templates/artifacts/next-run-handoff.md +21 -0
- package/templates/artifacts/plan-review.md +19 -0
- package/templates/artifacts/proposed-learning.template.json +12 -0
- package/templates/artifacts/research.md +21 -0
- package/templates/artifacts/research.template.json +12 -0
- package/templates/artifacts/review-findings.md +19 -0
- package/templates/artifacts/review.template.json +11 -0
- package/templates/artifacts/run-manifest.template.json +8 -0
- package/templates/artifacts/spec-challenge.md +19 -0
- package/templates/artifacts/spec-challenge.template.json +11 -0
- package/templates/artifacts/spec.md +21 -0
- package/templates/artifacts/spec.template.json +12 -0
- package/templates/artifacts/verification-proof.md +19 -0
- package/templates/artifacts/verification-proof.template.json +11 -0
- package/templates/examples/accepted-learning.example.json +14 -0
- package/templates/examples/author.example.json +152 -0
- package/templates/examples/clarification.example.json +15 -0
- package/templates/examples/docs-claim.example.json +8 -0
- package/templates/examples/export-manifest.example.json +7 -0
- package/templates/examples/host-export-package.example.json +11 -0
- package/templates/examples/implementation-plan.example.json +17 -0
- package/templates/examples/proposed-learning.example.json +13 -0
- package/templates/examples/research.example.json +15 -0
- package/templates/examples/research.example.md +6 -0
- package/templates/examples/review.example.json +17 -0
- package/templates/examples/run-manifest.example.json +9 -0
- package/templates/examples/spec-challenge.example.json +14 -0
- package/templates/examples/spec.example.json +21 -0
- package/templates/examples/verification-proof.example.json +21 -0
- package/templates/examples/wazir-manifest.example.yaml +65 -0
- package/templates/task-definition-schema.md +99 -0
- package/tooling/README.md +20 -0
- package/tooling/src/adapters/context-mode.js +50 -0
- package/tooling/src/capture/command.js +376 -0
- package/tooling/src/capture/store.js +99 -0
- package/tooling/src/capture/usage.js +270 -0
- package/tooling/src/checks/branches.js +50 -0
- package/tooling/src/checks/brand-truth.js +110 -0
- package/tooling/src/checks/changelog.js +231 -0
- package/tooling/src/checks/command-registry.js +36 -0
- package/tooling/src/checks/commits.js +102 -0
- package/tooling/src/checks/docs-drift.js +103 -0
- package/tooling/src/checks/docs-truth.js +201 -0
- package/tooling/src/checks/runtime-surface.js +156 -0
- package/tooling/src/cli.js +116 -0
- package/tooling/src/command-options.js +56 -0
- package/tooling/src/commands/validate.js +320 -0
- package/tooling/src/doctor/command.js +91 -0
- package/tooling/src/export/command.js +77 -0
- package/tooling/src/export/compiler.js +498 -0
- package/tooling/src/guards/loop-cap-guard.js +52 -0
- package/tooling/src/guards/protected-path-write-guard.js +67 -0
- package/tooling/src/index/command.js +152 -0
- package/tooling/src/index/storage.js +1061 -0
- package/tooling/src/index/summarizers.js +261 -0
- package/tooling/src/loaders.js +18 -0
- package/tooling/src/project-root.js +22 -0
- package/tooling/src/recall/command.js +225 -0
- package/tooling/src/schema-validator.js +30 -0
- package/tooling/src/state-root.js +40 -0
- package/tooling/src/status/command.js +71 -0
- package/wazir.manifest.yaml +135 -0
- package/workflows/README.md +19 -0
- package/workflows/author.md +42 -0
- package/workflows/clarify.md +38 -0
- package/workflows/design-review.md +46 -0
- package/workflows/design.md +44 -0
- package/workflows/discover.md +37 -0
- package/workflows/execute.md +48 -0
- package/workflows/learn.md +38 -0
- package/workflows/plan-review.md +42 -0
- package/workflows/plan.md +39 -0
- package/workflows/prepare-next.md +37 -0
- package/workflows/review.md +40 -0
- package/workflows/run-audit.md +41 -0
- package/workflows/spec-challenge.md +41 -0
- package/workflows/specify.md +38 -0
- package/workflows/verify.md +37 -0
|
@@ -0,0 +1,1360 @@
|
|
|
1
|
+
# Error Handling Anti-Patterns
|
|
2
|
+
|
|
3
|
+
**Module:** Code Anti-Patterns / Error Handling
|
|
4
|
+
**Severity range:** Low to Critical
|
|
5
|
+
**Applies to:** All languages with exception/error mechanisms
|
|
6
|
+
**Prerequisites:** Basic understanding of try/catch, async patterns, typed error systems
|
|
7
|
+
|
|
8
|
+
Error handling code is the least tested, least reviewed, and most dangerous code in any system.
|
|
9
|
+
A 2014 University of Toronto study of Cassandra, HBase, HDFS, MapReduce, and Redis found that
|
|
10
|
+
92% of catastrophic failures were caused by incorrect handling of non-fatal errors explicitly
|
|
11
|
+
signalled in software. In 35% of those cases, the error handler was empty, only contained a log
|
|
12
|
+
statement, or had clearly incomplete logic. These are not exotic edge cases -- they are trivial
|
|
13
|
+
mistakes that pass code review because error handling is treated as an afterthought. This module
|
|
14
|
+
catalogs 18 recurring anti-patterns, each with detection heuristics, real-world incident data,
|
|
15
|
+
and concrete fixes.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Anti-Pattern Catalog
|
|
20
|
+
|
|
21
|
+
### AP-01: Pokemon Exception Handling
|
|
22
|
+
|
|
23
|
+
**Also known as:** Catch 'Em All, Diaper Pattern, Catch-All
|
|
24
|
+
**Frequency:** Very Common
|
|
25
|
+
**Severity:** Critical
|
|
26
|
+
**Detection difficulty:** Easy
|
|
27
|
+
|
|
28
|
+
**What it looks like:**
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# Python
|
|
32
|
+
try:
|
|
33
|
+
process_order(order)
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# Java
|
|
38
|
+
try {
|
|
39
|
+
processOrder(order);
|
|
40
|
+
} catch (Exception e) {
|
|
41
|
+
// handle error
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# JavaScript
|
|
45
|
+
try {
|
|
46
|
+
processOrder(order);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.log("something went wrong");
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Why developers do it:**
|
|
53
|
+
|
|
54
|
+
Quick fix during development to "get it working." Often starts as a placeholder that survives to production. Developers also use it defensively when they do not know which exceptions a third-party library can throw. The CWE (Common Weakness Enumeration) lists this as record #397.
|
|
55
|
+
|
|
56
|
+
**What goes wrong:**
|
|
57
|
+
|
|
58
|
+
The handler catches `NameError`, `TypeError`, `KeyboardInterrupt`, `SystemExit`, and everything else indiscriminately. A misspelled variable name becomes indistinguishable from a network timeout. The University of Toronto study found this pattern directly contributed to catastrophic failures in distributed data stores -- the error handler swallowed a real failure signal, and the system continued operating on corrupted state.
|
|
59
|
+
|
|
60
|
+
In one documented case, an `undefined` variable raised a `NameError` inside a Pokemon catch block. The function returned `None` silently, causing downstream data corruption that was only discovered days later when customers reported incorrect billing amounts.
|
|
61
|
+
|
|
62
|
+
**The fix:**
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# Before: catches everything including SystemExit and KeyboardInterrupt
|
|
66
|
+
try:
|
|
67
|
+
result = external_api.fetch(order_id)
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# After: catch specific, actionable exceptions
|
|
72
|
+
try:
|
|
73
|
+
result = external_api.fetch(order_id)
|
|
74
|
+
except ConnectionTimeout:
|
|
75
|
+
result = cache.get_stale(order_id)
|
|
76
|
+
metrics.increment("api.timeout.fallback")
|
|
77
|
+
except ValidationError as e:
|
|
78
|
+
logger.warning("Invalid order %s: %s", order_id, e)
|
|
79
|
+
raise
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Detection rule:**
|
|
83
|
+
|
|
84
|
+
Flag any `catch(Exception)`, `catch(...)`, `except Exception`, or bare `except:` that does not re-raise. Lint rules: `pylint broad-except`, `SonarQube S2221`, `ESLint no-useless-catch`.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### AP-02: The Silent Swallow
|
|
89
|
+
|
|
90
|
+
**Also known as:** Exception Hiding, Error Swallowing, Empty Catch
|
|
91
|
+
**Frequency:** Very Common
|
|
92
|
+
**Severity:** Critical
|
|
93
|
+
**Detection difficulty:** Moderate
|
|
94
|
+
|
|
95
|
+
**What it looks like:**
|
|
96
|
+
|
|
97
|
+
```java
|
|
98
|
+
try {
|
|
99
|
+
db.executeUpdate(sql);
|
|
100
|
+
} catch (SQLException e) {
|
|
101
|
+
// TODO: handle this later
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
sendNotification(user);
|
|
106
|
+
} catch (Exception e) {
|
|
107
|
+
// not critical, ignore
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
try {
|
|
113
|
+
await saveToDatabase(record);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
// swallow
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Why developers do it:**
|
|
120
|
+
|
|
121
|
+
The developer believes the exception is non-critical ("it's just a notification") or plans to handle it later. Wikipedia documents this as "error hiding" -- the practice of catching an error and continuing without logging, processing, or reporting it.
|
|
122
|
+
|
|
123
|
+
**What goes wrong:**
|
|
124
|
+
|
|
125
|
+
Information about the error is permanently lost. Depending on the system, this can cause unintended side effects that cascade into other errors. The n8n workflow automation platform had a documented bug (Issue #19434) where a URL constructor threw an error inside a Code node. The try-catch swallowed it and returned an empty string. The real issue -- missing `URL` constructor in the sandboxed environment -- was invisible. Users saw only mysterious data-processing failures.
|
|
126
|
+
|
|
127
|
+
Harness.io reported that swallowed exceptions are a major factor causing production errors to go unnoticed, particularly hiding database rollback failures, file I/O errors, and network timeouts.
|
|
128
|
+
|
|
129
|
+
**The fix:**
|
|
130
|
+
|
|
131
|
+
```java
|
|
132
|
+
// Before
|
|
133
|
+
try {
|
|
134
|
+
db.executeUpdate(sql);
|
|
135
|
+
} catch (SQLException e) {}
|
|
136
|
+
|
|
137
|
+
// After: at minimum, log. Better: propagate or handle.
|
|
138
|
+
try {
|
|
139
|
+
db.executeUpdate(sql);
|
|
140
|
+
} catch (SQLException e) {
|
|
141
|
+
logger.error("Failed to execute update: {}", sql, e);
|
|
142
|
+
throw new DataAccessException("Update failed for query", e);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
If the exception genuinely can be ignored (e.g., best-effort cache write), document why explicitly:
|
|
147
|
+
|
|
148
|
+
```java
|
|
149
|
+
try {
|
|
150
|
+
cache.put(key, value);
|
|
151
|
+
} catch (CacheException e) {
|
|
152
|
+
// Intentionally ignored: cache miss is acceptable, DB is source of truth.
|
|
153
|
+
// Metric tracked separately via cache health monitor.
|
|
154
|
+
metrics.increment("cache.write.failure");
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Detection rule:**
|
|
159
|
+
|
|
160
|
+
Flag empty catch blocks and catch blocks containing only comments or TODO markers. Lint rules: `SonarQube S108`, `ESLint no-empty`, `clang-tidy bugprone-empty-catch`, `Checkstyle EmptyCatchBlock`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### AP-03: Exceptions as Flow Control
|
|
165
|
+
|
|
166
|
+
**Also known as:** Conditional Exceptions, Exception-Driven Logic, Vexing Exceptions
|
|
167
|
+
**Frequency:** Common
|
|
168
|
+
**Severity:** Medium
|
|
169
|
+
**Detection difficulty:** Moderate
|
|
170
|
+
|
|
171
|
+
**What it looks like:**
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# Using exception to check if user exists
|
|
175
|
+
def get_user(user_id):
|
|
176
|
+
try:
|
|
177
|
+
return db.query(f"SELECT * FROM users WHERE id = {user_id}")
|
|
178
|
+
except UserNotFoundError:
|
|
179
|
+
return create_default_user(user_id)
|
|
180
|
+
|
|
181
|
+
# Using exception for type checking
|
|
182
|
+
def parse_input(value):
|
|
183
|
+
try:
|
|
184
|
+
return int(value)
|
|
185
|
+
except ValueError:
|
|
186
|
+
try:
|
|
187
|
+
return float(value)
|
|
188
|
+
except ValueError:
|
|
189
|
+
return value
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```csharp
|
|
193
|
+
// C# classic: using Parse + catch instead of TryParse
|
|
194
|
+
int age;
|
|
195
|
+
try {
|
|
196
|
+
age = int.Parse(userInput);
|
|
197
|
+
} catch (FormatException) {
|
|
198
|
+
age = 0;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Why developers do it:**
|
|
203
|
+
|
|
204
|
+
It feels concise. In Python, "easier to ask forgiveness than permission" (EAFP) is sometimes cited as justification, but EAFP applies to race-condition avoidance, not routine control flow.
|
|
205
|
+
|
|
206
|
+
**What goes wrong:**
|
|
207
|
+
|
|
208
|
+
Joshua Bloch demonstrated in Effective Java that exception-based looping is roughly 2x slower than conditional checks because the JVM must capture the entire call stack on every throw. Microsoft's performance documentation explicitly labels this a "Performance Sin." Beyond speed, it obscures intent -- a reader cannot tell whether the exception path is the error case or the expected case.
|
|
209
|
+
|
|
210
|
+
The Databricks SDK for Python (Issue #1117) documented how inconsistent exception handling forced users into control-flow anti-patterns, checking HTTP status by catching exceptions rather than inspecting return values.
|
|
211
|
+
|
|
212
|
+
**The fix:**
|
|
213
|
+
|
|
214
|
+
```csharp
|
|
215
|
+
// Before: exception as flow control
|
|
216
|
+
int age;
|
|
217
|
+
try {
|
|
218
|
+
age = int.Parse(userInput);
|
|
219
|
+
} catch (FormatException) {
|
|
220
|
+
age = 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// After: use TryParse / conditional check
|
|
224
|
+
int age = int.TryParse(userInput, out var parsed) ? parsed : 0;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# Before
|
|
229
|
+
try:
|
|
230
|
+
value = my_dict[key]
|
|
231
|
+
except KeyError:
|
|
232
|
+
value = default
|
|
233
|
+
|
|
234
|
+
# After
|
|
235
|
+
value = my_dict.get(key, default)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Detection rule:**
|
|
239
|
+
|
|
240
|
+
Flag catch blocks where the exception type represents a predictable, non-exceptional condition (e.g., `KeyError`, `FormatException`, `NumberFormatException`) and where the catch body sets a default value. High ratio of caught-to-uncaught exceptions in production telemetry is a signal.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### AP-04: Null as Error Signal
|
|
245
|
+
|
|
246
|
+
**Also known as:** The Billion-Dollar Mistake, Null Return Pattern, Silent Null
|
|
247
|
+
**Frequency:** Very Common
|
|
248
|
+
**Severity:** High
|
|
249
|
+
**Detection difficulty:** Hard
|
|
250
|
+
|
|
251
|
+
**What it looks like:**
|
|
252
|
+
|
|
253
|
+
```java
|
|
254
|
+
public User findUser(String id) {
|
|
255
|
+
try {
|
|
256
|
+
return userRepo.findById(id);
|
|
257
|
+
} catch (DatabaseException e) {
|
|
258
|
+
return null; // caller has no idea why
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Caller side -- the NullPointerException lottery
|
|
263
|
+
User user = findUser(id);
|
|
264
|
+
String name = user.getName(); // NPE if user is null
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
function getConfig(key) {
|
|
269
|
+
const val = configStore[key];
|
|
270
|
+
if (!val) return null;
|
|
271
|
+
return val;
|
|
272
|
+
}
|
|
273
|
+
// Caller forgets null check -> TypeError: Cannot read property of null
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Why developers do it:**
|
|
277
|
+
|
|
278
|
+
It avoids throwing, which feels "simpler." The method signature stays clean. Tony Hoare, inventor of the null reference, called it his "billion-dollar mistake" at QCon London 2009, stating: "It has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years."
|
|
279
|
+
|
|
280
|
+
**What goes wrong:**
|
|
281
|
+
|
|
282
|
+
The null propagates through the system until it detonates far from the original error site. Debugging becomes a search through call chains to find where null was introduced. Every caller must defensively null-check, and inevitably one does not. The original error context (why the lookup failed) is permanently lost.
|
|
283
|
+
|
|
284
|
+
**The fix:**
|
|
285
|
+
|
|
286
|
+
```java
|
|
287
|
+
// Before
|
|
288
|
+
public User findUser(String id) {
|
|
289
|
+
try {
|
|
290
|
+
return userRepo.findById(id);
|
|
291
|
+
} catch (DatabaseException e) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// After: use Optional (Java), Result type (Rust), or throw
|
|
297
|
+
public Optional<User> findUser(String id) {
|
|
298
|
+
return userRepo.findOptionalById(id);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Or throw with context
|
|
302
|
+
public User findUser(String id) throws UserNotFoundException {
|
|
303
|
+
return userRepo.findById(id)
|
|
304
|
+
.orElseThrow(() -> new UserNotFoundException(id));
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// TypeScript: use strict null checks + union types
|
|
310
|
+
function getConfig(key: string): ConfigValue | undefined {
|
|
311
|
+
return configStore.get(key);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Detection rule:**
|
|
316
|
+
|
|
317
|
+
Flag methods that catch exceptions and return null. Flag methods returning null without `@Nullable` annotation. Enable `-strictNullChecks` in TypeScript, `Optional` enforcement in Java. Lint: `SonarQube S2637`, `NullAway`.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### AP-05: Log and Rethrow
|
|
322
|
+
|
|
323
|
+
**Also known as:** Double Logging, Log-and-Throw, Catch-Log-Rethrow
|
|
324
|
+
**Frequency:** Common
|
|
325
|
+
**Severity:** Medium
|
|
326
|
+
**Detection difficulty:** Easy
|
|
327
|
+
|
|
328
|
+
**What it looks like:**
|
|
329
|
+
|
|
330
|
+
```java
|
|
331
|
+
try {
|
|
332
|
+
processPayment(order);
|
|
333
|
+
} catch (PaymentException e) {
|
|
334
|
+
logger.error("Payment failed", e);
|
|
335
|
+
throw e; // same exception logged again at next catch
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Three layers up:
|
|
339
|
+
try {
|
|
340
|
+
handleOrder(order);
|
|
341
|
+
} catch (PaymentException e) {
|
|
342
|
+
logger.error("Order processing failed", e); // same stack trace, third time
|
|
343
|
+
throw new OrderException("Order failed", e);
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Why developers do it:**
|
|
348
|
+
|
|
349
|
+
Each layer wants to "make sure" the error is logged. Developers do not trust that higher layers will log the exception. The TheServerSide documented this as one of the most common Java exception anti-patterns.
|
|
350
|
+
|
|
351
|
+
**What goes wrong:**
|
|
352
|
+
|
|
353
|
+
The same stack trace appears 3-5 times in logs, each with different context text. During an incident, an engineer searching for the root cause finds dozens of duplicate log entries, making it harder to identify the actual first occurrence. Rolf Engelhard described the result: "a log file that contains every stacktrace in a dozen repetitions and variations -- a nightmare when you try to understand what is going on." SonarQube rule S2139 specifically targets this pattern.
|
|
354
|
+
|
|
355
|
+
**The fix:**
|
|
356
|
+
|
|
357
|
+
Either log OR rethrow. Never both.
|
|
358
|
+
|
|
359
|
+
```java
|
|
360
|
+
// Option A: Add context and rethrow (preferred in library code)
|
|
361
|
+
try {
|
|
362
|
+
processPayment(order);
|
|
363
|
+
} catch (PaymentException e) {
|
|
364
|
+
throw new OrderException("Payment failed for order " + order.getId(), e);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Option B: Log and handle (preferred at boundary/top level)
|
|
368
|
+
try {
|
|
369
|
+
processPayment(order);
|
|
370
|
+
} catch (PaymentException e) {
|
|
371
|
+
logger.error("Payment failed for order {}: {}", order.getId(), e.getMessage(), e);
|
|
372
|
+
return PaymentResult.failed(e.getMessage());
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Detection rule:**
|
|
377
|
+
|
|
378
|
+
Flag catch blocks that contain both a `logger.error()` call and a `throw` statement. Lint: `SonarQube S2139`, `PMD LoggerAndThrow`, Palantir baseline-error-prone `LogAndThrow`.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
### AP-06: Catching the Base Error Class
|
|
383
|
+
|
|
384
|
+
**Also known as:** Catching Throwable, Catching BaseException, Nuclear Catch
|
|
385
|
+
**Frequency:** Common
|
|
386
|
+
**Severity:** Critical
|
|
387
|
+
**Detection difficulty:** Easy
|
|
388
|
+
|
|
389
|
+
**What it looks like:**
|
|
390
|
+
|
|
391
|
+
```java
|
|
392
|
+
try {
|
|
393
|
+
processData(input);
|
|
394
|
+
} catch (Throwable t) {
|
|
395
|
+
logger.error("Error processing data", t);
|
|
396
|
+
return defaultResult();
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
try:
|
|
402
|
+
process_data(input)
|
|
403
|
+
except BaseException:
|
|
404
|
+
return default_result()
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**Why developers do it:**
|
|
408
|
+
|
|
409
|
+
"I want to catch absolutely everything so the system never crashes." It feels like defensive programming.
|
|
410
|
+
|
|
411
|
+
**What goes wrong:**
|
|
412
|
+
|
|
413
|
+
In Java, `Throwable` includes `OutOfMemoryError`, `StackOverflowError`, and `ThreadDeath`. Catching these prevents the JVM from properly reporting fatal conditions. An `OutOfMemoryError` caught and "handled" by returning a default value leaves the JVM in an unstable state -- subsequent allocations may fail unpredictably, and the application limps along corrupting data instead of failing fast.
|
|
414
|
+
|
|
415
|
+
In Python, catching `BaseException` also catches `SystemExit` and `KeyboardInterrupt`, meaning Ctrl+C no longer works and `sys.exit()` calls are silently ignored. Baeldung documented this as a definitive bad practice: "Throwable is the superclass of all exceptions and errors. Catching it will intercept serious errors that the application cannot or should not handle."
|
|
416
|
+
|
|
417
|
+
**The fix:**
|
|
418
|
+
|
|
419
|
+
```java
|
|
420
|
+
// Before
|
|
421
|
+
try {
|
|
422
|
+
processData(input);
|
|
423
|
+
} catch (Throwable t) {
|
|
424
|
+
return defaultResult();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// After: catch only recoverable exceptions
|
|
428
|
+
try {
|
|
429
|
+
processData(input);
|
|
430
|
+
} catch (ProcessingException | IOException e) {
|
|
431
|
+
logger.error("Data processing failed", e);
|
|
432
|
+
return defaultResult();
|
|
433
|
+
}
|
|
434
|
+
// Let OutOfMemoryError, StackOverflowError propagate and crash the process
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Detection rule:**
|
|
438
|
+
|
|
439
|
+
Flag `catch(Throwable)`, `catch(Error)` in Java; `except BaseException` in Python; `catch {}` without type in Swift. Lint: `SonarQube S1181`, `Checkstyle IllegalCatch`, `PMD AvoidCatchingThrowable`.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
### AP-07: Generic Error Messages
|
|
444
|
+
|
|
445
|
+
**Also known as:** "Something Went Wrong" Syndrome, Unhelpful Error, Context-Free Error
|
|
446
|
+
**Frequency:** Very Common
|
|
447
|
+
**Severity:** High
|
|
448
|
+
**Detection difficulty:** Moderate
|
|
449
|
+
|
|
450
|
+
**What it looks like:**
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
try {
|
|
454
|
+
await deployService(config);
|
|
455
|
+
} catch (err) {
|
|
456
|
+
throw new Error("Deployment failed");
|
|
457
|
+
// Original error: "EACCES: permission denied, open '/etc/nginx/conf.d/app.conf'"
|
|
458
|
+
// What the on-call engineer sees: "Deployment failed"
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
except DatabaseError as e:
|
|
464
|
+
raise AppError("An error occurred while processing your request")
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Why developers do it:**
|
|
468
|
+
|
|
469
|
+
Security guidance says "don't expose internals to users." Developers apply this rule everywhere, including internal logs and error chains, stripping context that only engineers would see.
|
|
470
|
+
|
|
471
|
+
**What goes wrong:**
|
|
472
|
+
|
|
473
|
+
The on-call engineer sees "Deployment failed" in the alert. They have no idea if it is a permission error, a network error, a config syntax error, or a disk space issue. Triage time increases from minutes to hours. The security concern about user-facing messages is valid, but internal error chains and server-side logs need full context.
|
|
474
|
+
|
|
475
|
+
CQR's security research acknowledges the tension: user-facing errors should be generic, but server-side logs must contain the full error chain with stack trace and contextual data, accessible only to authorized personnel.
|
|
476
|
+
|
|
477
|
+
**The fix:**
|
|
478
|
+
|
|
479
|
+
Separate user-facing messages from internal error context:
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
# Before
|
|
483
|
+
except DatabaseError as e:
|
|
484
|
+
raise AppError("An error occurred")
|
|
485
|
+
|
|
486
|
+
# After: preserve context internally, sanitize externally
|
|
487
|
+
except DatabaseError as e:
|
|
488
|
+
logger.error("DB query failed", extra={
|
|
489
|
+
"query": query_name,
|
|
490
|
+
"table": table,
|
|
491
|
+
"original_error": str(e),
|
|
492
|
+
})
|
|
493
|
+
raise AppError(
|
|
494
|
+
user_message="We're having trouble processing your request. Please try again.",
|
|
495
|
+
internal_message=f"DatabaseError in {query_name} on {table}: {e}",
|
|
496
|
+
original=e,
|
|
497
|
+
)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Detection rule:**
|
|
501
|
+
|
|
502
|
+
Flag `throw new Error(string_literal)` where the literal contains no variable interpolation. Flag catch blocks that discard the original exception's message. Custom lint rule: error constructors must include either the original error or at least one variable.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
### AP-08: Rethrowing Without Context
|
|
507
|
+
|
|
508
|
+
**Also known as:** Naked Rethrow, Context-Free Propagation, Blame Laundering
|
|
509
|
+
**Frequency:** Common
|
|
510
|
+
**Severity:** Medium
|
|
511
|
+
**Detection difficulty:** Moderate
|
|
512
|
+
|
|
513
|
+
**What it looks like:**
|
|
514
|
+
|
|
515
|
+
```java
|
|
516
|
+
try {
|
|
517
|
+
loadConfig(path);
|
|
518
|
+
} catch (IOException e) {
|
|
519
|
+
throw new ConfigException(e.getMessage());
|
|
520
|
+
// Lost: the original stack trace, the exception chain
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
try:
|
|
526
|
+
parse_config(path)
|
|
527
|
+
except yaml.YAMLError as e:
|
|
528
|
+
raise ConfigError(str(e)) # original traceback gone
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**Why developers do it:**
|
|
532
|
+
|
|
533
|
+
They want to translate the exception to a domain-specific type but forget to chain the cause. In Python, using `raise X` inside a catch without `from e` discards the original traceback.
|
|
534
|
+
|
|
535
|
+
**What goes wrong:**
|
|
536
|
+
|
|
537
|
+
The stack trace in the new exception starts at the rethrow point, not at the original failure. Debugging now requires guessing what happened before the rethrow. In large systems, this creates "stack trace gaps" where the most useful frames are missing.
|
|
538
|
+
|
|
539
|
+
**The fix:**
|
|
540
|
+
|
|
541
|
+
```java
|
|
542
|
+
// Before: breaks the chain
|
|
543
|
+
throw new ConfigException(e.getMessage());
|
|
544
|
+
|
|
545
|
+
// After: chain the cause
|
|
546
|
+
throw new ConfigException("Failed to load config from " + path, e);
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
```python
|
|
550
|
+
# Before
|
|
551
|
+
raise ConfigError(str(e))
|
|
552
|
+
|
|
553
|
+
# After: use 'from' to chain
|
|
554
|
+
raise ConfigError(f"Failed to parse {path}") from e
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**Detection rule:**
|
|
558
|
+
|
|
559
|
+
Flag `throw new XException(e.getMessage())` -- constructing a new exception from only the message string. Flag Python `raise X` inside an except block without `from`. Lint: `SonarQube S3438`, `Pylint W0707 (raise-missing-from)`.
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
### AP-09: Nested Try-Catch Pyramids
|
|
564
|
+
|
|
565
|
+
**Also known as:** Try-Catch Hell, Tower of Terror, Exception Nesting
|
|
566
|
+
**Frequency:** Common
|
|
567
|
+
**Severity:** Medium
|
|
568
|
+
**Detection difficulty:** Easy
|
|
569
|
+
|
|
570
|
+
**What it looks like:**
|
|
571
|
+
|
|
572
|
+
```javascript
|
|
573
|
+
try {
|
|
574
|
+
const config = loadConfig();
|
|
575
|
+
try {
|
|
576
|
+
const db = connectDB(config);
|
|
577
|
+
try {
|
|
578
|
+
const data = db.query(sql);
|
|
579
|
+
try {
|
|
580
|
+
return transform(data);
|
|
581
|
+
} catch (transformErr) {
|
|
582
|
+
logger.error("Transform failed", transformErr);
|
|
583
|
+
}
|
|
584
|
+
} catch (queryErr) {
|
|
585
|
+
logger.error("Query failed", queryErr);
|
|
586
|
+
}
|
|
587
|
+
} catch (dbErr) {
|
|
588
|
+
logger.error("DB connection failed", dbErr);
|
|
589
|
+
}
|
|
590
|
+
} catch (configErr) {
|
|
591
|
+
logger.error("Config load failed", configErr);
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**Why developers do it:**
|
|
596
|
+
|
|
597
|
+
Each operation needs different error handling, and developers nest them sequentially without extracting functions. Maximiliano Contieri cataloged this as Code Smell #80: "Exceptions are handy. But code is not clear when nesting."
|
|
598
|
+
|
|
599
|
+
**What goes wrong:**
|
|
600
|
+
|
|
601
|
+
Readability collapses. Error handling logic becomes tangled with business logic. The nested structure makes it hard to determine which catch corresponds to which try. Modifying one layer risks breaking the error handling of another.
|
|
602
|
+
|
|
603
|
+
**The fix:**
|
|
604
|
+
|
|
605
|
+
Extract into separate functions with single-level try-catch, or use early returns:
|
|
606
|
+
|
|
607
|
+
```javascript
|
|
608
|
+
// After: flat structure with extracted functions
|
|
609
|
+
function processRequest() {
|
|
610
|
+
const config = loadConfigSafe(); // throws ConfigError
|
|
611
|
+
const db = connectDBSafe(config); // throws DBError
|
|
612
|
+
const data = querySafe(db, sql); // throws QueryError
|
|
613
|
+
return transformSafe(data); // throws TransformError
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Each function handles its own cleanup, throws domain-specific error
|
|
617
|
+
function loadConfigSafe() {
|
|
618
|
+
try {
|
|
619
|
+
return loadConfig();
|
|
620
|
+
} catch (err) {
|
|
621
|
+
throw new ConfigError("Config load failed", { cause: err });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Detection rule:**
|
|
627
|
+
|
|
628
|
+
Flag try blocks nested more than 2 levels deep. Measure cyclomatic complexity inside catch blocks. Lint: custom rule checking AST nesting depth of try statements.
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
### AP-10: Ignoring Async Errors
|
|
633
|
+
|
|
634
|
+
**Also known as:** Floating Promises, Fire-and-Forget, Unhandled Rejection
|
|
635
|
+
**Frequency:** Very Common
|
|
636
|
+
**Severity:** Critical
|
|
637
|
+
**Detection difficulty:** Hard
|
|
638
|
+
|
|
639
|
+
**What it looks like:**
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
// No await, no .catch() -- the promise floats
|
|
643
|
+
app.post("/orders", (req, res) => {
|
|
644
|
+
processOrder(req.body); // returns a promise, nobody catches it
|
|
645
|
+
res.json({ status: "accepted" });
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Async callback without error handling
|
|
649
|
+
setTimeout(async () => {
|
|
650
|
+
await riskyOperation(); // if this throws, nothing catches it
|
|
651
|
+
}, 1000);
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
```python
|
|
655
|
+
# Python: creating a task and forgetting about it
|
|
656
|
+
import asyncio
|
|
657
|
+
|
|
658
|
+
async def handler(request):
|
|
659
|
+
asyncio.create_task(send_notification(request.user)) # fire and forget
|
|
660
|
+
return Response("OK")
|
|
661
|
+
# If send_notification raises, the error is silently dropped
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**Why developers do it:**
|
|
665
|
+
|
|
666
|
+
The "fire and forget" pattern seems appropriate for non-critical side effects. Developers forget that async functions return promises that need handling.
|
|
667
|
+
|
|
668
|
+
**What goes wrong:**
|
|
669
|
+
|
|
670
|
+
Starting in Node.js 15, unhandled promise rejections crash the process by default. DZone documented a production incident: "The tiny mistake that crashed our Node.js app" -- a missing `.catch()` on a database cleanup promise. In development it never failed; in production under load, the database connection pool exhausted, the promise rejected, and the entire server process terminated.
|
|
671
|
+
|
|
672
|
+
Before Node 15, unhandled rejections were silently swallowed (a `DeprecationWarning` was the only signal), meaning errors accumulated invisibly.
|
|
673
|
+
|
|
674
|
+
**The fix:**
|
|
675
|
+
|
|
676
|
+
```javascript
|
|
677
|
+
// Before: floating promise
|
|
678
|
+
processOrder(req.body);
|
|
679
|
+
|
|
680
|
+
// After: await with error handling
|
|
681
|
+
try {
|
|
682
|
+
await processOrder(req.body);
|
|
683
|
+
} catch (err) {
|
|
684
|
+
logger.error("Order processing failed", { orderId: req.body.id, err });
|
|
685
|
+
// decide: retry, dead-letter queue, or error response
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// For intentional fire-and-forget: explicit void with catch
|
|
689
|
+
void processOrder(req.body).catch(err => {
|
|
690
|
+
logger.error("Background order processing failed", err);
|
|
691
|
+
deadLetterQueue.push({ order: req.body, error: err });
|
|
692
|
+
});
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Add global safety nets (but do not rely on them):
|
|
696
|
+
|
|
697
|
+
```javascript
|
|
698
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
699
|
+
logger.fatal('Unhandled Rejection', { reason });
|
|
700
|
+
metrics.increment('unhandled.rejection');
|
|
701
|
+
process.exit(1); // fail fast
|
|
702
|
+
});
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
**Detection rule:**
|
|
706
|
+
|
|
707
|
+
Flag async function calls without `await`, `.then()`, or `.catch()`. TypeScript: enable `@typescript-eslint/no-floating-promises`. ESLint: `require-await`, `no-floating-promises`. Python: flag `asyncio.create_task()` without storing the returned task.
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
### AP-11: Using Assert for Runtime Validation
|
|
712
|
+
|
|
713
|
+
**Also known as:** Assert-as-Guard, Debug-Only Validation
|
|
714
|
+
**Frequency:** Occasional
|
|
715
|
+
**Severity:** Critical
|
|
716
|
+
**Detection difficulty:** Moderate
|
|
717
|
+
|
|
718
|
+
**What it looks like:**
|
|
719
|
+
|
|
720
|
+
```python
|
|
721
|
+
def transfer_money(from_account, to_account, amount):
|
|
722
|
+
assert amount > 0, "Transfer amount must be positive"
|
|
723
|
+
assert from_account.balance >= amount, "Insufficient funds"
|
|
724
|
+
# proceed with transfer...
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
```java
|
|
728
|
+
public void processPayment(double amount) {
|
|
729
|
+
assert amount > 0 : "Amount must be positive";
|
|
730
|
+
// Java assertions disabled by default in production
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
**Why developers do it:**
|
|
735
|
+
|
|
736
|
+
Assert reads cleanly and communicates intent. It works in tests and development. Developers forget that assertions can be -- and routinely are -- disabled in production.
|
|
737
|
+
|
|
738
|
+
**What goes wrong:**
|
|
739
|
+
|
|
740
|
+
Python's `-O` flag and `PYTHONOPTIMIZE` environment variable strip all assert statements at the bytecode level. Java requires explicit `-ea` to enable assertions, and virtually no production JVM runs with assertions enabled. Snyk's security research documented this as a vulnerability vector: "Disabling asserts in a production environment can be devastating. This practice can introduce various backdoors and breakpoints in the application."
|
|
741
|
+
|
|
742
|
+
A negative transfer amount or insufficient-balance check that only exists as an assert statement becomes a gaping hole in production. Money moves in wrong directions, and the validation that was supposed to prevent it has been compiled away.
|
|
743
|
+
|
|
744
|
+
**The fix:**
|
|
745
|
+
|
|
746
|
+
```python
|
|
747
|
+
# Before: assert (removed in production with -O)
|
|
748
|
+
assert amount > 0, "Transfer amount must be positive"
|
|
749
|
+
|
|
750
|
+
# After: explicit validation that cannot be disabled
|
|
751
|
+
if amount <= 0:
|
|
752
|
+
raise ValueError(f"Transfer amount must be positive, got {amount}")
|
|
753
|
+
|
|
754
|
+
if from_account.balance < amount:
|
|
755
|
+
raise InsufficientFundsError(
|
|
756
|
+
f"Account {from_account.id} has {from_account.balance}, "
|
|
757
|
+
f"needed {amount}"
|
|
758
|
+
)
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
Reserve `assert` for:
|
|
762
|
+
- Test assertions (`assert result == expected`)
|
|
763
|
+
- Invariants during development that should never fail (`assert len(sorted_list) == len(original)`)
|
|
764
|
+
- Never for input validation, authorization checks, or business rules
|
|
765
|
+
|
|
766
|
+
**Detection rule:**
|
|
767
|
+
|
|
768
|
+
Flag `assert` statements that reference function parameters, user input, or request data. Lint: `Bandit S101` (Python), `SonarQube S3869` (Java). Snyk rule `python/AssertUsedForSecurity`.
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
### AP-12: Conflating Validation and System Errors
|
|
773
|
+
|
|
774
|
+
**Also known as:** Error Type Confusion, User-Error-as-500, Mixed Error Domains
|
|
775
|
+
**Frequency:** Common
|
|
776
|
+
**Severity:** High
|
|
777
|
+
**Detection difficulty:** Hard
|
|
778
|
+
|
|
779
|
+
**What it looks like:**
|
|
780
|
+
|
|
781
|
+
```python
|
|
782
|
+
def create_user(data):
|
|
783
|
+
try:
|
|
784
|
+
validate_email(data["email"])
|
|
785
|
+
validate_age(data["age"])
|
|
786
|
+
db.insert(data)
|
|
787
|
+
except Exception as e:
|
|
788
|
+
return {"error": str(e)}, 500 # email typo returns 500 Internal Server Error
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
```javascript
|
|
792
|
+
app.post("/users", async (req, res) => {
|
|
793
|
+
try {
|
|
794
|
+
const user = await createUser(req.body);
|
|
795
|
+
res.json(user);
|
|
796
|
+
} catch (err) {
|
|
797
|
+
// ValidationError("invalid email") -> 500
|
|
798
|
+
// DatabaseError("connection refused") -> 500
|
|
799
|
+
// Both return the same status code
|
|
800
|
+
res.status(500).json({ error: "Internal server error" });
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
**Why developers do it:**
|
|
806
|
+
|
|
807
|
+
A single try-catch is simpler than distinguishing error types. Developers do not create separate exception hierarchies for validation versus system errors.
|
|
808
|
+
|
|
809
|
+
**What goes wrong:**
|
|
810
|
+
|
|
811
|
+
Monitoring systems fire alerts for "invalid email" validation failures as if they were system outages. On-call engineers investigate user typos at 3 AM. Conversely, actual system errors (database down) get the same treatment as bad input, so they may be dismissed as "just more validation noise." SRE teams lose trust in alerts. Users see "Internal Server Error" for fixable input mistakes.
|
|
812
|
+
|
|
813
|
+
**The fix:**
|
|
814
|
+
|
|
815
|
+
```python
|
|
816
|
+
class ValidationError(AppError):
|
|
817
|
+
"""Client-side input error (4xx)."""
|
|
818
|
+
status_code = 400
|
|
819
|
+
|
|
820
|
+
class SystemError(AppError):
|
|
821
|
+
"""Server-side infrastructure error (5xx)."""
|
|
822
|
+
status_code = 500
|
|
823
|
+
|
|
824
|
+
def create_user(data):
|
|
825
|
+
# Validation: explicit, returns 4xx
|
|
826
|
+
errors = validate_user_input(data)
|
|
827
|
+
if errors:
|
|
828
|
+
raise ValidationError(errors)
|
|
829
|
+
|
|
830
|
+
# System operation: may throw 5xx
|
|
831
|
+
try:
|
|
832
|
+
return db.insert(data)
|
|
833
|
+
except DatabaseError as e:
|
|
834
|
+
raise SystemError("Failed to create user") from e
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
**Detection rule:**
|
|
838
|
+
|
|
839
|
+
Flag catch blocks that return HTTP 500 for all exception types without distinguishing. Flag error hierarchies where validation and system exceptions share a common non-root base class.
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
### AP-13: Stringly-Typed Errors
|
|
844
|
+
|
|
845
|
+
**Also known as:** Error String Matching, Message-Based Dispatch, Error-Message-as-API
|
|
846
|
+
**Frequency:** Common
|
|
847
|
+
**Severity:** High
|
|
848
|
+
**Detection difficulty:** Moderate
|
|
849
|
+
|
|
850
|
+
**What it looks like:**
|
|
851
|
+
|
|
852
|
+
```go
|
|
853
|
+
if err != nil {
|
|
854
|
+
if strings.Contains(err.Error(), "connection refused") {
|
|
855
|
+
retryConnection()
|
|
856
|
+
} else if strings.Contains(err.Error(), "timeout") {
|
|
857
|
+
extendTimeout()
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
```javascript
|
|
863
|
+
try {
|
|
864
|
+
await api.call();
|
|
865
|
+
} catch (err) {
|
|
866
|
+
if (err.message.includes("rate limit")) {
|
|
867
|
+
await sleep(1000);
|
|
868
|
+
return api.call();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
**Why developers do it:**
|
|
874
|
+
|
|
875
|
+
The error type system is not expressive enough, or the library only returns generic errors with different messages. Quick pattern matching on strings is faster to write than proper type checking.
|
|
876
|
+
|
|
877
|
+
**What goes wrong:**
|
|
878
|
+
|
|
879
|
+
Dave Cheney, in his widely-cited Go blog post, stated: "Comparing the string form of an error is, in my opinion, a code smell. You should try to avoid it." The error message is for humans, not code. When a library author rephrases "connection refused" to "unable to connect," every string-matching caller silently breaks. There is no compiler or type checker to catch the regression. These bugs only manifest at runtime, under the exact failure condition being matched.
|
|
880
|
+
|
|
881
|
+
**The fix:**
|
|
882
|
+
|
|
883
|
+
```go
|
|
884
|
+
// Before: string matching
|
|
885
|
+
if strings.Contains(err.Error(), "connection refused") {
|
|
886
|
+
retryConnection()
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// After: use error types and errors.Is / errors.As
|
|
890
|
+
var connErr *net.OpError
|
|
891
|
+
if errors.As(err, &connErr) {
|
|
892
|
+
retryConnection()
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
```javascript
|
|
897
|
+
// Before: string matching
|
|
898
|
+
if (err.message.includes("rate limit")) { ... }
|
|
899
|
+
|
|
900
|
+
// After: use error codes or typed errors
|
|
901
|
+
if (err instanceof RateLimitError) { ... }
|
|
902
|
+
// or
|
|
903
|
+
if (err.code === "RATE_LIMITED") { ... }
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**Detection rule:**
|
|
907
|
+
|
|
908
|
+
Flag `err.Error()`, `err.message`, or `e.getMessage()` used inside string comparison functions (`contains`, `includes`, `equals`, `startsWith`). Lint: `errorlint` (Go), custom ESLint rule for `err.message.includes`.
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
### AP-14: Missing Error Boundaries
|
|
913
|
+
|
|
914
|
+
**Also known as:** White Screen of Death, Unbounded Error Propagation, No Blast Radius
|
|
915
|
+
**Frequency:** Common
|
|
916
|
+
**Severity:** High
|
|
917
|
+
**Detection difficulty:** Moderate
|
|
918
|
+
|
|
919
|
+
**What it looks like:**
|
|
920
|
+
|
|
921
|
+
```jsx
|
|
922
|
+
// React app with no error boundary
|
|
923
|
+
function App() {
|
|
924
|
+
return (
|
|
925
|
+
<Dashboard>
|
|
926
|
+
<UserProfile /> {/* if this throws, entire app dies */}
|
|
927
|
+
<OrderHistory />
|
|
928
|
+
<Notifications />
|
|
929
|
+
</Dashboard>
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
**Why developers do it:**
|
|
935
|
+
|
|
936
|
+
Error boundaries require class components in React (no hook equivalent for `componentDidCatch`). Developers building with functional components skip them because they feel archaic. In backend systems, developers rely on global exception handlers without module-level boundaries.
|
|
937
|
+
|
|
938
|
+
**What goes wrong:**
|
|
939
|
+
|
|
940
|
+
React's own documentation states: "As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree." A single `TypeError` in a `UserProfile` widget takes down the entire application, rendering a blank white page. Wix Engineering documented this as the "White Screen of Death" for React Native, where uncaught errors crashed the entire app instead of just the offending component.
|
|
941
|
+
|
|
942
|
+
FreeCodeCamp documented the pattern: "Everything is set up and works properly, but the application crashes in production because of a missed little error, and then the whole screen goes white while clicking some button."
|
|
943
|
+
|
|
944
|
+
**The fix:**
|
|
945
|
+
|
|
946
|
+
```jsx
|
|
947
|
+
// Wrap independent features in separate error boundaries
|
|
948
|
+
function App() {
|
|
949
|
+
return (
|
|
950
|
+
<Dashboard>
|
|
951
|
+
<ErrorBoundary fallback={<ProfileError />}>
|
|
952
|
+
<UserProfile />
|
|
953
|
+
</ErrorBoundary>
|
|
954
|
+
<ErrorBoundary fallback={<OrdersUnavailable />}>
|
|
955
|
+
<OrderHistory />
|
|
956
|
+
</ErrorBoundary>
|
|
957
|
+
<ErrorBoundary fallback={<NotificationsOff />}>
|
|
958
|
+
<Notifications />
|
|
959
|
+
</ErrorBoundary>
|
|
960
|
+
</Dashboard>
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
In backend systems, use bulkheads:
|
|
966
|
+
|
|
967
|
+
```python
|
|
968
|
+
# Process each item independently; one failure doesn't stop the batch
|
|
969
|
+
results = []
|
|
970
|
+
for item in batch:
|
|
971
|
+
try:
|
|
972
|
+
results.append(process(item))
|
|
973
|
+
except ProcessingError as e:
|
|
974
|
+
logger.error("Item %s failed: %s", item.id, e)
|
|
975
|
+
results.append(FailedResult(item.id, e))
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
**Detection rule:**
|
|
979
|
+
|
|
980
|
+
In React: flag component trees with more than 50 components and no `ErrorBoundary` wrapper. In backends: flag batch-processing loops without per-item error handling. Lint: `eslint-plugin-react` custom rule for error boundary coverage.
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
984
|
+
### AP-15: Not Using Finally / Cleanup
|
|
985
|
+
|
|
986
|
+
**Also known as:** Resource Leak Pattern, Missing Cleanup, Dangling Handles
|
|
987
|
+
**Frequency:** Common
|
|
988
|
+
**Severity:** High
|
|
989
|
+
**Detection difficulty:** Hard
|
|
990
|
+
|
|
991
|
+
**What it looks like:**
|
|
992
|
+
|
|
993
|
+
```java
|
|
994
|
+
Connection conn = null;
|
|
995
|
+
try {
|
|
996
|
+
conn = dataSource.getConnection();
|
|
997
|
+
PreparedStatement stmt = conn.prepareStatement(sql);
|
|
998
|
+
ResultSet rs = stmt.executeQuery();
|
|
999
|
+
return processResults(rs);
|
|
1000
|
+
} catch (SQLException e) {
|
|
1001
|
+
throw new DataAccessException(e);
|
|
1002
|
+
}
|
|
1003
|
+
// conn, stmt, rs never closed if exception occurs in processResults
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
```python
|
|
1007
|
+
f = open("/tmp/data.csv", "w")
|
|
1008
|
+
f.write(generate_report()) # if this throws, file handle leaks
|
|
1009
|
+
f.close()
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
**Why developers do it:**
|
|
1013
|
+
|
|
1014
|
+
They assume the happy path always completes. In garbage-collected languages, they assume the GC will clean up (it does not close file handles, database connections, or network sockets).
|
|
1015
|
+
|
|
1016
|
+
**What goes wrong:**
|
|
1017
|
+
|
|
1018
|
+
Connection pool exhaustion is the classic symptom. Under normal load, connections are returned. Under error conditions, leaked connections accumulate until the pool is exhausted and the entire application blocks waiting for connections. This failure mode only manifests under stress -- exactly when you can least afford it.
|
|
1019
|
+
|
|
1020
|
+
**The fix:**
|
|
1021
|
+
|
|
1022
|
+
```java
|
|
1023
|
+
// Before: manual cleanup (leak-prone)
|
|
1024
|
+
Connection conn = dataSource.getConnection();
|
|
1025
|
+
// ...
|
|
1026
|
+
|
|
1027
|
+
// After: try-with-resources (Java 7+)
|
|
1028
|
+
try (Connection conn = dataSource.getConnection();
|
|
1029
|
+
PreparedStatement stmt = conn.prepareStatement(sql);
|
|
1030
|
+
ResultSet rs = stmt.executeQuery()) {
|
|
1031
|
+
return processResults(rs);
|
|
1032
|
+
}
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
```python
|
|
1036
|
+
# Before
|
|
1037
|
+
f = open("/tmp/data.csv", "w")
|
|
1038
|
+
f.write(generate_report())
|
|
1039
|
+
f.close()
|
|
1040
|
+
|
|
1041
|
+
# After: context manager
|
|
1042
|
+
with open("/tmp/data.csv", "w") as f:
|
|
1043
|
+
f.write(generate_report())
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
```javascript
|
|
1047
|
+
// JavaScript: use finally for cleanup
|
|
1048
|
+
const client = await pool.connect();
|
|
1049
|
+
try {
|
|
1050
|
+
return await client.query(sql);
|
|
1051
|
+
} finally {
|
|
1052
|
+
client.release();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Or with Symbol.asyncDispose (TC39 Stage 3)
|
|
1056
|
+
await using client = await pool.connect();
|
|
1057
|
+
return await client.query(sql);
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
**Detection rule:**
|
|
1061
|
+
|
|
1062
|
+
Flag `Connection`, `InputStream`, `FileHandle` allocations not inside try-with-resources or `with` blocks. Lint: `SonarQube S2095`, `SpotBugs OBL_UNSATISFIED_OBLIGATION`, `Pylint R1732 (consider-using-with)`.
|
|
1063
|
+
|
|
1064
|
+
---
|
|
1065
|
+
|
|
1066
|
+
### AP-16: Error Codes vs Exceptions Confusion
|
|
1067
|
+
|
|
1068
|
+
**Also known as:** Mixed Signaling, Dual Error System, Return-Code-in-Exception-Land
|
|
1069
|
+
**Frequency:** Occasional
|
|
1070
|
+
**Severity:** Medium
|
|
1071
|
+
**Detection difficulty:** Hard
|
|
1072
|
+
|
|
1073
|
+
**What it looks like:**
|
|
1074
|
+
|
|
1075
|
+
```python
|
|
1076
|
+
# Mixing paradigms: returns error codes AND throws exceptions
|
|
1077
|
+
def process_payment(order):
|
|
1078
|
+
result = gateway.charge(order.amount)
|
|
1079
|
+
if result.status_code == -1:
|
|
1080
|
+
return {"error": "payment_declined", "code": -1}
|
|
1081
|
+
if result.status_code == -2:
|
|
1082
|
+
raise PaymentGatewayError("Gateway unreachable")
|
|
1083
|
+
if result.status_code == 0:
|
|
1084
|
+
return {"success": True}
|
|
1085
|
+
# What about status_code == -3? Undefined behavior.
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
```go
|
|
1089
|
+
// Go: returning both error and value, caller ignores error
|
|
1090
|
+
result, err := processPayment(order)
|
|
1091
|
+
// Developer checks result but not err
|
|
1092
|
+
if result.Success {
|
|
1093
|
+
completeOrder(order)
|
|
1094
|
+
}
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
**Why developers do it:**
|
|
1098
|
+
|
|
1099
|
+
Different team members prefer different styles. Legacy code uses error codes, new code uses exceptions, and they meet at integration points. Go's `(value, error)` convention can be undermined when callers check only the value.
|
|
1100
|
+
|
|
1101
|
+
**What goes wrong:**
|
|
1102
|
+
|
|
1103
|
+
Callers do not know which error channel to check. Some callers check the return value, others wrap in try-catch, and some do both. The LWN.net article on "Exceptions vs Error Codes" documents how mixing paradigms leads to unhandled error paths because callers make incorrect assumptions about which mechanism signals failure.
|
|
1104
|
+
|
|
1105
|
+
**The fix:**
|
|
1106
|
+
|
|
1107
|
+
Pick one paradigm per language/module boundary and enforce it:
|
|
1108
|
+
|
|
1109
|
+
```python
|
|
1110
|
+
# Before: mixed
|
|
1111
|
+
def process_payment(order):
|
|
1112
|
+
result = gateway.charge(order.amount)
|
|
1113
|
+
if result.status_code == -1:
|
|
1114
|
+
return {"error": "payment_declined"}
|
|
1115
|
+
if result.status_code == -2:
|
|
1116
|
+
raise PaymentGatewayError("unreachable")
|
|
1117
|
+
|
|
1118
|
+
# After: exceptions only (Python/Java/JS convention)
|
|
1119
|
+
def process_payment(order):
|
|
1120
|
+
"""Raises PaymentDeclined or GatewayUnreachable. Returns OrderReceipt on success."""
|
|
1121
|
+
result = gateway.charge(order.amount)
|
|
1122
|
+
if result.status_code == -1:
|
|
1123
|
+
raise PaymentDeclined(order_id=order.id, reason=result.message)
|
|
1124
|
+
if result.status_code == -2:
|
|
1125
|
+
raise GatewayUnreachable(gateway=gateway.name)
|
|
1126
|
+
return OrderReceipt(result)
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
**Detection rule:**
|
|
1130
|
+
|
|
1131
|
+
Flag functions that both `return error_dict` and `raise Exception` in different branches. Flag Go functions where the caller uses the first return value without checking `err != nil`. Lint: `errcheck` (Go), custom AST rule for mixed return/raise.
|
|
1132
|
+
|
|
1133
|
+
---
|
|
1134
|
+
|
|
1135
|
+
### AP-17: Panic-Driven Error Handling
|
|
1136
|
+
|
|
1137
|
+
**Also known as:** Crash-Happy Code, Fail-Loud Everywhere, Panic-as-Error
|
|
1138
|
+
**Frequency:** Occasional
|
|
1139
|
+
**Severity:** High
|
|
1140
|
+
**Detection difficulty:** Easy
|
|
1141
|
+
|
|
1142
|
+
**What it looks like:**
|
|
1143
|
+
|
|
1144
|
+
```go
|
|
1145
|
+
func loadConfig(path string) Config {
|
|
1146
|
+
data, err := os.ReadFile(path)
|
|
1147
|
+
if err != nil {
|
|
1148
|
+
panic("failed to read config: " + err.Error())
|
|
1149
|
+
}
|
|
1150
|
+
var cfg Config
|
|
1151
|
+
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
1152
|
+
panic("failed to parse config: " + err.Error())
|
|
1153
|
+
}
|
|
1154
|
+
return cfg
|
|
1155
|
+
}
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
```rust
|
|
1159
|
+
fn get_user(id: &str) -> User {
|
|
1160
|
+
db.find_user(id).unwrap() // panics on None
|
|
1161
|
+
}
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
**Why developers do it:**
|
|
1165
|
+
|
|
1166
|
+
"If this fails, nothing else matters" reasoning. In Go, the `panic`/`recover` mechanism looks like exceptions, tempting developers to use it as one. In Rust, `.unwrap()` is shorter than proper error propagation.
|
|
1167
|
+
|
|
1168
|
+
**What goes wrong:**
|
|
1169
|
+
|
|
1170
|
+
Go's official documentation states: "Don't use panic for normal error handling. Use error and multiple return values." Eli Bendersky documented how panic breaks the natural control flow of the program, surprising other developers. In Go's `net/http`, panics in request handlers are recovered by the server, but David Symonds (from the Go team) noted this may itself be an anti-pattern: "Quietly catching a panic might leave the server in an inconsistent or incorrect state."
|
|
1171
|
+
|
|
1172
|
+
A config file missing on one server in a fleet should not crash the process -- it should use a fallback or degrade gracefully. A database lookup returning no result is not a panic-worthy event.
|
|
1173
|
+
|
|
1174
|
+
**The fix:**
|
|
1175
|
+
|
|
1176
|
+
```go
|
|
1177
|
+
// Before: panic
|
|
1178
|
+
func loadConfig(path string) Config {
|
|
1179
|
+
data, err := os.ReadFile(path)
|
|
1180
|
+
if err != nil {
|
|
1181
|
+
panic("failed to read config")
|
|
1182
|
+
}
|
|
1183
|
+
// ...
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// After: return error
|
|
1187
|
+
func loadConfig(path string) (Config, error) {
|
|
1188
|
+
data, err := os.ReadFile(path)
|
|
1189
|
+
if err != nil {
|
|
1190
|
+
return Config{}, fmt.Errorf("reading config %s: %w", path, err)
|
|
1191
|
+
}
|
|
1192
|
+
var cfg Config
|
|
1193
|
+
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
1194
|
+
return Config{}, fmt.Errorf("parsing config %s: %w", path, err)
|
|
1195
|
+
}
|
|
1196
|
+
return cfg, nil
|
|
1197
|
+
}
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
```rust
|
|
1201
|
+
// Before
|
|
1202
|
+
fn get_user(id: &str) -> User {
|
|
1203
|
+
db.find_user(id).unwrap()
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// After
|
|
1207
|
+
fn get_user(id: &str) -> Result<User, DbError> {
|
|
1208
|
+
db.find_user(id).ok_or_else(|| DbError::NotFound(id.to_string()))
|
|
1209
|
+
}
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
**Detection rule:**
|
|
1213
|
+
|
|
1214
|
+
Flag `panic()` calls in Go outside of `init()` or test files. Flag `.unwrap()` in Rust outside of tests. Lint: `clippy::unwrap_used`, `golangci-lint gocritic panic` rule.
|
|
1215
|
+
|
|
1216
|
+
---
|
|
1217
|
+
|
|
1218
|
+
### AP-18: Destructive Finally Blocks
|
|
1219
|
+
|
|
1220
|
+
**Also known as:** Finally Swallows Exception, Return-in-Finally, Finally Override
|
|
1221
|
+
**Frequency:** Rare but Catastrophic
|
|
1222
|
+
**Severity:** Critical
|
|
1223
|
+
**Detection difficulty:** Very Hard
|
|
1224
|
+
|
|
1225
|
+
**What it looks like:**
|
|
1226
|
+
|
|
1227
|
+
```java
|
|
1228
|
+
public int calculate() {
|
|
1229
|
+
try {
|
|
1230
|
+
throw new RuntimeException("Critical error");
|
|
1231
|
+
} finally {
|
|
1232
|
+
return 42; // silently swallows the RuntimeException
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
// Returns 42. No exception propagated. No trace. Nothing.
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
```python
|
|
1239
|
+
def calculate():
|
|
1240
|
+
try:
|
|
1241
|
+
raise ValueError("Critical error")
|
|
1242
|
+
finally:
|
|
1243
|
+
return 42 # ValueError silently discarded
|
|
1244
|
+
```
|
|
1245
|
+
|
|
1246
|
+
**Why developers do it:**
|
|
1247
|
+
|
|
1248
|
+
They add a `return` in `finally` as a "safety net" to ensure the method always returns something. They do not realize that a return in `finally` overrides any exception being propagated.
|
|
1249
|
+
|
|
1250
|
+
**What goes wrong:**
|
|
1251
|
+
|
|
1252
|
+
The exception is silently discarded -- not caught, not logged, not propagated. It simply ceases to exist. Vishal Junghare documented this in Java: "A finally block can quietly swallow exceptions." This is one of the most insidious bugs because it leaves zero evidence. The method returns a plausible-looking value (42), making the bug extremely hard to detect.
|
|
1253
|
+
|
|
1254
|
+
**The fix:**
|
|
1255
|
+
|
|
1256
|
+
Never return from a finally block. Use finally only for cleanup:
|
|
1257
|
+
|
|
1258
|
+
```java
|
|
1259
|
+
// Before: return in finally swallows exception
|
|
1260
|
+
public int calculate() {
|
|
1261
|
+
try {
|
|
1262
|
+
return riskyComputation();
|
|
1263
|
+
} finally {
|
|
1264
|
+
return 42;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// After: finally only for cleanup
|
|
1269
|
+
public int calculate() {
|
|
1270
|
+
try {
|
|
1271
|
+
return riskyComputation();
|
|
1272
|
+
} finally {
|
|
1273
|
+
cleanupResources(); // no return statement
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
**Detection rule:**
|
|
1279
|
+
|
|
1280
|
+
Flag `return` statements inside `finally` blocks. Lint: `SonarQube S1143`, `PMD ReturnFromFinallyBlock`, `Pylint W0150 (lost-exception)`, `ESLint no-unsafe-finally`.
|
|
1281
|
+
|
|
1282
|
+
---
|
|
1283
|
+
|
|
1284
|
+
## Root Cause Analysis
|
|
1285
|
+
|
|
1286
|
+
| Root Cause | Anti-Patterns | Frequency |
|
|
1287
|
+
|---|---|---|
|
|
1288
|
+
| **Lack of testing error paths** | AP-01, AP-02, AP-06, AP-10, AP-18 | Very High |
|
|
1289
|
+
| **Time pressure / "ship it" culture** | AP-01, AP-02, AP-07, AP-09 | Very High |
|
|
1290
|
+
| **Not understanding language semantics** | AP-06, AP-11, AP-17, AP-18 | High |
|
|
1291
|
+
| **Cargo-cult defensive programming** | AP-01, AP-06, AP-04 | High |
|
|
1292
|
+
| **Missing error type hierarchy** | AP-07, AP-12, AP-13, AP-16 | High |
|
|
1293
|
+
| **Over-applying security guidance** | AP-07, AP-08 | Medium |
|
|
1294
|
+
| **Unfamiliarity with async model** | AP-10, AP-15 | High |
|
|
1295
|
+
| **No code review standards for error handling** | AP-02, AP-05, AP-09 | High |
|
|
1296
|
+
| **Copy-paste from Stack Overflow** | AP-03, AP-09, AP-17 | Medium |
|
|
1297
|
+
| **GC creating false sense of safety** | AP-15, AP-04 | Medium |
|
|
1298
|
+
|
|
1299
|
+
---
|
|
1300
|
+
|
|
1301
|
+
## Self-Check Questions
|
|
1302
|
+
|
|
1303
|
+
Use these during code review or self-review. A "yes" answer indicates a potential anti-pattern.
|
|
1304
|
+
|
|
1305
|
+
1. **Does any catch block have an empty body, or only a comment/TODO?** (AP-02)
|
|
1306
|
+
2. **Does any catch clause use the broadest possible exception type (`Exception`, `Throwable`, `BaseException`, bare `catch`)?** (AP-01, AP-06)
|
|
1307
|
+
3. **Are there catch blocks that both log the exception AND rethrow it?** (AP-05)
|
|
1308
|
+
4. **Does any function catch an exception and return null without documenting why?** (AP-04)
|
|
1309
|
+
5. **Are there `assert` statements validating user input, authorization, or business rules?** (AP-11)
|
|
1310
|
+
6. **Do any catch blocks construct a new exception using only `e.getMessage()` without chaining the cause?** (AP-08)
|
|
1311
|
+
7. **Is error string content (`e.message`, `err.Error()`) compared using `contains`, `includes`, or `equals`?** (AP-13)
|
|
1312
|
+
8. **Are there async function calls without `await`, `.catch()`, or stored task references?** (AP-10)
|
|
1313
|
+
9. **Do HTTP handlers return the same status code (usually 500) for validation errors and system errors?** (AP-12)
|
|
1314
|
+
10. **Are there `return` statements inside `finally` blocks?** (AP-18)
|
|
1315
|
+
11. **Do resource allocations (connections, file handles, locks) occur outside try-with-resources or context managers?** (AP-15)
|
|
1316
|
+
12. **Are try-catch blocks nested more than 2 levels deep?** (AP-09)
|
|
1317
|
+
13. **Does the codebase mix error-code returns and exceptions for the same type of failure?** (AP-16)
|
|
1318
|
+
14. **Are there `panic()` or `.unwrap()` calls in non-initialization, non-test code?** (AP-17)
|
|
1319
|
+
15. **Does the React component tree have more than 50 components without any `ErrorBoundary`?** (AP-14)
|
|
1320
|
+
|
|
1321
|
+
---
|
|
1322
|
+
|
|
1323
|
+
## Code Smell Quick Reference
|
|
1324
|
+
|
|
1325
|
+
| Smell | Pattern to Look For | Likely Anti-Pattern | Severity |
|
|
1326
|
+
|---|---|---|---|
|
|
1327
|
+
| `except Exception: pass` | Bare catch with empty body | AP-01 + AP-02 | Critical |
|
|
1328
|
+
| `catch (Throwable t)` | Catching JVM-level errors | AP-06 | Critical |
|
|
1329
|
+
| `return null` in catch block | Exception converted to null | AP-04 | High |
|
|
1330
|
+
| `logger.error(e); throw e;` | Log followed by rethrow | AP-05 | Medium |
|
|
1331
|
+
| `throw new X(e.getMessage())` | New exception from message only | AP-08 | Medium |
|
|
1332
|
+
| `assert user_input > 0` | Assert on external input | AP-11 | Critical |
|
|
1333
|
+
| `int.Parse` in try-catch | Exception as flow control | AP-03 | Medium |
|
|
1334
|
+
| `err.message.includes("...")` | String-matching on error text | AP-13 | High |
|
|
1335
|
+
| `processAsync(x)` (no await) | Floating promise | AP-10 | Critical |
|
|
1336
|
+
| Nested `try { try { try {` | 3+ nesting levels | AP-09 | Medium |
|
|
1337
|
+
| `return 42` inside `finally` | Return overriding exception | AP-18 | Critical |
|
|
1338
|
+
| `res.status(500)` for all errors | No error type distinction | AP-12 | High |
|
|
1339
|
+
| `panic("not found")` | Panic for expected condition | AP-17 | High |
|
|
1340
|
+
| No `ErrorBoundary` in React tree | Uncontained error blast radius | AP-14 | High |
|
|
1341
|
+
| `conn = getConnection()` without `try-finally` | Resource outside cleanup block | AP-15 | High |
|
|
1342
|
+
| Function returns error dict AND raises | Mixed error signaling | AP-16 | Medium |
|
|
1343
|
+
|
|
1344
|
+
---
|
|
1345
|
+
|
|
1346
|
+
## Key Research
|
|
1347
|
+
|
|
1348
|
+
The University of Toronto study "Simple Testing Can Prevent Most Critical Failures" (OSDI 2014, Yuan et al.) analyzed 198 failures across Cassandra, HBase, HDFS, MapReduce, and Redis. Core findings relevant to this module:
|
|
1349
|
+
|
|
1350
|
+
- **92%** of catastrophic failures resulted from incorrect handling of non-fatal errors
|
|
1351
|
+
- **35%** had trivial error-handling bugs (empty handlers, log-only handlers, incomplete logic)
|
|
1352
|
+
- **77%** of failures could be reproduced by a unit test
|
|
1353
|
+
- **98%** manifested on no more than 3 nodes
|
|
1354
|
+
- The authors built Aspirator, a static checker that automatically detected these bugs
|
|
1355
|
+
|
|
1356
|
+
This research provides empirical backing for why error handling anti-patterns are not theoretical concerns -- they are the primary cause of production catastrophes in distributed systems.
|
|
1357
|
+
|
|
1358
|
+
---
|
|
1359
|
+
|
|
1360
|
+
*Researched: 2026-03-08 | Sources: Yuan et al. "Simple Testing Can Prevent Most Critical Failures" (OSDI 2014), Harness.io "Swallowed Exceptions: The Silent Killer of Java Applications", DZone "The Tiny Mistake That Crashed Our Node.js App", Tony Hoare "Null References: The Billion-Dollar Mistake" (QCon 2009), Dave Cheney "Don't just check errors, handle them gracefully", TheServerSide "Either log or rethrow Java exceptions", Baeldung "Is It a Bad Practice to Catch Throwable?", React documentation on Error Boundaries, Wix Engineering "White Screen of Death", Snyk "The dangers of assert in Python", Eli Bendersky "On the uses and misuses of panics in Go", SonarQube rules S2139/S1181/S108/S2095, CWE-397, OWASP Top 10 Proactive Controls C10, Maximiliano Contieri "Code Smell 80 - Nested Try/Catch", n8n Issue #19434, Databricks SDK Issue #1117*
|