487
*/
488
if (!didError) {
489
const isRefLValue = isUseRefType(instr.lvalue.identifier);
490
- for (const operand of eachInstructionValueOperand(instr.value)) {
491
- /**
492
- * By default we check that function call operands are not refs,
493
- * ref values, or functions that can access refs.
494
- */
495
- if (
496
- isRefLValue ||
497
- (hookKind != null &&
498
- hookKind !== 'useState' &&
499
- hookKind !== 'useReducer')
500
- ) {
490
+ if (
491
+ isRefLValue ||
492
+ (hookKind != null &&
493
+ hookKind !== 'useState' &&
494
+ hookKind !== 'useReducer')
495
+ ) {
496
+ for (const operand of eachInstructionValueOperand(
497
+ instr.value,
498
+ )) {
499
/**
500
* Allow passing refs or ref-accessing functions when:
501
* 1. lvalue is a ref (mergeRefs pattern: `mergeRefs(ref1, ref2)`)
502
* 2. calling hooks (independently validated for ref safety)
503
*/
504
validateNoDirectRefValueAccess(errors, operand, env);
507
- } else if (interpolatedAsJsx.has(instr.lvalue.identifier.id)) {
505
+ }
506
+ } else if (interpolatedAsJsx.has(instr.lvalue.identifier.id)) {
507
+ for (const operand of eachInstructionValueOperand(
508
+ instr.value,
509
+ )) {
510
/**
511
* Special case: the lvalue is passed as a jsx child
512
*
515
* render function which attempts to obey the rules.
516
*/
517
validateNoRefValueAccess(errors, env, operand);
516
- } else {
518
+ }
519
+ } else if (hookKind == null && instr.effects != null) {
520
+ /**
521
+ * For non-hook functions with known aliasing effects, use the
522
+ * effects to determine what validation to apply for each place.
523
+ * Track visited id:kind pairs to avoid duplicate errors.
524
+ */
525
+ const visitedEffects: Set<string> = new Set();
526
+ for (const effect of instr.effects) {
527
+ let place: Place | null = null;
528
+ let validation: 'ref-passed' | 'direct-ref' | 'none' = 'none';
529
+ switch (effect.kind) {
530
+ case 'Freeze': {
531
+ place = effect.value;
532
+ validation = 'direct-ref';
533
+ break;
534
+ }
535
+ case 'Mutate':
536
+ case 'MutateTransitive':
537
+ case 'MutateConditionally':
538
+ case 'MutateTransitiveConditionally': {
539
+ place = effect.value;
540
+ validation = 'ref-passed';
541
+ break;
542
+ }
543
+ case 'Render': {
544
+ place = effect.place;
545
+ validation = 'ref-passed';
546
+ break;
547
+ }
548
+ case 'Capture':
549
+ case 'Alias':
550
+ case 'MaybeAlias':
551
+ case 'Assign':
552
+ case 'CreateFrom': {
553
+ place = effect.from;
554
+ validation = 'ref-passed';
555
+ break;
556
+ }
557
+ case 'ImmutableCapture': {
558
+ /**
559
+ * ImmutableCapture can come from two sources:
560
+ * 1. A known signature that explicitly freezes the operand
561
+ * (e.g. PanResponder.create) — safe, the function doesn't
562
+ * call callbacks during render.
563
+ * 2. Downgraded defaults when the operand is already frozen
564
+ * (e.g. foo(propRef)) — the function is unknown and may
565
+ * access the ref.
566
+ *
567
+ * We distinguish these by checking whether the same operand
568
+ * also has a Freeze effect on this instruction, which only
569
+ * comes from known signatures.
570
+ */
571
+ place = effect.from;
572
+ const isFrozen = instr.effects.some(
573
+ e =>
574
+ e.kind === 'Freeze' &&
575
+ e.value.identifier.id === effect.from.identifier.id,
576
+ );
577
+ validation = isFrozen ? 'direct-ref' : 'ref-passed';
578
+ break;
579
+ }
580
+ case 'Create':
581
+ case 'CreateFunction':
582
+ case 'Apply':
583
+ case 'Impure':
584
+ case 'MutateFrozen':
585
+ case 'MutateGlobal': {
586
+ break;
587
+ }
588
+ }
589
+ if (place !== null && validation !== 'none') {
590
+ const key = `${place.identifier.id}:${validation}`;
591
+ if (!visitedEffects.has(key)) {
592
+ visitedEffects.add(key);
593
+ if (validation === 'direct-ref') {
594
+ validateNoDirectRefValueAccess(errors, place, env);
595
+ } else {
596
+ validateNoRefPassedToFunction(
597
+ errors,
598
+ env,
599
+ place,
600
+ place.loc,
601
+ );
602
+ }
603
+ }
604
+ }
605
+ }
606
+ } else {
607
+ for (const operand of eachInstructionValueOperand(
608
+ instr.value,
609
+ )) {
610
validateNoRefPassedToFunction(
611
errors,
612
env,